CH32V103C8T6 usb gamepad joystick RISC-V内核 鼠标手柄键盘hid评估板学习板开发板指南

第一部分、硬件概述

1.1 实物概图

图1.1Gamepad实物概图
image

如图1.1所示Gamepad评估板配置了8个6*6轻触按键,一个摇杆(Joystick),搭载一颗WS2812B灯珠,并将UART1串口,编程接口(SWD),外接Joystick接口,microUSB接口引出;

1.2 Gamepad原理图

Gamepad原理图如图1.2所示,如看不清可打开Doc目录下的PDF文档查阅
图1.2 Gamepad原理图
image

第二部分、软件工具

2.1 软件概述

在 /Software 目录下是常用的工具软件:

  1. Dt2_4:配置USB设备Report描述符的工具;
  2. USBHID调试助手/呀呀USB: USB调试工具,相当于串口调试助手功能;
  3. BUSHound:总线调试工具;
  4. USBlyzer:一款专业的USB协议分析软件
  5. MDK:常用编译器;
  6. STM32CubeMX:代码生成工具;

第三部分、实战训练

3.1 实例Eg1_GamePad

目标是实现GamePad:枚举成XY轴的平面坐标和8个按键的USB设备。

3.1.1硬件设计

图1.3 Joystick原理图

image

其中VRX1与VRY1是摇杆的电位器输出的电压信号(ADC检测);SW1则是按键,右侧H1是外接的Joystick口;

图1.4 KEY原理图

image

如图1.4是KEY原理图,我们只要配置8个GPIO作为输入去检测按键信号;

3.1.2 软件设计

首先是初始化代码,我们通过STM32cubeMX软件去生成代码,具体配置请打开GamePad.ioc查阅,这里不再赘述;
我们的工程使用的是Keil-MDK编译器,生成的工程目录如图1.5

image
图1.5 工程目录
其中

  • Application/MDK-ARM 存放的是启动代码;
  • Application/User/Core: main函数,中断Handler,MSP相关代码;
  • Application/User/USB_DEVICE/App:* USB设备应用代码;
  • Application/User/USB_DEVICE/Target: USB设备配置代码;
  • Drivers/STM32F1xx_HAL_Driver: HAL库驱动代码
  • Drivers/CMSIS: CMSIS相关代码
  • Middlewares/USB_Device_Library/ USB设备库代码,对应cubemx Middleware;
  • Customer: 这是我们自定义的代码;
  • Doc: 存放说明文本文档;

工程目录这里只做一次介绍,后面的样例目录大同小异。接下来我们配置一下USB的报告描述符,我们定位到
"Application/User/USB_DEVICE/App/ usbd_custom_hid_if.c"文件,
打开usbd_custom_hid_if.c文件,并找到数组变量CUSTOM_HID_ReportDesc_FS;
另外需要注意USBD_CUSTOM_HID_REPORT_DESC_SIZE,
这个宏是报告描述符实际数组大小,大小不对会导致枚举失败;
"#define USBD_CUSTOM_HID_REPORT_DESC_SIZE 46"

通过报告描述符生成工具Dt2_4配置生成报告描述符,如下

__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
    0xa1, 0x02,                    //     COLLECTION (Logical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x35, 0x00,                    //     PHYSICAL_MINIMUM (0)
    0x46, 0xff, 0x00,              //     PHYSICAL_MAXIMUM (255)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x08,                    //     USAGE_MAXIMUM (Button 8)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x08,                    //     REPORT_COUNT (8)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                          //               END_COLLECTION

  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION	             */
};

由上述描述符可以看出X,Y轴定义成无符号8位数,XY的描述占用2个字;按键一共有8个,每个大小描述是bit,8个bit即1个byte;因此XY坐标+8个按键=3个byte;我们需要上报3个byte的数据给主机(HOST)。
最后是应用程序的编写,在main函数中,我们通过HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&AD_DATA, N)启动ADC DMA采集两个坐标数据;
bubble_sort是冒泡排序算法,对XY采样到的数据进行排序后取中间值几个进行求平均以减少误差;

  • 公式:

X=((Xtemp-Xmin)255)/(Xmax-Xmin);
Y=((Ytemp-Ymin)
255)/(Ymax-Ymin);

