USB学习【13】STM32+USB吸收数据过程详解

打印 上一主题 下一主题

主题 1651|帖子 1651|积分 4953

1.官方的描述




2.HAL的流程

以上的官方说法我们暂时按下不表。
如果吸收到数据,会激活中断进入到USB_LP_CAN1_RX0_IRQHandler()->HAL_PCD_IRQHandler()
因为所有的中断事件共用一个中断函数,以是第一步要进行中断类型检测,中断的信息都写在了ISTR寄存器中,判断代码如下:
  1.   if (__HAL_PCD_GET_FLAG(hpcd, USB_ISTR_CTR))
  2.   {
  3.     /* servicing of the endpoint correct transfer interrupt */
  4.     /* clear of the CTR flag into the sub */
  5.     (void)PCD_EP_ISR_Handler(hpcd);
  6.   }
复制代码
下一步自然而然的就是执行这个函数:
  1. (void)PCD_EP_ISR_Handler(hpcd);
复制代码
既然进入中断了,我们还是要验证一下ISTR的精确传输中断有没有置位
  1. while ((hpcd->Instance->ISTR & USB_ISTR_CTR) != 0U)
复制代码

技术手册提示:可以通过DIR和EPID来判断是哪个端点,接下来的代码也是符合这个步骤的,下面的步骤是判断了哪个端点,暂时没有判断方向,一些逻辑执行完毕后,就会判断方向了,具体看后面的代码。
  1. wIstr = hpcd->Instance->ISTR;
  2. /* extract highest priority endpoint number */
  3. epindex = (uint8_t)(wIstr & USB_ISTR_EP_ID);
  4. if (epindex == 0U)
  5. {
  6. }
  7.   else if (epindex == 1U)
  8.   {
  9.   //我们真正使用的端点
  10.   }
  11.    
复制代码
下一步拿到端点寄存器的数据,看看CTR_RX有没有吸收到数据
如果吸收到第一步就是把标记位清除掉
   下面的图片,发现精确吸收标记位置1,CTRM置位,就会产生中断,CTRM位说,如果中断状态寄存器置1就会产生中断,这里面就说明任何端点精确吸收置1后,中断状态寄存器也置1,这是一个联动反应
  1.   wEPVal = PCD_GET_ENDPOINT(hpcd->Instance, epindex);
  2.   if ((wEPVal & USB_EP_CTR_RX) != 0U)
  3.   {
  4.     /* clear int flag */
  5.     PCD_CLEAR_RX_EP_CTR(hpcd->Instance, epindex);
复制代码

紧接着,HAL库把OUT_ep结构体拿过来,这里面记载了所有规定的端点信息,在初始化阶段把这部门信息预先储存到结构体里面的。
  1. ep = &hpcd->OUT_ep[epindex];
  2.         /* OUT Single Buffering */
  3.         if (ep->doublebuffer == 0U)
  4.         {
  5.           count = (uint16_t)PCD_GET_EP_RX_CNT(hpcd->Instance, ep->num);
  6.           if (count != 0U)
  7.           {
  8.             USB_ReadPMA(hpcd->Instance, ep->xfer_buff, ep->pmaadress, count);
  9.           }
  10.         }
复制代码
此中 count = (uint16_t)PCD_GET_EP_RX_CNT(hpcd->Instance, ep->num);这条语句,
  1. //先拿到对应端点缓冲表USB_COUNTn_RX的数据
  2. #define PCD_EP_RX_CNT(USBx, bEpNum) ((uint16_t *)((((uint32_t)(USBx)->BTABLE\
  3.                                                     + ((uint32_t)(bEpNum) * 8U) + 6U) * PMA_ACCESS) + ((uint32_t)(USBx) + 0x400U)))
复制代码
  1. //把USB_COUNTn_RX中的高于9位的信息删除掉,只保留COUNTn_RX(实际接收到的数据)
  2. #define PCD_GET_EP_RX_CNT(USBx, bEpNum)        ((uint32_t)(*PCD_EP_RX_CNT((USBx), (bEpNum))) & 0x3ffU)
复制代码

到了对关键的一步,就数据从缓冲区里面,拷贝到用户的自定义区
  1. if (count != 0U)
  2.               {
  3.                 USB_ReadPMA(hpcd->Instance, ep->xfer_buff, ep->pmaadress, count);
  4.               }
