stm32 USB模块的HID设备开发.docx
- 文档编号:23600820
- 上传时间:2023-05-18
- 格式:DOCX
- 页数:76
- 大小:422.86KB
stm32 USB模块的HID设备开发.docx
《stm32 USB模块的HID设备开发.docx》由会员分享,可在线阅读,更多相关《stm32 USB模块的HID设备开发.docx(76页珍藏版)》请在冰豆网上搜索。
stm32USB模块的HID设备开发
内容:
Part1:
stm32的USB库结构
Part2:
USB枚举、描述符及请求
Part3:
USB模块相关改动的详细说明
Part1:
stm32的USB库结构
1、usb库的结构
1.1USB库分为两个层次:
A)USB库内核层:
管理使用USBIP硬件和USB标准协议的直接传输。
包括以下文件:
B)应用接口层:
1.2USB库内核模块介绍
1.2.1usb_type.h
定义USB库中使用的主要类型,这个文件独立于stm32固件库的数据类型定义文件stdint.h,保证USB库的独立性。
1.2.2usb_regs(.c,.h)
此模块实现了硬件的抽象层,定义了访问USB寄存器的基本函数
1.2.3usb_int(.c,.h)
处理传输中断服务程序,实现USB协议事件和库内核之间的连接。
主要有两个函数voidCTR_LP(void)和voidCTR_HP(void),前一个是数据传输完成处理的核心处理部分。
1.2.4usb_core(.c,.h)
USB库的内核,实现USB2.0规范中的功能。
覆盖了和ENDP0有关的变准请求处理以及为列举,并且利用结构体User_Standard_Requests实现了标准请求和用户实现之间的动态接口。
内核中使用的各种数据和函数结构如下:
A、内核将设备级的信息存储在结构为DEVICE类型的设备表结构中,定义为:
typedefstruct_DEVICE
{
uint8_tTotal_Endpoint;/*Numberofendpointsthatareused*/
uint8_tTotal_Configuration;/*Numberofconfigurationavailable*/
}
DEVICE;
包括了使用的总端点数和总的配置数,其赋值操作在usb_prop.c中实现。
B、内核将主机发送的用于实现usb设备的设置包保存在类型为DEVICE_INFOR的设备信息结构中,定义为:
typedefstruct_DEVICE_INFO
{
uint8_tUSBbmRequestType;/*bmRequestType*/
uint8_tUSBbRequest;/*bRequest*/
uint16_t_uint8_tUSBwValues;/*wValue*/
uint16_t_uint8_tUSBwIndexs;/*wIndex*/
uint16_t_uint8_tUSBwLengths;/*wLength*/
uint8_tControlState;/*oftypeCONTROL_STATE*/
uint8_tCurrent_Feature;
uint8_tCurrent_Configuration;/*Selectedconfiguration*/
uint8_tCurrent_Interface;/*Selectedinterfaceofcurrentconfiguration*/
uint8_tCurrent_AlternateSetting;/*SelectedAlternateSettingofcurrent
interface*/
ENDPOINT_INFOCtrl_Info;
}DEVICE_INFO
为了实现对结构中某些字段的访问(以16位或8位的格式),定义了一个共用体uint16_t_uint8_t,其定义为:
typedefunion
{
uint16_tw;
structBW
{
uint8_tbb1;
uint8_tbb0;
}
bw;
}uint16_t_uint8_t
DEVICE_INFO结构体中需要关心的字段:
-USBbmRequestType是设置包中的bmRequestType副本,说明设置包的请求属性。
-USBbRequest是设置包中bRequest的副本,指设置包的请求号。
-USBwValues是设置包中wValues的副本,对应不同的请求,有着不同的作用,它是uint16_t_uint8_t的结构。
其成员通过宏进行了定义:
#defineUSBwValueUSBwValues.w
#defineUSBwValue0USBwValues.bw.bb0
#defineUSBwValue1USBwValues.bw.bb1
因此通过这个宏就可以访问整个word,或其中的某个byte。
-USBwIndexs和USBwValues的定义和访问方式一样,作用是接口和端点的索引好,分别对应USB接口描述符中的bInterfaceNumber值和端点描述符中的bEndpointAddress值。
-USBwLengths和USBwValues的定义和访问方式一样,它的值代表着设备返回数据包的长度
C、内核在必要时将控制权交给用户,用户处理过程以结构体DEVICE_PROP类型给出,定义为:
typedefstruct_DEVICE_PROP
{
void(*Init)(void);
void(*Reset)(void);
void(*Process_Status_IN)(void);
void(*Process_Status_OUT)(void);
RESULT(*Class_Data_Setup)(uint8_tRequestNo);
RESULT(*Class_NoData_Setup)(uint8_tRequestNo);
RESULT(*Class_Get_Interface_Setting)(uint8_tInterface,uint8_tAlternateSetting);
uint8_t*(*GetDeviceDescriptor)(uint16_tLength);
uint8_t*(*GetConfigDescriptor)(uint16_tLength);
uint8_t*(*GetStringDescriptor)(uint16_tLength);
uint8_t*RxEP_buffer;
uint8_tMaxPacketSize;
}DEVICE_PROP
D、用户标准需求结构是用户代码与标准请求管理之间的接口,属于USER_STARDARD_REQUESTS类型。
必须在usb_prop中实现以上的几种结构。
1.2.5usb_mem(.c,.h)
负责拷贝数据,从用户内存区到USB模块内存区(PMA)或者从USB模块内存区(PMA)到用户内存区
1.2.6Usb_sil(.h,.c):
主要对以下两个函数(这两个函数定义在usb_mem.c中)进行封装
A)UserToPMABufferCopy()
B)PMAToUserBufferCopy()
1)对UserToPMABufferCopy()进行封装的函数:
uint32_tUSB_SIL_Write(uint8_tbEpAddr,uint8_t*pBufferPointer,uint32_twBufferSize)
Parameter:
bEpAddr:
端点地址,例如EP1_IN、EP1_OUT、EP2_IN、EP2_OUT等。
pBufferPointer:
需要发送的数据数组指针。
wBufferSize:
要发送的数据数组大小。
Function:
通过指定的端点将需要的数据数组从下位机发送到上位机,上位机再使用软件读出来。
2)对PMAToUserBufferCopy()进行封装的函数:
uint32_tUSB_SIL_Read(uint8_tbEpAddr,uint8_t*pBufferPointer)
Parameter:
bEpAddr:
端点地址,例如EP1_IN、EP1_OUT、EP2_IN、EP2_OUT等。
pBufferPointer:
接收上位机数据的数据数组指针。
Function:
在指定的端点接收上位机发送到下位机的数据,存到pBufferPointer指定的数组中
注意:
在stm32工程中没有引用USB_SIL_Read,而是使用未封装的PMAToUserBufferCopy(),在usb_istr.c中引用来接收数据,函数为PMAToUserBufferCopy(Receive_Buffer,ENDP2_RXADDR,50),作用是接收上位机数据到Receive_Buffer[50]中,上位机发送数据时datasize必须为50,无用的位可以写为0发送。
1.3USB库应用接口层
1.3.1usb_istr(.c)
Usb_istr.c提供了一个函数USB_istr()处理所有的USB宏单元中断。
函数void(*pEpInt_IN[7])(void)引用了所有EP*_IN(1-7)的回调函数,函数void(*pEpInt_OUT[7])(void)引用了所有EP*_out(1-7)的回调函数,如果需要调用相应的回调函数,则应该在usb_config.h中将对应的函数无效声明注释掉如//#defineEP2_OUT_CallbackNOP_Process,再添加函数的具体内容。
注:
在工程中,HID双向通信定义使用端点1发送数据,端点2接收数据,其中端点2接收数据需要用回调函数接收,所以在usb_config.h中将//#defineEP2_OUT_CallbackNOP_Process注释掉,并且在usb_istr.c中添加了EP2_OUT_Callback的定义:
voidEP2_OUT_Callback(void)
{
PMAToUserBufferCopy(Receive_Buffer,ENDP2_RXADDR,50);
SetEPRxStatus(ENDP2,EP_RX_VALID);
Usb_ReceivePro();
}
函数的第一句作用是将端点2接收的数据存入大小为50bytes的Receive_Buffer中。
第二句作用是使能端点2的数据接收,第三句是引用自己定义的函数,这个函数用来处理接收到的数据。
1.3.2usb_conf(.h)
Usb_conf.h定义了BTABLE和PMA中的所有端点地址
注:
EP_NUM指需要用到的端点数量。
以下这一段宏定义定义了端点发送或接收缓冲区的大小,
都定义为了64bytes
#defineBTABLE_ADDRESS(0x00)//这个是基地址
/*EP0*/
/*rx/txbufferbaseaddress*/
#defineENDP0_RXADDR(0x18)
#defineENDP0_TXADDR(0x58)
/*EP1*/
/*txbufferbaseaddress*/
#defineENDP1_TXADDR(0x100)
#defineENDP2_RXADDR(0x140)
下面这一段是回调函数的无效宏定义,相当于一个mask,需要相应的回调函数,则注释掉那一句。
#defineEP1_IN_CallbackNOP_Process
#defineEP2_IN_CallbackNOP_Process
#defineEP3_IN_CallbackNOP_Process
#defineEP4_IN_CallbackNOP_Process
#defineEP5_IN_CallbackNOP_Process
#defineEP6_IN_CallbackNOP_Process
#defineEP7_IN_CallbackNOP_Process
#defineEP1_OUT_CallbackNOP_Process
//#defineEP2_OUT_CallbackNOP_Process
#defineEP3_OUT_CallbackNOP_Process
#defineEP4_OUT_CallbackNOP_Process
#defineEP5_OUT_CallbackNOP_Process
#defineEP6_OUT_CallbackNOP_Process
#defineEP7_OUT_CallbackNOP_Process
1.3.3usb_prop(.c,.h)
Usb_prop模块实现了USB内核使用的Device_Table,Device_Property和USER_STANDARD_REQUEST结构。
A)Device_Table使用结构体DEVICE定义:
DEVICEDevice_Table=
{
EP_NUM,
1
}
可以知道Device_Table的EP_NUM就是需要的端点数,它的宏定义在usb_conf.h中。
Device_Table中成员1则代表设备只有一个配置
B)Device_Property使用结构体DEVICE_PROP定义:
DEVICE_PROPDevice_Property=
{
Joystick_init,
Joystick_Reset,
Joystick_Status_In,
Joystick_Status_Out,
Joystick_Data_Setup,
Joystick_NoData_Setup,
Joystick_Get_Interface_Setting,
Joystick_GetDeviceDescriptor,
Joystick_GetConfigDescriptor,
Joystick_GetStringDescriptor,
0,
0x40/*MAXPACKETSIZE*/
}
下面解释几个主要关心的回调函数:
1、Joystick_init作用为初始化设备。
2、Joystick_Reset主要作用是对需要的端点进行设置,在工程中使用了endp0,endp1_in,endp2_out,所以必须对这三个端点进行设置。
如下:
/*InitializeEndpoint0*/
SetEPType(ENDP0,EP_CONTROL);
SetEPTxStatus(ENDP0,EP_TX_STALL);
SetEPRxAddr(ENDP0,ENDP0_RXADDR);
SetEPTxAddr(ENDP0,ENDP0_TXADDR);
Clear_Status_Out(ENDP0);
SetEPRxCount(ENDP0,Device_Property.MaxPacketSize);
SetEPRxValid(ENDP0);
/*InitializeEndpoint1*/
SetEPType(ENDP1,EP_INTERRUPT);
SetEPTxAddr(ENDP1,ENDP1_TXADDR);
SetEPTxCount(ENDP1,14);
SetEPTxStatus(ENDP1,EP_TX_NAK);
/*InitializeEndpoint12*/
SetEPType(ENDP2,EP_INTERRUPT);
SetEPRxAddr(ENDP2,ENDP2_RXADDR);
SetEPRxCount(ENDP2,50);
SetEPRxStatus(ENDP2,EP_RX_VALID);
endp0为用作控制,不用自己设置。
endp1用作中断输入(方向为设备到主机)端点,在endp1初始化语句中,
setEPType(ENDP1,EP_INTERRUPT)设置端点1为中断方式,
SetEPTxAddr(ENDP1,ENDP1_TXADDR)设置其输入地址,
SetEPTxCount(ENDP1,14)设置配置Tx缓冲计数器,
SetEPTxStatus(ENDP1,EP_TX_NAK)设置端点1发送不应答。
endp2用作中断输出(方向主机到设备)端点,在endp2初始化语句中
SetEPType(ENDP2,EP_INTERRUPT)设置端点2为中断方式
SetEPRxAddr(ENDP2,ENDP2_RXADDR);设置输出地址
SetEPRxCount(ENDP2,50);配置RX缓冲计数器
SetEPRxStatus(ENDP2,EP_RX_VALID);设置输入有效
注:
配置TX缓冲计数器时注意它的值等于设备发送数据的最大宽度。
同理对RX一样。
3、Joystick_Data_Setup处理主机获取设备的特定描述符的请求,内核文件usb_core无法处理该请求,因此需要用户分析请求,准备好数据,再传送个内核,特定描述符请求包括获取HID描述符,报告描述符等。
注:
函数体RESULTJoystick_Data_Setup(uint8_tRequestNo)
{
uint8_t*(*CopyRoutine)(uint16_t);
CopyRoutine=NULL;
if((RequestNo==GET_DESCRIPTOR)
&&(Type_Recipient==(STANDARD_REQUEST|INTERFACE_RECIPIENT))
&&(pInformation->USBwIndex0==0))
{
if(pInformation->USBwValue1==REPORT_DESCRIPTOR)
{
CopyRoutine=Joystick_GetReportDescriptor;
}
elseif(pInformation->USBwValue1==HID_DESCRIPTOR_TYPE)
{
CopyRoutine=Joystick_GetHIDDescriptor;
}
}/*EndofGET_DESCRIPTOR*/
/***GET_PROTOCOL***/
elseif((Type_Recipient==(CLASS_REQUEST|INTERFACE_RECIPIENT))
&&RequestNo==GET_PROTOCOL)
{
CopyRoutine=Joystick_GetProtocolValue;
}
if(ReportOffset>=6)
ReportOffset=0;*/
if(CopyRoutine==NULL)
{
returnUSB_UNSUPPORT;
}
pInformation->Ctrl_Info.CopyData=CopyRoutine;
pInformation->Ctrl_Info.Usb_wOffset=0;
(*CopyRoutine)(0);
returnUSB_SUCCESS;
}
函数中的判断语句说明
if((RequestNo==GET_DESCRIPTOR)&&(Type_Recipient==(STANDARD_REQUEST|INTERFACE_RECIPIENT))&&(pInformation->USBwIndex0==0))
它的意思是判断主机发送的请求信息,如果是GET_DESCRIPTOR请求类型,并且是标准的USB请求,并且索引USBwIndex0为0,则执行后面的语句,在后面判断语句内部,再通过索引USBwIndex1确定是应该返回ReportDescriptor还是HIDDescriptor。
elseif((Type_Recipient==(CLASS_REQUEST|INTERFACE_RECIPIENT))&&RequestNo==GET_PROTOCOL)
这一句的意思请求为get_protocol,并且是class类请求,则返回协议值
返回描术符说明,以ReportDescriptor为例:
返回ReportDescriptor是通过函数Joystick_GetReportDescriptor实现的,在其函数体中只有一个语句returnStandard_GetDescriptorData(Length,&Joystick_Report_Descriptor),返回相应的描述符所在的指针地址。
standard_GetDescriptorData的函数体如下:
uint8_t*Standard_GetDescriptorData(uint16_tLength,ONE_DESCRIPTOR*pDesc)
{
uint32_twOffset;
wOffset=pInformation->Ctrl_Info.Usb_wOffset;
if(Length==0)
{
pInformation->Ctrl_Info.Usb_wLength=pDesc->Descriptor_Size-wOffset;
return0;
}
returnpDesc->Descriptor+wOffset;
}
函数功能:
根据主机请求中要求的长度,返回描述符对应的指针地址,如果Length参数为0,则把pInformation->Ctrl_Info.Usb_wLength字段设置为要传输的数据长度。
函数参数:
Length是需要返回的描述符大小Joystick_Report_Descriptor是ONE_DESCRIPTOR结构体定义的,包括描术符指针以及描术符大小两个成员。
C)USER_STANDARD_REQUEST功能是如果用户想要在收到标准USB请求后实现特定的代码,那么就得使用结构中的相应函数。
1.3.4usb_pwr(.c,.h)
管理USB设备的电源,有以下四种函数:
1.3.5Usb_ReceivePro(.c,.h)
处理通过endp2接收到的主机数据,更新到联合计算的参数数组内。
Part2:
USB枚举、描述符及请求
2.1USB的基本概念
2.1.1描述符的一些概念(参考USB协议数据流模型)
1)设备端点
一个端点是一个可唯一识别的USB设备的Portion,它是主机与设备间通信流的一个结束点。
一系列相互独立的端点在一起构成了USB逻辑设备。
每个逻辑设备有一个唯一的地址,这个地址是在设备连上主机时,由主机分配的,而设备中的每个端点在设备内部有唯一的端点号。
这个端点号是在设备设计时被给定的。
每个端点都是一个简单的连接点,或者支持数据流进设备,或者支持其流出设备,两者不可得兼。
一个端点的特性决定了它与客户软件进行的传送的类型。
一个端点有以下特性:
·端点的总线访问频率要求
·端点的总线延迟要求
·端点的带宽要求
·端点的端点号
·对错
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- stm32 USB模块的HID设备开发 USB 模块 HID 设备 开发