关于怎么用Cubemx生成的USBHID设备实现读取一体的鼠标键盘设备(改进版)

主要最近做了一个要用STM32实现读取鼠标键盘一体的那种USB设备,STM32的界面上要和电脑一样的能通过这个USB接口实现鼠标移动,键盘的按键。然后我就很自然的去参考了正点原子的例程,可是找了一圈,发现正点原子好像用的库函数,还是自己实现的,然后看了半天都看晕了,感觉自己实现不了,然后就主攻Cubemx实现的USB设备读取了。

在网上找了一圈,终于让我发现了一个可以用的博主的,而且实现了USB读取鼠标键盘一体的设备。
参考网址如下:
https://blog.csdn.net/weixin_50969532/article/details/134313495
基于STM32-USB中间库的多接口主机开发实战
感觉这个博主实现的非常好了,主要实现方法我都是参考这个博主的,只是基于Cubemx实现的还是和博主的实现有一点点的出入,我这里主要跟随博主的实现方法,和博主的文章对比着修改下博主的实现方法,并融合一下,并且实现的更好一点。

使用的硬件

根据网上很多的HID实现方法,大多数都是用F103一类的STM32设备,将STM32设置成Device,模拟鼠标键盘和电脑进行通信,相当于就是用STM32做一个鼠标键盘。但是我们要实现的正好是反着来的,我们要用STM32当做是电脑,读取真正的鼠标键盘的数据,然后在STM32上显示鼠标的移动,按键,键盘的按键。下面是我用的主要硬件。

STM32:正点原子的阿波罗F767开发板

鼠标键盘(一体的):不知道是什么杂牌子,反正写着型号T1,2.4G无线充电鼠标,电压4.2V,载波频率:2402Mhz。

第一步了解USB主机和设备

首先第一步,看下博主的博客。根据博主的说明,我们可以知道,主机和设备之间的USB基础知识。
USB:通用串行总线,是由主机发起通信、从机被动接收的主从机通信机制。

管道:主从机通信的通道,可以理解为两个海岸之间船只来往的航线。要想船只走得通,必须开放这条航线;要想进行通信,必须打开对应的管道。(其实生成的代码里仔细看也会有,生成的Cubemx代码里叫pipe,还分为进出pipe)

端点:主从机传输的最终对象,可以理解为船只来往的码头,航线的两头即端点。要想船只来往,必须开放码头;要想进行通信,必须开放对应的端点。(这个可以理解为Freertos里的队列的进出口,关键Cubemx里也真的是用的队列,队列实现的管道,写入队列和读取队列就是端点)

接口:几个端点的集合,实现某一具体功能。比如这2个码头用来运输食品,这就是一个食品接口;另外4个码头用来运输石油,这是一个石油接口。键鼠一体有两个接口,一个接口是键盘,一个接口是鼠标。(这里就是最重要的,按照博主的说明,接口的数量决定是只有一个鼠标或者一个键盘,还是两个都有。也就是说,如果一个USB的接口插在电脑上,可以接收很多个无线设备的数据,接口几个,就有几个设备)

在这里插入图片描述
这里参考的就是【半路程序媛】https://blog.csdn.net/weixin_50969532/article/details/134313495这位博主的图片,感谢博主的说明。。。

第二步 用Cubemx生成STM32代码

Cubemx来生成的STM32的代码,主要要设置Cubemx的两个部分,第一个,就是USB_OTG_FS,第二个是USB_HOST。(注意,STM32F1都实现不了HOST,只能是Device,所以买错STM32的小伙伴注意了)

然后就是打开Cubemx,选择自己的STM32,我这里用的硬件是F767IGT6,我就选择F767IGT6。
然后就是一般的CUbemx设置,选晶振,设置时钟,配置UART1作为调试接口输出到电脑,Freertos系统配置(是的,我用了系统,没试过不用系统的,但是有系统的话,为啥非要跑裸机呢~)。

好了,设置好了后,就可以开始用Cubemx配置USB设备了,首先是Connectivity里的USB_OTG_FS(我这里有USB_OTG_HS,但是我的设备主要用这个OTG_FS接口)
在这里插入图片描述

这里设置好了后,Middleware and Software里就会有一个USB_HOST(有的Cubemx版本可能不在这里,注意找一下,如果找不到,就是你的STM32不支持HOST),然后就是USB_HOST的配置。
在这里插入图片描述
User Constants不用,就用默认就可以了,然后就是Platform Settings这个设置要说明一下。

Platform Settings设置说明

这个设置的意思是STM32支持其中一个STM32的引脚来控制USB设备的供电,也就是在硬件上要有相同的设置。如果你的硬件电路板上,硬件工程师做了这么一个设置,可以用一个STM32的引脚来给外部USB供电,这个设置你就是必须要设置的,而且一定要问硬件工程师是设置的哪个引脚,引脚要一致。
但是如果你电路板本身就是USB有供电了,那这个的设置就对你毫无用处,随便设置一个不用的引脚吧,就是为了Cubemx能顺利生成代码而已。。而且在生成后的代码里,也没啥用处。。

如果你用的板子和我的一样,都是正点原子的阿波罗,那你要仔细看下硬件原理图了,正点原子的USB的供电引脚是用一个扩展芯片来实现的,叫pcf8574,这个芯片要用IIC驱动才能让USB供电上,不供电的话就算你怎么改代码都读不到USB的。。别被坑了。。。

回到正题,继续配置Cubemx

