打印
[STM32F4]

授人以渔 第二节:基于stm32f407usb例程讲解USB运行机制(下)

[复制链接]
4306|3
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 z755924843 于 2017-3-14 10:47 编辑

由于一个帖子写不下,只能分成两个来写了
三.终于进入正题了,按照这个思路,看看在程序中是如何执行的:
在main.c中的
只调用MX_USB_DEVICE_Init();
这个函数usb_device.c中定义的

/* init function*/                                       
void MX_USB_DEVICE_Init(void)
{
  /* Init Device Library,AddSupported Class and Start the library*/
  USBD_Init(&hUsbDeviceFS,&FS_Desc, DEVICE_FS);
  USBD_RegisterClass(&hUsbDeviceFS,&USBD_HID);
  USBD_Start(&hUsbDeviceFS);
}
这个函数的作用是,将usb的中的结构体都赋上值,因为在这个库里大部分的函数、变量都是一个指针的方式实现的,这也是为什么usb底层库移植性这么好多原因。
1).USBD_Init(&hUsbDeviceFS,&FS_Desc, DEVICE_FS);
作用是将描述符的结构体赋值给总的结构体
2).USBD_RegisterClass(&hUsbDeviceFS,&USBD_HID);
作用是将USBD_HID 结构体指针赋值给总的结构体
Usbd_hid 中定义了hid设备的处理函数
3).USBD_Start(&hUsbDeviceFS);
基于hUsbDeviceFS开始运行usb设备,当这个函数执行后,整个usb就运行起来,在stm32中usb有自己的堆栈空间,在开始运行之后,usb就基于中断开始运行了。

当整个运行起来之后,我们看看枚举过程中对应的stm32库中具体的函数。
我们可以在在stm32f4xx_it.c中找到usb的总中断函数
void OTG_FS_IRQHandler(void)
{
HAL_PCD_IRQHandler(&hpcd_USB_OTG_FS);
}
调用是HAL_PCD_IRQHandler(&hpcd_USB_OTG_FS);
函数位于stm32f4xx_hal_pcd.c中
在这个函数中找到了一个名为:HAL_PCD_SetupStageCallback(hpcd);的函数这个函数就是枚举过程中使用到的函数,这个函数是一个用weak定义的弱函数(关于weak的用法大家自行百度吧),这样就更好的实现了可移植性,在hid工程中定义在usbd_conf.c中
这个函数调用了
USBD_LL_SetupStage((USBD_HandleTypeDef*)hpcd->pData,(uint8_t *)hpcd->Setup);
这个函数,这个函数就是底层用来实现枚举过程的。而这个函数实际定义在usbd_core.c中
这样是不是就实现分层了,这是一个很好的分层方式,也是主流嵌入式软件的分层方式。
函数定义如下:
/**
* @brief  USBD_SetupStage
*         Handlethe setup stage
* @param  pdev: deviceinstance
* @retval status
*/
USBD_StatusTypeDefUSBD_LL_SetupStage(USBD_HandleTypeDef *pdev, uint8_t *psetup)
{

  USBD_ParseSetupRequest(&pdev->request,psetup);

  pdev->ep0_state =USBD_EP0_SETUP;
  pdev->ep0_data_len =pdev->request.wLength;

  switch(pdev->request.bmRequest & 0x1F)
  {
  caseUSB_REQ_RECIPIENT_DEVICE:  
    USBD_StdDevReq(pdev, &pdev->request);
    break;

  caseUSB_REQ_RECIPIENT_INTERFACE:   
    USBD_StdItfReq(pdev,&pdev->request);
    break;

  caseUSB_REQ_RECIPIENT_ENDPOINT:      
    USBD_StdEPReq(pdev,&pdev->request);  
    break;

  default:         
    USBD_LL_StallEP(pdev, pdev->request.bmRequest & 0x80);
    break;
  }
  return USBD_OK;
}

看到这个函数之后就不得不在继续学习一下usb的基础知识了,之前说过usb有四种传输,不知道还记不记得,在设备枚举阶段使用的就是控制传输,
控制传输有3个阶段组成:1.设置阶段(Setup)
                                       2.数据阶段(data)
                                       3.状态阶段(Status)
而上面的函数就是在用代码的形式实现设置阶段
设置阶段可以简单的理解成:主机向设备发送请求,设备根据主机的请求来设置自己的配置或者是返回给主机需要的状态
而这个请求被称为:令牌信息包