这两个公式是坐标解析算法,即两点一线方程;
按键的解析就是操作对应的key变量对应的位:如按键1按下,把最低位置1;按键1弹起,把最低位清零即可;
这样组成报告描述符,通过USBD_CUSTOM_HID_SendReport上报给主机,如下


Joystick_Report[0] =Y;
Joystick_Report[1]=X;
Joystick_Report[2]=key;
USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,(u8*)&Joystick_Report, 3);

至此,软件设计完成。(对于没有基础的同学,还是建议多看代码,精力有限,恕难面面俱到)

3.1.3 下载验证

我们把固件程序下载进去可以,打开“设备与打印机”可以看到USB设备枚举成了一个Gamepad,如下图。
image

图1.5 Gamepad设备
右键打开游戏控制器后,点击属性得到下图所示界面
image
图1.6 游戏控制器
我们可以摇Joystick和按按键可以发现上图游戏控制器界面也跟着响应。

3.2 实例Eg2_WS2812B

目标是实现点亮WS2812B灯珠。

3.2.1硬件设计

如图1.7和1.8所示MCU通过1根数据线控制WS2812B
image
图1.7 WS2812B单颗灯珠原理图
image
图1.8 MCU连接WS2812控制引脚
————————————————以下为引文start————————————————
WS2812通讯协议:
数据协议采用单线归零码的通讯方式, 像素点在上电复位以后, DIN端接受从控制器传输过来的数据, 首先送过来的24bit数据被第一个像素点提取后, 送到像素点内部的数据锁存器, 剩余的数据经过内部整形处理电路整形放大后通过DO端口开始转发输出给下一个级联的像素点, 每经过一个像素点的传输, 信号减少24bit。 像素点采用自动整形转发技术, 使得该像素点的级联个数不受信号传送的限制, 仅仅受限信号传输速度要求

image
从图中可以看出,数字“1”使用长脉宽来编码(占空比为68%),“0”使用短脉宽来编码(占空比为32%)。数据线低电平保持时间大于50us时,为复位信号。复位后,每个LED读取“Din”线上开始的24bit(绿:红:蓝为8:8:8)数据到驱动芯片内部缓存。除了开始的24bit数据,后面的数据都通过“Dout”脚传递到下一个LED,即每经过一个像素点的传输,信号减少24bit。内部缓存数据在下一个复位脉冲后被写入PWM控制器。一个bit为1.25us,一个LED有38bits=24bits,传输完  需要241.25us=30us。
数据传输方式:
image
————————————————end————————————————
版权声明:本文为CSDN博主「皮克斯之旅」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cheng_nnan/article/details/105490778

3.2.2 软件设计

我们将DIN配置为PWM+DMA的方式去驱动WS2812B,初始化配置如图1.9所示
image
图1.9 DIN引脚配置
MCU主频为72M,Counter period=89,即计数周期=1/72*89约等于1.23us

#define BIT_1 61u //1比较值为61 850us
#define BIT_0 28u //0比较值为28 400us

我们的代码中配置0码和1码如上,

1码计算:1/7261约等于0.84us
0码计算:1/72
28约等于0.38us

满足规格书中对传输时间的要求;代码实现,请参考示例源码;

3.2.3 下载验证

我们把固件程序下载进去可以,可以看到板载的5050灯珠,进行七彩变化;
image

3.3 实例Eg3_Mouse

目标是模拟鼠标功能。

3.3.1硬件设计

如图2.0所示,我们将使用VRX1和VRY1作为鼠标相对坐标,SW1作为鼠标中键
image
图2.0 Joystick原理图
image
如上图2.1,UP,DN,LF,RG分别代表鼠标滚轮向上,向下,鼠标左键,右键;

3.3.2 软件设计

我们将usbd_custom_hid_if.c中CUSTOM_HID_ReportDesc_FS修改为鼠标的报告描述符;然后按照报告描述符上面定义的数据,修改main.c中的Joystick_Report数组;详见代码

鼠标的报告描述符:

