三、USB的“JoyStickMouse”工作过程详细分析(1)
1、初始化过程叙述
从main()函数开始
1)Set_System(void)的工作过程;
由于这些代码都是采用库代码,所以主要分析每个代码具体做了什么工作。有些常用、类似的代码这里不列出了。
//* Function Name : Set_System
//* Description : Configures Main system clocks & power.* Function Name : Set_SYSTEM
* Description : Configures Main system clocks & power.
* Input : None.
* Return : None.
*******************************************************************************/
void Set_SYSTEM()
{
/* Setup the microcontroller system. Initialize the Embedded Flash Interface,
initialize the PLL and update the SystemFrequency variable. */
/*!< RCC system reset(for debug purpose) */
/*!< Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/*!< Reset SW[1:0], HPRE[3:0], PPRE1[2:0], PPRE2[2:0], ADCPRE[1:0] and MCO[2:0] bits */
RCC->CFGR &= (uint32_t)0xF8FF0000;
/*!< Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/*!< Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/*!< Reset PLLSRC, PLLXTPRE, PLLMUL[3:0] and USBPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
/*!< Disable all interrupts */
RCC->CIR = 0x00000000;
/*!< Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/*!< Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
}
先将RCC部分复位,系统使用内部振荡HSI,8MHz-RCC_DeInit();
使能HSE——RCC_HESConfig(RCC_HSE_ON);
设置HCLK = SYSCLK———RCC_HCLKConfig(RCC_SYSCLK_Div1);
设置PCLK2,PCLK1——RCC_PCLK2Config(RCC_HCLK_Div1);
设置PLL,使能PLL——PLL采用HSE,输出=HSEx9;
RCC_PLLConfig(RCC_PLL_Source_HSE_Div1, RCC_PLLMu1_9);
系统时钟采用PLL输出——RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
使能PWR控制,目的是为了控制CPU的低功耗模式;
将所有输入口初始化为模拟输入——GPIO_AINConfig();
使能USB上拉控制GPIO端口的时钟,这个端口设置为低电平时,USB外设会被集线器检测到,并报告给主机,这也是设备枚举的开始;
将这个端口的模式设置为开漏输出;
初始化上下左右四个按键为上下拉输入;
配置GPIOG8为EXTI8中断输入引脚,这个是在外部按键输入引起中断。
配置EXTI18中断。这个是发生在USB唤醒事件时用。
2)USB_Interrupt_Config(void)的工作过程;
//* Function Name : USB_Interrupts_Config.
//* Description : Configures the USB interrupts.
void USB_Interrupts_Config(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
设置向量表位置在FLASH起始位置:NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x00);
设置优先级分组,1位用于抢占优先级,其余用于子优先级:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
接下来配置、使能了三个中断,包括USB低优先级中断、USB唤醒中断、按键控制中断。
3)Set_USBClock()的工作过程;
* Function Name : Set_USBClock
* Description : Configures USB Clock input (48MHz).
* Input : None.
* Output : None.
* Return : None.
*******************************************************************************/
void Set_USBClock(void)
{
- /* Select USBCLK source */
RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5);
/* Enable USB clock */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE);
}
设置并使能USB时钟,从RCC输出可以看到,USB时钟是48MHz。
4)USB_Init()的工作过程;
//* Function Name : USB_Init
//* Description : USB system initialization
void USB_Init(void)
{
pInformation = &Device_Info;
pInformation->ControlState = 2;
pProperty = &Device_Property; //设备本身支持的属性和方法
pUser_Standard_Requests = &User_Standard_Requests; //主机请求的实现方法
/* Initialize devices one by one */
pProperty->Init(); //回调设备的初始化例程
}
//* Function Name : Joystick_init.
//* Description : Joystick_init init routine.
void Joystick_init(void)
{
/* Update the serial number string descriptor with the data from the unique ID*/
Get_SerialNum();
pInformation->Current_Configuration = 0;
/* Connect the device */
PowerOn();
/* USB interrupts initialization */
_SetISTR(0); /* clear pending interrupts */
wInterrupt_Mask = IMR_MSK;
_SetCNTR(wInterrupt_Mask); /* set interrupts mask */
bDeviceState = UNCONNECTED;
}
这里主要初始化了三个全局结构体指针:
pInformation表明当前连续的状态和信息;
pProerity表明设备支持的方法;
pUser_Standard_Requests主机请求实现的函数指针数组。
最后调用pProperty->Init(),实质就是调用Joystick_init(void);
这个函数首先获取设备版本号,并转换为Unicode存入版本号字符串——Get_SerialNum();
设备当前配置为0;
然后调用PowerOn(),将D+上拉,此时USB设备就能被集线器检测到了,因此进入下一流程。
2、进入设备检测状态
1)在PowerOn()中执行的情况
* Function Name : PowerOn
* Description :
* Input : None.
* Output : None.
* Return : USB_SUCCESS.
*******************************************************************************/
RESULT PowerOn(void)
{
uint16_t wRegVal;
/*** cable plugged-in ? ***/
USB_Cable_Config(ENABLE);
/*** CNTR_PWDN = 0 ***/
wRegVal = CNTR_FRES;
_SetCNTR(wRegVal);
/*** CNTR_FRES = 0 ***/
wInterrupt_Mask = 0;
_SetCNTR(wInterrupt_Mask);
/*** Clear pending interrupts ***/
_SetISTR(0);
/*** Set interrupt mask ***/
wInterrupt_Mask = CNTR_RESETM | CNTR_SUSPM | CNTR_WKUPM;
_SetCNTR(wInterrupt_Mask);
return USB_SUCCESS;
}
在上面的USB_init()中调用PowerOn(),而它先调用 USB_Cable_Config(ENABLE),这个函数实质上
将USB连接控制线设置为低电平,然后主机就可以检测到设备了。
当集线器报告设备连接状态,并收到指令后,会复位USB总线,这需要一定的时间(这段时间内设备应该
准备好处理复位指令)。但是现在设备初始化程序将继续向下进行,因为它还没有使能复位中断。
wRegVal = CNTR_FRES;
_SetCNTR(wRegVal); //这句话实际上使能了USB模块的电源,因为上电复位时,CNTR寄存器的
中断电控制位:PDWN是1,模块是断电的。
这句话虽然将强制复位USB模块,但由于复位中断允许位没有使能,不会引起复位中断,而间接上又使
PDWN = 0,模块开始工作。
_SetCNTR是一个宏,将wRegVal赋值给CNTR寄存器,此时所有的中断被屏蔽。
再接下来两句指令又将清除复位信号:
wInterrupt_Mask = 0;
_SetCNTR(wInterrupt_Mask);
然后清除所有的状态位:_SetISTR(0);
接下来是很关键的两句:
wInterrupt_Mask = CNTR_RESETM | CNTR_SUSPM | CNTR_WKUPM;
_SetCNTR(wInterrupt_Mask);
后面的一个语句执行后,复位中断已经被允许,而此时集线器多半已经开始复位端口了。或者说稍微有限
延迟,设备固件还能继续初始化一些部件,但已经不会影响整个工作流程了。
所以,接下来直接进入复位中断。
2)复位中断的处理
//* Function Name : USB_LP_CAN1_RX0_IRQHandler
//* Description : This function handles USB Low Priority or CAN RX0 interrupts
requests.void USB_LP_CAN1_RX0_IRQHandler(void)
{
USB_Istr();
}
//* Function Name : USB_Istr
//* Description : STR events interrupt service routine
void USB_Istr(void)
{
wIstr = _GetISTR();
.........
if (wIstr & ISTR_RESET & wInterrupt_Mask) //对中断位进行判断
{
_SetISTR((uint16_t)CLR_RESET); //先判断复位中断位
Device_Property.Reset(); //进入设备定义的复位过程。
- //实际上是调用JoyStick_Reset()函数进行处理
.........
}
.........
} /* USB_Istr */
当复位中断允许、且总线被集线器复位的时候,固件程序进入USB_LP中断。中断程序直接调用
USB_Istr(void)程序。
3)JoyStick_Reset()函数的处理
当复位中断允许、且总线被集线器复位的时候,固件程序进入USB_LP中断。中断程序直接调用
USB_Istr(void)程序。
//* Function Name : Joystick_Reset.
//* Description : Joystick Mouse reset routine.
void Joystick_Reset(void)
{
/* Set Joystick_DEVICE as not configured */
pInformation->Current_Configuration = 0; //当前配置为0
/*the default Interface*/
pInformation->Current_Interface = 0; //当前接口为0
/* Current Feature initialization */
pInformation->Current_Feature = Joystick_ConfigDescriptor[7]; //需要总线供电
SetBTABLE(BTABLE_ADDRESS); //设置包缓冲区地址
/* Initialize Endpoint 0 */
SetEPType(ENDP0, EP_CONTROL);
//端点0为控制端点SetEPTxStatus(ENDP0, EP_TX_STALL); //端点状态为发送无效,即主机IN令牌包来的时候,会送一个STALL
//
设置端点0描述符表,包括接收缓冲区地址、最大允许接收的字节数、发送缓冲区地址三个变量
SetEPRxAddr(ENDP0, ENDP0_RXADDR);
SetEPTxAddr(ENDP0, ENDP0_TXADDR); //这是发送缓冲区地址
//清除EP_KIND的STATUS_OUT位,如果该位被设置,在控制模式下只对0字节数据包响应。其它的都返回STALL。主要用于控制传输的状态过程。
Clear_Status_Out(ENDP0);
SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); //接收缓冲区支持64个字节
SetEPRxValid(ENDP0);
/* Initialize Endpoint 1 */
SetEPType(ENDP1, EP_INTERRUPT); //端点1为中断端点
SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置发送缓冲区地址
SetEPTxCount(ENDP1, 4); //每次发送四个字节
SetEPRxStatus(ENDP1, EP_RX_DIS); //接收禁止,只发送Mouse信息,不从主机接收
SetEPTxStatus(ENDP1, EP_TX_NAK); //现在发送端点还不允许发送数据
/* Set this device to response on default address */
SetDeviceAddress(0); //连接状态改为已经连接,默认地址状态
bDeviceState = ATTACHED; //地址默认为0
}
复位中断执行完后,开发板的USB接口能够以默认的地址对主机来的数据包进行响应了。这个阶段的分析结束,下面正式分析代码实现的枚举过程。