【转】STM32 USB 问题汇总

[复制链接]
7175|51
 楼主| sunmeat 发表于 2015-2-26 19:28 | 显示全部楼层
同样每个数据入过程也可能有若干次DATA_IN传输,每次需要向主机传输数据时,USB库都会调用一次回调函数CopyRoutine,参数length是本次传输所要发送的数据字节数目,CopyRoutine必须返回一个缓冲区指针,这个缓冲区中必须包含要求的数据字节,USB库将把用户缓冲区的数据拷贝到USB缓冲区并择机发送出去。
 楼主| sunmeat 发表于 2015-2-26 19:28 | 显示全部楼层
当以length=0调用CopyRoutine时,CopyRoutine需要返回用户缓冲区的长度,因为CopyRoutine的返回类型是一个指针,所以需要通过类型的强制转换返回缓冲区长度。这个功能是为了处理用户缓冲区的长度与主机SETUP数据请求长度不符的情况,而不至于造成用户缓冲区的溢出。
 楼主| sunmeat 发表于 2015-2-26 19:28 | 显示全部楼层
介绍完上述若干概念和回调函数,再看SET_REPORT的实现就很容易了。

SET_REPORT是一个数据出过程,因此需要实现一个Class_Data_Setup回调函数,示例如下:
  1. RESULT HID_Data_Setup(u8 RequestNo)
  2. {
  3.     u8 *(*CopyRoutine)(u16 length);
  4.     CopyRoutine = NULL;
  5.     if (pInformation->USBbmRequestType == CLASS_REQUEST|INTERFACE_RECIPIENT
  6.             && RequestNo == SET_REPORT)
  7.         CopyRoutine = My_Data_Request;

  8.     if (CopyRoutine == NULL)
  9.         return USB_UNSUPPORT;

  10.     pInformation->Ctrl_Info.CopyData = CopyRoutine;
  11.     pInformation->Ctrl_Info.Usb_wOffset = 0;
  12.     pInformation->Usb_wLength = (*CopyRoutine)(0);

  13.     return USB_SUCCESS;
  14. } // End of HID_Data_Setup()

  15. u8 My_Buffer[10];
  16. u8 *My_Data_Request(u16 length)
  17. {
  18.     if (length == 0)
  19.         return (u8*)10;    // 假定你的REPORT长度和Buffer长度为10

  20.     return My_Buffer;
  21. }
 楼主| sunmeat 发表于 2015-2-26 19:29 | 显示全部楼层
上面介绍的CopyRoutine用于把多次传输的数据包合并到一个完整的缓冲区中,因此只有到STATUS阶段才能够指导一次SETUP传输是否结束,所以用户程序需要在回调函数Process_Status_IN中处理从SET_REPORT接收到的数据。因为所有的回调函数都是USB中断处理的一部分,所以更好的办法是在Process_Status_IN中设置一个标记,然后在用户主程序中判断这个标记并做处理。
 楼主| sunmeat 发表于 2015-2-26 19:29 | 显示全部楼层
注意,STM32的USB库设计成以回调函数处理用户命令请求,包含类命令请求,是为了能够清晰地区分库程序和用户程序,使这两者不会混在一起,这样的好处是非常明显的,当USB库需要更新升级时,只需替换掉相应的程序模块,而不必修改用户已经完成的程序。

以上的介绍都可以在STM32 USB库的说明手册中找到。
 楼主| sunmeat 发表于 2015-2-26 20:11 | 显示全部楼层
上述示意代码是以My_Buffer长度为10字节为例,而USB库的默认包长度为16字节,因此My_Data_Request并没有多包的处理。

关于多包的缓冲区处理的示意代码可以是这样的:
  1. u8 *My_Data_Request(u16 length)
  2. {
  3.     if (length == 0)
  4.         return (u8*)100;    // 假定你的REPORT长度和Buffer长度为100

  5.     return &My_Buffer[pInformation->Ctrl_Info.Usb_wOffset];
  6. }
 楼主| sunmeat 发表于 2015-2-26 20:26 | 显示全部楼层
上述示意代码是以My_Buffer长度为10字节为例,而USB库的默认包长度为16字节,因此My_Data_Request并没有多包的处理。

关于多包的缓冲区处理的示意代码可以是这样的:
  1. u8 *My_Data_Request(u16 length)
  2. {
  3.     if (length == 0)
  4.         return (u8*)100;    // 假定你的REPORT长度和Buffer长度为100

  5.     return &My_Buffer[pInformation->Ctrl_Info.Usb_wOffset];
  6. }
 楼主| sunmeat 发表于 2015-2-26 20:26 | 显示全部楼层
这里有一个库中使用的变量pInformation->Ctrl_Info.Usb_wOffset,这个变量回在传输每个数据包时候由库中的程序按数据包长度增加,如最大包长为16字节时,第一次调用My_Data_Request时Usb_wOffset=0,第二次调用My_Data_Request时Usb_wOffset=16,第三次调用My_Data_Request时Usb_wOffset=32,依此类推。这样就可以使用Usb_wOffset作为My_Buffer的下标从My_Data_Request返回。
 楼主| sunmeat 发表于 2015-2-26 20:30 | 显示全部楼层
对于提问“如何传递length?在上面没有看到这个参数的传递过程”的回答:

参数length是用于检测缓冲区长度是否足够,如果你有足够长的缓冲区,可以不必检测,上述示例中使用了一个固定的缓冲区,所以不必使用参数length检测缓冲区长度
我是前行的狮子 发表于 2015-5-19 09:53 | 显示全部楼层
if (GetENDPOINT(ENDP3) & EP_DTOG_TX)//先判断本次接收到的数据是放在哪块缓冲区的 ??
请问一下大家,为什么这句话是判断放在哪个缓冲区的函数啊
 楼主| sunmeat 发表于 2015-5-20 09:02 | 显示全部楼层
我是前行的狮子 发表于 2015-5-19 09:53
if (GetENDPOINT(ENDP3) & EP_DTOG_TX)//先判断本次接收到的数据是放在哪块缓冲区的 ??
请问一下大家, ...

你看库中寄存器的定义就知道了。
Mrjiang88178 发表于 2016-12-14 09:56 | 显示全部楼层
好贴,mark      
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部