I have a ESP32-S3-USB-OTG dev board working as USB host for an Android phone. I followed the simple client task example in the ESP-IDF version 5.1 USB host API reference guide, and my USB host application is able to open the device, find the host interface and its endpoints, call usb_host_interface_claim(), and submit the control transfers to device. However it always returns error 0x103 ESP_ERR_INVALID_STATE when trying to kick off the BULK-IN transfer using usb_host_transfer_submit() in the following start_transfers().
Does anyone have an idea why this error can happen? I need to have the ESP32-S3-USB-OTG dev board communicate with Android phone but can't figure out this ESP_ERR_INVALID_STATE error in the following start_transfers() function.
Code: Select all
typedef struct {
usb_host_client_handle_t client_hdl;
uint8_t dev_addr;
usb_device_handle_t dev_hdl;
uint32_t actions;
} class_driver_t;
// connected android if any
android_dev_t android_dev = {0};
extern void handle_rx(uint8_t *data, size_t data_len, void *arg);
static void client_event_cb(const usb_host_client_event_msg_t *event_msg, void *arg)
{
class_driver_t *driver_obj = (class_driver_t *)arg;
switch (event_msg->event) {
case USB_HOST_CLIENT_EVENT_NEW_DEV:
if (driver_obj->dev_addr == 0) {
driver_obj->dev_addr = event_msg->new_dev.address;
//Open the device next
driver_obj->actions |= ACTION_OPEN_DEV;
}
break;
case USB_HOST_CLIENT_EVENT_DEV_GONE:
if (driver_obj->dev_hdl != NULL) {
//Cancel any other actions and close the device next
driver_obj->actions = ACTION_CLOSE_DEV;
}
break;
default:
//Should never occur
abort();
}
}
static esp_err_t find_intf_and_ep_desc(android_dev_t* android_dev, const usb_ep_desc_t **in_ep, const usb_ep_desc_t **out_ep)
{
const usb_config_desc_t *config_desc;
ESP_ERROR_CHECK(usb_host_get_active_config_descriptor(android_dev->dev_hdl, &config_desc));
bool interface_found = false;
int desc_offset = 0;
const usb_standard_desc_t *this_desc = (const usb_standard_desc_t *)config_desc;
do {
this_desc = usb_parse_next_descriptor_of_type(this_desc, config_desc->wTotalLength, USB_B_DESCRIPTOR_TYPE_INTERFACE, &desc_offset);
if (this_desc == NULL)
break; // Reached end of configuration descriptor
const usb_intf_desc_t *intf_desc = (const usb_intf_desc_t *)this_desc;
assert(intf_desc);
bool foundEp = false;
int temp_offset = desc_offset;
for (int i = 0; i < 2; i++) {
const usb_ep_desc_t *this_ep = usb_parse_endpoint_descriptor_by_index(intf_desc, i, config_desc->wTotalLength, &desc_offset);
assert(this_ep);
uint8_t ep_type = USB_BM_ATTRIBUTES_XFERTYPE_MASK & this_ep->bmAttributes;
if (ep_type == USB_BM_ATTRIBUTES_XFER_BULK) { // found bulk EP
foundEp = true;
if (USB_EP_DESC_GET_EP_DIR(this_ep) == 0) { // found OUT EP
*out_ep = this_ep;
ESP_LOGI(TAG, "found usb interface BULK-OUT ep address %d", this_ep->bEndpointAddress);
} else { // found IN EP
*in_ep = this_ep;
ESP_LOGI(TAG, "found usb interface BULK-IN ep address %d", this_ep->bEndpointAddress);
}
}
desc_offset = temp_offset;
}
if (foundEp) {
android_dev->data.intf_desc = intf_desc;
interface_found = true;
}
} while (!interface_found);
if (interface_found) {
return ESP_OK;
}
return ESP_ERR_NOT_FOUND;
}
/**
* @brief Check finished transfer status
*
* Return to on transfer completed OK.
* Cancel the transfer and issue user's callback in case of an error.
*
* @param[in] transfer Transfer to be checked
* @return true Transfer completed
* @return false Transfer NOT completed
*/
static bool is_transfer_completed(usb_transfer_t *transfer)
{
bool completed = false;
if (transfer->status == USB_TRANSFER_STATUS_COMPLETED) {
completed = true;
}
return completed;
}
static void in_xfer_cb(usb_transfer_t *transfer)
{
//ESP_LOGI(TAG, "Starting BULK IN transfer callback");
android_dev_t *android_dev = (android_dev_t *)transfer->context;
if (is_transfer_completed(transfer)) {
if (android_dev->data.in_cb) {
android_dev->data.in_cb(transfer->data_buffer, transfer->actual_num_bytes, android_dev->cb_arg);
}
//ESP_LOGI(TAG, "Submitting poll for BULK IN transfer");
usb_host_transfer_submit(android_dev->data.in_xfer);
}
}
static void out_xfer_cb(usb_transfer_t *transfer)
{
//ESP_LOGI(TAG, "out_xfer: BULK OUT transfer completed");
assert(transfer->context);
xSemaphoreGive((SemaphoreHandle_t)transfer->context);
}
static void free_transfers(android_dev_t *android_dev)
{
assert(android_dev);
usb_host_transfer_free(android_dev->data.in_xfer);
if (android_dev->data.out_xfer != NULL) {
usb_host_transfer_free(android_dev->data.out_xfer);
}
}
static esp_err_t allocate_transfers(android_dev_t *android_dev, const usb_ep_desc_t *in_ep_desc, const usb_ep_desc_t *out_ep_desc, size_t out_buf_len)
{
esp_err_t ret;
// Setup IN data transfer
ESP_GOTO_ON_ERROR(
usb_host_transfer_alloc(USB_EP_DESC_GET_MPS(in_ep_desc), 0, &android_dev->data.in_xfer),
err, TAG,
);
assert(android_dev->data.in_xfer);
android_dev->data.in_xfer->callback = in_xfer_cb;
android_dev->data.in_xfer->num_bytes = USB_EP_DESC_GET_MPS(in_ep_desc);
android_dev->data.in_xfer->bEndpointAddress = in_ep_desc->bEndpointAddress;
android_dev->data.in_xfer->device_handle = android_dev->dev_hdl;
android_dev->data.in_xfer->context = android_dev;
ESP_LOGI(TAG, "allocate BULK IN endpoint address #%d", android_dev->data.in_xfer->bEndpointAddress);
// Setup OUT bulk transfer (if it is required (out_buf_len > 0))
if (out_buf_len != 0) {
ESP_GOTO_ON_ERROR(
usb_host_transfer_alloc(out_buf_len, 0, &android_dev->data.out_xfer),
err, TAG,
);
assert(android_dev->data.out_xfer);
android_dev->data.out_xfer->callback = out_xfer_cb;
android_dev->data.out_xfer->device_handle = android_dev->dev_hdl;
// set the OUT transfer semaphore
android_dev->data.out_xfer->context = sending_sem;
android_dev->data.out_xfer->bEndpointAddress = out_ep_desc->bEndpointAddress;
ESP_LOGI(TAG, "allocate BULK OUT endpoint address #%d", android_dev->data.out_xfer->bEndpointAddress);
}
return ESP_OK;
err:
free_transfers(android_dev);
return ret;
}
static esp_err_t start_transfers(class_driver_t *driver_obj, android_dev_t *android_dev, android_data_callback_t in_cb)
{
esp_err_t ret = ESP_OK;
assert(android_dev);
android_dev->data.in_cb = in_cb;
android_dev->cb_arg = NULL;
ESP_LOGI(TAG, "Kick off BULK IN transfer");
ESP_ERROR_CHECK(usb_host_transfer_submit(android_dev->data.in_xfer));
// kick off OUT transfer
ESP_LOGI(TAG, "Kick off BULK OUT transfer");
xSemaphoreGive((SemaphoreHandle_t)android_dev->data.out_xfer->context);
// Nothing to do next
driver_obj->actions &= ~ACTION_START_ANDROID_TRANSFER;
return ret;
}
esp_err_t host_data_tx_blocking(android_dev_t *android_dev, const uint8_t *data, size_t data_len, uint32_t timeout_ms)
{
assert(android_dev);
USB_HOST_CHECK(data && (data_len > 0), ESP_ERR_INVALID_ARG);
USB_HOST_CHECK(android_dev->data.out_xfer, ESP_ERR_NOT_SUPPORTED); // Device was opened as read-only.
USB_HOST_CHECK(data_len <= android_dev->data.out_xfer->data_buffer_size, ESP_ERR_INVALID_SIZE);
ESP_LOGD(TAG, "submitting BULK OUT transfer");
memcpy(android_dev->data.out_xfer->data_buffer, data, data_len);
android_dev->data.out_xfer->num_bytes = data_len;
android_dev->data.out_xfer->timeout_ms = timeout_ms;
esp_err_t ret = usb_host_transfer_submit(android_dev->data.out_xfer);
return ret;
}
void class_driver_task(void *arg)
{
SemaphoreHandle_t signaling_sem = (SemaphoreHandle_t)arg;
class_driver_t driver_obj = {0};
//Wait until daemon task has installed USB Host Library
xSemaphoreTake(signaling_sem, portMAX_DELAY);
ESP_LOGI(TAG, "Registering Client");
usb_host_client_config_t client_config = {
.is_synchronous = false, //Synchronous clients currently not supported. Set this to false
.max_num_event_msg = CLIENT_NUM_EVENT_MSG,
.async = {
.client_event_callback = client_event_cb,
.callback_arg = (void *)&driver_obj,
},
};
ESP_ERROR_CHECK(usb_host_client_register(&client_config, &driver_obj.client_hdl));
//Allocate a USB transfer
usb_transfer_t *transfer_desc;
usb_host_transfer_alloc(CONFIG_USB_TRANSFER_OUT_BUF_SIZE, 0, &transfer_desc);
while (1) {
//CHECK_TWDT_ERROR_CODE(esp_task_wdt_reset(), ESP_OK);
if (driver_obj.actions == 0) {
usb_host_client_handle_events(driver_obj.client_hdl, portMAX_DELAY);
} else {
if (driver_obj.actions & ACTION_OPEN_DEV) {
action_open_dev(&driver_obj);
}
if (driver_obj.actions & ACTION_START_ANDROID_ACCESSORY) {
android_dev.dev_hdl = driver_obj.dev_hdl;
ESP_ERROR_CHECK(find_intf_and_ep_desc(&android_dev, &in_ep_usb, &out_ep_usb));
ESP_ERROR_CHECK(usb_host_interface_claim(driver_obj.client_hdl, android_dev.dev_hdl, android_dev.data.intf_desc->bInterfaceNumber, 0));
current_control_transfer_index = 0;
action_start_android_accessory(transfer_desc, &driver_obj);
}
if (driver_obj.actions & ACTION_START_ANDROID_TRANSFER) {
ESP_ERROR_CHECK(allocate_transfers(&android_dev, in_ep_usb, out_ep_usb, CONFIG_USB_TRANSFER_OUT_BUF_SIZE));
ESP_ERROR_CHECK(start_transfers(&driver_obj, &android_dev, handle_rx));
}
// disconnected
if (driver_obj.actions & ACTION_CLOSE_DEV) {
aciton_close_dev(&driver_obj, &android_dev);
}
if (driver_obj.actions & ACTION_EXIT) {
break;
}
} // else
} // while
ESP_LOGI(TAG, "Deregistering Client");
ESP_ERROR_CHECK(usb_host_client_deregister(driver_obj.client_hdl));
//Wait to be deleted
xSemaphoreGive(signaling_sem);
vTaskSuspend(NULL);
}
Code: Select all
I (782) DAEMON: Installing USB Host Library
I (822) TAM-CLASS: Registering Client
...
I (1212) main_task: Returned from app_main()
D (1652) HUB: Root port reset
D (1652) HUB: Stage done: START
D (1652) HUB: Stage done: GET_SHORT_DEV_DESC
D (1652) HUB: Stage done: CHECK_SHORT_DEV_DESC
D (1712) HUB: Stage done: SECOND_RESET
D (1712) HUB: Stage done: SET_ADDR
D (1712) HUB: Stage done: CHECK_ADDR
D (1722) HUB: Stage done: SET_ADDR_RECOVERY
D (1722) HUB: Stage done: GET_FULL_DEV_DESC
D (1722) HUB: Stage done: CHECK_FULL_DEV_DESC
D (1722) HUB: Stage done: GET_SHORT_CONFIG_DESC
D (1722) HUB: Stage done: CHECK_SHORT_CONFIG_DESC
D (1732) HUB: Stage done: GET_FULL_CONFIG_DESC
D (1732) HUB: Stage done: CHECK_FULL_CONFIG_DESC
D (1742) HUB: Stage done: SET_CONFIG
D (1742) HUB: Stage done: CHECK_CONFIG
D (1742) HUB: Stage done: GET_SHORT_LANGID_TABLE
D (1752) HUB: Stage done: CHECK_SHORT_LANGID_TABLE
D (1752) HUB: Stage done: GET_FULL_LANGID_TABLE
D (1762) HUB: Stage done: CHECK_FULL_LANGID_TABLE
D (1762) HUB: Stage done: GET_SHORT_MANU_STR_DESC
D (1772) HUB: Stage done: CHECK_SHORT_MANU_STR_DESC
D (1772) HUB: Stage done: GET_FULL_MANU_STR_DESC
D (1782) HUB: Stage done: CHECK_FULL_MANU_STR_DESC
D (1782) HUB: Stage done: GET_SHORT_PROD_STR_DESC
D (1792) HUB: Stage done: CHECK_SHORT_PROD_STR_DESC
D (1792) HUB: Stage done: GET_FULL_PROD_STR_DESC
D (1802) HUB: Stage done: CHECK_FULL_PROD_STR_DESC
D (1802) HUB: Stage done: GET_SHORT_SER_STR_DESC
D (1812) HUB: Stage done: CHECK_SHORT_SER_STR_DESC
D (1812) HUB: Stage done: GET_FULL_SER_STR_DESC
D (1822) HUB: Stage done: CHECK_FULL_SER_STR_DESC
D (1822) HUB: Stage done: CLEANUP
D (1822) USBH: Processing actions 0x100
D (1832) USBH: New device 1
D (1832) TAM-CLASS: Opening device at address 1
I (1842) TAM-CLASS: found usb interface BULK-OUT ep address 1
I (1842) TAM-CLASS: found usb interface BULK-IN ep address 129
I (1852) TAM-CLASS: found usb interface bInterfaceNumber 0
I (1852) TAM-CLASS: found usb interface bNumEndpoints 2
I (1862) TAM-CLASS: about to claim usb host interface #0
D (1872) transfer: attempting control ESP_OK
D (1872) USBH: Processing actions 0x4
D (1872) USBH: Default pipe device 1
D (1882) transfer: got desc 0, actual number of bytes transferred 15
D (1882) transfer: attempting control ESP_OK
D (1892) transfer: got desc 0, actual number of bytes transferred 17
D (1902) transfer: attempting control ESP_OK
D (1902) transfer: got desc 0, actual number of bytes transferred 17
D (1912) transfer: attempting control ESP_OK
D (1912) transfer: got desc 0, actual number of bytes transferred 11
D (1922) transfer: attempting control ESP_OK
D (1922) transfer: got desc 0, actual number of bytes transferred 29
D (1932) transfer: attempting control ESP_OK
D (1932) transfer: got desc 0, actual number of bytes transferred 12
D (1942) transfer: attempting control ESP_OK
D (1942) transfer: got desc 0, actual number of bytes transferred 8
I (1952) TAM-CLASS: allocate BULK IN endpoint address #129
I (1952) TAM-CLASS: allocate BULK OUT endpoint address #1
I (1962) TAM-CLASS: Kick off BULK IN transfer
ESP_ERROR_CHECK failed: esp_err_t 0x103 (ESP_ERR_INVALID_STATE) at 0x4200dfd8
0x4200dfd8: start_transfers(class_driver_t*, android_dev_s*, void (*)(unsigned char*, unsigned int, void*)) at X:/projects/jiesoft/esp/tam-controller/usb/class_driver.cc:644 (discriminator 1)
file: "./usb/class_driver.cc" line 644
func: esp_err_t start_transfers(class_driver_t*, android_dev_t*, android_data_callback_t)
expression: usb_host_transfer_submit(android_dev->data.in_xfer)
abort() was called at PC 0x4037b7b7 on core 0
0x4037b7b7: _esp_error_check_failed at X:/software/espressif/frameworks/esp-idf-v5.1/components/esp_system/esp_err.c:50