打印
[开发工具]

Mbed OS 的 USB Driver 应用

[复制链接]
851|1
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 susutata 于 2024-4-23 18:55 编辑

#申请原创# @21小跑堂


Mbed OS 的 USB Driver 应用

## 前言

本贴主要内容为 MbedOS 的 USB Driver 使用方法,及其 USB Device Stack 和 USB Component 介绍。

## 01 USB Device Stack

Mbed OS 的 USB Driver 包含三个部分:USB PHY、USB Device Stack 和 USB Component。USB PHY 是 USB 物理层,USB Device Stack 是 USB 设备堆栈,USB Component 是 USB 组件。

这里将 Mbed OS USB Driver 和 APM32 的 USB Driver、USB Library 进行一个粗略的对比,以便大家更好地理解 Mbed OS 的 USB Driver。

USB PHY 部分相当于 APM32 USB Driver 的 USBD Hardware Init 和 USBD Config 部分,USB Device Stack 部分相当于 APM32 USB Driver 的 USBD Core 部分,USB Component 部分相当于 APM32 USB Driver 的 USBD Class 部分。


下面是 Mbed OS 的官方文档中关于这三个部分的介绍:

> USBPhy provides raw access to USB in the role of a USB Device.
> USBDevice is the core of Mbed OS's USB stack and is responsible for state management and synchronization.
> USB component is code that inherits from USBDevice and provides the desired USB interfaces.

在官方文档的 USB PHY 、USB Device Stack 和 USB Component 的交互图中,我们可以看到很多熟悉的 API 名字。比如:
- connect()
- disconnect()
- configure()
- set_address()
- sof_enable()
- remote_wakeup()
- endpoint_stall()
- endpoint_unstall()



在这些交互 API 中,USB Component 会有一些回调函数用于获取 USB 设备的状态的 API:
- callback_state_change()


响应 USB 主机的请求的 API:
- callback_request()- callback_request_xfer_done()- callback_set_configuration()- callback_set_interface()

下图是官方文档中的控制请求状态机。



以及 IN/OUT 数据传输的 API:
  • callback_out()
  • callback_in()

下图是官方文档的 IN/OUT 数据传输状态机。

另外,还有一些端点的配置 API:
EndpointResolver resolver(endpoint_table());
resolver.endpoint_ctrl(CDC_MAX_PACKET_SIZE);
bulk_in = resolver.endpoint_in(USB_EP_TYPE_BULK, CDC_MAX_PACKET_SIZE);
bulk_out = resolver.endpoint_out(USB_EP_TYPE_BULK, CDC_MAX_PACKET_SIZE);
int_in = resolver.endpoint_in(USB_EP_TYPE_INT, CDC_MAX_PACKET_SIZE);

## 02 USB Component
Mbed OS 的 USB Component 包含以下的一系列 USB 设备类/组件:
- USB Audio
- USB CDC
- USB CDC ECM
- USB HID
- USB Keyboard
- USB Mouse
- USB MIDI
- USB MouseKeyboard
- USB MSD
- USB Serial

这些 Class 或 Component 的实现方式都差不多,都是继承自 USBDevice 类,然后实现自己的功能。以下内容以 USB CDC 为例,简单介绍一下 USB CDC 的实现及使用。

### USBCDC 构造函数
USBCDC 有两个构造函数,用户可以根据自己的需求选择不同的初始化方式。

#### 构造函数一
该构造函数可以利用 `connect_blocking` 参数来选择是否在创建对象时就立即连接并等待设备就绪。
USBCDC::USBCDC(bool connect_blocking, uint16_t vendor_id, uint16_t product_id, uint16_t product_release)
    : USBDevice(get_usb_phy(), vendor_id, product_id, product_release)
{
    _init();
    if (connect_blocking) {
        connect();
        wait_ready();
    } else {
        init();
    }
}

#### 构造函数二
该构造函数可以利用 `USBPhy` 参数来选择用户自己的 USB PHY 进行初始化。
USBCDC::USBCDC(USBPhy *phy, uint16_t vendor_id, uint16_t product_id, uint16_t product_release)
    : USBDevice(phy, vendor_id, product_id, product_release)
{
    _init();
}