Cubemx配置好了这两个以后,USB这方面就完成了。然后就是生成代码。下面我们来看下keil5的代码吧。

Keil5上看下生成的代码,修改代码来实现

Keil5生成的代码我们先看下文件树,我这里主要生成的文件是这样的。
在这里插入图片描述
在这里插入图片描述
这三个文件夹是个USB设备有关的,后面我们主要也是要修改这几个文件来实现一体化设备的数据读取。

关于Cubemx生成的代码里,本身是可以直接用的,要实现键盘的话,就本身生成好的就可以直接用。

Cubemx的代码可以直接驱动一个USB设备?

是的,Cubemx的代码确实直接生成好的就是可以直接插上USB设备就能用。
先看main。。。。呃。。我用的freertos的系统,看main没啥用,就不看了,主要看下在哪里初始化USB设备和怎么用吧。

在这里插入图片描述
我这里的这个函数是Cubemx上了freertos系统后,自动生成的一个任务,我直接把初始化放在任务里,然后初始化USB设备,下面的for(;;)里面只用写自己要处理的usb_demo(&hUsbHostFs)就可以了。下面是我的usb_demo。

下面展示一些 内联代码片

void USB_Demo(USBH_HandleTypeDef * phost)
{
    char c,i;
 
for(i=0;i<USBH_MAX_NUM_INTERFACES;i++)
{
if(InterfaceIndex[i] != 0xff)
{
    if(USBH_HID_GetDeviceType(&hUSBHost)==HID_KEYBOARD)     //键盘设备
    {
         if(Appli_state==APPLICATION_READY)
         {           //获取键盘信息
            if(k_pinfo!=NULL)
            {
                c=USBH_HID_GetASCIICode(k_pinfo);           //转换成ASCII码
                //这里是我自己加的一句,只用输出c就行了
                printf("%c",c);
                MYUSR_KEYBRD_ProcessData(c);                //在LCD上显示出键盘字符
                k_pinfo = NULL;
                memset(k_pinfo ,0,sizeof(HID_KEYBD_Info_TypeDef ));
            }
         }
    }
    else if (USBH_HID_GetDeviceType(&hUSBHost)==HID_MOUSE)  //鼠标设备
    {
        if(Appli_state==APPLICATION_READY)
        {
           // MOUSE_Demo(&hUSBHost);
            if(m_pinfo!=NULL)
            {
                MYUSR_MOUSE_ProcessData(&mouse_info);       //LCD上显示鼠标信息
                m_pinfo = NULL;
                memset(m_pinfo ,0,sizeof(HID_MOUSE_Info_TypeDef ));
            }
        }
    }else                                                   //无法识别的设备
    {
        //printf("无法识别的设备\r\n");
    }
}
}
}

这个代码其实就是楼上博主的demo代码,博主的文章中没有实现的两点是MYUSR_MOUSE_ProcessData(&mouse_info);和 MYUSR_KEYBRD_ProcessData©;
但是这两个函数我们这里可以先不要,因为本身的这个Cubemx生成的代码里,也只能实现键盘的读取,所以,代码运行到了c=USBH_HID_GetASCIICode(k_pinfo);的时候,就可以了,后面只要printf出来c,就是你按下的那个按键。

到这里Cubemx本身的使用就这样子了,实现一个键盘的按键读取,鼠标我还没试过,不过改改也是可以实现的,只是暂时没那个时间啦。
下面就是要根据参考的博主的步伐,改下Cubemx生成的代码,实现一个USB,读取鼠标键盘两个设备。

一个USB,读取鼠标和键盘两个设备

第一步,改代码,主要在usbh_hid.c里

我这里的修改主要对标博主的第三步和第四步。
把原来Cubemx生成的static函数都改成__WEAK,然后自己重写一遍这几个函数,因为这几个函数两个接口都会要用到。
在这里插入图片描述
原来博主的文章里写的是直接改这几个红框后面的函数,我这里按照博主的方法,把这几个函数重写,原来的函数就直接把static改成__WEAK,这样就算再用Cubemx修改生成一遍,改动也会很小。

第二步,重新建立自己的.c和.h文件,把改动都放在自己的文件里

我这里建立的名字是My_USB_MouseKey.h和My_USB_MouseKey.c,第一步的图里的函数都要重写,所以我把重写后的函数都放下面吧,大家复制粘贴一下,然后自己跑一下,看哪里没有包含.h的自己包含一下。

下面展示一些 内联代码片

My_USB_MouseKey.c
/*------------------函数修改的区域------------------------------*/
uint8_t USBH_HID_GetPollInterval(USBH_HandleTypeDef *phost)
{
	for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++)
	{
		HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];

		if ((phost->gState == HOST_CLASS_REQUEST) ||
				(phost->gState == HOST_INPUT) ||
				(phost->gState == HOST_SET_CONFIGURATION) ||
				(phost->gState == HOST_CHECK_CLASS) ||
				((phost->gState == HOST_CLASS)))
		{
			return (uint8_t)(HID_Handle->poll);
		}
		else
		{
			return 0U;
		}
	}
	return 0U;
}

USBH_StatusTypeDef USBH_HID_SOFProcess(USBH_HandleTypeDef *phost)
{
	for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++)
	{
		HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];

		if (HID_Handle->state == USBH_HID_POLL)
		{
			if ((phost->Timer - HID_Handle->timer) >= HID_Handle->poll)
			{
				HID_Handle->state = USBH_HID_GET_DATA;

			#if (USBH_USE_OS == 1U)
						phost->os_msg = (uint32_t)USBH_URB_EVENT;
			#if (osCMSIS < 0x20000U)
						(void)osMessagePut(phost->os_event, phost->os_msg, 0U);
			#else
						(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);
			#endif
			#endif
			}
		}
	}
  return USBH_OK;
}

