【USB】STM32模拟USB鼠标
1、前言本实验使用STM32F103ZET6开发板为例,实现了模拟USB鼠标的功能,并且能够在电脑上控制鼠标完成鼠标具备的功能。2、工程搭建使用STM32CubeMX配置工程,非常方便高效,配置如下:使用外部高速晶振作为时钟源。勾选使用USB外设,STM32F103ZET6只有USB Device功能,没有HOST功能,而且只有一个USB外设,使用PA11(USB_DM),PA12(USB_DP)
目录
1、前言
本实验使用STM32F103ZET6开发板为例,实现了模拟USB鼠标的功能,并且能够在电脑上控制鼠标完成鼠标具备的功能。
2、工程搭建
使用STM32CubeMX配置工程,非常方便高效,配置如下:
使用外部高速晶振作为时钟源。
勾选使用USB外设,STM32F103ZET6只有USB Device功能,没有HOST功能,而且只有一个USB外设,使用PA11(USB_DM),PA12(USB_DP)管脚。
勾选USB DEVICE功能为HID设备。
时钟配置按默认的自动配置,USB时钟会自动配置到48MHz。
配置MDK工程输出目录,
勾选只输出必要的文件,工程更简洁。最后点击GENERATE CODE,输出工程完毕。
3、代码修改
原始生成的这个工程编译之后代码下载到开发板,将开发板USB口连线接上电脑后,查看设备管理器,可以看到多了一个HID mouse设备。
上面STM32CubeMX生成的原始工程只是纯粹能够在电脑上显示有个鼠标设备而已,没有给出上报鼠标事件完成鼠标移动或点击等功能的demo。我们可以通过USBD_HID_SendReport这个函数来上报鼠标事件,举个例子。
int main(void)
{
/* USER CODE BEGIN 1 */
unsigned char buff[4] = {0};
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USB_DEVICE_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
buff[0] = 0x01; //鼠标左键按下
USBD_HID_SendReport(&hUsbDeviceFS, buff, sizeof(buff));
HAL_Delay(100);
buff[0] = 0x00; //鼠标左键松开
USBD_HID_SendReport(&hUsbDeviceFS, buff, sizeof(buff));
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
在main函数while循环中加入这段代码,可以看到鼠标每隔一秒进行一次左键单击事件。
下面搞复杂一点完善下鼠标的功能,我们加上鼠标事件上报的代码,完成鼠标的操作功能。我的开发板上有4个用户按键,分别编号key0到key3,原理图如下。
图中的KEY_UP为key0,定义4个按键完成的功能为:
key0单击:鼠标滚轮向上滑动。
key1单击:鼠标左键单击一次。
key2单击:鼠标滚轮向下滑动。
key3单击:鼠标右键单击一次。
key0长时间按下:鼠标向上移动。
key1长时间按下:鼠标向左移动。
key2长时间按下:鼠标向下移动。
key3长时间按下:鼠标向右移动。
最终的main.c代码如下:
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usb_device.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
extern USBD_HandleTypeDef hUsbDeviceFS;
extern uint8_t USBD_HID_SendReport (USBD_HandleTypeDef *pdev,
uint8_t *report,
uint16_t len);
typedef struct
{
char mouse_abs_left : 1; //鼠标左键单击
char mouse_abs_right : 1; //鼠标右键单击
char mouse_abs_wheel : 1; //鼠标中键单击
char reserve : 5; //常量0
char mouse_rel_x; //鼠标X轴移动值
char mouse_rel_y; //鼠标Y轴移动值
char mouse_rel_wheel; //鼠标滚轮移动值
}tyMouse_buff;
#define KEY0_Press (1 << 0)
#define KEY1_Press (1 << 1)
#define KEY2_Press (1 << 2)
#define KEY3_Press (1 << 3)
tyMouse_buff tMouse_buff;
void User_Init(void)
{
tMouse_buff.mouse_abs_left = 0;
tMouse_buff.mouse_abs_right = 0;
tMouse_buff.mouse_abs_wheel = 0;
tMouse_buff.reserve = 0;
tMouse_buff.mouse_rel_x = 0;
tMouse_buff.mouse_rel_y = 0;
tMouse_buff.mouse_rel_wheel = 0;
}
unsigned char Get_Key_State(void)
{
unsigned char keyState = 0;
if (HAL_GPIO_ReadPin(key0_GPIO_Port, key0_Pin) == GPIO_PIN_SET)
{
keyState |= KEY0_Press;
}
if (HAL_GPIO_ReadPin(GPIOE, key1_Pin) == GPIO_PIN_RESET)
{
keyState |= KEY1_Press;
}
if (HAL_GPIO_ReadPin(GPIOE, key2_Pin) == GPIO_PIN_RESET)
{
keyState |= KEY2_Press;
}
if (HAL_GPIO_ReadPin(GPIOE, key3_Pin) == GPIO_PIN_RESET)
{
keyState |= KEY3_Press;
}
return keyState;
}
void Send_mouse_msg(void)
{
USBD_HID_SendReport(&hUsbDeviceFS, (uint8_t *)&tMouse_buff, sizeof(tMouse_buff));
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
unsigned char keyState;
unsigned char keyStateLast;
int cnt;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
User_Init();
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_USB_DEVICE_Init();
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if ((keyState = Get_Key_State()) != 0)
{
HAL_Delay(10);
cnt = 0;
while ((keyState = Get_Key_State()) != 0)
{
keyStateLast = keyState;
if (cnt != 0)
{
if (keyState & KEY0_Press)
{
tMouse_buff.mouse_rel_y = -1;
}
else if (keyState & KEY2_Press)
{
tMouse_buff.mouse_rel_y = 1;
}
else
{
tMouse_buff.mouse_rel_y = 0;
}
if (keyState & KEY1_Press)
{
tMouse_buff.mouse_rel_x = -1;
}
else if (keyState & KEY3_Press)
{
tMouse_buff.mouse_rel_x = 1;
}
else
{
tMouse_buff.mouse_rel_x = 0;
}
}
if (cnt == 0)
{
HAL_Delay(200);
}
else
{
Send_mouse_msg();
HAL_Delay(10);
}
cnt++;
}
if (cnt == 1)
{
if (keyStateLast & KEY1_Press)
{
tMouse_buff.mouse_abs_left = -1;
}
if (keyStateLast & KEY3_Press)
{
tMouse_buff.mouse_abs_right = 1;
}
if (keyStateLast & KEY0_Press)
{
tMouse_buff.mouse_rel_wheel = 10;
}
if (keyStateLast & KEY2_Press)
{
tMouse_buff.mouse_rel_wheel = -10;
}
Send_mouse_msg();
HAL_Delay(10);
tMouse_buff.mouse_abs_left = 0;
tMouse_buff.mouse_abs_right = 0;
tMouse_buff.mouse_rel_wheel = 0;
Send_mouse_msg();
}
tMouse_buff.mouse_rel_x = 0;
tMouse_buff.mouse_rel_y = 0;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/**Initializes the CPU, AHB and APB busses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USB;
PeriphClkInit.UsbClockSelection = RCC_USBCLKSOURCE_PLL_DIV1_5;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pins : key1_Pin key2_Pin key3_Pin */
GPIO_InitStruct.Pin = key1_Pin|key2_Pin|key3_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/*Configure GPIO pin : key0_Pin */
GPIO_InitStruct.Pin = key0_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
HAL_GPIO_Init(key0_GPIO_Port, &GPIO_InitStruct);
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/
鼠标事件每次上报4个字节的信息,用结构体tyMouse_buff来表示了。
怎么知道哪些事件对应哪些位的呢?刚开始我是网上查的,还以为是标准定死的,后来才知道这个是由HID设备的报告描述符决定的,当然市面上的鼠标报告描述符基本都一样的,算是都有个默契了,在usbd_hid.c文件下有个数组static uint8_t HID_MOUSE_ReportDesc[HID_MOUSE_REPORT_DESC_SIZE]就是报告描述符。这个数组告知PC机要如何去解析HID设备上报的数据。这一串数据可以由HID Descriptor tool这个工具方便地生成(工具下载地址)。整个报告描述符的内容其实就是描述了tyMouse_buff这个结构体。
下面这个报告描述符是USB鼠标报告描述符。它描述了4个字节,第一个字节表示按键,第二个字节表示x轴(即鼠标左右移动,0表示不动,正值表示往右移,负值表示往左移),第三个字节表示y轴(即鼠标上下移动,0表示不动,正值表示往下移动,负值表示往上移动),第四个字节表示鼠标滚轮(正值为往上滚动,负值为往下滚动)。
code char MouseReportDescriptor[52] = {
//通用桌面设备
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//鼠标
0x09, 0x02, // USAGE (Mouse)
//集合
0xa1, 0x01, // COLLECTION (Application)
//指针设备
0x09, 0x01, // USAGE (Pointer)
//集合
0xa1, 0x00, // COLLECTION (Physical)
//按键
0x05, 0x09, // USAGE_PAGE (Button)
//使用最小值1
0x19, 0x01, // USAGE_MINIMUM (Button 1)
//使用最大值3。1表示左键,2表示右键,3表示中键
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
//逻辑最小值0
0x15, 0x00, // LOGICAL_MINIMUM (0)
//逻辑最大值1
0x25, 0x01, // LOGICAL_MAXIMUM (1)
//数量为3
0x95, 0x03, // REPORT_COUNT (3)
//大小为1bit
0x75, 0x01, // REPORT_SIZE (1)
//输入,变量,数值,绝对值
//以上3个bit分别表示鼠标的三个按键情况,最低位(bit-0)为左键
//bit-1为右键,bit-2为中键,按下时对应的位值为1,释放时对应的值为0
0x81, 0x02, // INPUT (Data,Var,Abs)
//填充5个bit,补足一个字节
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x05, // REPORT_SIZE (5)
0x81, 0x03, // INPUT (Cnst,Var,Abs)
//用途页为通用桌面
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
//用途为X
0x09, 0x30, // USAGE (X)
//用途为Y
0x09, 0x31, // USAGE (Y)
//用途为滚轮
0x09, 0x38, // USAGE (Wheel)
//逻辑最小值为-127
0x15, 0x81, // LOGICAL_MINIMUM (-127)
//逻辑最大值为+127
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
//大小为8个bits
0x75, 0x08, // REPORT_SIZE (8)
//数量为3个,即分别代表x,y,滚轮
0x95, 0x03, // REPORT_COUNT (3)
//输入,变量,值,相对值
0x81, 0x06, // INPUT (Data,Var,Rel)
//关集合
0xc0, // END_COLLECTION
0xc0 // END_COLLECTION
};
通过对上面的报告分析,我们知道报告返回4个字节,没有报告ID。如果鼠标左键按下,则返回01 00 00 00(十六进制值),如果右键按下,则返回02 00 00 00,如果中键按下,则返回04 00 00 00,如果三个键同时按下,则返回07 00 00 00。如果鼠标往右移动则第二字节返回正值,值越大移动速度越快。其它的类推。
4、参考资料
《利用STM32CubeMX来生成USB_HID_Mouse工程》
《Tutorial about USB HID Report Descriptors》
书籍 《微控制器USB的信号和协议实现》
下载资料:链接:https://pan.baidu.com/s/1irh2x1P5fLhwPRdAaJmiTQ 提取码:64jy
HID Descriptor tool.zip
usbHIDdemo-mouse.rar
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)