### USBCDC 初始化函数
从 USBCDC 的初始化函数中可以看到,USB CDC 配置了三个端点,分别是 `bulk_in`、`bulk_out` 和 `int_in`。其中 `bulk_in` 和 `bulk_out` 是 BULK 类型的端点,`int_in` 是 INT 类型的端点。这一点和 APM32 的 USB CDC 类似,都是符合 USB CDC Class 的规范。
void USBCDC::_init()
{
    memcpy(_cdc_line_coding, cdc_line_coding_default, sizeof(_cdc_line_coding));

    EndpointResolver resolver(endpoint_table());
    resolver.endpoint_ctrl(CDC_MAX_PACKET_SIZE);
    _bulk_in = resolver.endpoint_in(USB_EP_TYPE_BULK, CDC_MAX_PACKET_SIZE);
    _bulk_out = resolver.endpoint_out(USB_EP_TYPE_BULK, CDC_MAX_PACKET_SIZE);
    _int_in = resolver.endpoint_in(USB_EP_TYPE_INT, CDC_MAX_PACKET_SIZE);
    ...
}

### USBCDC 的请求处理函数
USBCDC 的请求处理函数主要是处理 USB 主机发来的请求。这里主要是处理 USB CDC Class 的请求。这些请求主要是 CDC Class 的请求,比如设置波特率、数据位、停止位、奇偶校验位等。这里就不详细展开了。
void USBCDC::callback_request(const setup_packet_t *setup);
void USBCDC::callback_request_xfer_done(const setup_packet_t *setup, bool aborted);
void USBCDC::callback_set_configuration(uint8_t configuration);
void USBCDC::callback_set_interface(uint16_t interface, uint8_t alternate);

### USBCDC 的数据传输函数
USBCDC 的数据传输函数主要是处理 USB 主机发来的数据, 以及向 USB 主机发送数据。还有一些中断回调函数。
bool USBCDC::send(uint8_t *buffer, uint32_t size);
void USBCDC::send_nb(uint8_t *buffer, uint32_t size, uint32_t *actual, bool now);
void USBCDC::_send_isr_start();
void USBCDC::_send_isr();
bool USBCDC::receive(uint8_t *buffer, uint32_t size,  uint32_t *size_read);
void USBCDC::receive_nb(uint8_t *buffer, uint32_t size,  uint32_t *size_read);
void USBCDC::_receive_isr_start();
void USBCDC::_receive_isr();

### USBCDC 的描述符
USBCDC 的描述符主要是 USB 设备的描述符,包括设备描述符、接口描述符、配置描述符和字符串描述符。如果用户需要自定义描述符,可以修改这些定义,但要注意这些修改会应用到所有使用该 Mbed OS 库的设备上。
const uint8_t *USBCDC::device_desc()
const uint8_t *USBCDC::string_iinterface_desc()
const uint8_t *USBCDC::string_iproduct_desc()
const uint8_t *USBCDC::configuration_desc(uint8_t index)

## 03 USB Driver
前面几个章节主要介绍了 Mbed OS 的 USB Device Stack 和 USB Component,最后我们来看一下 Mbed OS 的 USB Driver 具体是如何使用的。

在正式开始之前,还需要配置一下 Mbed OS 的工程,将 `SLEEP` 功能关闭,因为 USB 设备通常需要持续运行以响应主机的请求,如果设备进入睡眠模式,可能会导致无法正常响应主机的请求,从而影响 USB 功能的正常工作。在 Mbed OS 工程中新建 `mbed_app.json` 文件,并添加以下内容:
{
    "target_overrides": {
        "*": {
                "target.device_has_remove": ["SLEEP"]
            }
    }
}

### USB CDC

USB CDC 的使用非常简单,只需要创建一个 USBCDC 对象,然后调用 `send()` 函数发送数据即可。下面是一个简单的例子。

例子是使用第二种构造函数,并自定义了 Vendor ID 、Product ID 和 Product Release。如前面章节“USBCDC 构造函数”的介绍,使用第二种构造函数,在创建 USBCDC 对象时不会马上连接,可以在应用代码中自己控制连接的时机。这里我们在创建 USBCDC 对象后,调用 `connect()` 函数连接 USB 设备,然后调用 `wait_ready()` 函数等待 USB 设备就绪。

最后在主循环中,每隔 1s 发送一次数据。

#include "mbed.h"
#include "USBCDC.h"
#include "usb_phy_api.h"

#define MY_VID 0x314B
#define MY_PID 0x1234
#define MY_RELEASE 0x0001