USBH_StatusTypeDef USBH_HID_InterfaceInit(USBH_HandleTypeDef *phost)
{
  USBH_StatusTypeDef status;
  HID_HandleTypeDef *HID_Handle;
  uint8_t max_ep;
  uint8_t num = 0U;
  uint8_t interface;
	for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++)
	{
		interface = USBH_FindInterface(phost, phost->pActiveClass->ClassCode, HID_BOOT_CODE, i+1);
		#include "My_USB_MouseKey.h"
		InterfaceIndex[i] = interface;
		if ((interface == 0xFFU) || (interface >= USBH_MAX_NUM_INTERFACES)) /* No Valid Interface */
		{
			USBH_DbgLog("Cannot Find the interface for %s class.", phost->pActiveClass->Name);
			return USBH_FAIL;
		}

		status = USBH_SelectInterface(phost, interface);

		if (status != USBH_OK)
		{
			return USBH_FAIL;
		}

		phost->pActiveClass->pData[i] = (HID_HandleTypeDef *)USBH_malloc(sizeof(HID_HandleTypeDef));
		HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];

		if (HID_Handle == NULL)
		{
			USBH_DbgLog("Cannot allocate memory for HID Handle");
			return USBH_FAIL;
		}

		/* Initialize hid handler */
		(void)USBH_memset(HID_Handle, 0, sizeof(HID_HandleTypeDef));

		HID_Handle->state = USBH_HID_ERROR;

		/*Decode Bootclass Protocol: Mouse or Keyboard*/
		if (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bInterfaceProtocol == HID_KEYBRD_BOOT_CODE)
		{
			USBH_UsrLog("KeyBoard device found!");
			HID_Handle->Init = USBH_HID_KeybdInit;
		}
		else if (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bInterfaceProtocol  == HID_MOUSE_BOOT_CODE)
		{
			USBH_UsrLog("Mouse device found!");
			HID_Handle->Init = USBH_HID_MouseInit;
		}
		else
		{
			USBH_UsrLog("Protocol not supported.");
			return USBH_FAIL;
		}

		HID_Handle->state     = USBH_HID_INIT;
		HID_Handle->ctl_state = USBH_HID_REQ_INIT;
		HID_Handle->ep_addr   = phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[0].bEndpointAddress;
		HID_Handle->length    = phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[0].wMaxPacketSize;
		HID_Handle->poll      = phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[0].bInterval;

		if (HID_Handle->poll  < HID_MIN_POLL)
		{
			HID_Handle->poll = HID_MIN_POLL;
		}

		/* Check of available number of endpoints */
		/* Find the number of EPs in the Interface Descriptor */
		/* Choose the lower number in order not to overrun the buffer allocated */
		max_ep = ((phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bNumEndpoints <= USBH_MAX_NUM_ENDPOINTS) ?
							phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].bNumEndpoints : USBH_MAX_NUM_ENDPOINTS);


		/* Decode endpoint IN and OUT address from interface descriptor */
		for (num = 0U; num < max_ep; num++)
		{
			if ((phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress & 0x80U) != 0U)
			{
				HID_Handle->InEp = (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress);
				HID_Handle->InPipe = USBH_AllocPipe(phost, HID_Handle->InEp);

				/* Open pipe for IN endpoint */
				(void)USBH_OpenPipe(phost, HID_Handle->InPipe, HID_Handle->InEp, phost->device.address,
														phost->device.speed, USB_EP_TYPE_INTR, HID_Handle->length);

				(void)USBH_LL_SetToggle(phost, HID_Handle->InPipe, 0U);
			}
			else
			{
				HID_Handle->OutEp = (phost->device.CfgDesc.Itf_Desc[phost->device.current_interface].Ep_Desc[num].bEndpointAddress);
				HID_Handle->OutPipe  = USBH_AllocPipe(phost, HID_Handle->OutEp);

				/* Open pipe for OUT endpoint */
				(void)USBH_OpenPipe(phost, HID_Handle->OutPipe, HID_Handle->OutEp, phost->device.address,
														phost->device.speed, USB_EP_TYPE_INTR, HID_Handle->length);

				(void)USBH_LL_SetToggle(phost, HID_Handle->OutPipe, 0U);
			}
		}
	}
  return USBH_OK;
}

USBH_StatusTypeDef USBH_HID_InterfaceDeInit(USBH_HandleTypeDef *phost)
{
	for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++)
	{
		HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];

		if (HID_Handle->InPipe != 0x00U)
		{
			(void)USBH_ClosePipe(phost, HID_Handle->InPipe);
			(void)USBH_FreePipe(phost, HID_Handle->InPipe);
			HID_Handle->InPipe = 0U;     /* Reset the pipe as Free */
		}

		if (HID_Handle->OutPipe != 0x00U)
		{
			(void)USBH_ClosePipe(phost, HID_Handle->OutPipe);
			(void)USBH_FreePipe(phost, HID_Handle->OutPipe);
			HID_Handle->OutPipe = 0U;     /* Reset the pipe as Free */
		}

		if ((phost->pActiveClass->pData[i]) != NULL)
		{
			USBH_free(phost->pActiveClass->pData[i]);
			phost->pActiveClass->pData[i] = 0U;
		}
	}
  return USBH_OK;
}

