USB设备栈架构:基于Zephyr1.9.0版本
Zephyr嵌入式操作系统的USB设备栈分为三层:
(1) USB设备控制器驱动层 (USB Device Controller)-----跟硬件相关的寄存器配置 (2) USB设备核心驱动层 (USB Device Core Layer)-----跟硬件无关的 (3) USB设备设备类驱动层 (USB Device Class Layer)-----跟硬件无关的 |
一、控制器驱动层API简介
设备控制器驱动层实现了直接与底层硬件打交道的逻辑(配置IC的寄存器)。所有的设备控制器驱动都应当实现 usb_dc.h 中描述的 API。这样做的好处是当新的 USB 设备集成到系统中时不需要修改上层内容。
所有跟USB控制器驱动相关的API都在头文件usb_dc.h中有声明。Dc:Device_controller
源文件目录:\driver目录,头文件目录:\include\driver目录。
1. USB设备电气使能函数
函数原型 | int usb_dc_attach(void) { ………………………………………………. return 0 } |
函数功能 | 使能USB的PLL,如果使能成功,则USB 设备能够在 USB 总线上进行收发数据、产生中断。 |
参数 | 无 |
返回值 | 成功,则返回0;使能失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
调用示例1:
int usb_enable(struct usb_cfg_data *config) { …………………………………………………………….. int ret; ret = usb_dc_attach(); if (ret < 0) return ret; ……………………………………………………………… return 0; } |
2. 卸载USB设备函数
函数原型 | int usb_dc_detach (void) { ………………………………………………. return 0 } |
函数功能 | 卸载USB设备,执行成功,则USB设备的硬件PLL会被断电,设备失去通信功能。 |
参数 | 无 |
返回值 | 卸载成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
调用示例1:
int usb_disable(void) { int ret; …………………………………………………………. ret = usb_dc_detach(); if (ret < 0) return ret; …………………………………………………………. return 0; } |
注:在USB设备栈中,这两个函数成对使用,并且分别在对应的usb_enable()和usb_disable()中被调用。
3. USB控制器复位函数
函数原型 | int usb_dc_reset(void) { } |
函数功能 | 把USB 设备复位至默认状态 |
参数 | 无 |
返回值 | 复位成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
调用示例1:
/* 删除配置函数 */ int usb_deconfig(void) { ………………………………………………… /* Reset USB controller */ usb_dc_reset(); ……………………………………………… } |
4. 设置Device地址函数
函数原型 | int usb_dc_set_address(const u8_t addr) { } |
函数功能 | 该函数用于设置 USB 设备的地址。 |
参数 | Addr:设备的地址。 |
返回值 | 设置成功,则返回0;设置失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
调用示例1:在标准设备请求的回调函数中被调用
static bool usb_handle_std_device_req(struct usb_setup_packet *setup, s32_t *len, u8_t **data_buf) { …………………………………………………………………….. switch (setup->bRequest) { ……………………………………………………………………… /* 当主机的请求是:设置地址的请求时(这是一个标准请求) */ case REQ_SET_ADDRESS: SYS_LOG_ERR("REQ_SET_ADDRESS, addr=0x%x\n", setup->wValue); usb_dc_set_address(setup->wValue); break; } } |
5. 注册控制器的状态回调函数
函数原型 | int usb_dc_set_status_callback(const usb_dc_status_callback cb) { usb_aotg_ctrl.status_cb = cb; return 0; } |
函数功能 | 所注册的回调函数用于报告设备控制器的状态。状态码由枚举 usb_dc_status_code 进行描述。 |
参数 | 参数是一个函数指针(注册的时候,传入函数名) |
返回值 | 注册成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
usb_dc_status_code的数据结构如下:
enum usb_dc_status_code {
USB_DC_ERROR,
USB_DC_RESET,
USB_DC_CONNECTED,
USB_DC_CONFIGURED,
USB_DC_DISCONNECTED,
USB_DC_SUSPEND,
USB_DC_RESUME,
USB_DC_UNKNOWN
};
返回到注册的设备状态回调函数里面的状态码表
USB_DC_ERROR | 控制器返回的 USB 错误 |
USB_DC_RESET | USB 复位 |
USB_DC_CONNECTED | USB 连接已建立 - 硬件枚举已完成 |
USB_DC_CONFIGURED | USB 配置完成 |
USB_DC_DISCONNECTED | USB 连接断开 |
USB_DC_SUSPEND | USB 连接被主机挂起 |
USB_DC_RESUME | USB 连接被主机恢复 |
USB_DC_UNKNOWN | 初始化的 USB 连接状态 |
调用示例1:以HID设备状态回调为例子(示例仅供参考)
实例化一个HID设备配置
在core.c里面,其中就指定了设备的状态回调函数(hid_status_cb)。如下:
定义处:\subsys\usb\class\hid\core.c
USBD_CFG_DATA_DEFINE(hid) struct usb_cfg_data hid_config = { .usb_device_description = NULL, .interface_config = hid_interface_config, .interface_descriptor = &hid_cfg.if0, .cb_usb_status = hid_status_cb, .interface = { .class_handler = hid_class_handle_req, .custom_handler = hid_custom_handle_req, .payload_data = NULL, }, .num_endpoints = ARRAY_SIZE(hid_ep_data), .endpoint = hid_ep_data, }; |
函数hid_status_cb()在core.c中定义如下:
而hid_config在usb_hid_init()中被调用,如下:
针对usb_audio_dongle这个demo来说,usb_hid_init()又被usb_handler.c中的composite_pre_init()调用如下:
\actions_sdk_demo\usb_audio_dongle\src\usb_handler.c
倒过来分析整个流程:
//初始化入口处
composite_pre_init()[usb_handler.c]----------->调用----------->usb_hid_init()[core.c]
usb_hid_init()[core.c] ----------->调用----------->usb_set_config()/usb_enable()[usb_device.c]
调用usb_set_config() 和usb_enable(),传入的是整个结构体。
int usb_hid_init(void) { …………………………………………………………………………………………………. /* Initialize the USB driver with the right configuration */ ret = usb_set_config(&hid_config); if (ret < 0) { SYS_LOG_ERR("Failed to config USB"); return ret; }
/* Enable USB driver */ ret = usb_enable(&hid_config); if (ret < 0) { SYS_LOG_ERR("Failed to enable USB"); return ret; } ………………………………………………………………………………………………………….. return 0; } |
HID类设备的配置实例结构体里面,指定了状态回调函数。
USBD_CFG_DATA_DEFINE(hid) struct usb_cfg_data hid_config = { …………………………………………………………………………… .cb_usb_status = hid_status_cb, ……………………………………………………………………………… };
/*HID类设备状态回调函数*/ static void hid_status_cb(enum usb_dc_status_code status, u8_t *param) { /* Check the USB status and do needed action if required */ switch (status) { case USB_DC_ERROR: SYS_LOG_ERR("USB device error"); break; case USB_DC_RESET: SYS_LOG_ERR("USB device reset detected"); break; case USB_DC_CONNECTED: SYS_LOG_ERR("USB device connected"); break; case USB_DC_CONFIGURED: SYS_LOG_ERR("USB device configured"); break; case USB_DC_DISCONNECTED: SYS_LOG_ERR("USB device disconnected"); break; case USB_DC_SUSPEND: SYS_LOG_ERR("USB device suspended"); break; case USB_DC_RESUME: SYS_LOG_ERR("USB device resumed"); break; case USB_DC_UNKNOWN: default: SYS_LOG_ERR("USB unknown state"); break; } } |
usb_set_config()里调用了状态注册函数:\subsys\usb\usb_device.c
int usb_set_config(struct usb_cfg_data *config) { …………………………………………………………………………………………………. /* register status callback */ if (config->cb_usb_status != NULL) { usb_register_status_callback(config->cb_usb_status); } ………………………………………………………………………………………………….. return 0; } |
usb_enable()里调用了状态注册函数:ATS350B\subsys\usb\usb_device.c
int usb_set_config(struct usb_cfg_data *config) { …………………………………………………………………………………………………. /* register status callback */ if (config->cb_usb_status != NULL) { usb_register_status_callback(config->cb_usb_status); } ………………………………………………………………………………………………….. return 0; } |
总结:
(1)最终起重要作用的还是这行代码:
usb_register_status_callback(config->cb_usb_status);
|
(2)UAC等其他类,注册状态回调函数的流程跟HID类设备是一样的。
6. 端点配置函数
函数原型 | int usb_dc_ep_configure(const struct usb_dc_ep_cfg_data * const ep_cfg) { } |
函数功能 | 该函数用于配置一个端点 |
参数 | 端点配置结构体指针(传入端点,一个配置用一个结构体来描述) |
返回值 | 配置成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
调用示例1:配置控制输出/输入端点
\subsys\usb\usb_device.c
static int usb_composite_init(struct device *dev) { …………………………………………………………………………. struct usb_dc_ep_cfg_data ep0_cfg; ………………………………………………………………………. /* Configure control EP */ ep0_cfg.ep_mps = MAX_PACKET_SIZE0; ep0_cfg.ep_type = USB_DC_EP_CONTROL;
ep0_cfg.ep_addr = USB_CONTROL_OUT_EP0; ret = usb_dc_ep_configure(&ep0_cfg); if (ret < 0) { return ret; }
ep0_cfg.ep_addr = USB_CONTROL_IN_EP0; ret = usb_dc_ep_configure(&ep0_cfg); if (ret < 0) { return ret; } }
//先填充结构体,再传入整个结构体。 //一个结构体足以描述一个端点 |
7. 端点配置检查函数
函数原型 | int usb_dc_ep_check_cap(const struct usb_dc_ep_cfg_data * const cfg) { } |
函数功能 | 该函数用于检查某个端点的配置是否正确(类型、端点传输的最大包长、地址是否超出范围等) |
参数 | 端点配置结构体指针(传入端点,一个配置用一个结构体来描述) |
返回值 | 成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
调用示例1:这个函数在源码中用得比较少,可以自己尝试:先检查某个端点设置是否合法,如果合法,再进行配置。
8. 设置STALL
函数原型 | int usb_dc_ep_set_stall(const u8_t ep) { } |
函数功能 | 为端点设置STALL条件。STALL:暂停 |
参数 | 端点的地址 |
返回值 | 设置成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
9. 清除STALL
函数原型 | int usb_dc_ep_clear_stall(const u8_t ep) { } |
函数功能 | 为端点清除STALL条件。STALL:暂停 |
参数 | 端点的地址 |
返回值 | 成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
10. 判断某个端点是否是STALL
函数原型 | int usb_dc_ep_is_stalled(const u8_t ep, u8_t *const stalled) { } |
函数功能 | 查询某个端点是否是stall |
参数 | Ep:端点的地址 参数2:stall的状态 |
返回值 | 判断成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
11. 端点停止函数
函数原型 | int usb_dc_ep_halt(const u8_t ep) { } |
函数功能 | 停止端点 |
参数 | Ep:端点的地址 |
返回值 | 成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
12. 端点使能函数
函数原型 | int usb_dc_ep_enable(const u8_t ep) { } |
函数功能 | 该函数用于使能端点,如果使能成功,则相应终点的中断会被使能,并且收发数据已就绪(可进行数据收发)。 |
参数 | Ep:端点的地址 |
返回值 | 使能成功,则返回0;使能失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
示例1:使能控制端点0
int usb_enable(struct usb_cfg_data *config) { ………………………………………………………………………………………… /* enable control EP */ //#define USB_CONTROL_OUT_EP0 0 ret = usb_dc_ep_enable(USB_CONTROL_OUT_EP0); if (ret < 0) return ret;
//#define USB_CONTROL_IN_EP0 0x80 ret = usb_dc_ep_enable(USB_CONTROL_IN_EP0); if (ret < 0) return ret; ……………………………………………………………………………………………..
} |
13. 端点禁止函数
函数原型 | int usb_dc_ep_disable (const u8_t ep) { } |
函数功能 | 该函数用于禁止端点,禁止成功后,相应端点的中断被禁止,不可以进行收发数据。 |
参数 | Ep:端点的地址 |
返回值 | 禁止成功,则返回0;禁止失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
14. 清除端点的FIFO函数
函数原型 | int usb_dc_ep_flush(const u8_t ep); {
} |
函数功能 | 清除端点的FIFO |
参数 | Ep:端点的地址 |
返回值 | 成功,则返回0;失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
15. 端点写数据函数
函数原型 | int usb_dc_ep_write(const u8_t ep, const u8_t *const data, const u32_t data_len, u32_t * const ret_bytes) {
} |
函数功能 | 向指定的端点写数据 |
参数 | Ep:端点地址 Data:指向要发送数据Buffer的指针 data_len:要发送的数据长度。当发送的是0长度的状态数据包时,长度值可设置为0。 ret_bytes:计划传输的字节数。如果想把要发送的数据Buffer里面的数据全都发出去,那么,就设置为NULL。---指针 |
返回值 | 成功,则返回0;使能失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
16. 端点读数据函数
函数原型 | int usb_dc_ep_read(const u8_t ep, u8_t *const data, const u32_t max_data_len, u32_t *const read_bytes) { } |
函数功能 | 读取端点数据。该函数被端点句柄函数所调用,端点产生一个输出中断之后,应用程序使用所提供的 usb_ep_callback 函数间接调用本函数读取端点FIFO这个数据结构里面的数据。调用该函数,可清除端点的NAK,以至于能从主机接收更多的数据。 |
参数 | Ep:端点地址 Data:是一个指针,该指针指向要把数据写入的目标缓存区。 max_data_len:读取的数据的最大字节数。 ret_bytes:读取的字节数(这是个指针)。如果数据指针data指向NULL,并且max_data_len的值为0,那么就返回可供读取的字节数。 |
返回值 | 读数据成功,则返回0;读失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
示例1:以读取主机下发的第一个数据包SETUP包为例
ATS350B\subsys\usb\usb_device.c
/*端点句柄函数*/ static void usb_handle_control_transfer(u8_t ep, enum usb_dc_ep_cb_status_code ep_status) { ……………………………………………………………………………………………………… struct usb_setup_packet *setup = &usb_dev.setup; SYS_LOG_ERR("Transfer: ep=0x%02x, status=0x%02x\n",ep,ep_status); ……………………………………………………………………………………………………….. if (ep == USB_CONTROL_OUT_EP0 && ep_status == USB_DC_EP_SETUP) { SYS_LOG_ERR("Data Direction: Host---->Device\n"); /* * OUT transfer, Setup packet, * reset request message state machine */ /*先读取来自主机的第一个数据包(SETUP包[令牌包])*/ if (usb_dc_ep_read(ep, (u8_t *)setup, sizeof(*setup), NULL) < 0) { SYS_LOG_DBG("Read Setup Packet failed\n"); usb_dc_ep_set_stall(USB_CONTROL_IN_EP0); return; } ……………………………………………………………………………………………………… } |
17. 注册端点状态的回调函数
函数原型 | int usb_dc_ep_set_callback(const u8_t ep, const usb_dc_ep_callback cb) { } |
函数功能 | 用该函数来注册端点的回调函数。比如当端点收到数据,并且数据有效的时候;或者端点完成一次数据传输的时候,都需要一系列的回调函数,要使用这些函数来注册这些回调函数。 如果应用程序代码不需要回调,则为空。回调状态代码由usb_dc_ep_cb_status_code这个数据结构来描述。
在源码中,端点回调状态代码定义为枚举数据结构: /** * USB Endpoint Callback Status Codes */ /* 逻辑端点状态回调码: (1)收到令牌包(SETUP数据包) (2)端点上发生了输出事务,并且数据已经可读[收到来自Host的数据]。 (3)端点上结束了一次输入事务[结束了一次将数据发到Host的过程] */ enum usb_dc_ep_cb_status_code { USB_DC_EP_SETUP, /* SETUP received */ /* Out transaction on this EP, data is available for read */ USB_DC_EP_DATA_OUT, USB_DC_EP_DATA_IN, /* In transaction done on this EP */ };
|
参数 | Ep:端点地址 Cb: 回调函数名。回调的入口函数,回调的入口函数,书写的格式,定义如下。
所在头文件:usb_dc.h 注意它的两个参数,第一个参数是端点号;第二个参数是端点状态。也就是说,如果注册了一个端点状态回调函数,那么当端点状态发生改变的时候,回调函数会被回调,并且可以从回调函数的参数二中获得当前的端点状态,从而在回调函数实现对应的逻辑处理。 回调函数这一块,几乎都是这么一个套路,有注册回调函数的函数,有回调函数,如果发生符合回调的事件,那么回调函数会被调用,一般来说,回调函数被调用时,回调函数内部会传入一些有特定意义的数据,比如:状态码、发送成功的字节数、服务器返回的数据等。
|
返回值 | 注册成功,则返回0;注册失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
18. 端点读等待函数
函数原型 | int usb_dc_ep_read_wait(u8_t ep, u8_t *data, u32_t max_data_len, u32_t *read_bytes) {
} |
函数功能 | 这个函数和端点读数据函数usb_dc_ep_read很类似,形参都是一样的。不同的是,它不会清除端点的NAK信号,这就导致调用方(比如是设备这一端)没法响应更多的请求(收到一次数据之后,就没法收了),直到它处理完这一次的数据。调用者应该通过调用usb_dc_ep_read_continue()来重新激活端点,才能进行数据的接收。 |
参数 | Ep:端点地址 Data:是一个指针,该指针指向要把数据写入的目标缓存区。 max_data_len:读取的数据的最大字节数。 ret_bytes:读取的字节数(这是个指针)。如果数据指针data指向NULL,并且max_data_len的值为0,那么就返回可供读取的字节数。 |
返回值 | 读数据成功,则返回0;读失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
19. 端点读继续函数
函数原型 | int usb_dc_ep_read_continue(u8_t ep) {
} |
函数功能 | 清除端点NAK,使得端点能够接收来自主机的更多数据。通常来说,如果确认用户可以接收更多数据之后,在调用usb_dc_ep_read_wait()之后再调用该函数。 因此,这两个函数成对调用,一起充当数据流控制机制。 |
参数 | Ep:端点地址 |
返回值 | 读数据成功,则返回0;读失败,则返回一个负的错误代码值。 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
20. 获取端点最大包长函数
函数原型 | int usb_dc_ep_mps(u8_t ep) {
} |
函数功能 | 获得端点最大包长 |
参数 | Ep:端点地址 |
返回值 | 端点最大包长 |
定义处(源文件) | \drivers\usb\device\aotg_udc.c |
声明处(头文件) | \include\drivers\usb\usb_dc.h |
21. 小结
(1) 控制器驱动层是直接与底层硬件打交道的,所以是USB设备栈的最下面一层。
(2) 和控制器驱动层相关的源码文件(一个源文件、一个头文件)是:
\drivers\usb\device\aotg_udc.c \include\drivers\usb\usb_dc.h |
(3) 很多API都是成对使用的:
设备电气使能函数<---------------->卸载设备函数 设置STALL函数 <---------------->清除STALL函数 端点使能函数 <---------------->端点禁止函数 端点读数据函数 <---------------->端点写数据函数 端点读等待函数 <---------------->端点读继续函数 |
(4) 注意区分设备状态(也就是控制器状态)和端点状态。
(5) 大部分的API都是直接对端点进行操作,因为,端点(逻辑端点和物理端点)是最底层。
(6) 不同版本的zephyr中API名称或者参数可能有变化,但是调用关系和回调的流程基本上应该是一样的。
下一篇:https://blog.csdn.net/qq_40088639/article/details/110120639