[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/bertmelis/USBHostSerial/main/src/USBHostSerial.cpp [Back]  [Original]

/*
Copyright (c) 2024 Bert Melis. All rights reserved.

This work is licensed under the terms of the MIT license.  
For a copy, see  or
the LICENSE file.

Based on example code:
SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: CC0-1.0
*/

#include "USBHostSerial.h"

using namespace esp_usb;

USBHostSerial::USBHostSerial(uint16_t vid, uint16_t pid)
: _host_config{}
, _line_coding{}
, _tx_buf_mem{}
, _tx_buf_handle(nullptr)
, _tx_buf_data{}
, _rx_buf_mem{}
, _rx_buf_handle(nullptr)
, _rx_buf_data{}
, _setupDone(false)
, _fallback(false)
, _vid(vid)
, _pid(pid)
, _device_disconnected_sem(nullptr)
, _usb_lib_task_handle(nullptr)
, _logger(nullptr) {
  _tx_buf_handle = xRingbufferCreateStatic(USBHOSTSERIAL_BUFFERSIZE, RINGBUF_TYPE_BYTEBUF, _tx_buf_mem, &_tx_buf_data);
  _rx_buf_handle = xRingbufferCreateStatic(USBHOSTSERIAL_BUFFERSIZE, RINGBUF_TYPE_BYTEBUF, _rx_buf_mem, &_rx_buf_data);
  if (!_tx_buf_handle || !_rx_buf_handle) {
    abort();
  }
}

USBHostSerial::~USBHostSerial() {
  // TODO (bertmelis): implement destruction.
  vRingbufferDelete(_tx_buf_handle);
  vRingbufferDelete(_rx_buf_handle);
  abort();
}

USBHostSerial::operator bool() const {
  if (xSemaphoreTake(_device_disconnected_sem, 0) == pdTRUE) {
    xSemaphoreGive(_device_disconnected_sem);
    return false;
  }
  return true;
}

bool USBHostSerial::begin(int baud, int stopbits, int parity, int databits) {
  if (!_setupDone) {
    _setupDone = true;
    _setup();
  }

  _line_coding.dwDTERate = baud;
  _line_coding.bCharFormat = stopbits;
  _line_coding.bParityType = parity;
  _line_coding.bDataBits = databits;

  if (xTaskCreate(_USBHostSerial_task, "usb_dev_lib", 4096, this, 1, &_USBHostSerial_task_handle) == pdTRUE) {
    _log("USB setup done");
    return true;
  }
  _log("USB setup failed");
  return false;
}

void USBHostSerial::end() {
  // TODO (bertmelis): implement end, together with destruction.
}

std::size_t USBHostSerial::write(uint8_t data) {
  if (xRingbufferSend(_tx_buf_handle, &data, 1, pdMS_TO_TICKS(1)) == pdTRUE) {
    return 1;
  }
  return 0;
}

std::size_t USBHostSerial::write(const uint8_t *data, std::size_t len) {
  UBaseType_t numItemsWaiting;
  vRingbufferGetInfo(_tx_buf_handle, nullptr, nullptr, nullptr, nullptr, &numItemsWaiting);
  std::size_t maxSize = USBHOSTSERIAL_BUFFERSIZE - numItemsWaiting;
  if (maxSize < len) {
    char buf[40];
    snprintf(buf, 40, "USB buf overflow: %u-%u", len, maxSize);
    _log(buf);
  } else {
    if (xRingbufferSend(_tx_buf_handle, data, len, pdMS_TO_TICKS(1)) == pdTRUE) {
      return len;
    }
  }
  return 0;
}

std::size_t USBHostSerial::available() {
  UBaseType_t numItemsWaiting;
  vRingbufferGetInfo(_rx_buf_handle, nullptr, nullptr, nullptr, nullptr, &numItemsWaiting);
  return numItemsWaiting;
}

uint8_t USBHostSerial::read() {
  std::size_t pxItemSize = 0;
  uint8_t retVal = 0;
  void* ret = xRingbufferReceiveUpTo(_rx_buf_handle, &pxItemSize, pdMS_TO_TICKS(1), 1);
  if (pxItemSize > 0) {
    retVal = *reinterpret_cast(ret);
    vRingbufferReturnItem(_rx_buf_handle, ret);
  }
  return retVal;
}

std::size_t USBHostSerial::read(uint8_t *dest, std::size_t size) {
  std::size_t retVal = 0;
  std::size_t pxItemSize = 0;
  while (size > retVal) {
    std::size_t remaining = size - retVal;
    if(remaining == 0){
      break;
    }

    void *ret = xRingbufferReceiveUpTo(_rx_buf_handle, &pxItemSize, pdMS_TO_TICKS(1), remaining);

    if(!ret || pxItemSize == 0){
      break;
    }

    std::memcpy(dest + retVal, ret, pxItemSize);
    retVal += pxItemSize;
    vRingbufferReturnItem(_rx_buf_handle, ret);
  }
  return retVal;
}

void USBHostSerial::setLogger(USBHostSerialLoggerFunc logger) {
  _logger = logger;
}

void USBHostSerial::_setup() {
  _device_disconnected_sem = xSemaphoreCreateBinary();
  assert(_device_disconnected_sem);
  xSemaphoreGive(_device_disconnected_sem);  // make available for first use

  // Install USB Host driver. Should only be called once in entire application
  _host_config.skip_phy_setup = false;
  _host_config.intr_flags = ESP_INTR_FLAG_LEVEL1;
  ESP_ERROR_CHECK(usb_host_install(&_host_config));

  // Create a task that will handle USB library events
  BaseType_t task_created = xTaskCreate(_usb_lib_task, "usb_lib", 4096, this, 1, &_usb_lib_task_handle);
  assert(task_created == pdTRUE);

  ESP_ERROR_CHECK(cdc_acm_host_install(NULL));

  // Register VCP drivers to VCP service
  VCP::register_driver();
  VCP::register_driver();
  VCP::register_driver();
}

bool USBHostSerial::_handle_rx(const uint8_t *data, size_t data_len, void *arg) {
  std::size_t lenReceived = 0;
  while (lenReceived < data_len && xRingbufferSend(static_cast(arg)->_rx_buf_handle, &data[lenReceived], 1, pdMS_TO_TICKS(10)) == pdTRUE) {
    ++lenReceived;
  }
  if (lenReceived < data_len) {
    // log overflow warning
    static_cast(arg)->_log("USB rx buf overflow");
  }
  return true;
}

void USBHostSerial::_handle_event(const cdc_acm_host_dev_event_data_t *event, void *user_ctx) {
  if (event->type == CDC_ACM_HOST_DEVICE_DISCONNECTED) {
    static_cast(user_ctx)->_log("USB device disconnected event");
    xSemaphoreGive(static_cast(user_ctx)->_device_disconnected_sem);
  }
}

void USBHostSerial::_usb_lib_task(void *arg) {
  while (1) {
    uint32_t event_flags;
    usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
    if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
      ESP_ERROR_CHECK(usb_host_device_free_all());
    }
  }
}

