如何使用STM32的USB库支持控制端点0

发布者:qpb1234最新更新时间:2015-04-14 来源: eechina关键字:STM32  USB库  控制端点 手机看文章 扫描二维码
随时随地手机看文章
首先我们先回顾一下控制端点的传输方式:
 
控制端点的传输有三个阶段,SETUP阶段、数据阶段和状态阶段;数据阶段又分为数据入(DATA IN)和数据出(DATA OUT),控制端点传输可以没有数据阶段;状态阶段有状态入(STATUS IN)和状态出(STATUS OUT)。
 
总结起来,控制端点有如下三种可能的传输过程(以下括号中的0或1表示DATA0或DATA1传输):
 
一、 SETUP  DATA_IN(0)  DATA_IN(1)  DATA_IN(0)  ......  STATUS_OUT(1)
二、 SETUP  DATA_OUT(0)  DATA_OUT(1)  DATA_OUT(0)  ...... STATUS_IN(1)
三、 SETUP  STATUS_IN(1)
 
这里做一个约定,把上述过程一定义为“数据入过程”,过程二定义为“数据出过程”,过程三定义为“无数据过程”。所有的USB控制端点的数据传输都可以而且只用这三种传输过程表示。HID的SET_REPORT是数据出过程,HID的GET_REPORT是数据入过程,USB的GET DEVICE DESCRIPTOR是数据入过程,USB的SET CONFIGURATION是无数据过程,等等。
 
接下来,我们看看STM32的USB库是如何处理控制端点0的传输。
 
根据USB协议,每个SETUP包都由8个字节构成,用户程序可以通过结构体Device_Info(类型DEVICE_INFO)访问SETUP包的数据,因为在整个的USB处理中都要用到结构体Device_Info的内容,库中定义了一个全局的指针pInformation指向这个结构体,用户可以通过这个指针访问结构体的内容。
 
对应SETUP包的8个字节,用户可以用下述方式访问:
 
  pInformation->USBbmRequestType (字节类型)
  pInformation->USBbRequest  (字节类型)
  pInformation->USBwValue  (双字节类型)
  pInformation->USBwIndex  (双字节类型)
  pInformation->USBwLength (双字节类型)
 
使用pInformation->USBwValue0访问wValue的低字节,pInformation->USBwValue1访问wValue的高字节。
 
使用pInformation->USBwIndex0访问USBwIndex的低字节,pInformation->USBwIndex1访问USBwIndex的高字节。
 
使用pInformation->USBwLength0访问USBwLength的低字节,pInformation->USBwLength1访问USBwLength的高字节。
 
通过分析SETUP包的8个字节,可以判断出一个SETUP的传输过程是属于数据入过程、数据出过程还是无数据过程。STM32的USB库中处理了所有的USB协议文本中定义的标准SETUP命令,对于USB协议文本中未定义的命令,USB库按照数据入过程、数据出过程或无数据过程通过回调函数交给用户程序处理。
 
全局变量Device_Property(DEVICE_PROP类型)封装了所有的回调函数,DEVICE_PROP定义如下:
 
typedef struct _DEVICE_PROP
{
  void (*Init)(void);        // 设备初始化回调函数
  void (*Reset)(void);       // USB复位回调函数
 
  void (*Process_Status_IN)(void);  // STATUS_IN阶段处理回调函数
  void (*Process_Status_OUT)(void); // STATUS_OUT阶段处理回调函数
 
  RESULT (*Class_Data_Setup)(u8 RequestNo);   // 数据入/出过程处理回调函数
  RESULT (*Class_NoData_Setup)(u8 RequestNo); // 无数据过程处理回调函数
 
  RESULT  (*Class_Get_Interface_Setting)(u8 Interface, u8 AlternateSetting); // GET_INTERFACE 回调函数
 
  u8* (*GetDeviceDescriptor)(u16 Length); // GET_DEVICE_DESCRIPTION回调函数
  u8* (*GetConfigDescriptor)(u16 Length); // GET_CONFIGURATION_DESCRIPTION回调函数
  u8* (*GetStringDescriptor)(u16 Length); // GET_STRING_DESCRIPTION回调函数
  u8 MaxPacketSize; // 最大包长度
} DEVICE_PROP;
 
结合SETUP的三种传输过程,用户通过实现不同的回调函数即可完成对各种USB类命令的处理,下面以HID的SET REPORT为例说明。
 
在介绍具体实现之前,先介绍一下另一个回调函数CopyRoutine的概念,这个函数的原型是:
 
   u8 *CopyRoutine(u16 length);    // 返回一个缓冲区指针
 
USB库通过这个函数获得用户的数据缓冲区地址,从而可以在数据出过程中把收到的数据拷贝到用户缓冲区,或在数据入过程中把用户缓冲区的数据拷贝到USB发送缓冲区。每个数据出过程可能有若干次DATA_OUT传输,USB库每完成一次这样的传输都会调用一次回调函数CopyRoutine,参数length是本次传输所收到的数据字节数目,CopyRoutine必须返回一个缓冲区指针,这个缓冲区必须能够容纳length字节的数据,CopyRoutine返回到USB库之后,USB库将把收到的数据拷贝到用户指定的缓冲区。同样每个数据入过程也可能有若干次DATA_IN传输,每次需要向主机传输数据时,USB库都会调用一次回调函数CopyRoutine,参数length是本次传输所要发送的数据字节数目,CopyRoutine必须返回一个缓冲区指针,这个缓冲区中必须包含要求的数据字节,USB库将把用户缓冲区的数据拷贝到USB缓冲区并择机发送出去。
 