USBH_StatusTypeDef USBH_HID_ClassRequest(USBH_HandleTypeDef *phost)
{

  USBH_StatusTypeDef status         = USBH_BUSY;
  USBH_StatusTypeDef classReqStatus = USBH_BUSY;
	for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++)
	{
		HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];

		/* Switch HID state machine */
		switch (HID_Handle->ctl_state)
		{
			case USBH_HID_REQ_INIT:
			case USBH_HID_REQ_GET_HID_DESC:

				USBH_HID_ParseHIDDesc(&HID_Handle->HID_Desc, phost->device.CfgDesc_Raw);

				HID_Handle->ctl_state = USBH_HID_REQ_GET_REPORT_DESC;

				break;
			case USBH_HID_REQ_GET_REPORT_DESC:

				/* Get Report Desc */
				classReqStatus = USBH_HID_GetHIDReportDescriptor(phost, HID_Handle->HID_Desc.wItemLength);
				if (classReqStatus == USBH_OK)
				{
					/* The descriptor is available in phost->device.Data */
					HID_Handle->ctl_state = USBH_HID_REQ_SET_IDLE;
				}
				else if (classReqStatus == USBH_NOT_SUPPORTED)
				{
					USBH_ErrLog("Control error: HID: Device Get Report Descriptor request failed");
					status = USBH_FAIL;
				}
				else
				{
					/* .. */
				}

				break;

			case USBH_HID_REQ_SET_IDLE:

				classReqStatus = USBH_HID_SetIdle(phost, 0U, 0U);

				/* set Idle */
				if (classReqStatus == USBH_OK)
				{
					HID_Handle->ctl_state = USBH_HID_REQ_SET_PROTOCOL;
				}
				else
				{
					if (classReqStatus == USBH_NOT_SUPPORTED)
					{
						HID_Handle->ctl_state = USBH_HID_REQ_SET_PROTOCOL;
					}
				}
				break;

			case USBH_HID_REQ_SET_PROTOCOL:
				/* set protocol */
				classReqStatus = USBH_HID_SetProtocol(phost, 0U);
				if (classReqStatus == USBH_OK)
				{
					HID_Handle->ctl_state = USBH_HID_REQ_IDLE;

					/* all requests performed*/
					phost->pUser(phost, HOST_USER_CLASS_ACTIVE);
					status = USBH_OK;
				}
				else if (classReqStatus == USBH_NOT_SUPPORTED)
				{
					USBH_ErrLog("Control error: HID: Device Set protocol request failed");
					status = USBH_FAIL;
				}
				else
				{
					/* .. */
				}
				break;

			case USBH_HID_REQ_IDLE:
			default:
				break;
		}
	}
  return status;
}

USBH_StatusTypeDef USBH_HID_Process(USBH_HandleTypeDef *phost)
{
  USBH_StatusTypeDef status = USBH_OK;
	for(int i=0;i<USBH_MAX_NUM_INTERFACES;i++)
	{
		HID_HandleTypeDef *HID_Handle = (HID_HandleTypeDef *) phost->pActiveClass->pData[i];
		uint32_t XferSize;

		switch (HID_Handle->state)
		{
			case USBH_HID_INIT:
				HID_Handle->Init(phost);
				HID_Handle->state = USBH_HID_IDLE;

				#if (USBH_USE_OS == 1U)
							phost->os_msg = (uint32_t)USBH_URB_EVENT;
				#if (osCMSIS < 0x20000U)
							(void)osMessagePut(phost->os_event, phost->os_msg, 0U);
				#else
							(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);
				#endif
				#endif
							break;

			case USBH_HID_IDLE:
				status = USBH_HID_GetReport(phost, 0x01U, 0U, HID_Handle->pData, (uint8_t)HID_Handle->length);
				if (status == USBH_OK)
				{
					HID_Handle->state = USBH_HID_SYNC;
				}
				else if (status == USBH_BUSY)
				{
					HID_Handle->state = USBH_HID_IDLE;
					status = USBH_OK;
				}
				else if (status == USBH_NOT_SUPPORTED)
				{
					HID_Handle->state = USBH_HID_SYNC;
					status = USBH_OK;
				}
				else
				{
					HID_Handle->state = USBH_HID_ERROR;
					status = USBH_FAIL;
				}

	#if (USBH_USE_OS == 1U)
				phost->os_msg = (uint32_t)USBH_URB_EVENT;
	#if (osCMSIS < 0x20000U)
				(void)osMessagePut(phost->os_event, phost->os_msg, 0U);
	#else
				(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);
	#endif
	#endif
				break;

			case USBH_HID_SYNC:
				/* Sync with start of Even Frame */
				if ((phost->Timer & 1U) != 0U)
				{
					HID_Handle->state = USBH_HID_GET_DATA;
				}

	#if (USBH_USE_OS == 1U)
				phost->os_msg = (uint32_t)USBH_URB_EVENT;
	#if (osCMSIS < 0x20000U)
				(void)osMessagePut(phost->os_event, phost->os_msg, 0U);
	#else
				(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);
	#endif
	#endif
				break;

			case USBH_HID_GET_DATA:
				(void)USBH_InterruptReceiveData(phost, HID_Handle->pData,
																				(uint8_t)HID_Handle->length,
																				HID_Handle->InPipe);

				HID_Handle->state = USBH_HID_POLL;
				HID_Handle->timer = phost->Timer;
				HID_Handle->DataReady = 0U;
				break;

			case USBH_HID_POLL:
				if (USBH_LL_GetURBState(phost, HID_Handle->InPipe) == USBH_URB_DONE)
				{
					XferSize = USBH_LL_GetLastXferSize(phost, HID_Handle->InPipe);

					if ((HID_Handle->DataReady == 0U) && (XferSize != 0U))
					{
						(void)USBH_HID_FifoWrite(&HID_Handle->fifo, HID_Handle->pData, HID_Handle->length);
						HID_Handle->DataReady = 1U;
						
						USBH_HID_EventCallback(phost,i);

	#if (USBH_USE_OS == 1U)
						phost->os_msg = (uint32_t)USBH_URB_EVENT;
	#if (osCMSIS < 0x20000U)
						(void)osMessagePut(phost->os_event, phost->os_msg, 0U);
	#else
						(void)osMessageQueuePut(phost->os_event, &phost->os_msg, 0U, 0U);
	#endif
	#endif
					}
				}
				else
				{
					/* IN Endpoint Stalled */
					if (USBH_LL_GetURBState(phost, HID_Handle->InPipe) == USBH_URB_STALL)
					{
						/* Issue Clear Feature on interrupt IN endpoint */
						if (USBH_ClrFeature(phost, HID_Handle->ep_addr) == USBH_OK)
						{
							/* Change state to issue next IN token */
							HID_Handle->state = USBH_HID_GET_DATA;
						}
					}
				}
				break;

			default:
				break;
		}
	}
  return status;
}