__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */
  0x05,   0x01,
  0x09,   0x02,
  0xA1,   0x01,
  0x09,   0x01,

  0xA1,   0x00,
  0x05,   0x09,
  0x19,   0x01,
  0x29,   0x03,

  0x15,   0x00,
  0x25,   0x01,
  0x95,   0x03,
  0x75,   0x01,

  0x81,   0x02,
  0x95,   0x01,
  0x75,   0x05,
  0x81,   0x01,

  0x05,   0x01,
  0x09,   0x30,
  0x09,   0x31,
  0x09,   0x38,

  0x15,   0x81,
  0x25,   0x7F,
  0x75,   0x08,
  0x95,   0x03,

  0x81,   0x06,
  0xC0,   0x09,
  0x3c,   0x05,
  0xff,   0x09,

  0x01,   0x15,
  0x00,   0x25,
  0x01,   0x75,
  0x01,   0x95,

  0x02,   0xb1,
  0x22,   0x75,
  0x06,   0x95,
  0x01,   0xb1,

  0x01,

  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION	             */
};

报文解析在main函数中

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
    static uint16_t tick=0,c_tick=0;
    u8 key=0;
    u8 i;
    u8 XIndex = 0,YIndex = 0;
    static uint32_t Xmax = AD_XMAX, Ymax = AD_YMAX;		
    static uint32_t Xmin = AD_XMIN, Ymin = AD_YMIN;

    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_DMA_Init();
    MX_ADC1_Init();
    MX_USB_DEVICE_Init();
    MX_USART1_UART_Init();
    MX_TIM3_Init();
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&AD_DATA, N);

  while (1)
  {
		key=0;
		memset(Joystick_Report,0,4);
			
		if((LFKEY)==0)
			key|=0x01;
		if((RGKEY)==0)
			key|=0x02;
		if(SW1!=0)
			key|=0x04;			
		XIndex=0,YIndex=0;
		for(i = 0; i < N;)
		{
				XBuf[XIndex] = AD_DATA[i];
				i++;XIndex++;
				YBuf[YIndex] = AD_DATA[i];
				i++;YIndex++;
		}		
		bubble_sort(XBuf, 10);
		bubble_sort(YBuf, 10);		
		Xtemp=(XBuf[3]+XBuf[4]+XBuf[5]+XBuf[6])>>2;
		Ytemp=(YBuf[3]+YBuf[4]+YBuf[5]+YBuf[6])>>2;
		if(Xtemp>Xmax)
			Xtemp=Xmax;
		if(Xtemp<Xmin)
			Xtemp=Xmin;
		if(Ytemp>=Ymax)
			Ytemp=Ymax;
		if(Ytemp<=Ymin)
			Ytemp=Ymin;		
		//根据坐标极点确定坐标(两点直线方程)
	//	X=(((Xtemp-Xmin)/(Xmax-Xmin))*255)&0xFFFF;
	//	Y=(((Ytemp-Ymin)/(Ymax-Ymin))*255)&0xFFFF;
		X=((Xtemp-Xmin)*255)/(Xmax-Xmin);
		Y=((Ytemp-Ymin)*255)/(Ymax-Ymin);
		if(X>(X_BASE+20))
		{
			Joystick_Report[2]=1;
		}
		if(X<(X_BASE-20))
		{
			Joystick_Report[2]=(u8)-1;
		}
		if(Y>(Y_BASE+20))
		{
			Joystick_Report[1]=1;
		}
		if(Y<(Y_BASE-20))
		{
			Joystick_Report[1]=(u8)-1;
		}		

		if((UPKEY)==0)
		{
			if(c_tick++>5)
			{
				Joystick_Report[3]=1;
				c_tick=0;
			}
		}
		if((DNKEY)==0)
		{
			if(c_tick++>5)
			{
				Joystick_Report[3]=(u8)-1;
				c_tick=0;
			}			
			
		}	
		Joystick_Report[0]=key;
		USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,(u8*)&Joystick_Report, 4);

		if(++tick>100)
		{
			tick=0;
			Colorful_Gradient_Pro();
		}
		HAL_Delay(1);

		printf("Xtemp=%d\r\n",X);
		printf("Ytemp=%d\r\n",Y);		

  }
}

3.3.3 下载验证

我们把固件程序下载进去,摇动摇杆,桌面鼠标指针对应移动,按对应按键,鼠标按键作出相对应的反应;

3.4 实例Eg4_ComDev_GM

目标是实现Gamepad和Mouse的组合,即把实例1和实例三组合成一个设备,通过传感按键SW7切换;