USBPhy *phy = get_usb_phy();

USBCDC cdc(phy, MY_VID, MY_PID, MY_RELEASE);

int main(void)
{
    // Start the USB connect sequence
    cdc.connect();

    // Block until ready
    cdc.wait_ready();

    while (1) {
        char msg[] = "Hello world\r\n";
        cdc.send((uint8_t *)msg, strlen(msg));
        ThisThread::sleep_for(1s);
    }
}
用 Mbed Studio 编译和下载。


下载完成后,可以在设备管理器看到新的 USB 串行设备。


用串口工具连接后可以看到打印的数据。



### USB HID
USB HID 的使用和 USB CDC 类似,创建一个 USBHID 对象,然后调用 `printf()` 函数发送数据即可。

例子中使用了第一种构造函数,创建 USBHID 对象时就连接并等待设备就绪。然后在主循环中,每隔 1s 打印一次数据。

#include "mbed.h"
#include "USBKeyboard.h"
#include "usb_phy_api.h"

#define MY_VID 0x314B
#define MY_PID 0x1234
#define MY_RELEASE 0x0001

USBKeyboard key(true, MY_VID, MY_PID, MY_RELEASE);

int main(void)
{
    while (1) {
        key.printf("Hello World\r\n");
        ThisThread::sleep_for(1s);
    }
}

用 Mbed Studio 编译和下载完成后,可以在设备管理器看到新的 USB Keyboard 设备。


打开记事本,可以看到每隔 1s 在光标后打印一次数据。


### USB MSD

USB MSD 的使用稍微复杂一点,它需要一个 BlockDevice 对象,用于存储数据。例子中使用了 HeapBlockDevice 对象,并在初始化时格式化了文件系统。然后创建一个 USBMSD 对象,将 BlockDevice 对象传入,然后调用 `connect()` 函数连接 USB 设备。
#include "mbed.h"
#include "USBMSD.h"
#include "FATFileSystem.h"
#include "usb_phy_api.h"

#define MY_VID 0x314B
#define MY_PID 0x1234
#define MY_RELEASE 0x0001

#define DEFAULT_BLOCK_SIZE  512
#define HEAP_BLOCK_DEVICE_SIZE (128 * DEFAULT_BLOCK_SIZE)

USBPhy *phy = get_usb_phy();

FATFileSystem heap_fs("heap_fs");
HeapBlockDevice bd(HEAP_BLOCK_DEVICE_SIZE, DEFAULT_BLOCK_SIZE);

int main()
{
    bd.init();

    FATFileSystem::format(&bd);

    int err = heap_fs.mount(&bd);

    if (err) {
        printf("%s filesystem mount failed\ntry to reformat device... \r\n", heap_fs.getName());
        err = heap_fs.reformat(&bd);
    }

    // If still error, then report failure
    if (err) {
        printf("Error: Unable to format/mount the device.\r\n");
        while (1);
    }

    USBMSD msd(&bd, phy, MY_VID, MY_PID, MY_RELEASE);

    // Start the USB connect sequence
    msd.connect();

    while (true) {
        msd.process();
    }

    return 0;
}

用 Mbed Studio 编译和下载完成后,可以在设备管理器看到新的 USB MSD 设备。



打开文件管理器,可以看到新的 U 盘设备。


## 04 总结

至此,Mbed OS 的 USB 设备类/组件的使用方法已经介绍完毕。可以看到,Mbed OS 的 USB Driver 非常简单易用,但从目前接触的程度看,官方组件都是单个设备类的,如果需要复合设备类,还是需要自己实现的。

## 参考资料

https://bbs.21ic.com/icview-3369506-1-1.html

https://os.mbed.com/docs/mbed-os/v6.16/apis/usb-apis.html

附件是文章中使用的工程代码,该工程不包含Mbed OS库,该库需要到 Geehy Github 中下载。



mbed-os-example-usb.zip

18.41 KB

Mbed OS USB Driver

使用特权

评论回复
评论
21小跑堂 2024-4-28 15:52 回复TA
一篇基于极海MCU的Mbed OS 的 USB Driver 应用,文章详细介绍了USB Device Stack 和 USB Component 介绍,并辅以实际代码操作,完成USB Driver应用。文章结构紧凑合理,图文及代码详细清晰。 
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

16

主题

24

帖子

3

粉丝