void USBHostSerial::_USBHostSerial_task(void *arg) {
  USBHostSerial* thisInstance = static_cast(arg);
  esp_err_t err = ESP_OK;  // reusable
  while (1) {
    // try to open USB VCP device
    const cdc_acm_host_device_config_t dev_config = {
      .connection_timeout_ms = 10,
      .out_buffer_size = USBHOSTSERIAL_BUFFERSIZE,
      .in_buffer_size = USBHOSTSERIAL_BUFFERSIZE,
      .event_cb = _handle_event,
      .data_cb = _handle_rx,
      .user_arg = thisInstance,
    };
    cdc_acm_dev_hdl_t cdc_dev = NULL;
    auto vcp = std::unique_ptr(VCP::open(&dev_config));
    if (vcp == nullptr) {
      thisInstance->_log("USB VCP open returned null, falling back to CDC");
      // try to fallback to CDC
      err = cdc_acm_host_open(thisInstance->_vid, thisInstance->_pid, 0, &dev_config, &cdc_dev);
      if (err != ESP_OK) {
        vTaskDelay(pdMS_TO_TICKS(500));
        continue;
      }
      thisInstance->_fallback = true;
      thisInstance->_log("USB CDC device opened");
    } else {
      thisInstance->_fallback = false;
      thisInstance->_log("USB VCP device opened");
    }

    // Clear any stale disconnect token from a previous cycle.
    while (xSemaphoreTake(thisInstance->_device_disconnected_sem, 0) == pdTRUE) {
    }

    // set line coding and control line states
    err = ESP_OK;
    if (thisInstance->_fallback) {
      err = cdc_acm_host_line_coding_set(cdc_dev, &(thisInstance->_line_coding));
      if (err != ESP_OK) {
        thisInstance->_log("USB line coding set error");
        if (cdc_dev != NULL) {
          cdc_acm_host_close(cdc_dev);
          cdc_dev = NULL;
        }
        vTaskDelay(pdMS_TO_TICKS(500));
        continue;
      }
      thisInstance->_log("USB line coding set");

      err = cdc_acm_host_set_control_line_state(cdc_dev, true, true);
      if (err == ESP_ERR_NOT_SUPPORTED) {
        thisInstance->_log("USB control line state not supported");
      } else if (err != ESP_OK) {
        thisInstance->_log("USB control line state set error");
        if (cdc_dev != NULL) {
          cdc_acm_host_close(cdc_dev);
          cdc_dev = NULL;
        }
        vTaskDelay(pdMS_TO_TICKS(500));
        continue;
      } else {
        thisInstance->_log("USB control line state set");
      }
    } else {
      err = vcp->line_coding_set(&(thisInstance->_line_coding));
      if (err != ESP_OK) {
        thisInstance->_log("USB line coding set error");
        vTaskDelay(pdMS_TO_TICKS(500));
        continue;
      }
      thisInstance->_log("USB line coding set");

      err = vcp->set_control_line_state(true, true);
      if (err == ESP_ERR_NOT_SUPPORTED) {
        thisInstance->_log("USB control line state not supported");
      } else if (err != ESP_OK) {
        thisInstance->_log("USB control line state set error");
        vTaskDelay(pdMS_TO_TICKS(500));
        continue;
      } else {
        thisInstance->_log("USB control line state set");
      }
    }

    // all set, enter loop to start sending
    while (1) {
      // check if still connected
      if (xSemaphoreTake(thisInstance->_device_disconnected_sem, 0) == pdTRUE) {
        break;
      }

      // check for data to send
      std::size_t pxItemSize = 0;
      void *data = xRingbufferReceiveUpTo(thisInstance->_tx_buf_handle, &pxItemSize, pdMS_TO_TICKS(10), USBHOSTSERIAL_BUFFERSIZE);
      if (data) {
        if (thisInstance->_fallback) {
          err = cdc_acm_host_data_tx_blocking(cdc_dev, (uint8_t*)data, pxItemSize, 1000);
        } else {
          err = vcp->tx_blocking((uint8_t*)data, pxItemSize, 1000);
        }
        if (err == ESP_OK) {
          vRingbufferReturnItem(thisInstance->_tx_buf_handle, data);
        } else {
          vRingbufferReturnItem(thisInstance->_tx_buf_handle, data);
          thisInstance->_log("Error writing to USB");
          // Device likely reset/disconnected; break to cleanup/reopen path.
          if (err == ESP_ERR_INVALID_STATE || err == ESP_ERR_INVALID_RESPONSE || err == ESP_FAIL) {
            break;
          }
        }
      }
      taskYIELD();
    }

    // Ensure CDC handles are released before attempting to reopen.
    if (thisInstance->_fallback && cdc_dev != NULL) {
      cdc_acm_host_close(cdc_dev);
      cdc_dev = NULL;
    }
    vTaskDelay(pdMS_TO_TICKS(500));
  }
}

void USBHostSerial::_log(const char* msg) {
  if (_logger) {
    _logger(msg);
  }
}

Web Proxy Viewer  |  New URL  |  Original Page