STM32 USB 主机的鼠标和键盘驱动
本文基于 <STM32 USB Host 连接鼠标和键盘>样例, 继续详细说明鼠标和键盘的驱动.
首先介绍鼠标, 当前鼠标的格式有很多有先发送按键的有先发送移动的, 我的鼠标是先发送按键的, 且是鼠标键盘公用一个USB接口, 然后键盘的特殊字符(例如调节音量, 播放控制等)通过鼠标发送, 当为鼠标报文时, 第一个字符为1, 当发送键盘特殊报文时, 第一个字符为0, 具体格式是:
- 鼠标报文
| 地址 | 说明 |
|---|---|
| [0] | 报文类型, 1为鼠标报文, 0为键盘特殊字符报文 |
| [1] | 按键状态, 包括0位左键, 1位右键,2位中键 |
| [2] | X轴移动值 |
| [3] | Y轴移动值 |
| [4] | 滚轮旋转值 |
- 键盘特殊报文
| 地址 | 说明 |
|---|---|
| [0] | 报文类型, 1为鼠标报文, 0为键盘特殊字符报文 |
| [1] | 键盘特殊字符 |
| [2] | 空 |
| [3] | 空 |
| [4] | 空 |
这种格式与usbh_hid_mouse.c中给的格式完全不同, 所以要对整个文件都要修改.
- usbh_hid_mouse.h
因为当前鼠标多了一个滚轮和中键, 所以要修改结构体HID_MOUSE_Info_TypeDef:
typedef struct _HID_MOUSE_Info
{
/* [ */
uint8_t type;
uint8_t buttons[3];
/* ] */
int8_t x;
int8_t y;
/* [ */
int8_t z;
uint8_t key;
/* ] */
}
HID_MOUSE_Info_TypeDef;
- usbh_hid_mouse.c
因为我的鼠标报文最大5个字节, 而默认是4个字节, 所以我把接收缓冲区改成了数组
/** @defgroup USBH_HID_MOUSE_Private_Variables
* @{
*/
HID_MOUSE_Info_TypeDef mouse_info;
uint32_t mouse_report_data[2];
uint32_t mouse_rx_report_buf[2]; // 这个buf在最新的USBH库中已经添加, 但不是数组, 之前是需要自己创建的, 防止接收与提取的buf冲突
然后是添加和修改按键和移动数据的顺序:
/* Structures defining how to access items in a HID mouse report */
/* Access message status. */
static const HID_Report_ItemTypedef prop_type = {
(uint8_t *)(void *)mouse_report_data + 0, /*data*/
8, /*size*/
0, /*shift*/
0, /*count (only for array items)*/
1, /*signed?*/
0, /*min value read can return*/
0xFF, /*max value read can return*/
0, /*min vale device can report*/
0xFF, /*max value device can report*/
1 /*resolution*/
};
/* Access the special key, when the message status is 3. */
static const HID_Report_ItemTypedef prop_key = {
(uint8_t *)(void *)mouse_report_data + 1, /*data*/
8, /*size*/
0, /*shift*/
0, /*count (only for array items)*/
1, /*signed?*/
0, /*min value read can return*/
0xFF, /*max value read can return*/
0, /*min vale device can report*/
0xFF, /*max value device can report*/
1 /*resolution*/
};
/* Access button 1 state. */
static const HID_Report_ItemTypedef prop_b1 = {
(uint8_t *)(void *)mouse_report_data + 1, /*data*/
1, /*size*/
0, /*shift*/
0, /*count (only for array items)*/
0, /*signed?*/
0, /*min value read can return*/
1, /*max value read can return*/
0, /*min value device can report*/
1, /*max value device can report*/
1 /*resolution*/
};
/* Access button 2 state. */
static const HID_Report_ItemTypedef prop_b2 = {
(uint8_t *)(void *)mouse_report_data + 1, /*data*/
1, /*size*/
1, /*shift*/
0, /*count (only for array items)*/
0, /*signed?*/
0, /*min value read can return*/
1, /*max value read can return*/
0, /*min value device can report*/
1, /*max value device can report*/
1 /*resolution*/
};
/* Access button 3 state. */
static const HID_Report_ItemTypedef prop_b3 = {
(uint8_t *)(void *)mouse_report_data + 1, /*data*/
1, /*size*/
2, /*shift*/
0, /*count (only for array items)*/
0, /*signed?*/
0, /*min value read can return*/
1, /*max value read can return*/
0, /*min vale device can report*/
1, /*max value device can report*/
1 /*resolution*/
};
/* Access x coordinate change. */
static const HID_Report_ItemTypedef prop_x = {
(uint8_t *)(void *)mouse_report_data + 2, /*data*/
8, /*size*/
0, /*shift*/
0, /*count (only for array items)*/
1, /*signed?*/
0, /*min value read can return*/
0xFF, /*max value read can return*/
0, /*min vale device can report*/
0xFF, /*max value device can report*/
1 /*resolution*/
};
/* Access y coordinate change. */
static const HID_Report_ItemTypedef prop_y = {
(uint8_t *)(void *)mouse_report_data + 3, /*data*/
8, /*size*/
0, /*shift*/
0, /*count (only for array items)*/
1, /*signed?*/
0, /*min value read can return*/
0xFF, /*max value read can return*/
0, /*min vale device can report*/
0xFF, /*max value device can report*/
1 /*resolution*/
};
/* Access z coordinate change. */
static const HID_Report_ItemTypedef prop_z = {
(uint8_t *)(void *)mouse_report_data + 4, /*data*/
8, /*size*/
0, /*shift*/
0, /*count (only for array items)*/
1, /*signed?*/
0, /*min value read can return*/
0xFF, /*max value read can return*/
0, /*min vale device can report*/
0xFF, /*max value device can report*/
1 /*resolution*/
};
接下来是修改初始化函数USBH_HID_MouseInit,
就是对刚修改的数组初始化, 并将地址存到HID_Handle->pData
/**
* @brief USBH_HID_MouseInit
* The function init the HID mouse.
* @param phost: Host handle
* @retval USBH Status
*/
USBH_StatusTypeDef USBH_HID_MouseInit(USBH_HandleTypeDef *phost)
{
uint32_t i;
HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[phost->pActiveClass->CurrInterface];
mouse_info.x = 0U;
mouse_info.y = 0U;
mouse_info.z = 0U;
mouse_info.buttons[0] = 0U;
mouse_info.buttons[1] = 0U;
mouse_info.buttons[2] = 0U;
for (i = 0U; i < (sizeof(mouse_report_data) / sizeof(uint32_t)); i++)
{
mouse_report_data[i] = 0U;
mouse_rx_report_buf[i] = 0U;
}
if (HID_Handle->length > sizeof(mouse_report_data))
{
HID_Handle->length = sizeof(mouse_report_data);
}
HID_Handle->pData = (uint8_t *)(void *)mouse_rx_report_buf;
USBH_HID_FifoInit(&HID_Handle->fifo, phost->device.Data, HID_QUEUE_SIZE * sizeof(mouse_report_data));
return USBH_OK;
}
最后是修改接收报文的解析函数USBH_HID_MouseDecode
/**
* @brief USBH_HID_MouseDecode
* The function decode mouse data.
* @param phost: Host handle
* @retval USBH Status
*/
static USBH_StatusTypeDef USBH_HID_MouseDecode(USBH_HandleTypeDef *phost)
{
HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[phost->pActiveClass->CurrInterface];
if (HID_Handle->length == 0U)
{
return USBH_FAIL;
}
/*Fill report */
if (USBH_HID_FifoRead(&HID_Handle->fifo, &mouse_report_data, HID_Handle->length) == HID_Handle->length)
{
/*Decode report */
mouse_info.type = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_type, 0U);
if (mouse_info.type == 1) {
mouse_info.buttons[0] = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_b1, 0U);
mouse_info.buttons[1] = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_b2, 0U);
mouse_info.buttons[2] = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_b3, 0U);
mouse_info.x = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_x, 0U);
mouse_info.y = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_y, 0U);
mouse_info.z = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_z, 0U);
} else {
mouse_info.key = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *) &prop_key, 0U);
}
return USBH_OK;
}
return USBH_FAIL;
}
这样鼠标驱动就完成了, 剩下的就是解析完以后的HID回调函数要做的事了, 例如向emwin发送鼠标事件或自己写鼠标事件, 这些就跟应用强相关了
然后是键盘驱动, 键盘格式基本都是通用的, 只有一些特殊字符不同, 主要就是修改解析函数就行了,
USBH_HID_KeybdDecode
键盘的数据解析主要就是在同时按下多个按键的时候比较麻烦, 如果是一指禅的话, 库自带的驱动是可以使用的. 我是想把键盘当做重要的输入工具处理的, 打字、输入命令啥的, 所以还是需要对多按键进行处理的, 主要思路是通过history数组记录上次输入的按键, 然后比较这次和上次的不同, 得到最新按下的按键
uint8_t keybd_len = 0;
uint8_t history[6] = { 0 };
uint8_t history_len = 0;
uint8_t capsLk_led = 0;
/**
* @brief USBH_HID_KeybdDecode
* The function decode keyboard data.
* @param phost: Host handle
* @retval USBH Status
*/
static USBH_StatusTypeDef USBH_HID_KeybdDecode(USBH_HandleTypeDef *phost)
{
USBH_StatusTypeDef status = USBH_FAIL;
uint8_t x;
uint8_t len = 1;
HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *)phost->pActiveClass->pData[phost->pActiveClass->CurrInterface];
if (HID_Handle->length == 0U)
return USBH_FAIL;
/*Fill report */
if (USBH_HID_FifoRead(&HID_Handle->fifo, &keybd_report_data, HID_Handle->length) == HID_Handle->length) {
keybd_info.lctrl = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_lctrl, 0U);
keybd_info.lshift = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_lshift, 0U);
keybd_info.lalt = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_lalt, 0U);
keybd_info.lgui = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_lgui, 0U);
keybd_info.rctrl = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_rctrl, 0U);
keybd_info.rshift = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_rshift, 0U);
keybd_info.ralt = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_ralt, 0U);
keybd_info.rgui = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_rgui, 0U);
for (x = 0U; x < sizeof(keybd_info.keys); x++) {
keybd_info.keys[x] = (uint8_t)HID_ReadItem((HID_Report_ItemTypedef *)&imp_0_key_array, x);
if (keybd_info.keys[x])
len = x + 1;
}
keybd_len = len;
if ((keybd_len == history_len == 1) &&
(keybd_info.keys[0] == history[0]))
return USBH_FAIL;
if (keybd_len >= history_len) {
if (find_new_value(history, &history_len, keybd_info.keys, keybd_len) == 1)
memcpy(keybd_info.keys, history, keybd_len);
if (keybd_info.keys[keybd_len - 1] == KEY_CAPS_LOCK)
capsLk_led = ~capsLk_led;
status = USBH_OK;
} else if ((keybd_len == 1) && (keybd_info.keys[0] == 0)) {
memset(history, 0, 6);
history_len = keybd_len;
}
if ((keybd_info.keys[0] != 0) && (status == USBH_FAIL)) {
memcpy(history, keybd_info.keys, keybd_len);
history_len = keybd_len;
}
}
return status;
}
查找新按键函数
static int find_new_value(uint8_t *out, uint8_t *out_len, const uint8_t *in, uint8_t in_len)
{
int res;
int n, m;
if (in_len < *out_len)
return -1;
if (in_len == *out_len == 1) {
out[0] = in[0];
return 0;
}
for (n = 0; n < in_len; n++) {
res = 0;
for (m = 0; m < *out_len; m++) {
if (out[m] == in[n]) {
res |= 1;
break;
}
}
if (res == 0)
out[(*out_len)++] = in[n];
if (in_len == *out_len)
break;
}
return 1;
}
最后是返回当前按键值USBH_HID_GetASCIICode, 最新值储存在数组末尾, 并根据Shift和capsLK状态输出不同值
USBH_HID_GetKeyCode直接返回按键值, 主要是检测是否为功能按键
/**
* @brief USBH_HID_GetASCIICode
* The function decode keyboard data into ASCII characters.
* @param phost: Host handle
* @param info: Keyboard information
* @retval ASCII code
*/
uint8_t USBH_HID_GetASCIICode(HID_KEYBD_Info_TypeDef *info)
{
uint8_t output;
uint8_t keys = info->keys[keybd_len - 1];
if ((info->lshift == 1U) || (info->rshift == 1U)) {
if ((capsLk_led) &&
((keys >= KEY_A) &&
(keys <= KEY_Z)))
output = HID_KEYBRD_Key[HID_KEYBRD_Codes[keys]];
else
output = HID_KEYBRD_ShiftKey[HID_KEYBRD_Codes[keys]];
} else {
if ((capsLk_led) &&
((keys >= KEY_A) &&
(keys <= KEY_Z)))
output = HID_KEYBRD_ShiftKey[HID_KEYBRD_Codes[keys]];
else
output = HID_KEYBRD_Key[HID_KEYBRD_Codes[keys]];
}
return output;
}
uint8_t USBH_HID_GetKeyCode(HID_KEYBD_Info_TypeDef *info)
{
uint8_t output;
output = info->keys[keybd_len - 1];
return output;
}
此驱动并不适用所有键盘、鼠标, 仅做参考.

本文详细介绍了STM32 USB主机环境下,如何驱动鼠标和键盘。针对鼠标,由于其特殊报文格式,需要修改`usbh_hid_mouse.c`和`usbh_hid_mouse.h`文件,调整结构体并更新接收缓冲区。对于键盘,主要是修改解析函数以处理多按键情况,并通过历史记录来确定最新按键。提供的驱动适用于特定类型的鼠标和键盘,可作为参考。
7708





