banner
yono

yono

哈喽~欢迎光临
follow
github

usbx 的主從 OTG

usbx 基本介紹#

usbx 是 threadx 操作系統的 USB 設備棧,支持多種主機驅動、從機枚舉、以及 OTG

開源庫地址在這裡

eclipse-threadx/usbx: Eclipse ThreadX - USBX is a high-performance USB host, device, and on-the-go (OTG) embedded stack, that is fully integrated with Eclipse ThreadX RTOS

介紹在這裡

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_ONLYUX_DEVICE_SIDE_ONLY 的部分。這樣庫的 OTG 支持就開啟了。另外有 UX_DEVICE_BIDIRECTIONAL_ENDPOINT_SUPPORTUX_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


載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。