第五十九章 USB鼠标键盘(Host)实验
1.硬件平台:正点原子探索者STM32F407开发板 2.软件平台:MDK5.1 3.固件库版本:V1.4.0
上一章我们向大家介绍了如何利用
STM32F4的
USB HOST接口来驱动
U盘,本章,我们将利用
STM32F4的
USB HOST来驱动
USB鼠标
/键盘。本章分为如下几个部分:
59.1 USB鼠标键盘简介
59.2 硬件设计
59.3 软件设计
59.4 下载验证
59.1 USB鼠标键盘简介
传统的鼠标和键盘是采用
PS/2接口和电脑通信的,但是现在
PS/2接口在电脑上逐渐消失,所以现在越来越多的鼠标键盘采用的是
USB接口,而不是
PS/2接口的了。
USB鼠标键盘属于
USB HID设备。
USB HID即:
Human Interface Device(人机交互设备)的缩写,键盘、鼠标与游戏杆等都属于此类设备。不过
HID设备并不一定要有人机接口,只要符合
HID类别规范的设备都是
HID设备。关于
USB HID的知识,我们这里就不详细介绍了,请大家自行百度学习。
本章,我们同上一章一样,我们直接移植官方的
USB HID例程,官方例程路径:光盘à
8,
STM32参考资料à
STM32 USB 学习资料à
STM32_USB-Host-Device_Lib_V2.1.0à
Projectà
USB_Host_Examplesà
HID,该例程支持
USB鼠标和键盘等
USB HID设备,本章我们将移植这个例程到探索者
STM32F407开发板上。
59.2 硬件设计
本节实验功能简介:开机的时候先显示一些提示信息,然后初始化USB HOST,并不断轮询。当检测到USB鼠标/键盘的插入后,显示设备类型,并显示设备输入数据,
如果是USB鼠标:将显示鼠标移动的坐标(X,Y坐标),滚轮滚动数值(Z坐标)以及按键(左中右)。
如果是USB键盘:将显示键盘输入的数字/字母等内容(不是所有按键都支持,部分按键没有做解码支持,比如F1~F12)。
最后,还是用DS0提示程序正在运行。
所要用到的硬件资源如下:
1) 指示灯
DS0
2) 串口
3) TFTLCD模块
4) USB HOST接口
这几个部分,在之前的实例中都已经介绍过了,我们在此就不多说了。这里再次提醒大家,
P11的连接,要通过跳线帽连接
PA11和
D-以及
PA12和
D+。
59.3 软件设计
本章,我们在第十八章实验 (实验
13 TFTLCD显示实验 )的基础上修改,先打开实验
13的工程,在
HARDWARE文件夹所在文件夹下新建一个
USB的文件夹,对照官方
HID例子,将相关文件拷贝到
USB文件夹下。
然后,我们在工程里面添加
USB HID相关代码,最终得到如图
59.3.1所示的工程:
图
59.3.1
USB鼠标键盘工程截图
可以看到,
USB部分代码,同上一章的在结构上是一模一样的,只是
.c文件稍微有些变化。同样,我们移植需要修改的代码,就是
USB_APP里面的这两个
.c文件了。
其中
usb_bsp.c的代码,和之前的章节一模一样,可以用上一章的代码直接替换即可正常使用。
usbh_usr.c里面的代码,则有所变化,重点代码如下:
//下面两个函数
,为
ALIENTEK添加
,以防止
USB死机
//USB枚举状态死机检测
,防止
USB枚举失败导致的死机
//phost:USB_HOST结构体指针
//返回值
:0,没有死机
// 1,死机了
,外部必须重新启动
USB连接
.
u8 USBH_Check_EnumeDead(USBH_HOST *phost)
{
static
u16 errcnt=0;
//这个状态
,如果持续存在
,则说明
USB死机了
.
if(phost->gState==HOST_CTRL_XFER&&(phost->EnumState==ENUM_IDLE||
phost->EnumState==ENUM_GET_FULL_DEV_DESC))
{
errcnt++;
if(errcnt>2000)//死机了
{
errcnt=0;
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS,ENABLE);
//USB OTG FS 复位
delay_ms(5);
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS,DISABLE);
//复位结束
return
1;
}
}else
errcnt=0;
return
0;
}
//USB HID通信死机检测
,防止
USB通信死机
(暂时仅针对
TERR,即
Data toggle error)
//pcore:USB_OTG_Core_dev_HANDLE结构体指针
//phidm:HID_Machine_TypeDef结构体指针
//返回值
:0,没有死机
// 1,死机了
,外部必须重新启动
USB连接
.
u8 USBH_Check_HIDCommDead(USB_OTG_CORE_HANDLE
*pcore,
HID_Machine_TypeDef *phidm)
{
if(pcore->host.HC_Status[phidm->hc_num_in]==HC_DATATGLERR)//DTERR错误
{
return
1;
}
return
0;
}
//USB键盘鼠标数据处理
//鼠标初始化
void USR_MOUSE_Init (void)
{
USBH_Msg_Show(2); //USB 鼠标
USB_FIRST_PLUGIN_FLAG=1;//标记第一次插入
}
//键盘初始化
void
USR_KEYBRD_Init(void)
{
USBH_Msg_Show(1); //USB 键盘
USB_FIRST_PLUGIN_FLAG=1;//标记第一次插入
}
//临时数组
,用于存放鼠标坐标
/键盘输入内容
(4.3屏
,最大可以输入
2016字节
)
__align(4) u8 tbuf[2017];
//USB鼠标数据处理
//data:USB鼠标数据结构体指针
void USR_MOUSE_ProcessData(HID_MOUSE_Data_TypeDef
*data)
{
static
signed short x,y,z;
if(USB_FIRST_PLUGIN_FLAG)//第一次插入
,将数据清零
{
USB_FIRST_PLUGIN_FLAG=0;
x=y=z=0;
}
x+=(signed
char)data->x;
if(x>9999)x=9999;
if(x<-9999)x=-9999;
y+=(signed
char)data->y;
if(y>9999)y=9999;
if(y<-9999)y=-9999;
z+=(signed
char)data->z;
if(z>9999)z=9999;
if(z<-9999)z=-9999;
POINT_COLOR=BLUE;
sprintf((char*)tbuf,"BUTTON:");
if(data->button&0X01)strcat((char*)tbuf,"LEFT");
if((data->button&0X03)==0X02)strcat((char*)tbuf,"RIGHT");
else
if((data->button&0X03)==0X03)strcat((char*)tbuf,"+RIGHT");
if((data->button&0X07)==0X04)strcat((char*)tbuf,"MID");
else
if((data->button&0X07)>0X04)strcat((char*)tbuf,"+MID");
LCD_Fill(30+56,180,lcddev.width,180+16,WHITE);
LCD_ShowString(30,180,210,16,16,tbuf);
sprintf((char*)tbuf,"X
POS:%05d",x);
LCD_ShowString(30,200,200,16,16,tbuf);
sprintf((char*)tbuf,"Y
POS:%05d",y);
LCD_ShowString(30,220,200,16,16,tbuf);
sprintf((char*)tbuf,"Z
POS:%05d",z);
LCD_ShowString(30,240,200,16,16,tbuf);
}
//USB键盘数据处理
//data:USB键盘数据结构体指针
void
USR_KEYBRD_ProcessData (uint8_t data)
{
static
u16 pos,endx,endy,maxinputchar;
u8
buf[4];
if(USB_FIRST_PLUGIN_FLAG)//第一次插入
,将数据清零
{
USB_FIRST_PLUGIN_FLAG=0;
endx=((lcddev.width-30)/8)*8+30; //得到
endx值
endy=((lcddev.height-220)/16)*16+220; //得到
endy值
maxinputchar=((lcddev.width-30)/8);
maxinputchar*=(lcddev.height-220)/16; //当前
LCD最大可以显示的字符数
.
pos=0;
}
POINT_COLOR=BLUE;
sprintf((char*)buf,"%02X",data);
LCD_ShowString(30+56,180,200,16,16,buf);//显示键值
if(data>='
'&&data<='~')
{
tbuf[pos++]=data;
tbuf[pos]=0; //添加结束符
.
if(pos>maxinputchar)pos=maxinputchar;//最大输入这么多
}else
if(data==0X0D) //退格键
{
if(pos)pos--;
tbuf[pos]=0; //添加结束符
.
}
if(pos<=maxinputchar) //没有超过显示区
{
LCD_Fill(30,220,endx,endy,WHITE);
LCD_ShowString(30,220,endx-30,endy-220,16,tbuf);
}
}
ST官方的
USB HID例程,仅仅是能用,很多地方还要改善,比如识别率低,容易死机(枚举
/通信都可能死机)等问题,这里:
USBH_Check_EnumeDead和
USBH_Check_HIDCommDead这两个函数,就是我们针对官方
HID例程现有
bug做出的改进处理,通过这两个函数,可以检测枚举
/通信是否正常,当出现异常时,直接重启
USB内核,重新连接设备,这样可以防止死机造成的程序无响应情况。
另外,为了提高对鼠标键盘的识别率和兼容性,对
usbh_hid_core.c里面的两处代码进行了修改:
1,
USBH_HID_ClassRequest函数,修改代码(
351行)为:
classReqStatus = USBH_Set_Idle (pdev, pphost, 100,
0);//这里
duration官方设置的是
0,修改为
//100,提高兼容性
2,
USBH_Set_Idle函数,修改代码(
542行)为:
phost->Control.setup.b.wLength.w = 100; //官方的这里设置的是
0,导致部分鼠标无法识别
,
//这里修改为
100以后
,识别率明显提高
.
以上两处地方,官方默认值都是设置的
0,我们修改为
100后,可以明显提高
USB鼠标
/键盘的识别率,兼容性好很多。
再回到
usbh_usr.c,
USR_MOUSE_Init和
USR_MOUSE_ProcessData用于处理鼠标数据,这两个函数在
usbh_hid_mouse.c里面被调用,
USR_MOUSE_Init在鼠标初始化的时候被调用,而
USR_MOUSE_ProcessData函数,则在鼠标初始化成功,轮询数据的时候调用,处理鼠标数据,该函数将得到的鼠标数据显示在
LCD上面。
同样,
USR_KEYBRD_Init和
USR_KEYBRD_ProcessData用于处理键盘数据,这两个函数在
usbh_hid_keybd.c里面被调用,
USR_KEYBRD_Init在键盘初始化的时候被调用,而
USR_KEYBRD_ProcessData函数,则在键盘初始化成功,轮询数据的时候调用,处理键盘数据,该函数将键盘输入的字符显示在
LCD上面。
其他代码,我们就不再介绍了,请大家参考开发板光盘本例程源码。
最后,来看看
main.c里面的代码,如下:
USBH_HOST
USB_Host;
USB_OTG_CORE_HANDLE USB_OTG_Core_dev;
extern HID_Machine_TypeDef HID_Machine;
//USB信息显示
//msgx:0,USB无连接
1,USB键盘
// 2,USB鼠标
3,不支持的
USB设备
void USBH_Msg_Show(u8 msgx)
{
POINT_COLOR=RED;
switch(msgx)
{
case
0: //USB无连接
LCD_ShowString(30,130,200,16,16,"USB
Connecting...");
LCD_Fill(0,150,lcddev.width,lcddev.height,WHITE);
break;
case
1: //USB键盘
LCD_ShowString(30,130,200,16,16,"USB
Connected ");
LCD_ShowString(30,150,200,16,16,"USB
KeyBoard");
LCD_ShowString(30,180,210,16,16,"KEYVAL:");
LCD_ShowString(30,200,210,16,16,"INPUT
STRING:");
break;
case
2: //USB鼠标
LCD_ShowString(30,130,200,16,16,"USB
Connected ");
LCD_ShowString(30,150,200,16,16,"USB
Mouse");
LCD_ShowString(30,180,210,16,16,"BUTTON:");
LCD_ShowString(30,200,210,16,16,"X
POS:");
LCD_ShowString(30,220,210,16,16,"Y
POS:");
LCD_ShowString(30,240,210,16,16,"Z
POS:");
break;
case
3: //不支持的
USB设备
LCD_ShowString(30,130,200,16,16,"USB
Connected ");
LCD_ShowString(30,150,200,16,16,"Unknow
Device");
break;
}
}
//HID重新连接
void USBH_HID_Reconnect(void)
{
//关闭之前的连接
USBH_DeInit(&USB_OTG_Core_dev,&USB_Host); //复位
USB HOST
USB_OTG_StopHost(&USB_OTG_Core_dev); //停止
USBhost
if(USB_Host.usr_cb->DeviceDisconnected) //存在
,才禁止
{
USB_Host.usr_cb->DeviceDisconnected();
//关闭
USB连接
USBH_DeInit(&USB_OTG_Core_dev,
&USB_Host);
USB_Host.usr_cb->DeInit();
USB_Host.class_cb->DeInit(&USB_OTG_Core_dev,&USB_Host.device_prop);
}
USB_OTG_DisableGlobalInt(&USB_OTG_Core_dev);//关闭所有中断
//重新复位
USB
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS,ENABLE);//复位
delay_ms(5);
RCC_AHB2PeriphClockCmd(RCC_AHB2Periph_OTG_FS,DISABLE); //复位结束
memset(&USB_OTG_Core_dev,0,sizeof(USB_OTG_CORE_HANDLE));
memset(&USB_Host,0,sizeof(USB_Host));
//重新连接
USB HID设备
USBH_Init(&USB_OTG_Core_dev,USB_OTG_FS_CORE_ID,&USB_Host,&HID_cb,
&USR_Callbacks);
}
int main(void)
{
u32 t;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组
2
delay_init(168); //初始化延时函数
uart_init(115200); //初始化串口波特率为
115200
LED_Init(); //初始化
LED
LCD_Init();
//初始化
LCD
POINT_COLOR=RED;
LCD_ShowString(30,50,200,16,16,"Explorer
STM32F4");
LCD_ShowString(30,70,200,16,16,"USB
MOUSE/KEYBOARD TEST");
LCD_ShowString(30,90,200,16,16,"ATOM@ALIENTEK");
LCD_ShowString(30,110,200,16,16,"2014/7/23");
LCD_ShowString(30,130,200,16,16,"USB
Connecting...");
//初始化
USB主机
USBH_Init(&USB_OTG_Core_dev,USB_OTG_FS_CORE_ID,&USB_Host,&HID_cb,
&USR_Callbacks);
while(1)
{
USBH_Process(&USB_OTG_Core_dev,
&USB_Host);
if(bDeviceState==1)//连接建立了
{
if(USBH_Check_HIDCommDead(&USB_OTG_Core_dev,&HID_Machine))
//检测
USB HID通信
,是否还正常
?
{
USBH_HID_Reconnect();//重连
}
}else //连接未建立的时候
,检测
{
//检测
USB HOST 枚举是否死机了
?死机了
,则重新初始化
if(USBH_Check_EnumeDead(&USB_Host))
USBH_HID_Reconnect();//重连
}
t++;
if(t==200000){
LED0=!LED0; t=0;}
}
}
这里总共三个函数:
USBH_Msg_Show用于显示一些提示信息,在
usbh_usr.c里面被相关函数调用。
USBH_HID_Reconnect则用于
USB HID重新连接,当发现枚举
/通信死机的时候,调用该函数实现
USB复位重启,以重新连接;最后,
main函数就比较简单了,处理方式和上一章几乎一样,只是多了一些通信死机处理。
软件设计部分就为大家介绍到这里。
59.4 下载验证
在代码编译成功之后,我们下载到探索者
STM32F4开发板上,然后在
USB_HOST端子插入
USB鼠标
/键盘,
注意:此时USB SLAVE口不要插USB线到电脑,否则会干扰!!
等
USB鼠标
/键盘成功识别后,便可以看到
LCD显示
USB Connected,并显示设备类型:
USB Mouse或者
USB KeyBoard,同时也会显示输入的数据,如图
58.4.1和图
58.4.2所示:
图
59.4.1 USB鼠标测试
图
59.4.2 USB键盘测试
其中,图
59.4.1是
USB鼠标测试界面,图
59.4.2是
USB键盘测试界面。
最后,特别提醒大家,由于例程的
HID内核,只处理了第一个接口描述符,所以对于
USB符合设备,只能识别第一个描述符所代表的设备。体现到实际使用中,就是:
USB无线鼠标,一般是无法使用(被识别为键盘),而
USB无线键盘,可以使用,因为键盘在第一个描述符,鼠标在第二个描述符。
如果想支持
USB无线鼠标,可以通过修改
usbh_hid_core.c里面的
USBH_HID_InterfaceInit函数来支持。
实验详细手册和源码下载地址:http://www.openedv.com/posts/list/41586.htm
正点原子探索者STM32F407开发板购买地址:http://item.taobao.com/item.htm?id=41855882779
---------------------------------
请教是用哪个函数读取USB键盘的值,键值放在什么变量里呢?
一周热门 更多>