华拓科技网
您的当前位置:首页官方USB程序和例程

官方USB程序和例程

来源:华拓科技网
官方USB程序和例程

一、USB的“JoyStickMouse”例程结构分析 1、例程的结构 (1)底层结构

包括5个文件:usb_core.c(USB总线数据处理的核心文件),usb_init.c,usb_int.c(用于端点数据输入输入中断处理),usb_mem.c(用于缓冲区操作),usb_regs.c(用于寄存器操作)。它们都包含了头文件“usb_lib.h”。在这个头文件中,又有以下定义:

#include \"usb_type.h\" #include \"usb_regs.h\" #include \"usb_def.h\" #include \"usb_core.h\" #include \"usb_init.h\" #include \"usb_mem.h\" #include \"usb_int.h\"

usb_lib.h中又包含了七个头文件,其中usb_type.h中主要是用typedef为stm32支持的数据类型取一些新的名称。usb_def.h中主要是定义一些相关的数据类型。

还有一个未包含在usb_lib.h中的头文件,usb_conf.h用于USB设备的配置。

(2)上层结构

上层结构总共5个文件:hw_config.c(用于USB硬件配置)、usb_pwr.c(用于USB连接、断开操作)、usb_istr.c(直接处理USB中断)、usb_prop.c(用于上层协议处理,比如HID协议,大容量存储设备协议)、usb_desc.c(具体设备的相关描述符定义和处理)。

可见,ST的USB操作库结构十分清晰明了,我先不准备直接阅读源代码。而是先利用MDK的软件模拟器仿真执行,先了解一下设备初始化的流程。

2、设备初始化所做的工作

(1)Set_System(void)

这个是main函数中首先调用的函数,它位于hw_config.c文件中。它的主要功能是初始化时钟系统、使能相关的外围设备电源。

配置了JoyStickMouse所用到的5个按键,并且配置了两个EXTI中断,一个是用于把USB从挂起模式唤醒,还有一个用途未知。

(2)USB_Interrupts_Config();

这个是main函数中调用的第二个函数,它也位于hw_config.c文件中。主要功能是配置USB所用到的中断。

跟踪到代码中,主要设配置了USB低优先级中断和唤醒中断,又有一个EXTI中断功能未知。

(3)Set_USBClock()

这个是main函数中调用的第三个函数,它也位于hw_config.c文件中。它的功能是配置和使能USB时钟。

(4)USB_Init(void)

这个是main函数中调用的第四个函数,它也位于usb_init.c文件中。它初始化了三个全局指针,指向DEVICE_INFO、USER_STANDARD_REQUESTS和DEVICE_PROP 结构体。

后面两个是函数指针结构体,里面都是USB请求实现、功能实现的函数指针。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(); }

这三个结构体都是与具体设备枚举和功能实现相关的,定义在usb_prop.c和usb_desc.c文件中。

DEVICE_PROP Device_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 /*MAX PACKET SIZE*/ };

USER_STANDARD_REQUESTS User_Standard_Requests = { Joystick_GetConfiguration, Joystick_SetConfiguration, Joystick_GetInterface, Joystick_SetInterface, Joystick_GetStatus, Joystick_ClearFeature, Joystick_SetEndPointFeature, Joystick_SetDeviceFeature, Joystick_SetDeviceAddress };

Usb_init()函数调用pProperty->Init()(实质上就是Joystick_init)完成设备的初始化。

上层程序调用下次函数是常规性的操作。而下层函数(usb_init相对于usb_prop 是输入底层操作文件)调用上层文件函数我们称之为回调。

回调函数的意义在于同一种操作模式、提供不同的回调函数则可以实现不同的功能。Windows中处理消息,好像也用到了这种模式。

回调函数的实现方法是函数指针数组。这是指针的高级应用。 这是函数的代码: void Joystick_init(void)