当以length=0调用CopyRoutine时,CopyRoutine需要返回用户缓冲区的长度,因为CopyRoutine的返回类型是一个指针,所以需要通过类型的强制转换返回缓冲区长度。这个功能是为了处理用户缓冲区的长度与主机SETUP数据请求长度不符的情况,而不至于造成用户缓冲区的溢出。
 
介绍完上述若干概念和回调函数,再看SET_REPORT的实现就很容易了。
 
SET_REPORT是一个数据出过程,因此需要实现一个Class_Data_Setup回调函数,示例如下:
 
RESULT HID_Data_Setup(u8 RequestNo)
{
    u8 *(*CopyRoutine)(u16 length);
    CopyRoutine = NULL;
    if (pInformation->USBbmRequestType == CLASS_REQUEST|INTERFACE_RECIPIENT
            && RequestNo == SET_REPORT)
        CopyRoutine = My_Data_Request;
 
    if (CopyRoutine == NULL)
        return USB_UNSUPPORT;
 
    pInformation->Ctrl_Info.CopyData = CopyRoutine;
    pInformation->Ctrl_Info.Usb_wOffset = 0;
    pInformation->Usb_wLength = (*CopyRoutine)(0);
 
    return USB_SUCCESS;
} // End of HID_Data_Setup()
 
u8 My_Buffer[10];
u8 *My_Data_Request(u16 length)
{
    if (length == 0)
        return (u8*)10;    // 假定你的REPORT长度和Buffer长度为10
 
    return My_Buffer;
}
 
上面介绍的CopyRoutine用于把多次传输的数据包合并到一个完整的缓冲区中,因此只有到STATUS阶段才能够知道一次SETUP传输是否结束,所以用户程序需要在回调函数 Process_Status_IN中处理从SET_REPORT接收到的数据。因为所有的回调函数都是USB中断处理的一部分,所以更好的办法是在 Process_Status_IN中设置一个标记,然后在用户主程序中判断这个标记并做处理。
 
上述示意代码是以My_Buffer长度为10字节为例,而USB库的默认包长度为16字节,因此My_Data_Request并没有多包的处理。
 
关于多包的缓冲区处理的示意代码可以是这样的:
 
u8 *My_Data_Request(u16 length)
{
    if (length == 0)
        return (u8*)100;    // 假定你的REPORT长度和Buffer长度为100
 
    return &My_Buffer[pInformation->Ctrl_Info.Usb_wOffset];
}
 
这里有一个库中使用的变量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返回相应的缓冲区地址。
 
前面已经说明,参数length是用于检测缓冲区长度是否足够,如果你有足够长的缓冲区,可以不必检测,上述示例中使用了一个固定的缓冲区,所以不必使用参数length检测缓冲区长度。
 
注意,STM32的USB库设计成以回调函数处理用户命令请求,包含类命令请求,是为了能够清晰地区分库程序和用户程序,使这两者不会混在一起,这样的好处是非常明显的,当USB库需要更新升级时,只需替换掉相应的程序模块,而不必修改用户已经完成的程序。
 
以上的介绍都可以在STM32 USB库的说明手册中找到。
 
关键字:STM32  USB库  控制端点 引用地址:如何使用STM32的USB库支持控制端点0

上一篇:基于ARM技术的嵌入式系统的自动化配送系统
下一篇:STM32(Cortex-M3)中的优先级概念

推荐阅读最新更新时间:2024-03-16 13:58