3.4.1硬件设计

参考原理图,本实例除了ws2812灯珠,其他硬件皆用到;

3.4.2 软件设计

本节重点在软件部分,在于如何增加接口端点去上报需要组合两个设备的Raw Data,下面是我们总结的步骤;
STM32 USB HAL库CustomerHID类设备增加接口端点步骤:

  1. 为端点增加PAM:
    定位到MX_USB_DEVICE_Init->USBD_Init->USBD_LL_Init;在USBD_LL_Init函数中找到HAL_PCDEx_PMAConfig,通过该接口为EP(端点)配置PMA(Packet Buffer Memory Area ,即USB硬件缓冲区))Eg:
    HAL_PCDEx_PMAConfig((PCD_HandleTypeDef*)pdev->pData,MOUSE_HID_EPIN_ADDR , PCD_SNG_BUF,0x98);
    
  2. 打开和配置端点:
    定位到USBD_CUSTOM_HID->USBD_CUSTOM_HID_Init通过USBD_LL_OpenEP函数中和pdev->ep_in[EP_ADDR & 0xFU].is_used = 1U;打开并使能端点,同时将USBD_LL_PrepareReceive中第四个参数改为大(如果用到接收端点);Eg:
    USBD_LL_OpenEP(pdev, MOUSE_HID_EPIN_ADDR, USBD_EP_TYPE_INTR,MOUSE_HID_EPIN_SIZE);
    pdev->ep_in[MOUSE_HID_EPIN_ADDR & 0xFU].is_used = 1U;
    USBD_LL_PrepareReceive(pdev, CUSTOM_HID_EPOUT_ADDR, hhid->Report_buf,
                            USBD_CUSTOMHID_OUTREPORT_BUF_SIZE);
    
  3. 根据接口索引修改获取描述符的请求:
    定位到USBD_CUSTOM_HID->USBD_CUSTOM_HID_Setup在USB_REQ_GET_DESCRIPTOR请求中通过req->wIndex增加报告描述符的请求;Eg:
    if(req->wIndex==0)
    {
        len = MIN(USBD_MOUSE_HID_REPORT_DESC_SIZE, req->wLength);
        pbuf = ((USBD_CUSTOM_HID_ItfTypeDef *)pdev->pUserData)->MouseReport;
    }
    
  4. 修改配置、接口、端点描述符:定位到USBD_CUSTOM_HID->USBD_CUSTOM_HID_GetFSCfgDesc->USBD_CUSTOM_HID_CfgFSDesc正确修改即可,Eg:略
  5. 增加报告描述符:在usbd_custom_hid_if.c中增加报告描述符及其大小;
  6. 修改发送报告函数:在usbd_customhid.c中修改USBD_CUSTOM_HID_SendReport中的USBD_LL_Transmit的第二个参数地址;

3.4.3 下载验证

7我们把固件程序下载进去,摇动摇杆,按住SW7大于5s,切换成鼠标模式,桌面鼠标指针对应移动,按对应按键,鼠标按键作出相对应的反应;

3.5 实例Eg5_Keyboard

目标是实现Keyboard,模拟键盘;实现键盘的shift、1~8键,分别对应评估板的SW1(摇杆上的按键)、SW2、SW3、SW4、SW5、SW9、SW8、SW7、SW6;

3.5.1硬件设计

参考原理图;

3.5.2 软件设计

本节重点在软件部分,在于如何修改配置描述符,看懂报告描述符;以及如何把按键数据解析为报文;

