目录

1、前言

2、工程搭建

3、代码修改

4、参考资料


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工程》

《Hid Report Descriptor 报告描述符》

《The USB ID Repository》

《Tutorial about USB HID Report Descriptors》

  书籍 《微控制器USB的信号和协议实现》

 

下载资料:链接:https://pan.baidu.com/s/1irh2x1P5fLhwPRdAaJmiTQ  提取码:64jy 

HID Descriptor tool.zip

usbHIDdemo-mouse.rar

 

 

 

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