STM32官方USB例程JoyStick详解.docx

上传人:b****5 文档编号:7822108 上传时间:2023-01-26 格式:DOCX 页数:32 大小:29.84KB
下载 相关 举报
STM32官方USB例程JoyStick详解.docx_第1页
第1页 / 共32页
STM32官方USB例程JoyStick详解.docx_第2页
第2页 / 共32页
STM32官方USB例程JoyStick详解.docx_第3页
第3页 / 共32页
STM32官方USB例程JoyStick详解.docx_第4页
第4页 / 共32页
STM32官方USB例程JoyStick详解.docx_第5页
第5页 / 共32页
点击查看更多>>
下载资源
资源描述

STM32官方USB例程JoyStick详解.docx

《STM32官方USB例程JoyStick详解.docx》由会员分享,可在线阅读,更多相关《STM32官方USB例程JoyStick详解.docx(32页珍藏版)》请在冰豆网上搜索。

STM32官方USB例程JoyStick详解.docx

STM32官方USB例程JoyStick详解

转载:

http:

//www.usr.cc/thread-51423-1-1.html

作者:

追风

一、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请求实现、功能实现的函数指针。

voidUSB_Init(void)

{

pInformation=&Device_Info;

pInformation->ControlState=2;

pProperty=&Device_Property;

pUser_Standard_Requests=&User_Standard_Requests;

/*Initializedevicesonebyone*/

pProperty->Init();

}

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

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*/

};

USER_STANDARD_REQUESTSUser_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中处理消息,好像也用到了这种模式。

回调函数的实现方法是函数指针数组。

这是指针的高级应用。

这是函数的代码:

voidJoystick_init(void)

{/*Updatetheserialnumberstringdescriptorwiththedatafromtheunique

ID*/

Get_SerialNum();

//获取设备序列号,转变为unicode字符串

pInformation->Current_Configuration=0;

/*Connectthedevice*/

PowerOn();

//连接USB设备,实质是能让主机检测到了。

/*USBinterruptsinitialization*/

_SetISTR(0);

/*clearpendinginterrupts*/

wInterrupt_Mask=IMR_MSK;

_SetCNTR(wInterrupt_Mask);/*setinterruptsmask*/

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);/*enableendpointfortransmission*/

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)/*checkifSUSPENDispossible*/

{

Suspend();

}

else

{

/*ifnotpossiblethenresumeafterxxms*/

Resume(RESUME_LATER);

}

/*clearoftheISTRbitmustbedoneaftersettingofCNTR_FSUSP*/

_SetISTR((u16)CLR_SUSP);

}

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

if(wIstr&ISTR_CTR&wInterrupt_Mask)

{

CTR_LP();/*servicingoftheendpointcorrecttransferinterrupt*/

}

二、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有效,如果SIE置位传输完成标志,则相应的数据传输完成中断发生。

第15位

包缓冲区溢出中断允许位

错误中断允许位

唤醒中断允许位。

WKUPM。

1有效,如果唤醒请求标志位置位,则产生唤醒中断。

挂起中断允许位。

SUSPM,1有效,当总线挂起标志置位时,发生挂起中断。

复位中断允许位。

RESETM。

1有效,软件强制复位和总线复位信号,都能触发复位中断。

帧首中断允许位

期望帧首中断允许位。

ESOFM。

它的含义是没有收到帧首信号,允许发生中断。

第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,输出=HSEX9;

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;//USBresumefromsuspendmode

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()的工作过程

voidUSB_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,意义现在还不十分明了。

typedefstruct_DEVICE_INFO

{

u8USBbmRequestType;

/*bmRequestType*/

u8USBbRequest;

/*bRequest*/

u16_u8USBwValues;

/*wValue*/

u16_u8USBwIndexs;

/*wIndex*/

u16_u8USBwLengths;

/*wLength*/

u8ControlState;

/*oftypeCONTROL_STATE*/

u8Current_Feature;

u8Current_Configuration;

/*Selectedconfiguration*/

u8Current_Interface;

/*Selectedinterfaceofcurrentconfiguration*/

u8Current_AlternateSetting;/*SelectedAlternateSettingofcurrent

interface*/

ENDPOINT_INFOCtrl_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_SUSPM|CNTR_WKUPM;

_SetCNTR(wInterrupt_Mask);

后面一个语句执行后,复位中断已经被允许,而此时集线器多半已经开始复位端口了。

或者说稍微有限延迟,设备固件还能继续初始化一些部件,但已经不会影响整个工作流程了。

所以接下来,分析直接进入复位中断。

(2)复位中断的处理。

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

中断程序直接调用USB_Istr(void)程序。

接下来讲对中断位进行判断:

if(wIstr&ISTR_RESET&wInterrupt_Mask)

{

_SetISTR((u16)CLR_RESET);

//先清除复位中断位

Device_Property.Reset();

//进入设备定义的复位过程。

实际上是调用JoyStick_Reset()函数进行处理。

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 农林牧渔 > 林学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1