复制代码
留意一下参数:从上面看,最后吸收到的数据,其实是放在对应端点结构体的xfter_buff里面的

把吸收到的数据从PMA拷贝到用户本身定义的空间中

  1. /**
  2.   * @brief Copy data from packet memory area (PMA) to user memory buffer
  3.   * @param   USBx USB peripheral instance register address.
  4.   * @param   pbUsrBuf pointer to user memory area.
  5.   * @param   wPMABufAddr address into PMA.
  6.   * @param   wNBytes no. of bytes to be copied.
  7.   * @retval None
  8.   */
  9. /**
  10.   * @brief Copy data from packet memory area (PMA) to user memory buffer
  11.   * @param   USBx USB peripheral instance register address.
  12.   * @param   pbUsrBuf pointer to user memory area.
  13.   * @param   wPMABufAddr address into PMA.
  14.   * @param   wNBytes no. of bytes to be copied.
  15.   * @retval None
  16.   */
  17. void USB_ReadPMA(USB_TypeDef *USBx, uint8_t *pbUsrBuf, uint16_t wPMABufAddr, uint16_t wNBytes)
  18. {
  19.   // 计算需要处理的16位数据单元的数量(每个单元包含2个字节),比如接收了64字节,>>1,变成了32字节
  20.   uint32_t n = (uint32_t)wNBytes >> 1;
  21.   // 计算USB外设的基地址
  22.   uint32_t BaseAddr = (uint32_t)USBx;
  23.   // 用于循环计数和临时存储数据的变量
  24.   uint32_t i, temp;
  25.   // 定义一个指向PMA中16位数据的指针
  26.   __IO uint16_t *pdwVal;
  27.   // 定义一个指向用户缓冲区的指针
  28.   uint8_t *pBuf = pbUsrBuf;
  29.   // 计算PMA中目标数据的起始地址
  30.   pdwVal = (__IO uint16_t *)(BaseAddr + 0x400U + ((uint32_t)wPMABufAddr * PMA_ACCESS));
  31.   // 循环处理每个16位数据单元
  32.   for (i = n; i != 0U; i--)
  33.   {
  34.     // 从PMA中读取一个16位数据
  35.     temp = *(__IO uint16_t *)pdwVal;
  36.     pdwVal++;
  37.     // 将16位数据的低8位存储到用户缓冲区
  38.     *pBuf = (uint8_t)((temp >> 0) & 0xFFU);
  39.     pBuf++;
  40.     // 将16位数据的高8位存储到用户缓冲区
  41.     *pBuf = (uint8_t)((temp >> 8) & 0xFFU);
  42.     pBuf++;
  43.     // 如果PMA访问的步长大于1,则跳过一个额外的单元(可能是因为硬件设计)
  44. #if PMA_ACCESS > 1U
  45.     pdwVal++;
  46. #endif
  47.   }
  48.   // 如果剩余的字节数不是2的倍数(即还有1个字节未处理)
  49.   if ((wNBytes % 2U) != 0U)
  50.   {
  51.     // 从PMA中读取最后一个字节
  52.     temp = *pdwVal;
  53.     *pBuf = (uint8_t)((temp >> 0) & 0xFFU);
  54.   }
  55. }
  56. #endif /* defined (USB) */