My_USB_MouseKey.h

下面展示一些 内联代码片


#ifndef __MY_USB_MOUSEKEY_H__
#define __MY_USB_MOUSEKEY_H__		

#include "main.h"	 
#include "usbh_def.h"
#include "usart.h"
#include "usbh_hid_mouse.h"
#include "usb_host.h"
#include "usbh_hid.h"
#include "usbh_hid_parser.h"
#ifdef __cplusplus
 extern "C" {
#endif

typedef enum{
	EXTRA_NULL=0,
	LEFT_PRESSED,
	RIGHT_PRESSED,
	EXTRA_NULL2,
	MIDDLE_PRESSED,
	EXTRA_NULL3,
	EXTRA_NULL4,
	EXTRA_NULL5,
	SIDE_PRESSED,
	EXTRA_NULL6,
	EXTRA_NULL7,
	EXTRA_NULL8,
	EXTRA_NULL9,
	EXTRA_NULL10,
	EXTRA_NULL11,
	EXTRA_NULL12,
	EXTRA_PRESSED,
}mouse_status;	

typedef enum{
		MOVEMENT_NULL = 0,
		UP,
		DOWN,
		LEFT,
		RIGHT,
		UP_LEFT,
		UP_RIGHT,
		DOWN_LEFT,
		DOWN_RIGHT,
}mouse_movement_status;

extern void USBH_HID_ParseHIDDesc(HID_DescTypeDef *desc, uint8_t *buf);
extern ApplicationTypeDef Appli_state;	 
extern  HID_MOUSE_Info_TypeDef    mouse_info;
#ifdef __cplusplus
}
#endif

#endif 

这就是我的两个.c和.h文件,其中有一些要说明一下。先说.h文件。

我的.h文件里的#include不用说,都要包含进来,然后是mouse_status,这个枚举是用来最后对标鼠标按键用的枚举;mouse_movement_status是用来对标鼠标移动的枚举。其他的extern主要都是我自己写的函数要用到,所以要extern出来。

然后是.c文件的说明,上面的.c文件里,都是原来的Cubemx里生成的文件,但是要按照博主的方法修改的函数。

第三步,修改struct

我这里对标的是博主的第二步。

修改类结构体,原定义中只有一个数据接口,将这个改成数组形式,就可实现多个接口/类。
下面展示一些 内联代码片

/* USB Host Class structure */
typedef struct 
{
  const char          *Name;
  uint8_t              ClassCode;  
  USBH_StatusTypeDef  (*Init)        (struct _USBH_HandleTypeDef *phost);
  USBH_StatusTypeDef  (*DeInit)      (struct _USBH_HandleTypeDef *phost);
  USBH_StatusTypeDef  (*Requests)    (struct _USBH_HandleTypeDef *phost);  
  USBH_StatusTypeDef  (*BgndProcess) (struct _USBH_HandleTypeDef *phost);
  USBH_StatusTypeDef  (*SOFProcess) (struct _USBH_HandleTypeDef *phost);  
  void*                pData[USBH_MAX_NUM_INTERFACES];//原为pData修改为数组形式
} USBH_ClassTypeDef;

第四步,补充USBH_HID_EventCallbcak函数

这里对标博主的第五步。
补充USBH_HID_EventCallbcak函数。此函数被调用于USBH_HID_Process->case HID_POLL,当接收到数据填入fifo后,立即调用USBH_HID_EventCallbcak函数将数据读取到对应的fifo中,防止被下一接口数据覆盖。KeyboardFlag和MouseFlag 用于标记是哪个接口接收了数据。

关于我自己的修改:我这里的修改其实和博主不太一样,因为每次按下键盘或者鼠标都会触发这个函数,所以我把这个函数放在了我自己写的My_USB_MouseKey.h和My_USB_MouseKey.c中。原生的Cubemx的代码我也是直接__WEAK掉了。

Cubemx的代码修改:
在这里插入图片描述