stm32在Keil5调试提示: uvision IDE已停止工作
1 http://blog.csdn.net/qq_33259138/article/details/70224581 现象是程序编译下载都没有问题,开始Debug调试之后,只要进入C源代码文件,立即崩溃。 在论坛上找到一种解决办法,将你的工程拷出来,发到一个短路径文件夹下,再次编译,崩溃就没有了。 按论坛里大神说法是,Debug调试程序路径不能超过170个字节。 2http://www.openedv.com/posts/list/52892.htm 1, 是不是安装在中文目录了? 2,是不是系统用户名是中文的? 3 https://weibo.com/p/2304187e4cac0d0102vyxf?s
[单片机]
<font color='red'>stm32</font>在Keil5调试提示: uvision IDE已停止工作
STM32学习4 复位
STM32F10XX支持三种复位形式,分别为系统复位,上电复位,备份区域复位。 当发生以下任一事件时,产生一个系统复位: 1,NRST引脚上的低电平复位 2,窗口看门狗计数终止(WWDG复位) 3,独立看门狗计数终止(IWDG复位) 4,软件复位(SW复位) 5,低功耗管理复位 三种不同的时钟源可被用来驱动系统时钟: 1,HSI振荡器时钟 2,HSE振荡器时钟 3,PLL时钟 AHB和APB2域的最大频率是72MHZ,APB1域的最大允许频率是36MHZ。SDIO接口的时钟频率固定为HCLK/2. 外部晶体/陶瓷谐振器:为4~16MHZ外部振荡器可为系统提供更为精确的主时钟。 内部PLL可以用来倍频HSI RC的输
[单片机]
STM32 + ESP8266 + MQTT协议连接OneNet
一、环境介绍 单片机 采用: STM32 F103C8T6 上网方式:采用 ESP8266 ,也可以使用其他设备代替,只要支持TCP协议即可。比如:GSM模块、有线网卡等。 开发软件: keil 5 硬件 连接功能:ESP8266接在STM32的串口3上。通过AT指令与ESP8266进行 通信 。 注意:本篇文章没有贴ESP8266的底层 编程 代码,如果不会ESP8266底层编程,请看这里: https://blog.csdn.net/xiaolong1126626497/ar ti cle/det ai ls/107379554 如果需要了解ESP8266+MQTT协议连接阿里云 物联网 服务器请看这里:https:/
[单片机]
<font color='red'>STM32</font> + ESP8266 + MQTT协议连接OneNet
STM32_USB之完全双缓存(包括发送和接收) -- 更新中断处理
STM32的USB双缓存接收代码其实已经可以在ST提供的USB示例代码中找到,只要稍加修改,就可以得到将近1MB的数据接收性能。虽然Datasheet中说明USB发送也同样可以使用双缓存,但并没有示例代码,由于为了测试性能,自己做了一个,测试中没有发现问题,虽然对性能的提升不如在USB接收上实现双缓存那么多。 注意: FreeUserBuffer的作用是切换当前的USB缓存。 1.接收双缓冲: EPX_OUT_Callback中,此代码只是在ST的示例程序的基础上稍加修改,并且不是偶写的,而是一个网友测试的: if(GetENDPOINT(ENDP3) & EP_DTOG_TX) { FreeUserBuff
[单片机]
STM32常用数据类型 u8、u16、u32
1.unsigned int 32 (C语言标准表达方法) 2.uint32_t ; 3.u32; 这三种方式都是在表达同一个意思。ST 搞这么多花样,无非是想开发人员在写代码时定义数据类型能少写几个符号,然后又因为前后版本升级,为了兼容旧版本(主要是V2.0)才会出现这么多表示方法。不管他怎么换,都是基于标准C来的,看清楚以下几个文件你就OK了:core_cm3.h ;stm32f10x.h ; stdint.h; 其中每个文件大概作用如下: stdint.h 这里放着C语言的标准表达方式//第36行开始 typedef signed char int8_t; // 标准表达方式
[单片机]
STM32开发板例程讲解之二:GPIO的描述和配置
上一讲创建了一个stm32工程,从本讲开始将深入stm32内核与外设讲解。 首先介绍stm32的GPIO,这是入门的起点,也是最容易上手的部分。 一、GPIO的综合描述 stm32每一个GPIO端口拥有2个32bits的configuration寄存器(GPIOx_CRL,GPIOx_CRH),2个32bits的数据寄存器(GPIOx_IDR,GPIOx_ODR),1个32bits的set/reset寄存器(GPIOx_BSRR),1个16bits的reset寄存器(GPIOx_BRR)和1个32bits的Lock寄存器(GPIOx_LCKR)。 (一)每一个IO引脚都可以使用软件配置为以下几种模
[单片机]
基于L298N的STM32的直流电机PWM调速控制
这两天研究了一下基于L298N与stm32的直流电机调速,实验结果良好,可以根据调节STM32的PWM占空比来进行直流电机的调速。实验系统图如下: 首先整个PWM实验选用的是stm32f103系列,驱动模块选择的是L298N驱动模块,以及12V直流电源。简单的来说,电机调速就是通过stm32输出PWM来进行控制L298N的使能端ENA。 L298N驱动模块 使用方法: 输出A:通道A输出,连接直流电机 输出B:通道B输出,连接电机 12V供电:主电源正极输入 GND:主电源正负极输入 5V输出:5V电压输出端,可用于给STM32开发板进行供电 ENA:通道A使能 ENB:通道B使能 IN1—IN4:逻辑输入IN1-IN
[单片机]
基于L298N的<font color='red'>STM32</font>的直流电机PWM调速<font color='red'>控制</font>
STM32之DAC固定电压输出配置
STM32F103VCT6自带两个12位DAC,DAC的转换速度一直没有查到,网上有人说是1MHZ的频率,那就是1us了。ADC的转换时间在56MHZ工作频率下为1us,在72MHZ工作频率下为1.17us。如果AD和DA有对称关系的话,那么很可能跟ADC的时间相同,刚入手分析的,不见得正确! 由于我此次使用是DA输出电压。STM32的DAC固定电压配置和波形输出配置相似,不同的地方在于它要多调用一个函数:DAC_SoftwareTriggerCmd(DAC_Channel_1,ENABLE);这样才会输出固定的电平。 具体配置如下: void DAC_VOLTAGE_Configuration(void) { D
[单片机]
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved