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 プロトコルスタックのマスター / スレーブ機能を同時に使用できることを意味します。これにより、ホストオブジェクトとデバイスオブジェクトが同時に正常に駆動されますが、ハードウェア層は 1 つまたは複数のインターフェースであっても、usbx は気にしません。
まず、以下の能力を確認してください。
- C 言語ライブラリのマクロ設定を使用して、いくつかのコードの動作を制御する方法について基本的な理解があること。
- 公式の例を理解する能力があり、公式の例の main 主フローの各行が何をしているかを理解できること。
- 手動またはツールを使用してチップの周辺機器を設定する能力があり、後続の周辺機器ドライバ部分には関与しないこと。
ライブラリの基本的な使い方#
以下はホストプロトコルスタックを開始するための基本的な使い方です。全体的には、メモリプールとしてメモリ領域を定義し、そのメモリプールから 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;
}
以下はデバイスプロトコルスタックを開始するための基本的な使い方です。全体的には、ホスト操作と似ており、メモリプールを作成し、メモリを取得し、初期化および登録を行い、スレーブアプリケーションスレッドを開始します。
#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 の 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
という関数が呼び出されていることに注意してください。これは、例を直接コピーしたり、cubemx を使用してホストとスレーブを含むプロジェクトを直接生成したりすることができない原因です。ux_system_initialize
は二度呼び出すことができず、一方の機能に異常を引き起こし、内部の処理スレッドでハングします。正しい方法は、3 つのメモリスペースを事前に定義し、3 つのメモリプールを使用して、最初のメモリを優先的に取得して ux_system_initialize
を行い、2 番目と 3 番目のメモリプールでホストとスレーブの init を行い、そのプロセス中で再び ux_system_initialize
を行わないことです。その後、正常に使用できるようになります。
その他の細則#
Windows の CDC_ACM ドライバは、固定的にエンドポイント 0x01 と 0x81 を使用して IN/OUT を行います。この部分はエンドポイント番号の変更を禁止します。
一部のメッセージは長さが 4 バイトに揃っている必要があり、以下に優れたゼロパディング方法があります。
uint8_t *data; // 元のメッセージバッファ
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); // 一般的なセットアップエンドポイント
/* 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