然后自己写了一遍,自己写的如下:
主要实现的函数是USBH_HID_MouseDecode(phost);和USBH_HID_GetASCIICode(k_pinfo);
但是这两个函数是Cubemx生成的,所以可以直接用。自己要写的数据处理函数是mouse_data_process(mouse_info);
keypad_data_process©;
这两个函数。
下面展示一些 内联代码片

void USBH_HID_EventCallback(USBH_HandleTypeDef *phost,uint8_t flag)
{
  #include "usb_host.h"
	#include "usbh_hid_mouse.h"
  char c;
  HID_KEYBD_Info_TypeDef *k_pinfo;
  HID_MOUSE_Info_TypeDef *m_pinfo;
  if(1 == flag)
	{
		m_pinfo = USBH_HID_GetMouseInfo(phost); 
		if (m_pinfo != NULL)
		{
				//LED_0_State = 0;
				USBH_HID_MouseDecode(phost);
				mouse_data_process(mouse_info);
				memset(m_pinfo ,0,sizeof(HID_MOUSE_Info_TypeDef ));
		}

	}
	else
	{
		k_pinfo = USBH_HID_GetKeybdInfo(phost); /* ?????? */
		if(k_pinfo == NULL)
		{
			 uint8_t err = 0xff;
		}
		if (k_pinfo != NULL)
		{
				c = USBH_HID_GetASCIICode(k_pinfo); /* ???ASCII? */
				keypad_data_process(c);
				
				memset(k_pinfo ,0,sizeof(HID_KEYBD_Info_TypeDef ));
		}
   
	}
}
// An highlighted block

关于参考的博主的第六步和第七步

关于博主的第六步和第七步我都没有去做,第一是我发现做了以后,整个USB就跑不动了,不知道哪里出了问题,第二就是我觉得博主的第七步说的很对,根本不需要跑demo了,直接所有的都在USBH_HID_EventCallback里已经处理了。

关于我自己做的一些改进

我根据自己的硬件设备,做了一些读取USB键盘和鼠标的改进,因为原生的Cubemx生成的鼠标还有一些问题,现在的设备的鼠标的数据和Cubemx生成代码的解码还是有一些出入,这里说明一下这些有出入的地方和我自己的改进部分。

键盘的改进

我上面的代码里不是有实现keypad_data_process©;这个函数么,我这个函数就是输出一下按键到UART1这种调试接口,用电脑显示一下,我的函数如下:
下面展示一些 内联代码片

void keypad_data_process(uint8_t data)
{
	printf("%c",data);
}	
		

鼠标的改进

因为原生的Cubmx生成的代码在鼠标上和现在的键盘鼠标一体的设备的出入比较大,所以在原生的Cubemx生成的代码上,还要做一些修改。

第一步

USBH_StatusTypeDef USBH_HID_MouseDecode(USBH_HandleTypeDef *phost)函数要修改。
修改后的函数如下,大家可以对比下Cubemx生成的代码,看下区别。
在这里插入图片描述

第二步

我这里的是比原生的代码多出来的部分,我这里其实是增加了下面的这个prop_z,这个你们也可以按照我的方法自己加一下,主要是打开usbh_hid_mouse.c文件,差不多150行左右,复制prop_y,粘贴在prop_y下面,并且把mouse_report_data + 2改成mouse_report_data + 3就行了,其他的不变。
在这里插入图片描述

第三步

这个.h里要加一个z,只有z才包含鼠标的上下移动的数据。
在这里插入图片描述

这样就好了,这里我要说明下为啥要加这个。
首先,我发现mouse_info这个变量,也就是用HID_MOUSE_Info_TypeDef定义的是这样的,原生的代码只有x,y,buttons[3],这三个变量,但是我测试下来发现,x里面只有鼠标按键的左键、右键、中建、边按键、第二个边键的信息。y里面只有鼠标的左右移动的信息,所以还缺失了鼠标上下移动的信息。所以在原生的代码里,还要加上这条,才能实现鼠标的上下移动的代码。关于buttons[3],这个就没啥用了,但是每次发送回来的数据里button[3]都是一样的数据,所以可以当做是一种验证码,如果哪次发送回来的buttons【3】数据不一样,就说明这次是错误数据。

关于鼠标的解析

因为经过不断的测试,我的鼠标的y和z两个函数和上下左右的关联都有,所以必须要一起处理,单独处理的话,就会很容易解析出错,明明是左移数据,就可能会解析成左和下都有,所以花乐2个小时又不断测试修改,最后成了下面的这些函数。

我自己的鼠标的解析函数如下:

下面展示一些 内联代码片

int InterfaceIndex[USBH_MAX_NUM_INTERFACES] ={0};
uint8_t Pressed[5]={0};

void CLear_MousePressed()
{
	for(int i =0;i<5;i++)
	{
		Pressed[i]=0;
	}
}

void Show_MousePressed_Status(uint8_t status)
{
	if((status & LEFT_PRESSED) != 0)
	{
		Pressed[0] = 1;
		printf("左键");
	}
	if((status & RIGHT_PRESSED) != 0)
	{
		Pressed[1] = 1;
		printf("右键");
	}
	if((status & MIDDLE_PRESSED) != 0)
	{
		Pressed[2] = 1;
		printf("中建");
	}
	if((status & SIDE_PRESSED) != 0)
	{
		Pressed[3] = 1;
		printf("边按键");
	}
	if((status & EXTRA_PRESSED) != 0)
	{
		Pressed[4] = 1;
		printf("边2按键");
	}
}