{/* Update the serial number string descriptor with the data from the unique

ID*/

Get_SerialNum();

//获取设备序列号,转变为unicode字符串 pInformation->Current_Configuration = 0; /* Connect the device */ PowerOn();

//连接USB设备,实质是能让主机检测到了。 /* USB interrupts initialization */ _SetISTR(0);

/* clear pending interrupts */ wInterrupt_Mask = IMR_MSK;

_SetCNTR(wInterrupt_Mask); /* set interrupts mask */ bDeviceState = UNCONNECTED; }

实质上,代码执行到这里,开发板已经可以响应主机发来的数据了。但我还是先把main()函数的代码看完吧。

(5)SysTick_Config();

这个函数调用主要是为程序中用到的精确延时作配置。 3、进入主循环

进入主循环的工作就两个: Joystick_Send(JoyState())。 JoyState()用来获取按键的状态。

Joystick_Send(JoyState())用来把按键状态发到主机。当然这里

真正的发送工作并不是由该代码完成的。它的工作只是将数据写入IN端点缓冲区,主机的IN 令牌包来的时候,SIE负责把它返回给主机。

主要代码如下:

UserToPMABufferCopy(Mouse_Buffer, GetEPTxAddr(ENDP1), 4);

//从用户复制四个字节到端点1缓冲区,控制端点的输入缓冲区。SetEPTxValid(ENDP1); /* enable endpoint for transmission */

4、中断处理过程大致理解

(1)usb_istr()函数中的中断处理简单分析

有用的代码大概以下几段,首先是处理复位的代码,调用设备结构中的复位处理函数。

wIstr = _GetISTR();