__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */
  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
  0x09, 0x06, // USAGE (Keyboard)
  0xa1, 0x01, // COLLECTION (Application)
  0x05, 0x07, // USAGE_PAGE (Keyboard)
  0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
  0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
  0x15, 0x00, // LOGICAL_MINIMUM (0)
  0x25, 0x01, // LOGICAL_MAXIMUM (1)
  0x75, 0x01, // REPORT_SIZE (1)
  0x95, 0x08, // REPORT_COUNT (8)
  0x81, 0x02, // INPUT (Data,Var,Abs)
  0x95, 0x01, // REPORT_COUNT (1)
  0x75, 0x08, // REPORT_SIZE (8)
  0x81, 0x03, // INPUT (Cnst,Var,Abs)
  0x95, 0x05, // REPORT_COUNT (5)
  0x75, 0x01, // REPORT_SIZE (1)
  0x05, 0x08, // USAGE_PAGE (LEDs)
  0x19, 0x01, // USAGE_MINIMUM (Num Lock)
  0x29, 0x05, // USAGE_MAXIMUM (Kana)
  0x91, 0x02, // OUTPUT (Data,Var,Abs)
  0x95, 0x01, // REPORT_COUNT (1)
  0x75, 0x03, // REPORT_SIZE (3)
  0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
  0x95, 0x06, // REPORT_COUNT (6)
  0x75, 0x08, // REPORT_SIZE (8)
  0x15, 0x00, // LOGICAL_MINIMUM (0)
  0x25, 0xFF, // LOGICAL_MAXIMUM (255)
  0x05, 0x07, // USAGE_PAGE (Keyboard)
  0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
  0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
  0x81, 0x00, // INPUT (Data,Ary,Abs)


  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION	             */
};

数据解析:

void keyboard_Handle(void)
{
	memset(Keyboad_Buf,0,8);
	uint8_t i=0;uint8_t idx=2;

	if(SW1==1)
	{
		currentshift|=0x02;
		Keyboad_Buf[0]=currentshift;
	}else{
		
		currentshift&=(~0x02);
		Keyboad_Buf[0]=currentshift;
	}
	
	if(UPKEY==0)
	{
		currentkeycode[0]=CODE1;
	}else{		
		currentkeycode[0]=0x00;
	}	
	if(DNKEY==0)
	{
		currentkeycode[1]=CODE2;
	}else{		
		currentkeycode[1]=0x00;
	}	
	if(LFKEY==0)
	{
		currentkeycode[2]=CODE3;
	}else{		
		currentkeycode[2]=0x00;
	}	
	if(RGKEY==0)
	{
		currentkeycode[3]=CODE4;
	}else{		
		currentkeycode[3]=0x00;
	}
	if(BKKEY==0)
	{
		currentkeycode[4]=CODE5;
	}else{		
		currentkeycode[4]=0x00;
	}	
	if(MDKEY==0)
	{
		currentkeycode[5]=CODE6;
	}else{		
		currentkeycode[5]=0x00;
	}	
	if(STKEY==0)
	{
		currentkeycode[6]=CODE7;
	}else{		
		currentkeycode[6]=0x00;
	}	
	if(TBKEY==0)
	{
		currentkeycode[7]=CODE8;
	}else{		
		currentkeycode[7]=0x00;
	}

	for(i=0;i<8;i++)
	{
		if(currentkeycode[i]!=lastkeycode[i])
		{
			Keyboad_Buf[idx]=currentkeycode[i];
			if(++idx>=8)
			{
				idx=2;
			}
			KdataFL=1;		
		}else{
			Keyboad_Buf[idx]=0x00;
		}
	}
	if(currentshift!=lastshift)
	{
		KdataFL=1;
	}
	if(KdataFL!=0)
	{
		KdataFL=0;
		USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,(u8*)&Keyboad_Buf, 8,Keypad_Type);
	}
	HAL_Delay(10);
	memcpy(lastkeycode,currentkeycode,8);
	lastshift=currentshift;

}

3.5.3 下载验证

我们把固件程序下载进去,pc端的设备与打印机面板显示枚举成功的键盘设备;按下SW1即是shift键按下,SW2、SW3、SW4、SW5、SW9、SW8、SW7、SW6按下也对应是主键盘上的1~8键按下,按住“shift+1”也可以打印“!”,其他组合键也同理可得。

3.5 实例Eg5_Keyboard

目标是实现Keyboard,模拟键盘;实现键盘的shift、1~8键,分别对应评估板的SW1(摇杆上的按键)、SW2、SW3、SW4、SW5、SW9、SW8、SW7、SW6;

3.5.1硬件设计

参考原理图;

3.5.2 软件设计

本节重点在软件部分,在于如何修改配置描述符,看懂报告描述符;以及如何把按键数据解析为报文;