uint8_t Movement_Measure(uint8_t status1,uint8_t status2)
{
	
	if(status1 == 0)
	{
		if(status2 != 0)
		{
			if(status2>255/2)
			{
				return UP;
			}
			else
			{
				return DOWN;
			}
		}
		else
		{
			return 0;
		}
	}
	else
	{
		if(status2 != 0)
		{
			if((status1>255/2) && (status2<16))
			{
				return LEFT;//0,253,15
			}
			else
			{
				if((status1>255/2) && (status2>255/2))
				{
					return UP_LEFT;//0,255,239
				}
				else
				{
					if((status1<255/2) && (status2>255/2))
					{
						return UP_RIGHT;//0,3,240
					}
					else
					{
						if((status1>255/2) && (status2<255/2))
						{
							return DOWN_LEFT;//0,254,31
						}
						else
						{
							return DOWN_RIGHT;//0,2,32
						}
					}
				}
			}
		}
		else
		{
			if(status1<255/2 )
			{
				return RIGHT;
			}
			
		}
	}
	return 0;
}


void Show_MouseMoved_Status(uint8_t status1,uint8_t status2)
{
	switch(Movement_Measure(status1,status2))
	{
		case LEFT:{printf("左\r\n");break;}
		case RIGHT:{printf("右\r\n");break;}
		case UP:{printf("上\r\n");break;}
		case DOWN:{printf("下\r\n");break;}
		case UP_LEFT:{printf("左上\r\n");break;}
		case UP_RIGHT:{printf("右上\r\n");break;}
		case DOWN_LEFT:{printf("左下\r\n");break;}
		case DOWN_RIGHT:{printf("右下\r\n");break;}
		default:break;
	}
}
	
void keypad_data_process(uint8_t data)
{
	printf("%c",data);
}	
			 
void mouse_data_process(HID_MOUSE_Info_TypeDef data)
{
	if((data.buttons[0] == 0x00) &&(data.buttons[1] == 0x01)&&(data.buttons[2] == 0x00))
	{
		
		if(data.x !=0)
		{
		//鼠标按键
			Show_MousePressed_Status(data.x);
		}
		if((data.y != 0x00)||(data.z != 0x00))
		{
		//鼠标移动
			Show_MouseMoved_Status(data.y,data.z);
		}
	}
	CLear_MousePressed();
}	

关于鼠标的知识

参考这位博主的文章,这位博主的解析是正确的。
链接: Linux之解析鼠标input事件数据

根据博主的解释,对应我们STM32的HID_MOUSE_Info_TypeDef的数据结构可知,x是鼠标按键,而且x是uint8_t的8位数据。
x的第一位是左键按下
x的第二位是右键按下
x的第三位是中键按下
x的第四位是边键按下
x的第5位是边2键按下

博主文章中的,data 数组的第1个字节:表示鼠标的水平位移;
也就是对应我们的y。

博主文章中的,data 数组的第2个字节:表示鼠标的垂直位移;
也就是对应我们自己新加的z。

关于后续

因为我这个项目只需要用到这几个按键,和移动,所以我也就测试到这里了,如果还有需要鼠标滚轮的事件的,可以参考
链接: Linux之解析鼠标input事件数据
这位博主的继续修改代码,读取滚轮的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/689636.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

短剧看剧系统投流版系统搭建,前端uni-app

目录 前言&#xff1a; 一、短剧看剧系统常规款短剧系统和投流版的区别&#xff1f; 二、后端体系 1.管理端&#xff1a; 2.代理投流端 三、功能区别 总结&#xff1a; 前言&#xff1a; 23年上半年共上新微短剧481部&#xff0c;相较于2022年全年上新的454部&#xff0…

使用el-tree封装一个权限管理的小功能

使用el-tree封装一个权限管理的小功能 使用el-tree封装权限管理, 选中人员并且在右侧回显, 此组件用到了递归, 我只是将需要显示的数据进行了动态传递, 其他数据小伙伴可以自己封装 父组件 <template><div><authorityManage ref"authorityManage" :…

Vuepress 2从0-1保姆级进阶教程——标准化流程

Vuepress 2 专栏目录 1. 入门阶段 Vuepress 2从0-1保姆级入门教程——环境配置篇Vuepress 2从0-1保姆级入门教程——安装流程篇Vuepress 2从0-1保姆级入门教程——文档配置篇Vuepress 2从0-1保姆级入门教程——范例与部署 2.进阶阶段 Vuepress 2从0-1保姆级进阶教程——全文搜索…

学习笔记——路由网络基础——路由概述

一、路由概述 1、路由定义与作用 路由(routing)是指导报文转发路径信息&#xff0c;通过路由可以确认转发IP报文的路径。 路由&#xff1a;是指路由器从一个接口上收到数据包&#xff0c;根据数据包的目的地址进行定向并转发到另一个接口的过程。 路由(routing)的定义是指分…

极简主义在UI设计中的应用及解析

极简主义&#xff0c;即“少就是多”。在设计中&#xff0c;极简主义是许多艺术概念之一&#xff0c;它描述了一种内容形式&#xff0c;可以在许多方面使用。现在移动UI界面和网页设计中的极简主义设计越来越多。即时设计认为&#xff0c;极简主义UI界面不仅美观&#xff0c;而…

【一百零八】【算法分析与设计】P1908 逆序对,P1637 三元上升子序列,树状数组区间和应用