if (wIstr & ISTR_RESET & wInterrupt_Mask) {

_SetISTR((u16)CLR_RESET); //清复位中断 Device_Property.Reset(); }

处理唤醒的代码:

if (wIstr & ISTR_WKUP & wInterrupt_Mask) {

_SetISTR((u16)CLR_WKUP); Resume(RESUME_EXTERNAL); }

处理总线挂起的代码:

if (wIstr & ISTR_SUSP & wInterrupt_Mask) {

if (fSuspendEnabled) /* check if SUSPEND is possible */ {

Suspend(); }

else {

/* if not possible then resume after xx ms */ Resume(RESUME_LATER); }

/* clear of the ISTR bit must be done after setting of CNTR_FSUSP */

_SetISTR((u16)CLR_SUSP); }

处理端点传输完成的代码,这段是最重要的,它调用底层usb_int.c()文件中的CTR_LP()函数来处理端点数据传输完成中断。

if (wIstr & ISTR_CTR & wInterrupt_Mask) {

CTR_LP(); /* servicing of the endpoint correct transfer interrupt */

}

二、STM32处理器的USB接口 1、接口模块的内部结构

在书上有一个很好的USB内部接口模块内部结构图,比较好的解释了各个模块之间的关系,我这里试着用我自己的理解阐述一下吧。

首先在总线端(与D+、D-相连的那一端),通过模拟收发器与SIE连接。SIE 使用48MHz的专用时钟。

与SIE相关的的有三大块:CPU内部控制、中断和端点控制寄存器,挂起定时器(这个好像是USB协议的要求,总线在一定时间内没有活动,SIE模块能够进入SUSPEND状态以节约电能),还有包缓冲区接口模块。

说到包缓冲区接口模块,这个对应的含义是,USB设备应该提供USB包缓冲区。这块缓冲区同时受到SIE和CPU核心的控制,用于CPU与SIE共享达到数据传输的目的。

所以CPU通过APB1总线接口访问,SIE通过包缓冲区接口模块

访问,中间通过Arbiter来协调访问。

当然我们关注的中心点是控制、中断和端点控制寄存器。我们通过这些寄存器来获取总线传输的状态,控制各个端点的状态,并可以产生中断来让CPU处理当前的USB事件。

CPU可以通过APB1总线接口来访问这些寄存器。它们使用的都是PCLK1时钟。

2、USB模块的寄存器认识 (1)

控制寄存器CNTR

传输完成中断允许位。CTRM,1有效,如包缓冲 区溢出 中断允 许位 错误中 断允许 位 唤醒中断 允许位。 WKUPM。1 有效,如 果唤醒请 挂起中 断允许 位。 SUSPM,1 有效,当 复位中断 允许位。 RESETM。1 有效,软

件强制复 帧首中 断允许 位 期望帧 首中断 允许位。 ESOFM。 它的含

果SIE 置位传输完成标志,则相应的数据传输完成中断发生。 第15位求标志位 置位,则 产生唤醒 中断。 总线挂 起标志 置位时, 发生挂 起中断。 位和总线 复位信 号,都能 触发复位 中断。 义是没 有收到 帧首信 号,允许 发生中 断。

第8位 向主机发 送的唤醒 请求, RESUME。1 有效,主 机收到该 信号,将 唤醒设 备。这个 由软件置 位。 第4位 强制挂 起控制, FSUSP。1 有效。与 由于总 线无活 动引起 挂起的 效果相 同。 低功耗模 式。前提 是先进入 挂起状 态。由软 件设置, 一般又硬

件复位 (被唤醒 后自动清 零)。 断电模 式控制 位。 PDWN。此 位为1 时,USB 模块关 闭。 强制复 位控制。 FRES。与 总线上 的复位 信号产 生相同 的效果。 也能产 生复位 中断. 第0位。 (2)

中断状态寄存器ISTR

这个寄存器主要是反映USB模块当前的状态的。第15-8为与控制寄存器的中断允许是意义对应的。相应的标志位置位,且中断未屏蔽,则向CPU发出对应的中断。

CTR标

志,数据传输完成后硬件置1.PMAOVR标 志 ERR标 志 WKUP请 求,总线 检测到 主机唤 醒请求 时由硬 件置位。 SUSP请 求标志 位。 RESET请 求标志 位。 SOF帧 首标志 ESOF,期 待帧首 标志。

DIR传输方向,此 位由硬件控制。IN

发生数据传输的端点的地址。 时为0,OUT为1. 第4位。

(3) USB设备地址寄存器

第7位,EF,USB模块允许位。如果EF=0,则USB模块将停止工作。

第6-0位。USB当前使用的地址。复位时为0. (4)

端点状态和配置寄存器,8个寄存器,支持8个双向端点和16个单向端点。

CTR_RX,正确接收标志位。 第15位。DTOG_RX,用于检测 的数据翻转位。一 般由硬件自动设 置,软件写1可使 其手动翻转。 STAT_RX,占据两位。

00表示该端点不可用,无回应。 01表示响应STALL 10响应NAK

11表示端点有效,可接收数据。

SETUP标志。收到SETUP令牌包时置位。用户收到数据后需检查次位。

第11位。EP_TYPE,两位,表示端点类型。 00表示批量端点。 01表示控制端点 10表示等时端点。 11表示中断端点。 EP_KIND,端点特殊 类型。在

EP_TYPE=01时,表 示设备期望主机的 0字节状态包。 CTR_TX。

正确发送标志。主机的IN包之后。 第7位。DTOG_TX,用于检测

的数据翻转位。一 般由硬件自动设 置,软件写1可使 其手动翻转。 STAT_TX,占据两位。

00表示该端点不可用,无回应。 01表示响应STALL 10响应NAK

11表示端点有效,可发送数据。

端点地址:EA【3:0】,表明该寄存器对应的端点号码。比如1、2号寄存器都可以对应端点1(在双缓冲情况下)。

第3-0位。 (5)

端点描述符表相关寄存器

首先有一个描述符表地址寄存器,指明了包缓冲区内端点描述符表的地址。

每一个端点都对应一个描述附表。描述符表也在包缓冲区内。每个端点寄存器对应的描述符表的地址可根据公式计算。

单缓冲、双向的端点描述符表有四项,每项占据两个字节:分别是端点n的发送缓冲区地址、发送字节数、接收缓冲区地址、接收字节数。

了解USB相关寄存器的知识以后,接下来就可以分析“JoyStickMouse”详细的工作过程了。

三、USB的“JoyStickMouse”工作过程详细分析 1、初始化过程叙述 从main()函数开始

(1)Set_System(void)的工作过程

由于这些代码都是采用库代码,所以我主要分析每个代码具体做了什么工作。有些常用、类似的代码这里就不列出来了。

先将RCC部分复位,系统使用内部振荡HSI,8MHz——

RCC_DeInit();。

使能HSE——RCC_HSEConfig(RCC_HSE_ON); 设

HCLK

=

SYSCLK——

RCC_HCLKConfig(RCC_SYSCLK_Div1);

设置PCLK2,PCLK1——RCC_PCLK2Config(RCC_HCLK_Div1); 设置PLL,使能PLL——PLL采用HSE,输出=HSE X 9; RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); 系统时钟采用PLL输出——

RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); 使能PWR控制,目的是为了控制CPU的低功耗模式; 将所有输入口初始化为模拟输入——GPIO_AINConfig();

使能USB上拉控制GPIO端口的时钟,这个端口设置为低电平时,USB外设会被集线器检测到,并报告给主机,这也是设备枚举的开始;

将这个端口的模式设置为开漏输出; 初始化上下左右四个按键为上下拉输入;

配置GPIOG8为EXTI8中断输入引脚,这个是在外部按键输入引起中断。

配置EXTI18中断。这个是发生USB唤醒事件时用。

EXTI_InitStructure.EXTI_Line = EXTI_Line18; // USB resume from suspend mode

EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising; EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure);

(2)USB_Interrupts_Config(void)的工作过程 设置向量表位置在FLASH起始位置——

NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x00);

设置优先级分组,1位用于抢占组级别。其余用于子优先级—— NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);

接下来配置、使能了三个中断,包括USB低优先级中断、USB唤醒中断(EXTI18)、和EXTI8(按键控制)中断。

它的优先级设置有些问题,明明只有一位用于抢占优先级。它把EXTI8的抢占优先级设为2。结果在调试时发现,它的抢占优先级仍然是0。

(3)Set_USBClock()的工作过程 这个代码就两句话:

RCC_USBCLKConfig(RCC_USBCLKSource_PLLCLK_1Div5); RCC_APB1PeriphClockCmd(RCC_APB1Periph_USB, ENABLE); 作用是设置并使能USB时钟,从RCC输出可以看到,USB时钟是48MHz。(4)USB_Init()的工作过程

