第五十四章 触控USB鼠标实验 STM32F103系列芯片都自带了USB,不过STM32F103的USB都只能用来做设备,而不能用作主机。既便如此,对于一般应用来说已经足够了。本章,我们将向大家介绍如何在ALIENTEK战舰STM32开发板上虚拟一个USB鼠标。本章分为如下几个部分:
54.1 USB简介
54.2 硬件设计
54.3 软件设计
54.4 下载验证
54.1 USB简介USB ,是英文Universal Serial BUS(通用串行总线)的缩写,而其中文简称为“通串线,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯。是应用在PC领域的接口技术。USB接口支持设备的即插即用和热插拔功能。USB是在1994年底由英特尔、康柏、IBM、Microsoft等多家公司联合提出的。
USB发展到现在已经有USB1.0/1.1/2.0/3.0等多个版本。目前用的最多的就是USB1.1和USB2.0,USB3.0目前已经开始普及。STM32F103自带的USB符合USB2.0规范。
标准USB共四根线组成,除VCC/GND外,另外为D+,D-; 这两根数据线采用的是差分电压的方式进行数据传输的。在USB主机上,D-和D+都是接了15K的电阻到低的,所以在没有设备接入的时候,D+、D-均是低电平。而在USB设备中,如果是高速设备,则会在D+上接一个1.5K的电阻到VCC,而如果是低速设备,则会在D-上接一个1.5K的电阻到VCC。这样当设备接入主机的时候,主机就可以判断是否有设备接入,并能判断设备是高速设备还是低速设备。接下来,我们简单介绍一下STM32的USB控制器。
STM32F103的MCU自带USB从控制器,符合USB规范的通信连接;PC主机和微控制器之间的数据传输是通过共享一专用的数据缓冲区来完成的,该数据缓冲区能被USB外设直接访问。这块专用数据缓冲区的大小由所使用的端点数目和每个端点最大的数据分组大小所决定,每个端点最大可使用512字节缓冲区(专用的512字节,和CAN共用),最多可用于16个单向或8个双向端点。USB模块同PC主机通信,根据USB规范实现令牌分组的检测,数据发送/接收的处理,和握手分组的处理。整个传输的格式由硬件完成,其中包括CRC的生成和校验。
每个端点都有一个缓冲区描述块,描述该端点使用的缓冲区地址、大小和需要传输的字节数。当USB模块识别出一个有效的功能/端点的令牌分组时,(如果需要传输数据并且端点已配置)随之发生相关的数据传输。USB模块通过一个内部的16位寄存器实现端口与专用缓冲区的数据交换。在所有的数据传输完成后,如果需要,则根据传输的方向,发送或接收适当的握手分组。在数据传输结束时,USB模块将触发与端点相关的中断,通过读状态寄存器和/或者利用不同的中断来处理。
USB的中断映射单元:将可能产生中断的USB事件映射到三个不同的NVIC请求线上:
1、USB低优先级中断(通道20):可由所有USB事件触发(正确传输,USB复位等)。固件在处理中断前应当首先确定中断源。
2、USB高优先级中断(通道19):仅能由同步和双缓冲批量传输的正确传输事件触发,目的是保证最大的传输速率。
3、USB唤醒中断(通道42):由USB挂起模式的唤醒事件触发。
USB设备框图如图54.1.1所示:
图54.1.1 USB设备框图整个USB通信的详细过程是很复杂的,本书篇幅有限,这里我们就不再详细介绍其各个环节,感兴趣的读者,可以去看看电脑圈圈的《圈圈教你玩USB》这本书,该书对USB的讲解是很详细的。USB部分,ST提供了几个例程,这些例程对于我们了解STM32F103的USB会有不少帮助,尤其在不懂的时候,看看ST的例程,会有意想不到的收获。本实验的USB部分就是移植ST的JoyStickMouse例程相关部分而来,再加上我们的触摸屏,做成一个触控鼠标。ST提供的USB例程在X:\Keil3.80\ARM\Examples\ST\STM32F10xUSBLib\Demos文件夹下(X是你的安装盘)。
54.2 硬件设计本章实验功能简介:开机的时候先检测触摸屏是否校准过,如果没有,则校准。如果校准过了,则开始触摸屏画图,然后将我们的坐标数据上传到电脑(假定USB已经配置成功了,DS1亮),这样就可以用触摸屏来控制电脑的鼠标了。我们用按键KEY0模拟鼠标右键,用按键KEY2模拟鼠标左键,用按键WK_UP和KEY1模拟鼠标滚轮的上下滚动。同样我们也是用DS0来指示程序正在运行。
所要用到的硬件资源如下:
1) 指示灯DS0 、DS1
2) 四个按键(KEY0/KEY1/KEY2/WK_UP)
3) 串口
4) TFTLCD模块
5) USB接口
前面5部分,在之前的实例中都介绍过了,我们在此就不介绍了。接下来看看我们电脑USB与STM32的USB连接口。ALIENTEK战舰STM32采用的是5PIN的MiniUSB接头,用来和STM32的USB相连接,连接电路如图54.2.1所示:
图54.2.1 MiniUSB
接口与STM32
的连接电路图从上图可以看出,USB座没有直接连接到STM32上面,而是通过P13转接,所以我们需要通过跳线帽将PA11和PA12分别连接到D-和D+,如图54.2.2所示:
图54.2.2 硬件连接示意图
54.3 软件设计本章,我们在第三十一章实验(实验26【这里指源码,下同】)的基础上修改,所以先打开第三十一章的工程,在HARDWARE文件夹所在的文件夹下新建一个USB的文件夹,然后在USB文件夹下面新建LIB和CONFIG两个文件夹,分别用来存放与USB核相关的代码以及配置部分代码。这两部分代码我们就不细说了(详见光盘本例程源码),给大家看看在这两个文件夹内的代码都有哪些,如图54.3.1所示:
图54.3.1 USB相关部分代码以上代码,就是ST提供的USB固件库代码,LIB文件夹内的是固件库文件,而CONFIG文件夹内的是一些配置文件。LIB文件夹下的.c文件源码来自:X:\Keil3.80A\ARM\RV31\LIB\
ST\STM32F10x\USB目录下,而.h文件来自X:\Keil3.80A\ARM\INC\ST\STM32F10x\USB目录下。CONFIG文件夹下的.c和.h文件源码来自X:\Keil3.80A\ARM\Examples\ST\STM32F10xUSBL
ib\Demos\JoyStickMouse下的source和include文件夹(X为你安装MDK的磁盘)。
现在,我们先来介绍一下LIB文件夹下的几个.c文件。
usb_regs.c文件,该文件主要负责USB 控制寄存器的底层操作,里面有个中USB寄存器的底层操作函数。
usb_init.c文件,该文件里面只有一个函数:USB_Init,用于USB控制器的初始化,不过对USB控制器的初始化,是USB_Init调用用其他文件的函数实现的,USB_Init只不过是把他们连接一下罢了,这样使得代码比较规范。
usb_int.c文件,该文件里面只有两个函数CTR_LP和CTR_HP,CTR_LP负责USB低优先级中断的处理。而CTR_HP负责USB高优先级中断的处理。
usb_mem.c文件,该文件用于处理PMA数据,PMA全称为Packet memory area,是STM32内部用于USB/CAN的专用数据缓冲区,该文件内也只有2个函数即: PMAToUserBufferCopy和UserToPMABufferCopy,分别用于将USB 端点的数据传送给主机和主机的数据传送到USB 端点。
usb_croe.c 文件,该文件用于处理USB2.0 协议。
以上几个文件具有很强的独立性,除特殊情况,不需要用户修改,直接调用内部的函数即可。接着我们介绍CONFIG文件夹里面的几个.c文件。
usb_pwr.c文件,该文件用于USB 控制器的电源管理;
usb_istr.c 文件,该文件用于处理USB 中断。
usb_prop.c文件,该文件用于处理Joystick的相关事件,包括Joystick的初始化、复位等等操作。
usb_desc.c文件,该文件用于Joystick描述符的处理。
hw_config.c文件,该文件用于硬件的配置,比如初始化USB时钟、USB中断、低功耗模式处理等。
另外stm32f10x_it.c就是中断服务函数的集合了,这里面我们只保留了两个函数,第一个函数是:USB_LP_CAN1_RX0_IRQHandler函数,我们在该函数里面调用USB_Istr函数,用于处理USB发生的各种中断,注意,这些代码,有些是经过修改了的,尤其是在stm32f10x_it.h文件里面,我们需要屏蔽一段代码,该段代码如下:
void NMI_Handler(void) __attribute__ ((alias("NMIException")));
void HardFault_Handler(void) __attribute__ ((alias("HardFaultException")));
void MemManage_Handler(void) __attribute__ ((alias("MemManageException")));
void BusFault_Handler(void) __attribute__ ((alias("BusFaultException")));
void UsageFault_Handler(void) __attribute__ ((alias("UsageFaultException")));
void DebugMon_Handler(void) __attribute__ ((alias("DebugMonitor")));
void SVC_Handler(void) __attribute__ ((alias("SVCHandler")));
void PendSV_Handler(void) __attribute__ ((alias("PendSVC")));
void SysTick_Handler(void) __attribute__ ((alias("SysTickHandler")));
这部分代码因为没有实现具体的函数,所以会报错,这里我们将它们全部屏蔽。后续只要有USB的实验,我们都需要屏蔽这段代码,或者你可以实现这些函数,就不需要屏蔽了。
另外一个函数就是USBWakeUp_IRQHandler函数,我们在该函数就做了一件事:清除中断标志。USB相关代码,就给大家介绍到这里。
接着我们在工程文件里面新建USB和USBCFG组,分别加入USB\LIB下面的代码和USB\CONFIG下面的代码。然后把LIB和CONFIG文件夹加入头文件包含路径。
在test.c里面,我们修改main函数如下:
//装载画图界面
void Load_Draw_Dialog(void)
{
LCD_Clear(WHITE);//清屏
POINT_COLOR=BLUE;//设置字体为蓝色
LCD_ShowString(lcddev.width-24,0,200,16,16,"RST");//显示清屏区域
POINT_COLOR=RED;//设置画笔蓝色
}
//计算x1,x2的绝对值
u32 usb_abs(u32 x1,u32 x2)
{
if(x1>x2)return x1-x2;
else return x2-x1;
}
//设置USB 连接/断线
//enable:0,断开
// 1,允许连接
void usb_port_set(u8 enable)
{
RCC->APB2ENR|=1<<2; //使能PORTA时钟
if(enable)_SetCNTR(_GetCNTR()&(~(1<<1)));//退出断电模式
else
{
_SetCNTR(_GetCNTR()|(1<<1)); // 断电模式
GPIOA->CRH&=0XFFF00FFF;
GPIOA->CRH|=0X00033000;
PAout(12)=0;
}
}
int main(void)
{
u8 key;
u8 i=0;
s8 x0; //发送到电脑端的坐标值
s8 y0;
u8 keysta; //[0]:0,左键松开;1,左键按下;
//[1]:0,右键松开;1,右键按下
//[2]:0,中键松开;1,中键按下
u8 tpsta=0; //0,触摸屏第一次按下;1,触摸屏滑动
short xlast; //最后一次按下的坐标值
short ylast;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,9600); //串口1初始化
LCD_Init(); //初始化液晶
LED_Init(); //LED初始化
KEY_Init(); //按键初始化
TP_Init(); //初始化触摸屏
usmart_dev.init(72); //usmart初始化
POINT_COLOR=RED;
LCD_ShowString(60,50,200,16,16,"WarShip STM32");
LCD_ShowString(60,70,200,16,16,"USB Mouse TEST");
LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(60,110,200,16,16,"2012/9/24");
LCD_ShowString(60,130,200,16,16,"KEY_UP:SCROLL +");
LCD_ShowString(60,150,200,16,16,"KEY_DOWN:SCROLL -");
LCD_ShowString(60,170,200,16,16,"KEY_RIGHT:RIGHT BTN");
LCD_ShowString(60,190,200,16,16,"KEY_LEFT:LEFT BTN");
delay_ms(1800); usb_port_set(0); //USB先断开
delay_ms(300); usb_port_set(1); //USB再次连接
//USB配置
USB_Interrupts_Config();
Set_USBClock();
USB_Init();
Load_Draw_Dialog();
while(1)
{
key=KEY_Scan(1);//支持连按
if(key)
{
if(key==KEY_UP)Joystick_Send(0,0,0,1); //发送滚轮数据到电脑
else if(key==KEY_DOWN)Joystick_Send(0,0,0,(u8)-1); //发送滚轮数据到电脑
else
{
if(key==KEY_LEFT)keysta|=0X01; //发送鼠标左键
if(key==KEY_RIGHT)keysta|=0X02; //发送鼠标右键
Joystick_Send(keysta,0,0,0); //发送给电脑
}
}else if(keysta)//之前有按下
{
keysta=0;
Joystick_Send(0,0,0,0); //发送松开命令给电脑
}
tp_dev.scan(0);
if(tp_dev.sta&TP_PRES_DOWN) //触摸屏被按下
{
//最少移动5个单位,才算滑动
if(((usb_abs(tp_dev.x,xlast)>4)||(usb_abs(tp_dev.y,ylast)>4))&&tpsta==0)//滑动
{
xlast=tp_dev.x; //记录刚按下的坐标
ylast=tp_dev.y;
tpsta=1;
}
if(tp_dev.x
{
if(tp_dev.x>216&&tp_dev.y<16)Load_Draw_Dialog();//清除
else TP_Draw_Big_Point(tp_dev.x,tp_dev.y,RED); //画图
if(bDeviceState==CONFIGURED)
{
if(tpsta)//滑动
{
x0=(xlast-tp_dev.x)*3; //上次坐标值与新坐标值之差,扩大3倍
y0=(ylast-tp_dev.y)*3;
xlast=tp_dev.x; //记录刚按下的坐标
ylast=tp_dev.y;
Joystick_Send(keysta,-x0,-y0,0); //发送数据到电脑
delay_ms(5);
}
}
}
}else { tpsta=0;delay_ms(1);}//清除
if(bDeviceState==CONFIGURED)LED1=0;//USB配置成功,LED1亮,否则,灭
else LED1=1;
i++;
if(i==200) {i=0; LED0=!LED0;}
}
}
在此部分代码用于实现我们在硬件设计部分提到的功能,USB的配置通过三个函数完成:USB_Interrupts_Config()、Set_USBClock()和USB_Init(),第一个函数用于设置USB唤醒中断和USB低优先级数据处理中断,Set_USBClock函数用于 配置USB时钟,也就是从72M的主频得到48M的USB时钟(1.5分频)。最后USB_Init()函数用于初始化USB,最主要的就是调用了Joystick_init函数,开启了USB部分的电源等。这里需要特别说明的是,USB配置并没有对PA11和PA12这两个IO口进行设置,是因为,一旦开启了USB电源(USB_CNTR的PDWN位清零)PA11和PA12将不再作为其他功能使用,仅供USB使用,所以在开启了USB电源之后不论你怎么配置这两个IO口,都是无效的。要在此获取这两个IO口的配置权,则需要关闭USB电源,也就是置位USB_CNTR的PDWN位,我们通过usb_port_set函数来禁止/允许USB连接,在复位的时候,先禁止,再允许,这样每次我们按复位电脑都可以识别到USB鼠标,而不需要我们每次都拔USB线。
USB数据发送,我们采用Joystick_Send来实现,我们将得到的鼠标数据,在Joystick_Send函数里面打包,并通过USB端点1发送到电脑。
软件设计部分,就给大家介绍到这里。
54.4 下载验证在代码编译成功之后,我们下载代码到ALIENTEK战舰STM32开发板上,在USB没有配置成功的时候,其界面同第三十一章的实验是一模一样的,如图54.4.1所示:
图54.4.1 USB无连接时的界面此时DS1不亮,DS0闪烁,其实就是一个触摸屏画图的功能,而一旦我们将USB连接上(将USB线接到USB接头上,而不是USB_232接头上,如果你有两根USB线,则可以两个同时都接上,他们不会相互影响),则可以看到DS1亮了,而且在电脑上会提示发现新硬件如图54.4.2所示:
图54.4.2 电脑提示找到新硬件在硬件安装完成之后,我们在设备管理器里面可以发现多出了一个人体学输入设备,如图54.4.3所示:
图54.4.3 USB人体学输入设备