P1908 逆序对 逆序对 题目描述 猫猫 TOM 和小老鼠 JERRY 最近又较量上了&#xff0c;但是毕竟都是成年人&#xff0c;他们已经不喜欢再玩那种你追我赶的游戏&#xff0c;现在他们喜欢玩统计。 最近&#xff0c;TOM 老猫查阅到一个人类称之为“逆序对”的东西&#xff0c;这东西…

【漏洞复现】海康威视综合安防管理平台 多处 FastJson反序列化RCE漏洞

0x01 产品简介 海康威视综合安防管理平台是一套“集成化”、“智能化”的平台,通过接入视频监控、一卡通、停车场、报警检测等系统的设备。海康威视集成化综合管理软件平台,可以对接入的视频监控点集中管理,实现统一部署、统一配置、统一管理和统一调度。 0x02 漏洞概述 由于…

在Cisco Packet Tracer上配置NAT

目录 前言一、搭建网络拓扑1.1 配置PC机1.2 配置客户路由器1.3 配置ISP路由器 二、配置NAT2.1 在客户路由器中配置NAT2.2 测试是否配置成功 总结 前言 本篇文章是在了解NAT的原理基础上&#xff0c;通过使用Cisco Packet Tracer 网络模拟器实现模拟对NAT的配置&#xff0c;以加…

源码、反码和补码

对于有符号数而言&#xff0c;原码就是一个数的二进制表示。二进制的最高位是符号位&#xff0c;0 表示正数&#xff0c;1 表示负数。 计算机用数的原码进行显示&#xff0c;数的计算和存储是用补码进行的。 正数的原码&#xff0c;反码和补码都一样&#xff0c;即正数三码合…

Cesium中的坐标系统简单说明(2026-06-08)

一、坐标系统 cesium中坐标系统分为地理坐标、世界坐标&#xff08;X,Y,Z&#xff09;、屏幕坐标三种。 通常使用地理坐标来进行位置表达&#xff0c;笛卡尔空间坐标系常用来做一些空间位置变换如平移旋转缩放&#xff0c;屏幕坐标常用来做用户交互等&#xff0c;三者之间相互…

win7补丁下载

目的 一般来说&#xff0c;安装上windows系统就带着补丁了&#xff0c;但有时&#xff0c;安装的是原始版的操作系统是不带补丁的&#xff0c;一般直接更新就可以了&#xff0c;但有时&#xff0c;电脑不能联网&#xff0c;只能通过安装包进行升级&#xff0c;所以下面介绍如何…

Docker 学习总结(83)—— 配置文件daemon.json介绍及优化建议

一、daemon.json 文件概述 daemon.json是Docker守护进程的配置文件,它允许系统管理员自定义Docker守护程序的行为。此文件通常位于/etc/docker/目录下。通过修改daemon.json,可以调整Docker守护进程的多种设置,包括网络配置、日志记录、存储驱动等。 二、daemon.json 文件结…

【Vue】路由的基本使用

文章目录 一、固定5个固定的步骤二、代码示例三、两个核心步骤四、完整代码 vue-router插件作用 修改地址栏路径时&#xff0c;切换显示匹配的组件 说明 Vue 官方的一个路由插件&#xff0c;是一个第三方包 官网 https://v3.router.vuejs.org/zh/ VueRouter的使用&#xff0…

【安装笔记-20240607-Linux-适合个人用户及初创企业的 SSL 证书服务】

安装笔记-系列文章目录 安装笔记-20240607-Linux-适合个人用户及初创企业的 SSL 证书服务 文章目录 安装笔记-系列文章目录安装笔记-20240607-Linux-适合个人用户及初创企业的 SSL 证书服务 前言一、软件介绍名称&#xff1a;acme.sh主页官方介绍 二、安装步骤测试版本&#x…

React Hooks 封装可粘贴图片的输入框组件(wangeditor)

需求是需要一个文本框 但是可以支持右键或者ctrlv粘贴图片&#xff0c;原生js很麻烦&#xff0c;那不如用插件来实现吧~我这里用的wangeditor插件&#xff0c;初次写初次用&#xff0c;可能不太好&#xff0c;但目前是可以达到实现需求的一个效果啦&#xff01;后面再改进吧~ …

两张图片进行分析

两张图片进行分析&#xff0c;可以拖动左边图片进行放大、缩小查看图片差异 底图 <template><div class"box_container"><section><div class"" v-for"item in imgData.imgDataVal" :key"item.id"><img :s…

JavaSE--【类和对象】

本篇目标 1. 掌握类的定义方式以及对象的实例化 2. 掌握类中的成员变量和成员方法的使用 3. 掌握对象的整个初始化过程 一、面向对象的初步认知 1.1 面向对象的初步认知 Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里…

Execl数据导入 EasyExcel实现

官网 1. 需求简介 读取下面表格数据 第一行和第二行是计划信息 第三行是计划详情的抬头信息,以下行是计划详情信息 总段包含多个分段,总段使用了单元格合并功能 2. 实现读取功能 2.1 引入easyexcel依赖 <dependency><groupId>com.alibaba</groupId><…

移动端 UI 风格,视觉盛宴

移动端 UI 风格&#xff0c;视觉盛宴

10.dockerfile自动构建镜像

dockerfile自动构建镜像 类似ansible剧本&#xff0c;大小几kb 手动做镜像&#xff1a;大小几百M 首先创建一个dockerfile的路径&#xff0c;便于在路径下存在多个路径每个路径下都是dockerfile命名的脚本 注释&#xff1a;文件必须为&#xff1a;dockerfile或者Dockerfile …