复制代码
吸收完成标记
可以看到这里有一个判断条件if ((ep->xfer_len == 0U) || (count <= ep->maxpacket)),此中ep->xfer_len == 0U)是给控制传输用的,他需要严格的控制包数,count <= ep->maxpacket是给中断传输用的,只要不大于最大包,就是传输完成(短包)
  1.    /* multi-packet on the NON control OUT endpoint */
  2.     ep->xfer_count += count;
  3.     ep->xfer_buff += count;
  4.     if ((ep->xfer_len == 0U) || (count <= ep->maxpacket))
  5.     {
  6.       /* RX COMPLETE */
复制代码
3.处理吸收到的数据



  1.         USBD_StatusTypeDef USBD_LL_DataOutStage(USBD_HandleTypeDef *pdev,
  2.                                         uint8_t epnum, uint8_t *pdata)
  3. {
  4.   USBD_EndpointTypeDef *pep;
  5.   if (epnum == 0U)
  6.   {
  7.                 //一些实现代码
  8.   }
  9.   else if ((pdev->pClass->DataOut != NULL) &&
  10.            (pdev->dev_state == USBD_STATE_CONFIGURED))
  11.   {
  12.     pdev->pClass->DataOut(pdev, epnum);
  13.   }
  14.   else
  15.   {
  16.     /* should never be in this condition */
  17.     return USBD_FAIL;
  18.   }
  19.   return USBD_OK;
  20. }
复制代码
  1. USBD_ClassTypeDef  USBD_CUSTOM_HID =
  2. {
  3.   USBD_CUSTOM_HID_Init,
  4.   USBD_CUSTOM_HID_DeInit,
  5.   USBD_CUSTOM_HID_Setup,
  6.   NULL, /*EP0_TxSent*/
  7.   USBD_CUSTOM_HID_EP0_RxReady, /*EP0_RxReady*/ /* STATUS STAGE IN */
  8.   USBD_CUSTOM_HID_DataIn, /*DataIn*/
  9.   USBD_CUSTOM_HID_DataOut,
  10.   NULL, /*SOF */
  11.   NULL,
  12.   NULL,
  13.   USBD_CUSTOM_HID_GetHSCfgDesc,
  14.   USBD_CUSTOM_HID_GetFSCfgDesc,
  15.   USBD_CUSTOM_HID_GetOtherSpeedCfgDesc,
  16.   USBD_CUSTOM_HID_GetDeviceQualifierDesc,
  17. };
复制代码
阅读上面代码,发现最终执行了pdev->pClass->DataOut(pdev, epnum);这个函数指针。
下图是在初始化阶段就绑定好的函数指针,可以找到真正执行的函数名字是
USBD_CUSTOM_HID_DataOut->
  1. /**
  2. /**
  3.   * @brief  USBD_CUSTOM_HID_DataOut
  4.   *         handle data OUT Stage
  5.   * @param  pdev: device instance
  6.   * @param  epnum: endpoint index
  7.   * @retval status
  8.   */
  9. static uint8_t  USBD_CUSTOM_HID_DataOut(USBD_HandleTypeDef *pdev,
  10.                                         uint8_t epnum)
  11. {
  12.   USBD_CUSTOM_HID_HandleTypeDef     *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)pdev->pClassData;
  13.   ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(epnum,hhid->Report_buf[0],
  14.                                                             hhid->Report_buf[1]);
  15.   USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR, hhid->Report_buf,
  16.                          USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
  17.   return USBD_OK;
  18. }
复制代码
留意这个代码,我们吸收到的数据,第一个字节是陈诉描述符ID号码
  1.   ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->OutEvent(epnum,hhid->Report_buf[0],
  2.                                                             hhid->Report_buf[1]);
复制代码
最终把数据储存到了USB_Recive_Buffer = hhid->Report_buf;
并且调用了这个函数来剖析数据HID_RxCpltCallback((uint8_t)epnum,&USB_Recive_Buffer);
  1. /**
  2.   * @brief  Manage the CUSTOM HID class events
  3.   * @param  event_idx: Event index
  4.   * @param  state: Event state
  5.   * @retval USBD_OK if all operations are OK else USBD_FAIL
  6.   */
  7. static int8_t CUSTOM_HID_OutEvent_FS(uint8_t epnum, uint8_t event_idx, uint8_t state)
  8. {
  9.     /* USER CODE BEGIN 6 */
  10.     USBD_CUSTOM_HID_HandleTypeDef  *hhid = (USBD_CUSTOM_HID_HandleTypeDef*)hUsbDeviceFS.pClassData;
  11.     for( uint8_t i  = 0;i <USBD_CUSTOMHID_OUTREPORT_BUF_SIZE;i++){
  12.         USB_Recive_Buffer[i] = hhid->Report_buf[i];
  13.     }
  14.     HID_RxCpltCallback((uint8_t)epnum,&USB_Recive_Buffer);
  15.     return (USBD_OK);
  16.     /* USER CODE END 6 */
  17. }
复制代码
4.最后再次开启预备吸收工作



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

梦见你的名字

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表