以上的的设置大家就自行百度把,而上面函数中的
case USB_REQ_RECIPIENT_DEVICE:下
USBD_StdDevReq (pdev,&pdev->request)对应的就是以上的请求
跳转进这个函数,看看这个函数是怎么实现的:(usbd_ctlreq.c中)
USBD_StatusTypeDef  USBD_StdDevReq(USBD_HandleTypeDef *pdev , USBD_SetupReqTypedef  *req)
{
  USBD_StatusTypeDef ret =USBD_OK;
  switch (req->bRequest)
  {
  caseUSB_REQ_GET_DESCRIPTOR://获取描述符
    USBD_GetDescriptor(pdev, req) ;
    break;
  case USB_REQ_SET_ADDRESS://设置地址                     
    USBD_SetAddress(pdev,req);
    break;
  caseUSB_REQ_SET_CONFIGURATION:                  
    USBD_SetConfig(pdev , req);
    break;
  caseUSB_REQ_GET_CONFIGURATION:               
    USBD_GetConfig(pdev , req);
    break;
  caseUSB_REQ_GET_STATUS:                                 
    USBD_GetStatus(pdev , req);
    break;


  caseUSB_REQ_SET_FEATURE:  
    USBD_SetFeature(pdev , req);   
    break;

  caseUSB_REQ_CLEAR_FEATURE:                                 
    USBD_ClrFeature(pdev , req);
    break;

  default:
    USBD_CtlError(pdev, req);
    break;
  }
  return ret;
}
按照这个流程走下来,枚举差不多也就完成了。

那么usb设备是如何收发数据那?
之前介绍过端点的概念,endpoints,usb收发数据都是靠着端点来实现的,
端点从0开始计数,一个usb设备最多有16端点,端点号为0~15,每一个设备都必须有1个端点0作为配置使用,也就是说我们想用的话只能从端点1开始用。
那么我们如何使用端点那?
首先要初始化,初始化函数在usbd_hid.c中的
/**
  * @brief  USBD_HID_Init
  *         Initializethe HID interface
  * @param  pdev:device instance
  * @param  cfgidx:Configuration index
  * @retval status
  */
static uint8_t  USBD_HID_Init(USBD_HandleTypeDef *pdev,
                               uint8_tcfgidx)
{
  uint8_t ret = 0;

  /* Open EP IN */
  USBD_LL_OpenEP(pdev,
                 HID_EPIN_ADDR,//端点地址
                 USBD_EP_TYPE_INTR,//中断传输
                 HID_EPIN_SIZE);  //端点传输最大数据

  pdev->pClassData =USBD_malloc(sizeof (USBD_HID_HandleTypeDef));

  if(pdev->pClassData ==NULL)
  {
    ret = 1;
  }
  else
  {
    ((USBD_HID_HandleTypeDef*)pdev->pClassData)->state = HID_IDLE;
  }
  return ret;
}
由于此历程中除了ep0外只使用了以输入端点
#define HID_EPIN_ADDR                 0x81
使用USBD_LL_OpenEP()函数打开此端点

初始化完成之后就可以调用usbd_hid.c中的
uint8_tUSBD_HID_SendReport     (USBD_HandleTypeDef  *pdev,
                                 uint8_t*report,
                                 uint16_tlen)
函数进行发送数据。

其中hid设备与其他协议有一点不同就是hid设备在配置描述符后面还增加了报告描述符,
报告描述符的作用就是规定了设备的通信格式,以此例程为例
__ALIGN_BEGIN static uint8_tHID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]  __ALIGN_END =
数组中定义了报告描述符
这个报告描述符就规定鼠标的数据传输格式
长度为4个字节,第一个字节为左右键,第二字节为x轴相对坐标,第三个字节为y轴相对坐标,第四个字节为鼠标滚轮
所以当我们使用USBD_HID_SendReport发送数据时
uint8_t report[4]={0,10,0,0}
USBD_HID_SendReport     (USBD_HandleTypeDef  *pdev,
                                 report,
                                 4)
调用此函数以上函数后我们会发现鼠标指针向又移动了10个像素的位置。

注:我所写到的只是我的个人理解,如果有错漏之处还请大家指出,也好让我提高。还有就是由于篇幅所限,usb的好多知识都没有写,如果有时间的朋友可以自行查看官方的usb2.0.pdf

沙发
Xy201207| | 2017-3-15 11:18 | 只看该作者
坐等跟新

使用特权

评论回复
板凳
backlugin| | 2017-3-15 11:44 | 只看该作者

使用特权

评论回复
地板
backlugin| | 2017-3-15 11:46 | 只看该作者
挂载U盘看看。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:嵌入式相关网站喜欢的朋友了解一下http://www.micropython.group

31

主题

260

帖子

39

粉丝