usbx 基本介紹#
usbx 是 threadx 操作系統的 USB 設備棧,支持多種主機驅動、從機枚舉、以及 OTG
開源庫地址在這裡
介紹在這裡
rtos-docs/rtos-docs/usbx/overview-usbx.md at main · eclipse-threadx/rtos-docs
如果使用 threadx RTOS,使用這個 USB 設備棧單獨作為主機或從機的實現是非常簡單的,但是如果希望同時使用這個庫的主從功能 (OTG) 則會稍顯困難,因為官方文檔只有只言片語,各個論壇也沒有一個完整細緻的例子。
usbx 的 OTG 並非指硬件 OTG,而是協議層的 OTG,意味著可以同時使用 usbx 協議棧的主從功能,使得 host 對象和 device 對象能同時正常驅動,而硬件層是一個還是多個接口,usbx 是不會在意的。
首先確認自己具備以下的能力
- 對 C 語言庫如何使用宏配置,控制一些代碼的行為,有基礎的了解。
- 具備看懂官方例程的能力,指能看懂官方例程 main 主流程的每一句在幹什麼。
- 手動或借助工具配置芯片外設的能力,後續不涉及外設驅動部分。
使用庫的基本姿勢#
以下是開啟 host 協議棧的基本姿勢,總的來說,就是定義一片內存區域作為內存池,然後從這個內存池取一片內存給 usbx 系統使用,將需要使用的主機類註冊一下,再取一片內存開啟主機應用線程。主機應用線程裡需要做什麼可以參考官方例程。
#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;
}
以下是開啟 drvice 協議棧的基本姿勢,總的來說,和主機操作類似,建立內存池,取內存,初始化和註冊,開啟從機應用線程。
#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 txAppUSBXDrviceInit(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;
}
關於開啟 usbx OTG 的特殊操作#
首先使用 usbx,添加編譯全局 define UX_INCLUDE_USER_DEFINE_FILE
,是一個好的操作。可以使用 ux_user.h 控制裁剪 usbx 的各個功能。庫源碼中應該有 ux_user_sample.h 這樣命名的文件,複製一份並重命名為 ux_user.h 加入工程。
其中應該有類似如下的部分,這是帶來疑惑的主要內容,原版示例中將 UX_OTG_SUPPORT
的定義部分註釋掉了,在自己的 ux_user.h 中需要將 UX_OTG_SUPPORT 調整為不要註釋,同時註釋掉 ux_user.h 中 define UX_HOST_SIDE_ONLY 和 UX_DEVICE_SIDE_ONLY 的部分。這樣庫的 OTG 支持就開啟了。另外有 UX_DEVICE_BIDIRECTIONAL_ENDPOINT_SUPPORT 和 UX_DEVICE_CLASS_CDC_ACM_WRITE_AUTO_ZLP 宏也建議開啟。
#ifndef UX_HOST_SIDE_ONLY
#ifndef UX_DEVICE_SIDE_ONLY
/* #define UX_OTG_SUPPORT */
#endif
#endif
注意到基本姿勢中,主機與從機的 init 流程中都調用了 ux_system_initialize
這個函數,這也是直接 copy 例程或者使用 cubemx 直接生成包括主機和從機的工程都不能使用的症結。ux_system_initialize
不可以調用兩次,會導致其中一側的功能異常,且會死在一個內部的處理線程中。正確的姿勢是預定義三個內存空間,使用三個內存池,第一個優先取一片內存進行 ux_system_initialize
,第二第三個內存池再進行主機與從機的 init,同時流程中不要再進行 ux_system_initialize
。後續就可以正常使用。
另外的細則#
windows 的 CDC_ACM 免驅串口固定使用端點 0x01 和 0x81 進行 IN/OUT,這部分禁止修改端點號。
部分報文要求長度必須 4 字節對齊,以下有一個優秀的補零方法。
uint8_t *data; // 原有報文buffer
uint32_t len = OriginalLen; // 原有長度
for(uint32_t i = len; i < ((len + 0x3) & ~0x3U); i++)
{
data[i] = 0x00;
}
_write(data, ((len + 0x3) & ~0x3U));
對於組合枚舉,描述符最好在 cubemx 生成的 ux_device_descriptors.c 基礎上修改,其實這個生成描述符的源碼真的非常優秀了。
需要注意的是,當使用端點時,對應的 USB Txfifo 應當正確開啟,有以下示例。
/* Set Rx FIFO */
HAL_PCDEx_SetRxFiFo(&hpcd_USB_OTG_FS, 0x200);
/* Set Tx FIFO 0 */
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 0, 0x80); // 通用的 setup 端點
/* Set Tx FIFO 1 */
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 1, 0x100); // 開啟了 0x01 和 0x81 端點
/* Set Tx FIFO 3 */
HAL_PCDEx_SetTxFiFo(&hpcd_USB_OTG_FS, 3, 0x100); // 開啟了 0x03 和 0x83 端點
此文由 Mix Space 同步更新至 xLog
原始鏈接為 https://www.yono233.cn/posts/novel/24_12_14_usbx%20%E7%9A%84%E4%B8%BB%E4%BB%8E%20OTG