Basic Introduction to usbx#
usbx is the USB device stack of the ThreadX operating system, supporting various host drivers, slave enumeration, and OTG.
The open-source library can be found here:
Introduction can be found here:
rtos-docs/rtos-docs/usbx/overview-usbx.md at main · eclipse-threadx/rtos-docs
If you are using ThreadX RTOS, implementing this USB device stack separately as a host or slave is very simple. However, if you want to use the master-slave functionality (OTG) of this library simultaneously, it can be somewhat challenging, as the official documentation only provides brief information, and there are no complete and detailed examples in various forums.
The OTG of usbx does not refer to hardware OTG, but rather to protocol layer OTG, which means that the master and slave functionalities of the usbx protocol stack can be used simultaneously, allowing the host object and device object to be driven normally at the same time, regardless of whether the hardware layer has one or multiple interfaces; usbx does not care.
First, confirm that you have the following capabilities:
- A basic understanding of how to use macro configurations in the C language library to control some code behaviors.
- The ability to understand the official examples, specifically being able to comprehend what each line in the main flow of the official example is doing.
- The ability to manually or with tools configure chip peripherals, with no further involvement in the peripheral driver part.
Basic Usage of the Library#
The following is the basic approach to enabling the host protocol stack. In general, it involves defining a memory area as a memory pool, then allocating a piece of memory from this memory pool for the usbx system to use, registering the required host classes, and then allocating another piece of memory to start the host application thread. What needs to be done in the host application thread can be referenced from the official examples.
#define UX_HOST_APP_MEM_POOL_SIZE 1024 * 44
__ALIGN_BEGIN static UCHAR ux_host_byte_pool_buffer[UX_HOST_APP_MEM_POOL_SIZE] __ALIGN_END;
static TX_BYTE_POOL ux_host_app_byte_pool;
void txAppUSBXHostInit(void)
{
UINT status = TX_SUCCESS;
VOID *memory_ptr;
if(tx_byte_pool_create(&ux_host_app_byte_pool, "Ux App memory pool", ux_host_byte_pool_buffer, UX_HOST_APP_MEM_POOL_SIZE) != TX_SUCCESS)
{
}
else
{
memory_ptr = (VOID *)&ux_host_app_byte_pool;
status = MX_USBX_Host_Init(memory_ptr);
if(status != UX_SUCCESS)
{
while(1)
{
}
}
}
}
UINT MX_USBX_Host_Init(VOID *memory_ptr)
{
UINT ret = UX_SUCCESS;
UCHAR *pointer;
TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL *)memory_ptr;
if(tx_byte_allocate(byte_pool, (VOID **)&pointer, USBX_HOST_MEMORY_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
{
return TX_POOL_ERROR;
}
if(ux_system_initialize(pointer, USBX_HOST_MEMORY_STACK_SIZE, UX_NULL, 0) != UX_SUCCESS)
{
return UX_ERROR;
}
if(ux_host_stack_initialize(ux_host_event_callback) != UX_SUCCESS)
{
return UX_ERROR;
}
ux_utility_error_callback_register(&ux_host_error_callback);
if(ux_host_stack_class_register(_ux_system_host_class_storage_name, ux_host_class_storage_entry) != UX_SUCCESS)
{
return UX_ERROR;
}
if(tx_byte_allocate(byte_pool, (VOID **)&pointer, UX_HOST_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
{
return TX_POOL_ERROR;
}
if(tx_thread_create(&ux_host_app_thread, UX_HOST_APP_THREAD_NAME, app_ux_host_thread_entry, 0, pointer, UX_HOST_APP_THREAD_STACK_SIZE, UX_HOST_APP_THREAD_PRIO, UX_HOST_APP_THREAD_PREEMPTION_THRESHOLD, UX_HOST_APP_THREAD_TIME_SLICE, UX_HOST_APP_THREAD_START_OPTION) != TX_SUCCESS)
{
return TX_THREAD_ERROR;
}
return ret;
}
The following is the basic approach to enabling the device protocol stack. In general, it is similar to the host operation: establish a memory pool, allocate memory, initialize and register, and start the slave application thread.
#define UX_DEVICE_APP_MEM_POOL_SIZE 1024 * 24
__ALIGN_BEGIN static UCHAR ux_device_byte_pool_buffer[UX_DEVICE_APP_MEM_POOL_SIZE] __ALIGN_END;
static TX_BYTE_POOL ux_device_app_byte_pool;
void txAppUSBXDeviceInit(void)
{
UINT status = TX_SUCCESS;
VOID *memory_ptr;
if(tx_byte_pool_create(&ux_device_app_byte_pool, "Ux App memory pool", ux_device_byte_pool_buffer, UX_DEVICE_APP_MEM_POOL_SIZE) != TX_SUCCESS)
{
}
else
{
memory_ptr = (VOID *)&ux_device_app_byte_pool;
status = MX_USBX_Device_Init(memory_ptr);
if(status != UX_SUCCESS)
{
while(1)
{
}
}
}
}
UINT MX_USBX_Device_Init(VOID *memory_ptr)
{
UINT ret = UX_SUCCESS;
UCHAR *device_framework_high_speed;
UCHAR *device_framework_full_speed;
ULONG device_framework_hs_length;
ULONG device_framework_fs_length;
ULONG string_framework_length;
ULONG language_id_framework_length;
UCHAR *string_framework;
UCHAR *language_id_framework;
UCHAR *pointer;
TX_BYTE_POOL *byte_pool = (TX_BYTE_POOL *)memory_ptr;
if(tx_byte_allocate(byte_pool, (VOID **)&pointer, USBX_DEVICE_MEMORY_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
{
return TX_POOL_ERROR;
}
if(ux_system_initialize(pointer, USBX_DEVICE_MEMORY_STACK_SIZE, UX_NULL, 0) != UX_SUCCESS)
{
return UX_ERROR;
}
device_framework_high_speed = USBD_Get_Device_Framework_Speed(USBD_HIGH_SPEED, &device_framework_hs_length);
device_framework_full_speed = USBD_Get_Device_Framework_Speed(USBD_FULL_SPEED, &device_framework_fs_length);
string_framework = USBD_Get_String_Framework(&string_framework_length);
language_id_framework = USBD_Get_Language_Id_Framework(&language_id_framework_length);
if(ux_device_stack_initialize(device_framework_high_speed, device_framework_hs_length, device_framework_full_speed, device_framework_fs_length, string_framework, string_framework_length, language_id_framework, language_id_framework_length, UX_NULL) != UX_SUCCESS)
{
return UX_ERROR;
}
cdc_acm_parameter.ux_slave_class_cdc_acm_instance_activate = USBD_CDC_ACM_Activate;
cdc_acm_parameter.ux_slave_class_cdc_acm_instance_deactivate = USBD_CDC_ACM_Deactivate;
cdc_acm_parameter.ux_slave_class_cdc_acm_parameter_change = USBD_CDC_ACM_ParameterChange;
cdc_acm_configuration_number = USBD_Get_Configuration_Number(CLASS_TYPE_CDC_ACM, 0);
cdc_acm_interface_number = USBD_Get_Interface_Number(CLASS_TYPE_CDC_ACM, 0);
if(ux_device_stack_class_register(_ux_system_slave_class_cdc_acm_name, ux_device_class_cdc_acm_entry, cdc_acm_configuration_number, cdc_acm_interface_number, &cdc_acm_parameter) != UX_SUCCESS)
{
return UX_ERROR;
}
if(tx_byte_allocate(byte_pool, (VOID **)&pointer, UX_DEVICE_APP_THREAD_STACK_SIZE, TX_NO_WAIT) != TX_SUCCESS)
{
return TX_POOL_ERROR;
}
if(tx_thread_create(&ux_device_app_thread, UX_DEVICE_APP_THREAD_NAME, app_ux_device_thread_entry, 0, pointer, UX_DEVICE_APP_THREAD_STACK_SIZE, UX_DEVICE_APP_THREAD_PRIO, UX_DEVICE_APP_THREAD_PREEMPTION_THRESHOLD, UX_DEVICE_APP_THREAD_TIME_SLICE, UX_DEVICE_APP_THREAD_START_OPTION) != TX_SUCCESS)
{
return TX_THREAD_ERROR;
}
if(tx_byte_allocate(byte_pool, (VOID **)&pointer, 1024, TX_NO_WAIT) != TX_SUCCESS)
{
return TX_POOL_ERROR;
}
if(tx_thread_create(&ux_cdc_read_thread, "cdc_acm_read_usbx_app_thread_entry", usbx_cdc_acm_read_thread_entry, 1, pointer, 1024, 20, 20, TX_NO_TIME_SLICE, TX_AUTO_START) != TX_SUCCESS)
{
return TX_THREAD_ERROR;
}
return ret;
}
Special Operations for Enabling usbx OTG#
First, when using usbx, adding the global define UX_INCLUDE_USER_DEFINE_FILE
is a good practice. You can use ux_user.h to control the trimming of various functionalities of usbx. There should be a file named ux_user_sample.h in the library source code; copy it and rename it to ux_user.h and add it to the project.
It should contain parts similar to the following, which is the main content that causes confusion. In the original example, the definition part of UX_OTG_SUPPORT
is commented out. In your own ux_user.h, you need to ensure that UX_OTG_SUPPORT is uncommented, while commenting out the definitions of UX_HOST_SIDE_ONLY and UX_DEVICE_SIDE_ONLY in ux_user.h. This way, the library's OTG support will be enabled. Additionally, it is also recommended to enable UX_DEVICE_BIDIRECTIONAL_ENDPOINT_SUPPORT and UX_DEVICE_CLASS_CDC_ACM_WRITE_AUTO_ZLP macros.
#ifndef UX_HOST_SIDE_ONLY
#ifndef UX_DEVICE_SIDE_ONLY
/* #define UX_OTG_SUPPORT */
#endif
#endif
Note that in the basic approach, both the host and slave initialization processes call the ux_system_initialize
function. This is also the crux of why directly copying examples or using CubeMX to generate projects that include both host and slave cannot be used. ux_system_initialize
cannot be called twice, as it will cause one side's functionality to malfunction and will hang in an internal processing thread. The correct approach is to predefine three memory spaces, use three memory pools, allocate memory from the first pool for ux_system_initialize
, and then use the second and third memory pools for the initialization of the host and slave, without calling ux_system_initialize
again in the process. After that, it can be used normally.
Additional Details#
The CDC_ACM driverless serial port on Windows uses endpoint 0x01 and 0x81 for IN/OUT, and this part should not modify the endpoint numbers.
Some message lengths must be 4-byte aligned. Below is an excellent method for padding with zeros.
uint8_t *data; // Original message buffer
uint32_t len = OriginalLen; // Original length
for(uint32_t i = len; i < ((len + 0x3) & ~0x3U); i++)
{
data[i] = 0x00;
}
_write(data, ((len + 0x3) & ~0x3U));
For composite enumeration, it is best to modify the descriptors based on the ux_device_descriptors.c generated by CubeMX. In fact, the source code for generating descriptors is really excellent.
It should be noted that when using endpoints, the corresponding USB TxFIFO should be correctly enabled, as shown in the following example.
/* Set Rx FIFO */
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x200);
/* Set Tx FIFO 0 */
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x80); // General setup endpoint
/* Set Tx FIFO 1 */
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x100); // Enabled endpoints 0x01 and 0x81
/* Set Tx FIFO 3 */
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 3, 0x100); // Enabled endpoints 0x03 and 0x83
This article was updated by Mix Space to xLog. The original link is https://www.yono233.cn/posts/novel/24_12_14_usbx%20%E7%9A%84%E4%B8%BB%E4%BB%8E%20OTG