Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
USB
rtthread USB CDC调用流程
发布于 2022-04-17 21:53:36 浏览:2944
订阅该版
[tocm] 上一篇文章讲了[在STM32F407 Disc1上使用RT-Thread CDC](https://club.rt-thread.org/ask/article/3687.html) 这次看一下内部的具体调用流程,顺便解决枚举时间过长问题 # 1 数据结构 ## 1.1 涉及的文件 | files | description | | | ------------------------------------------------------ | ---------------------------- | ---- | | components/drivers/usb/usbdevice/core/usbdevice.c | usb device 注册 | | | components/drivers/usb/usbdevice/core/usbdevice_core.c | rtthread usb内核文件 | | | components/drivers/usb/usbdevice/class/cdc_vcom.c | cdc类注册及数据处理 | | | drivers/drv_usbd.c | 对接STM32 HAL driver 和 内核 | | 对比STM32官方的框架,发现整体框架差不多, 就是把USB中间层换成了rtthread自己的了 ![image-20220415154054301.png](https://oss-club.rt-thread.org/uploads/20220417/4e2c2848fafcf623f0d68051c439457d.png.webp) > 图片来源:CSDN博主「king_jie0210」 > 原文链接:https://blog.csdn.net/king_jie0210/article/details/76713938 ## 1.2 usb device_list 存在一个全局的device_list 管理 udevice设备。 udevice中有两个重要的成员: - cfg_list 管理 uconfig 链表,uconfig->func_list->inf_list 获取接口 - udcd_t 管理具体的内核和Hal层的接口 ![image-20220415155928887.png](https://oss-club.rt-thread.org/uploads/20220417/70a8111ff673824ba8093b6c45328156.png.webp) ## 1.3 cdc creat 另一个重要的链表,主要在cdc_vcom.c中完成构造,这要是设备接口和端点的构造。 ![image-20220415115946355.png](https://oss-club.rt-thread.org/uploads/20220417/14e43bfc9f3ca208a4859b993eb3882a.png.webp) # 2 枚举 ## 2.1 Callbacks 1)首先在drv_usbd.c中实现了USB的中断处理函数USBD_IRQ_HANDLER(OTG_FS_IRQHandler的重定义),里面调用了ST 提供的HAL_PCD_IRQHandler ![image-20220415202241676.png](https://oss-club.rt-thread.org/uploads/20220417/8c332a0b0ee55969a5525a57040a2b0e.png.webp) 2)HAL_PCD_IRQHandler处理不同类型的中断源,然后调用具体的回调函数。这些回调函数在stm32f4xx_hal_pcd.c中均定义为__WEAK弱函数,并未实现具体内容,需要具体协议或接口部分来实现。rtthread把这部分也放在了drv_usbd.c中 ![image-20220415203327103.png](https://oss-club.rt-thread.org/uploads/20220417/90e148256bd2674f4f8d910ea0c4f85b.png.webp) 各回调函数简介如下 | Calllback | Description | Note | | ---------------------------- | ----------------------------------------------------- | ---- | | HAL_PCD_ResetCallback | 复位设备(禁用),设置设备状态为USB_STATE_NOTATTACHED | | | HAL_PCD_SetupStageCallback | Setup令牌包的处理 | | | HAL_PCD_DataInStageCallback | IN令牌包的处理 | | | HAL_PCD_ConnectCallback | 设置设备状态为USB_STATE_ATTACHED | | | HAL_PCD_SOFCallback | SOF令牌包的处理(空函数,不处理) | | | HAL_PCD_DisconnectCallback | 禁用设备,设置设备状态为USB_STATE_NOTATTACHED | | | HAL_PCD_DataOutStageCallback | OUT令牌包的处理 | | | HAL_PCDEx_SetConnectionState | STM32F4上为空函数 | | 下面按照枚举过程分析一下具体的内部处理 ## 2.2 获取设备描述符 大致的调用流程如下: ```c USBD_IRQ_HANDLER ->HAL_PCD_SetupStageCallback ->rt_usbd_ep0_setup_handler -> msg.type = USB_MSG_SETUP_NOTIFY; msg.dcd = dcd; rt_usbd_event_signal(&msg); ->rt_mq_send(&usb_mq, (void*)msg, sizeof(struct udev_msg)) ------------------------------------------------------------------------------------------------------------------------- rt_usbd_thread_entry ->rt_mq_recv(&usb_mq, &msg, sizeof(struct udev_msg),RT_WAITING_FOREVER) switch (msg.type) { case USB_MSG_SETUP_NOTIFY: _setup_request(device, &msg.content.setup); ->_standard_request(device, setup); -> _get_descriptor(device, setup): if(setup->request_type == USB_REQ_TYPE_DIR_IN) { switch(setup->wValue >> 8) { case USB_DESC_TYPE_DEVICE: _get_device_descriptor(device, setup); break; case USB_DESC_TYPE_CONFIGURATION: _get_config_descriptor(device, setup); break; case USB_DESC_TYPE_STRING: _get_string_descriptor(device, setup); break; ``` 1. HAL_PCD_SetupStageCallback调用内核的rt_usbd_ep0_setup_handler,传入usb控制器 _stm_udc 和已经在HAL_PCD_IRQHandler中解析出来的setup包两个参数 2. rt_usbd_ep0_setup_handler向USB内核发送了一条msg, type类型为 `USB_MSG_SETUP_NOTIFY` 3. 在**usbdevice_core.c**创建的rt_usbd_thread_entry 线程接收该消息后处理 4. 根据msg type类型为 `USB_MSG_SETUP_NOTIFY` 调用_setup_request() 5. 根据setup->request_type的请求类型(USB_REQ_TYPE_STANDARD)进一步调用_standard_request 6. 根据setup->request_type的接收者(USB_REQ_TYPE_DEVICE)和setup->bRequest请求码(USB_REQ_GET_DESCRIPTOR)进一步调用_get_descriptor 7. 根据setup->wValue描述符类型(USB_DESC_TYPE_DEVICE),最终调用_get_device_descriptor返回具体的设备描述符 ## 2.3 获取配置描述符 获取配置描述符的流程和获取设备描述符一样,只是最后根据setup->wValue的值选择调用的是 _get_config_descriptor ``` /** * This function will handle get_descriptor bRequest. * * @param device the usb device object. * @param setup the setup bRequest. * * @return RT_EOK on successful. */ static rt_err_t _get_descriptor(struct udevice* device, ureq_t setup) { /* parameter check */ RT_ASSERT(device != RT_NULL); RT_ASSERT(setup != RT_NULL); if(setup->request_type == USB_REQ_TYPE_DIR_IN) { switch(setup->wValue >> 8) { case USB_DESC_TYPE_DEVICE: _get_device_descriptor(device, setup); break; case USB_DESC_TYPE_CONFIGURATION: _get_config_descriptor(device, setup); break; case USB_DESC_TYPE_STRING: _get_string_descriptor(device, setup); break; case USB_DESC_TYPE_DEVICEQUALIFIER: /* If a full-speed only device (with a device descriptor version number equal to 0200H) receives a GetDescriptor() request for a device_qualifier, it must respond with a request error. The host must not make a request for an other_speed_configuration descriptor unless it first successfully retrieves the device_qualifier descriptor. */ if(device->dcd->device_is_hs) { _get_qualifier_descriptor(device, setup); } else { rt_usbd_ep0_set_stall(device); } break; case USB_DESC_TYPE_OTHERSPEED: _get_config_descriptor(device, setup); break; default: rt_kprintf("unsupported descriptor request\n"); rt_usbd_ep0_set_stall(device); break; } } else { rt_kprintf("request direction error\n"); rt_usbd_ep0_set_stall(device); } return RT_EOK; } ``` ## 2.4 获取字符串描述符 同上,最后根据setup->wValue的值选择调用的是 _get_string_descriptor ## 2.5 设置配置 set configuration本身也属于标准请求 ``` ->_standard_request(device, setup); ->_set_config(device, setup); ``` _set_config处理如下: 1. 设置 device->curr_cfg = cfg; 2. dcd_set_config(device->dcd, value); 3. 使能端点 4. FUNC_ENABLE(func) 使能function, 准备接受主机数据 > set configuration意味着设备枚举完成,可以正常接受数据了 > > 稍后我们会在涉及到FUNC_ENABLE(func) ## 2.6 枚举时间过长 在上一篇文章中,发现当前rtthread CDC 设备枚举时间过长,大概8s左右,实在不能接受。 ### 2.6.1 原因 这次直接上分析仪看下 ![image-20220416123503406.png](https://oss-club.rt-thread.org/uploads/20220417/1b9c38ba830a918080a4d381ebc7d545.png) 从抓包来看时间主要浪费在了获取 DeviceQualifier。 显示具体细节,发现主机一直在等待设备对该请求的明确回应,但设备端一直在回复NAK,浪费了很多时间 ![image-20220416123645237.png](https://oss-club.rt-thread.org/uploads/20220417/0df82ac4e2a7b477f5d745dff6d01c10.png) ### 2.6.2 DeviceQualifier Descriptor(设备限定描述符) 设备限定描述符(Device Qualifier Descriptor)说明了能进行高速操作的设备在其他速度时产生的变化信息。例如,如果设备当前在全速下操作,设备限定描述符返回它如何在高速运行的信息。 如果设备既支持全速状态又支持高速状态,那么就必须含有设备限定描述符(Device Qualifier Descriptor)。设备限定描述符(Device Qualifier Descriptor)中含有当前没有使用的速度下这些字段的取值。 如果只能进行全速(full-speed)操作的设备(设备描述符的版本号等于0200H)接收到请求设备限定符的Get Descriptor请求,它必须用请求错误响应,回复STALL来响应。 主机端只能在成功取得设备限定描述符(Device Qualifier Descriptor)之后,才能请求其他速度配置(other_speed_configuration)描述符。 > 以上摘自CSDN:https://blog.csdn.net/u012028275/article/details/109276309 ### 2.6.3 解决 STM32F407-Disc虽然支持High speed ,但是需要外加PHY才行,当前工程默认使用的还是Full speed Device。 从2.6.2可知在接收到请求设备限定符的Get Descriptor请求时,应该及时返回STALL握手包告知主机: 设备不支持限定描述符,无法执行这个请求。 开始review codes, 参考第3节首先找到Get DeviceQualifier Descriptor标准请求最后的处理: ![image-20220416125403083.png](https://oss-club.rt-thread.org/uploads/20220417/5a0965455722be186c928653cbd984a5.png.webp) 在_get_descriptor里确实对不同speed的设备做了处理, 也有stall的处理。继续深入 ``` rt_usbd_ep0_set_stall(device); ->dcd_ep_set_stall(device->dcd, 0); ->dcd->ops->ep_set_stall(0); ->_ep_set_stall(0) ->HAL_PCD_EP_SetStall(&_stm_pcd, 0); ``` 最后还是由HAL函数HAL_PCD_EP_SetStall处理 ![image-20220416152550929.png](https://oss-club.rt-thread.org/uploads/20220417/a2b24ef25be7dcd2c40780272cc261ee.png.webp) 底层寄存器操作: ![image-20220416171835906.png](https://oss-club.rt-thread.org/uploads/20220417/4afba635c068f7e60ff8b18c55d05ba7.png) 通过代码可以看出,当前传入的ep_addr是0, 那么ep->is_in= 0,最终设置的是端点0的DOEPCTL的STALL域为1。但是当前是输入事务,应该设置DIEPCTL的STALL域为1。参考一下ST官方的做法,是区分0x80和0x0的 ``` /** * @brief USBD_CtlError * Handle USB low level Error * @param pdev: device instance * @param req: usb request * @retval None */ void USBD_CtlError(USBD_HandleTypeDef *pdev, USBD_SetupReqTypedef *req) { UNUSED(req); (void)USBD_LL_StallEP(pdev, 0x80U); (void)USBD_LL_StallEP(pdev, 0U); } /** * @brief Sets a Stall condition on an endpoint of the Low Level Driver. * @param pdev: Device handle * @param ep_addr: Endpoint number * @retval USBD status */ USBD_StatusTypeDef USBD_LL_StallEP(USBD_HandleTypeDef *pdev, uint8_t ep_addr) { HAL_StatusTypeDef hal_status = HAL_OK; USBD_StatusTypeDef usb_status = USBD_OK; hal_status = HAL_PCD_EP_SetStall(pdev->pData, ep_addr); usb_status = USBD_Get_USB_Status(hal_status); return usb_status; } ``` ST的做法是一次性把端点0的输入和输出全部STALL, CherryUSB的做法只处理了输入部分 ``` /* Default USB control EP, always 0 and 0x80 */ #define USB_CONTROL_OUT_EP0 0 #define USB_CONTROL_IN_EP0 0x80 ``` ![image-20220416180931257.png](https://oss-club.rt-thread.org/uploads/20220417/3797cf5250bad61595bd436bf3230555.png.webp) 1.STALL握手包均是由device发向Host,即均是由IN令牌包处理的,涉及的是端点0的输入方向 - IN事务,设备直接在IN令牌包后,回复Data/NAK/ACK - OUT事务,设备先接收数据,然后根据情况发送ACK/NAK/STALL(批量事务还存在NYET) 如果OUT方向STALL,回复STALL握手包这是协议规定的; IN方向设置了STALL,也会导致IN状态包回复一个STALL握手包,效果一样。 2.控制断点0的输出方向其实不受状态的影响(为了保证setup包能被一直接收成功),而且一旦接收到setup包会自动清零两个方向 所以我们直接按照CherryUSB的做法,直接设置0x80输入方向的STALL即可。 ![image-20220416183559074.png](https://oss-club.rt-thread.org/uploads/20220417/a5888a21b2f188d6e46d778c24903200.png.webp) > 非控制端点0,STALL是需要根据端点方向设置的 > > ``` > rt_err_t rt_usbd_ep_set_stall(udevice_t device, uep_t ep) > { > rt_err_t ret; > > RT_ASSERT(device != RT_NULL); > RT_ASSERT(ep != RT_NULL); > RT_ASSERT(ep->ep_desc != RT_NULL); > > ret = dcd_ep_set_stall(device->dcd, EP_ADDRESS(ep)); > if(ret == RT_EOK) > { > ep->stalled = RT_TRUE; > } > > return ret; > } > ``` 重新编译后,不支持的Get DeviceQualifier Descriptor,设备很快返回了STALL握手包,枚举时间正常1s以内。 ![image-20220416183524572.png](https://oss-club.rt-thread.org/uploads/20220417/1fd7aefec879d8c975f953567d0e26f6.png) > 有没有发现,获取描述符很有意思: > > 就像一个痴男或者痴女(Host),一直在向另一方(Device)要求一个结果(IN package) > > 接受(Return Data): Host接收确认后,再发一个Out状态确认一下,圆满了,恭喜 ! > > 模棱两可(NAK) : 我还没准备好啊!(害苦了痴男痴女,一直不停询问) > > 明确拒绝(STALL) : 痴男痴女瞬间觉醒,不再坚持 # 3 数据传输 除了默认的控制端点0和CDC Communication类接口使用了一个中断输入端点,CDC Data类接口中还使用了一对批量端点,工程中使用的是批量输入端点1,批量输出端点1。下面章节主要说明批量端点上的数据传输。 ![image-20220417104308972.png](https://oss-club.rt-thread.org/uploads/20220417/62b53844eed8f6f3d7a679aee6e3eb56.png.webp) BULK端点数据收发均由usbdevice_core.c中的 rt_usbd_io_request函数完成 ``` rt_size_t rt_usbd_io_request(udevice_t device, uep_t ep, uio_request_t req) { rt_size_t size = 0; RT_ASSERT(device != RT_NULL); RT_ASSERT(req != RT_NULL); if(ep->stalled == RT_FALSE) { switch(req->req_type) { case UIO_REQUEST_READ_BEST: case UIO_REQUEST_READ_FULL: ep->request.remain_size = ep->request.size; size = rt_usbd_ep_read_prepare(device, ep, req->buffer, req->size); break; case UIO_REQUEST_WRITE: ep->request.remain_size = ep->request.size; size = rt_usbd_ep_write(device, ep, req->buffer, req->size); break; default: rt_kprintf("unknown request type\n"); break; } } else { rt_list_insert_before(&ep->request_list, &req->list); RT_DEBUG_LOG(RT_DEBUG_USB, ("suspend a request\n")); } return size; } ``` ## 3.1 workflows 1. rt_usbd_io_request 发起一个USB端点的读或写请求 2. 数据传输完成触发USBD_IRQ_HANDLER,然后调用drv_usbd.c中的HAL_PCD_DataOutStageCallback/HAL_PCD_DataInStageCallback 3. 调用rt_usbd_ep_out_handler(&_stm_udc, epnum, hpcd->OUT_ep[epnum].xfer_count)或 rt_usbd_ep_in_handler(&_stm_udc, 0x80 | epnum, hpcd->IN_ep[epnum].xfer_count) 4. 向内核发送一个usb_mq消息,type类型为 `USB_MSG_DATA_NOTIFY` 5. 在**usbdevice_core.c**创建的rt_usbd_thread_entry 线程接收该消息后处理 6. 根据msg type类型为 `USB_MSG_DATA_NOTIFY` 调用`_data_notify()` ``` USBD_IRQ_HANDLER ->HAL_PCD_DataOutStageCallback/HAL_PCD_DataInStageCallback ->rt_usbd_ep_out_handler(&_stm_udc, epnum, hpcd->OUT_ep[epnum].xfer_count) /rt_usbd_ep_in_handler(&_stm_udc, 0x80 | epnum, hpcd->IN_ep[epnum].xfer_count) -> msg.type = USB_MSG_DATA_NOTIFY; msg.dcd = dcd; msg.content.ep_msg.ep_addr = address; msg.content.ep_msg.size = size; rt_usbd_event_signal(&msg); ->rt_mq_send(&usb_mq, (void*)msg, sizeof(struct udev_msg)) ----------------------------------------------------------------------------------------------------------------------------- rt_usbd_thread_entry ->rt_mq_recv(&usb_mq, &msg, sizeof(struct udev_msg),RT_WAITING_FOREVER) _data_notify(device, &msg.content.ep_msg); ``` > 到了`_data_notify`意味着已经收到或者发送了一包数据,下面是处理剩余的数据,是否再次发起rt_usbd_io_request请求 ## 3.2 ep_out ### 3.2.1 _function_enable 如我们经常使用的主机设备模型(设备一直等待接收host的命令,然后处理), CDC类设备也要时刻准备这接收Host的发来的数据 回顾一下2.4中提到的FUNC_ENABLE(func),它在set configuration后被调用 ``` #define FUNC_ENABLE(func) do{ \ if(func->ops->enable != RT_NULL && \ func->enabled == RT_FALSE) \ { \ if(func->ops->enable(func) == RT_EOK) \ func->enabled = RT_TRUE; \ } \ ``` 该宏最后调用的是cdc_vcom.c中的`_function_enable`。很明显在set configuration 完成后,做的第一件事情就是,准备接收数据。 ![image-20220417101915524.png](https://oss-club.rt-thread.org/uploads/20220417/f73bc85e4a2e99b2a4228310179dd78f.png.webp) 接收到Host发送的数据,处理主要在_data_notify的else分支,而且ep->request.req_type == UIO_REQUEST_READ_BEST,所以直接进入 ![image-20220417164421233.png](https://oss-club.rt-thread.org/uploads/20220417/8d21c56312e43b47833be581fee55b5a.png.webp) EP_HANDLER根据ep端点最终调用的是vcom_cdc.c中的_ep_out_handler函数,主要完成: 1. 把接收的数据发放vcom设备的rx_ringbuffer中 2. 通知serial设备 3. 再次发起一个设备读请求,准备接收下一包数据 ``` static rt_err_t _ep_out_handler(ufunction_t func, rt_size_t size) { rt_uint32_t level; struct vcom *data; RT_ASSERT(func != RT_NULL); RT_DEBUG_LOG(RT_DEBUG_USB, ("_ep_out_handler %d\n", size)); data = (struct vcom*)func->user_data; /* ensure serial is active */ if((data->serial.parent.flag & RT_DEVICE_FLAG_ACTIVATED) && (data->serial.parent.open_flag & RT_DEVICE_OFLAG_OPEN)) { /* receive data from USB VCOM */ level = rt_hw_interrupt_disable(); rt_ringbuffer_put(&data->rx_ringbuffer, data->ep_out->buffer, size); rt_hw_interrupt_enable(level); /* notify receive data */ rt_hw_serial_isr(&data->serial,RT_SERIAL_EVENT_RX_IND); } data->ep_out->request.buffer = data->ep_out->buffer; data->ep_out->request.size = EP_MAXPACKET(data->ep_out); data->ep_out->request.req_type = UIO_REQUEST_READ_BEST; rt_usbd_io_request(func->device, data->ep_out, &data->ep_out->request); return RT_EOK; } ``` ### 3.2.2 size< wMaxPacketSize ![image-20220417165823535.png](https://oss-club.rt-thread.org/uploads/20220417/96fbe89abd51a35b2d3d41c3cb9cf9d4.png) 这种情况下,主机接收到数据小于最大包长MPS, 认为传输完成 ### 3.2.3 size == wMaxPacketSize*n ![image-20220417170942590.png](https://oss-club.rt-thread.org/uploads/20220417/c9b4d71c9850bb0f1f8e741b2b42ff05.png) 因为设备端都是按照MPS接收的,一个或多个包 ### 3.2.4 size > wMaxPacketSize && size % wMaxPacketSize !=0 ![image-20220417171117082.png](https://oss-club.rt-thread.org/uploads/20220417/e48ed8e7b81ed21d49e6496932e2f97a.png) 多MPS包,和一个不够MSP长度的包(结束包) ## 3.3 ep_in ### 3.3.1 发送数据流程 按照当前的CDC结构,由注册的一个serial设备,调用rt_device_write向其tx_ringbuffer写入数据 ![image-20220417191541343.png](https://oss-club.rt-thread.org/uploads/20220417/f44552d0960efb5e7c2eb4ee550e5056.png.webp) 在cdc_vcom.c里注册了一个vcom_tx_thread_entry线程 ```c while(rt_ringbuffer_data_len(&data->tx_ringbuffer)) { level = rt_hw_interrupt_disable(); res = rt_ringbuffer_get(&data->tx_ringbuffer, ch, CDC_BULKIN_MAXSIZE); rt_hw_interrupt_enable(level); ..... rt_completion_init(&data->wait); data->ep_in->request.buffer = ch; data->ep_in->request.size = res; data->ep_in->request.req_type = UIO_REQUEST_WRITE; rt_usbd_io_request(func->device, data->ep_in, &data->ep_in->request); if (rt_completion_wait(&data->wait, VCOM_TX_TIMEOUT) != RT_EOK) { RT_DEBUG_LOG(RT_DEBUG_USB, ("vcom tx timeout\n")); } if(data->serial.parent.open_flag & RT_DEVICE_FLAG_INT_TX) { rt_hw_serial_isr(&data->serial,RT_SERIAL_EVENT_TX_DONE); rt_event_send(&data->tx_event, CDC_TX_HAS_SPACE); } ``` > 代码部分省略精简 vcom_tx_thread_entry 主要做了几件事: 1)查询tx_ringbuffer是否有数据,无数据继续查询 2)发现数据,发起一个USB IO写请求,向Host发送数据 3)如ep_out一样,会进入`_data_notify`,但走的是if分支 ![image-20220417193528868.png](https://oss-club.rt-thread.org/uploads/20220417/ff9e08d7607662eba8c0ad93eaa1b219.png.webp) 1. 如果剩余待发送的数据size > MPS,发送一个MPS长度的包 2. 如果剩余待发送的数据size>0 (size<= MPS) ,发生剩余长度 3. 如果剩余待发送的数据size==0,数据全部发生完成进入EP_HANDLER,具体是进入_ep_in_handler,完成data->wait完成量 ``` static rt_err_t _ep_in_handler(ufunction_t func, rt_size_t size) { struct vcom *data; rt_size_t request_size; RT_ASSERT(func != RT_NULL); data = (struct vcom*)func->user_data; request_size = data->ep_in->request.size; RT_DEBUG_LOG(RT_DEBUG_USB, ("_ep_in_handler %d\n", request_size)); if ((request_size != 0) && ((request_size % EP_MAXPACKET(data->ep_in)) == 0)) { /* don't have data right now. Send a zero-length-packet to * terminate the transaction. * * FIXME: actually, this might not be the right place to send zlp. * Only the rt_device_write could know how much data is sending. */ data->in_sending = RT_TRUE; data->ep_in->request.buffer = RT_NULL; data->ep_in->request.size = 0; data->ep_in->request.req_type = UIO_REQUEST_WRITE; rt_usbd_io_request(func->device, data->ep_in, &data->ep_in->request); return RT_EOK; } rt_completion_done(&data->wait); return RT_EOK; } ``` 4.vcom_tx_thread_entry等到data->wait完成量,通知serial设备,发送完成 ### 3.3.2 size < wMaxPacketSize 只进入_data_notify一次,然后调用_ _ep_in_handler ![image-20220417194845649.png](https://oss-club.rt-thread.org/uploads/20220417/baf2944b40461e0ab4dc91fec6ffbb94.png) ### 3.3.3 size == wMaxPacketSize*n _ep_in_handler里需要追加一个ZLP包,告知Host传输结束,由于ringbuffer的使用,不太好抓,暂不展示 ### 3.3.4 size > wMaxPacketSize && size % wMaxPacketSize !=0 多个MPS 包 + 一个小于 MPS作为结束包 # 4.总结 总体来说,rtthread USB协议栈和CherryUSB,ST官方的整体调用框架差不多,但比CherryUSB和ST官方的稍显复杂,主要体现在它的数据结构上,初看有点懵。 另外它的描述符部分做的不太好,先定义了全局Const结构体,然后在注册构造设备时又malloc空间,memcpy到内存,感觉没什么必要。 还有就是不太建议在当前CDC基础上直接添加复杂用户协议(可以参考它,改成Customer自定义设备),如果是用于UART设备倒是可以的。
12
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
blta
这家伙很懒,什么也没写!
文章
12
回答
9
被采纳
2
关注TA
发私信
相关文章
1
请教USB Host
2
STM32F4调试USB 读卡器(Slave)提示格式化
3
急求 STM32F4 USB Device MSC+SD 的相关问题
4
USB 框架问题
5
USB键盘
6
LPC17xx 如何添加USB HOST设备
7
RT-Thread目前支持USB HOST了吗?
8
USB HOST的支持问题
9
RTT 2.0.1 USB存储设备问题,枚举到USBREQ_GET_MAX_LUN后复位
10
USB库已经很久没更新了
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
ART-Pi
FinSH
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
rt-smart
FAL
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
cubemx
PWM
flash
packages_软件包
freemodbus
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
keil_MDK
SFUD
rt_mq_消息队列_msg_queue
C++_cpp
at_device
ulog
本月问答贡献
出出啊
1516
个答案
342
次被采纳
小小李sunny
1440
个答案
289
次被采纳
张世争
793
个答案
171
次被采纳
crystal266
547
个答案
161
次被采纳
whj467467222
1222
个答案
148
次被采纳
本月文章贡献
出出啊
1
篇文章
1
次点赞
小小李sunny
1
篇文章
1
次点赞
张世争
1
篇文章
4
次点赞
crystal266
2
篇文章
2
次点赞
whj467467222
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部