__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */
  0x05, 0x01, // USAGE_PAGE (Generic Desktop)
  0x09, 0x06, // USAGE (Keyboard)
  0xa1, 0x01, // COLLECTION (Application)
  0x05, 0x07, // USAGE_PAGE (Keyboard)
  0x19, 0xe0, // USAGE_MINIMUM (Keyboard LeftControl)
  0x29, 0xe7, // USAGE_MAXIMUM (Keyboard Right GUI)
  0x15, 0x00, // LOGICAL_MINIMUM (0)
  0x25, 0x01, // LOGICAL_MAXIMUM (1)
  0x75, 0x01, // REPORT_SIZE (1)
  0x95, 0x08, // REPORT_COUNT (8)
  0x81, 0x02, // INPUT (Data,Var,Abs)
  0x95, 0x01, // REPORT_COUNT (1)
  0x75, 0x08, // REPORT_SIZE (8)
  0x81, 0x03, // INPUT (Cnst,Var,Abs)
  0x95, 0x05, // REPORT_COUNT (5)
  0x75, 0x01, // REPORT_SIZE (1)
  0x05, 0x08, // USAGE_PAGE (LEDs)
  0x19, 0x01, // USAGE_MINIMUM (Num Lock)
  0x29, 0x05, // USAGE_MAXIMUM (Kana)
  0x91, 0x02, // OUTPUT (Data,Var,Abs)
  0x95, 0x01, // REPORT_COUNT (1)
  0x75, 0x03, // REPORT_SIZE (3)
  0x91, 0x03, // OUTPUT (Cnst,Var,Abs)
  0x95, 0x06, // REPORT_COUNT (6)
  0x75, 0x08, // REPORT_SIZE (8)
  0x15, 0x00, // LOGICAL_MINIMUM (0)
  0x25, 0xFF, // LOGICAL_MAXIMUM (255)
  0x05, 0x07, // USAGE_PAGE (Keyboard)
  0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
  0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
  0x81, 0x00, // INPUT (Data,Ary,Abs)


  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION	             */
};

数据解析:

void keyboard_Handle(void)
{
	memset(Keyboad_Buf,0,8);
	uint8_t i=0;uint8_t idx=2;

	if(SW1==1)
	{
		currentshift|=0x02;
		Keyboad_Buf[0]=currentshift;
	}else{
		
		currentshift&=(~0x02);
		Keyboad_Buf[0]=currentshift;
	}
	
	if(UPKEY==0)
	{
		currentkeycode[0]=CODE1;
	}else{		
		currentkeycode[0]=0x00;
	}	
	if(DNKEY==0)
	{
		currentkeycode[1]=CODE2;
	}else{		
		currentkeycode[1]=0x00;
	}	
	if(LFKEY==0)
	{
		currentkeycode[2]=CODE3;
	}else{		
		currentkeycode[2]=0x00;
	}	
	if(RGKEY==0)
	{
		currentkeycode[3]=CODE4;
	}else{		
		currentkeycode[3]=0x00;
	}
	if(BKKEY==0)
	{
		currentkeycode[4]=CODE5;
	}else{		
		currentkeycode[4]=0x00;
	}	
	if(MDKEY==0)
	{
		currentkeycode[5]=CODE6;
	}else{		
		currentkeycode[5]=0x00;
	}	
	if(STKEY==0)
	{
		currentkeycode[6]=CODE7;
	}else{		
		currentkeycode[6]=0x00;
	}	
	if(TBKEY==0)
	{
		currentkeycode[7]=CODE8;
	}else{		
		currentkeycode[7]=0x00;
	}

	for(i=0;i<8;i++)
	{
		if(currentkeycode[i]!=lastkeycode[i])
		{
			Keyboad_Buf[idx]=currentkeycode[i];
			if(++idx>=8)
			{
				idx=2;
			}
			KdataFL=1;		
		}else{
			Keyboad_Buf[idx]=0x00;
		}
	}
	if(currentshift!=lastshift)
	{
		KdataFL=1;
	}
	if(KdataFL!=0)
	{
		KdataFL=0;
		USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,(u8*)&Keyboad_Buf, 8,Keypad_Type);
	}
	HAL_Delay(10);
	memcpy(lastkeycode,currentkeycode,8);
	lastshift=currentshift;

}

3.5.3 下载验证

