STM32学习笔记USB HID
―――――STM32 USB HID固件学习分析
1.系统复位和上电复位
发生系统复位或者上电复位时,应用程序首先需要做的是提供USB 模块所需要的
时钟信号,然后清除复位信号,使程序可以访问USB 模块的寄存器。复位之后的
初始化流程如下所述:
首先,由应用程序激活寄存器单元的时钟,再配置设备时钟管理逻辑单元的相关
控制位,清除复位信号.
void USB_Init (void) {
RCC->APB1ENR |= (1 << 23); /* enable clock for USB时钟信号*/
/* Enable USB Interrupts */
NVIC->IPR [5] |= 0x00000010; /* set priority lower than SVC */
NVIC->ISER[0] |= (1 << (USB_LP_CAN_RX0_IRQChannel & 0x1F));
/* Control USB connecting via SW */
RCC->APB2ENR |= (1 << 5); /* enable clock for GPIOD */
}
2. 开启模拟单元,使USB处于复位状态,直到模拟但愿准备完毕。
其次,必须配置CNTR 寄存器的PDWN 位用以开启USB 收发器相关的模拟部
分,程序体现CNTR = CNTR_FRES;模拟单元准备完毕后,程序复位撤销,即一次复位操作。然后使能复位中断,那么主机将会检测到USB设备的复位,给出复位事件给USB设备。
void USB_Connect (BOOL con) {
//==============================================================
GPIO_InitTypeDef m_GPIO_InitTypeDef;
m_GPIO_InitTypeDef.GPIO_Pin = GPIO_Pin_2;
m_GPIO_InitTypeDef.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
m_GPIO_InitTypeDef.GPIO_Speed = GPIO_Speed_2MHz;//速度2M
GPIO_Init(GPIOD,&m_GPIO_InitTypeDef);
//===============================================================
//让usb处于复位状态,让USB模拟电路准备
CNTR = CNTR_FRES; /* Force USB Reset */
ISTR = 0; /* Clear Interrupt Status */
if (con) //:连接USB
{//清除USB复位信号,向USB主机发送"恢复请求",并且使能复位中断请求
//则当USB主机向本机发送“恢复请求”后,相应中断,并调用USB_Reset函数.
//说明:如果此操作在1-15ms有效,USB主机将对USB模块发出"唤醒操作"
CNTR = CNTR_RESETM; /* USB Reset Interrupt Mask */
GPIO_SetBits(GPIOD,GPIO_Pin_2);
}
else //:断开USB
{//清除USB复位信号,关闭USB电源
CNTR = CNTR_FRES | CNTR_PDWN; /* Switch Off USB Device */
GPIO_ResetBits(GPIOD,GPIO_Pin_2);
}
}
3.USB主机发送给USB device复位事件,STM32的复位事件
void USB_Reset (void) {
/* Double Buffering is not yet supported */
ISTR = 0; /* Clear Interrupt Status */
//:CNTR_RESETM在使能连接时被中断使能
//:CNTR_CTRM此处添加(正确传输中断使能==>某个端点成功完成一次传输.)
//:下一次事件中断将检测CNTR_CTRM中断
// 究竟是哪个端点成功传输(ISTR:EP_ID[3:0]),数据方向(ISTR:DIR)
CNTR = CNTR_CTRM | CNTR_RESETM |
(USB_SUSPEND_EVENT ? CNTR_SUSPM : 0) |
(USB_WAKEUP_EVENT ? CNTR_WKUPM : 0) |
(USB_ERROR_EVENT ? CNTR_ERRM : 0) |
(USB_ERROR_EVENT ? CNTR_PMAOVRM : 0) |
(USB_SOF_EVENT ? CNTR_SOFM : 0) |
(USB_SOF_EVENT ? CNTR_ESOFM : 0);
FreeBufAddr = EP_BUF_ADDR; //自由缓冲区地址
BTABLE = 0x00; /* set BTABLE Address */
/* Setup Control Endpoint 0 */
pBUF_DSCR->ADDR_TX = FreeBufAddr; //:
FreeBufAddr += USB_MAX_PACKET0; //:
pBUF_DSCR->ADDR_RX = FreeBufAddr; //:
FreeBufAddr += USB_MAX_PACKET0; //:
if (USB_MAX_PACKET0 > 62)
{
pBUF_DSCR->COUNT_RX = ((USB_MAX_PACKET0 << 5) - 1) | 0x8000;
}
else
{
pBUF_DSCR->COUNT_RX = USB_MAX_PACKET0 << 9;
}
//配置端点0:
//1: EP_CONTROL(控制端点) bit[10:9](EP_TYPE[1:0])
//2: EP_RX_VALID(数据接受状态位)bit[13:12](STAT_RX[1:0]) ==>端点可用于接受
EPxREG(0) = EP_CONTROL | EP_RX_VALID;
//设置USB地址为0
DADDR = DADDR_EF | 0; /* Enable USB Default Address */
}
4. STM32的中断函数
void USB_LP_CAN_RX0_IRQHandler (void)
{
DWORD istr, num, val;
istr = ISTR;
/*==========USB Reset Request=============*/
if (istr & ISTR_RESET)
{
USB_Reset();
#if USB_RESET_EVENT
USB_Reset_Event();
#endif
ISTR = ~ISTR_RESET;
#ifdef Debug_Uart
printf("USB_RESET_EVENT\n");
#endif
}
//……………略过一些程序………………
/*==========EndpointInterrupts===========================================*/
while ((istr = ISTR) & ISTR_CTR)
{//istr = ISTR:表示不是前面用到的中断
//:CNTR_CTRM表示某个端点成功完成一次传输.在复位中使能此中断
//:第一次进入这个中断后,开始枚举,枚举一般都是端点0的
//USB_EVT_IN、USB_EVT_SETUP事件。USB_EVT_IN是USB_EVT_SETUP事件需要//返回相应数据而使用的。枚举事务几乎都是USB_EVT_SETUP事务。
// 究竟是哪个端点成功传输(ISTR:EP_ID[3:0]),数据方向(ISTR:DIR)
ISTR = ~ISTR_CTR;
num = istr & ISTR_EP_ID; //num端点号
//========================
//#define EPxREG(x) REG(USB_BASE_ADDR + 4*(x)) /* EndPoint Registers */
//#define REG(x) (*((volatile unsigned int *)(x)))
//:通过前面两句知道val = EPxREG(num);就是读取端点num寄存器的值。
val = EPxREG(num);
if (val & EP_CTR_RX)
{//:端口num EP_CTR_RX正确接受标志
//bit[10:9]=EP_TYPE[1:0]:端点类型
EPxREG(num) = val & ~EP_CTR_RX & EP_MASK; //清除相关标志位
if (USB_P_EP[num])
{
if (val & EP_SETUP)
{//如果使SETUP包
USB_P_EP[num](USB_EVT_SETUP);
}
else
{//否则为OUT包
USB_P_EP[num](USB_EVT_OUT);
}
}
}
if (val & EP_CTR_TX)
{//::端口num EP_CTR_TX正确发送标志
EPxREG(num) = val & ~EP_CTR_TX & EP_MASK;
if (USB_P_EP[num])
{//为IN包
USB_P_EP[num](USB_EVT_IN);
}
}
}
}
5.枚举过程分析。
5.1 说明:枚举使用端点0,在void USB_EndPoint0 (DWORD event)函数处理。
STM32USB枚举串口调试打印输出:
USB_RESET_EVENT
USB_RESET_EVENT
USB_Suspend
USB_RESET_EVENT
USB_WakeUp
USB_EVT_SETUP
...REQUEST_STANDARD
......USB_REQUEST_GET_DESCRIPTOR
USB_EVT_IN
USB_RESET_EVENT
USB_EVT_SETUP
...REQUEST_STANDARD
......USB_REQUEST_SET_ADDRESS
USB_EVT_SETUP
...REQUEST_STANDARD
......USB_REQUEST_GET_DESCRIPTOR
USB_EVT_IN
USB_EVT_IN
USB_EVT_IN
USB_EVT_SETUP
...REQUEST_STANDARD
......USB_REQUEST_GET_DESCRIPTOR
USB_EVT_IN
USB_EVT_IN
USB_EVT_SETUP
...REQUEST_STANDARD
......USB_REQUEST_GET_DESCRIPTOR
USB_EVT_IN
USB_EVT_RUUSBEUUUSB_EVT
UUSBUUSUSBCUUSB.USUSB_EVT_SETUP
...REQUEST_STUUUSB.SUSB_PUNUSB__USB_EVT_SETUP
...REQUEST_CLASS
......REQUEST_TO_INTERFACE
USB_EVT_SETUP
...REQUEST_STANDARD
......USB_REQUEST_GET_DESCRIPTOR
USB_EVUE
5.2 端点0 USB_EVT_SETUP事件函数分析说明
5.2.1 void USB_EndPoint0 (DWORD event)程序结构:
<!--[if !vml]-->
<!--[endif]-->
在枚举时,USB_EVT_OUT包没有使用,只使用了
USB_EVT_IN、USB_EVT_SETUP
5.2.2 USB_EVT_IN分析
<!--[if !vml]-->
<!--[endif]-->
可查看函数USB_DataInStage()功能为传送通道EndPoint0的数据。在USB_EVT_IN事件中只需要传输在枚举时需要的数据到USB主机,需要传输的数据都时在枚举事务中与USB主机商量好的长度。
5.2.3 USB_EVT_SETUP分析
A.
<!--[if !vml]-->
<!--[endif]-->
分析:USB_SetupStage()函数为将端点0的数据读到SetupPacket变量。SetupPacket定义格式如下:
typedef __packed struct _USB_SETUP_PACKET {
REQUEST_TYPE bmRequestType;
BYTE bRequest;
WORD_BYTE wValue;
WORD_BYTE wIndex;
WORD wLength;
} USB_SETUP_PACKET;
可以看到这为一个USB总线Setup包格式,具体可参照USB协议第9章。
SetupPacket.wLength这个域表明第二阶段的数据传输长度。传输方向由bmRequstType域的Direction位指出。wLength域为0则表明无数据传输。在输入请求下,设备返回的数据长度不应多于wLength,但可以少于。在输出请求下,wLength指出主机发出的确切数据量。如果主机发送多于wLength的数据,设备做出的响应是无定义的。
比如响应一个GET_DESCRIPTOR的setup包,则下一阶段需要传回
GET_DESCRIPTOR的内容,而这个内容时固定的,这时长度就为
SetupPacket.wLengt,说明下一个阶段为GET_DESCRIPTOR数据传入USB主机(一个USB_EVT_IN事务实现),那在GET_DESCRIPTOR的内容的地址给EP0Data的地址(后面说明会指明在哪儿),而长度在这儿指定。则下一个IN事务直接传输这个EP0Data的内容,直到将EP0Data.Count数据传输完毕。
B.下面内容均可参考USB协议第9章内容(将已附件格式打包)。
<!--[if !vml]-->
<!--[endif]-->
分析:bmRequestType(D6..5: 种类)00=标准请求:用于枚举
01=类 :指定类 的OUT/IN事务
02=厂商
C.由于在枚举时几乎都是用标准请求的。所以只是分析REQUEST_STANDARD请求
<!--[if !vml]-->
<!--[endif]-->
下面为第9章的标准请求列表,和上面吻合。
USB协议第9章:
keil程序
<!--[endif]-->
文章评论(0条评论)
登录后参与讨论