void USB_Init(void) {

pInformation = &Device_Info; pInformation->ControlState = 2; pProperty = &Device_Property; //这个是设备本身支持的属性和方法

pUser_Standard_Requests = &User_Standard_Requests; //这个是主机请求的实现方法。

pProperty->Init(); //回调设备的初始化例程。

这个主要是初始化了三个全局结构体指针,pInformation表明当前连接的状态和信息,pProperty表明设备支持的方法,pUser_Standard_Requests是主机请求实现的函数指针数组。

Device_Info是一个结构体,包括11个成员变量。这里是将它的ControlState 设为2,意义现在还不十分明了。

typedef struct _DEVICE_INFO {

u8 USBbmRequestType; /* bmRequestType */ u8 USBbRequest; /* bRequest */

u16_u8 USBwValues; /* wValue */

u16_u8 USBwIndexs; /* wIndex */

u16_u8 USBwLengths; /* wLength */ u8 ControlState;

/* of type CONTROL_STATE */ u8 Current_Feature; u8 Current_Configuration; /* Selected configuration */ u8 Current_Interface;

/* Selected interface of current configuration */

u8 Current_AlternateSetting;/* Selected Alternate Setting of current interface*/

ENDPOINT_INFO Ctrl_Info; //端点信息结构体 }DEVICE_INFO;

最后调用pProperty->Init(),实质就是调用Joystick_init(void)。 在这个函数中,首先获取设备版本,并转换为Unicode存入版本号字符串。——Get_SerialNum();

设备当前配置置为0。然后调用PowerOn(),这个函数实质上将D+上拉,此时USB设备就能被集线器检测到了。因此分析进入下一个流程。

2、进入设备检测状态

(1)在PowerOn()中执行的情况。 在

USB_init()中调用

PowerOn(),而它先调用

USB_Cable_Config(ENABLE),这个函数实质上将USB连接控制线设置为低电平,然后设备就可以检测到设备了。

当集线器报告设备连接状态,并收到主机指令后,会复位USB总

线,这需要一定的时间(这段时间内设备应该准备好处理复位指令)。但是现在设备初始化程序将继续往下进行,因为它还没有使能复位中断。

wRegVal = CNTR_FRES; _SetCNTR(wRegVal);

//这句话实际上使能了USB模块的电源,因为上电复位时,CNTR寄存器的断电控制为PDWN位是1,模块是断电的。

这句话虽然将强制复位USB模块,但由于复位中断允许位没有使能,不会引起复位中断,而间接上由使PDWN=0,模块开始工作。

_SetCNTR是一个宏,将wRegVal赋值给CNTR寄存器,此时所有的中断被屏蔽。

再接下来两句指令又将清除复位信号。 然后清除所有的状态位。——_SetISTR(0); 接下来是很关键的两句话:

wInterrupt_Mask=CNTR_RESETM| CNTR_WKUPM;

_SetCNTR(wInterrupt_Mask);

后面一个语句执行后,复位中断已经被允许,而此时集线器多半已经开始复位端口了。或者说稍微有限延迟,设备固件还能继续初始化一些部件,但已经不会影响整个工作流程了。

所以接下来,分析直接进入复位中断。 (2)复位中断的处理。

当复位中断允许、且总线被集线器复位的时候,固件程序进入USB_LP中断。

中断程序直接调用USB_Istr(void)程序。 接下来讲对中断位进行判断:

if (wIstr & ISTR_RESET & wInterrupt_Mask) {

_SetISTR((u16)CLR_RESET); //先清除复位中断位

CNTR_SUSPM

|

Device_Property.Reset();

//进入设备定义的复位过程。实际上是调用JoyStick_Reset()函数进行处理。}

(3)JoyStick_Reset()函数的处理。 这里将一句句来分析: void Joystick_Reset(void) {

pInformation->Current_Configuration = 0; //当前配置为0

pInformation->Current_Interface = 0;/当前接口为0 pInformation->Current_Feature Joystick_ConfigDescriptor[7];

//需要总线供电

SetBTABLE(BTABLE_ADDRESS); //设置包缓冲区地址。 SetEPType(ENDP0, EP_CONTROL); //端点0为控制端点

SetEPTxStatus(ENDP0, EP_TX_STALL);

//端点状态为发送无效,也就是主机IN令牌包来的时候,回送一个STALL。

SetEPRxAddr(ENDP0, ENDP0_RXADDR); //设置端点0描述符表,包括接收缓冲区地址、最大允许接收的字节数、发送缓冲区地址三个量。

SetEPTxAddr(ENDP0, ENDP0_TXADDR); //这是发送缓冲区地址 Clear_Status_Out(ENDP0);

//清除EP_KIND的STATUS_OUT位,如果改位被设置,在控制模式下只对0字节数据包相应。其它的都返回STALL。主要用于控制传输的状态过程。

SetEPRxCount(ENDP0, Device_Property.MaxPacketSize); //接收缓冲区支持个字节。

SetEPRxValid(ENDP0);

=

//使能端点0的接收,因为很快就要接收SETUP令牌包后面跟着的数据包了。

SetEPType(ENDP1, EP_INTERRUPT); //端点1为中断端点。

SetEPTxAddr(ENDP1, ENDP1_TXADDR); //设置发送缓冲区地址。 SetEPTxCount(ENDP1, 4); //每次发送四个字节

SetEPRxStatus(ENDP1, EP_RX_DIS);

//接收禁止,只发送Mouse信息,而不从主机接收。

SetEPTxStatus(ENDP1, EP_TX_NAK); //现在发送端点还不允许发送数据。

bDeviceState = ATTACHED;

//连接状态改为已经连接,默认地址状态。 SetDeviceAddress(0); //地址默认为0. }

因篇幅问题不能全部显示,请点此查看更多更全内容