我们把固件程序下载进去,pc端的设备与打印机面板显示枚举成功的键盘设备;按下SW1即是shift键按下,SW2、SW3、SW4、SW5、SW9、SW8、SW7、SW6按下也对应是主键盘上的1~8键按下,按住“shift+1”也可以打印“!”,其他组合键也同理可得。

3.6 实例Eg6_DoubleJoystick

目标是实现一个USB带两个joystick摇杆;功能完全与实例Eg1_GamePad一致;

3.6.1硬件设计

参考原理图;

3.6.2 软件设计

首先要修改的是报表描述符:

/** Usb HID report descriptor. */
__ALIGN_BEGIN static uint8_t CUSTOM_HID_ReportDesc_FS[USBD_CUSTOM_HID_REPORT_DESC_SIZE] __ALIGN_END =
{
  /* USER CODE BEGIN 0 */
    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
	0x85, 0x01,                    //   REPORT_ID (1)
    0xa1, 0x02,                    //     COLLECTION (Logical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x35, 0x00,                    //     PHYSICAL_MINIMUM (0)
    0x46, 0xff, 0x00,              //     PHYSICAL_MAXIMUM (255)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x08,                    //     USAGE_MAXIMUM (Button 8)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x08,                    //     REPORT_COUNT (8)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0, 0xc0,                    //               END_COLLECTION

    0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
    0x09, 0x04,                    // USAGE (Joystick)
    0xa1, 0x01,                    // COLLECTION (Application)
	0x85, 0x02,                    //   REPORT_ID (2)
    0xa1, 0x02,                    //     COLLECTION (Logical)
    0x09, 0x30,                    //     USAGE (X)
    0x09, 0x31,                    //     USAGE (Y)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x26, 0xff, 0x00,              //     LOGICAL_MAXIMUM (255)
    0x35, 0x00,                    //     PHYSICAL_MINIMUM (0)
    0x46, 0xff, 0x00,              //     PHYSICAL_MAXIMUM (255)
    0x75, 0x08,                    //     REPORT_SIZE (8)
    0x95, 0x02,                    //     REPORT_COUNT (2)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0x05, 0x09,                    //     USAGE_PAGE (Button)
    0x19, 0x01,                    //     USAGE_MINIMUM (Button 1)
    0x29, 0x08,                    //     USAGE_MAXIMUM (Button 8)
    0x15, 0x00,                    //     LOGICAL_MINIMUM (0)
    0x25, 0x01,                    //     LOGICAL_MAXIMUM (1)
    0x95, 0x08,                    //     REPORT_COUNT (8)
    0x75, 0x01,                    //     REPORT_SIZE (1)
    0x81, 0x02,                    //     INPUT (Data,Var,Abs)
    0xc0,                      //               END_COLLECTION
  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION	             */
};

与前面几个实例不同的是,这里增加了Report ID,就是说每个USB接口都支持多个Report ID;每个Report ID都支持不同的报表描述符,如某些复合的USB键鼠一体设备,就是通过USB Report ID区分的键盘与鼠标的;

数据解析:XY_Handle是解析X,Y坐标的,key_scan是对8颗按键进行扫描,Joystick_Report[0]就是Report ID,占用1Byte,也就是如果带宽允许,最大支持255个报表;

void GamepadHandle(void)
{
	XY_Handle();
	Joystick_Report[0]=1;//Report 1;
	Joystick_Report[1]=Y;
	Joystick_Report[2]=X;
	key_scan(&Joystick_Report[3]);	
	USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,(u8*)&Joystick_Report, JOYBUFSIZE);
	HAL_Delay(8);
	Joystick_Report[0]=2;//Report 1;
	USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS,(u8*)&Joystick_Report, JOYBUFSIZE);
	HAL_Delay(8);	
}

3.6.3 下载验证

我们把固件程序下载进去,可以看到游戏控制器界面有两个控制器,调开属性界面两个都可以控制;
image

我们可以打开Bus Hound,抓取报文,端点0传输的是枚举过程,然后我们看Device:66.1的4个字节的,报文01 7e 7d 00是Report id为1的报文, 报文02 7e 7d 00是Report id为2的报文,因为我们上报的是相同的数据,就ID不同,故而控制的是两个joystick设备;

image

posted @ 2022-02-15 23:23  LiJin_hh  阅读(930)  评论(0编辑  收藏  举报