使用Custom HID制作全键无冲的键盘(STM32)

配置CuBeMX

启用USB,选择从机模式

启用HSE,在时钟树中将USB速率设置为48MHz。

Ctrl+s保存并生成代码

配置HID报告描述符

打开Project\USB_Device\App\usbd_custom_hid_if.c,找到第九十行,在下方大括号内填入描述符:

		  /* 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)
				    0x95, 0x08,                    //   REPORT_COUNT (8)
				    0x75, 0x01,                    //   REPORT_SIZE (1)
				    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
				    0x95, 0x01,                    //   REPORT_COUNT (1)
				    0x75, 0x08,                    //   REPORT_SIZE (8)
				    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
				    0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
				    0x19, 0x04,                    //   USAGE_MINIMUM (Keyboard a and A)
				    0x29, 0x65,                    //   USAGE_MAXIMUM (Keyboard Application)
				    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
				    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
				    0x95, 0x62,                    //   REPORT_COUNT (98)
				    0x75, 0x01,                    //   REPORT_SIZE (1)
				    0x81, 0x02,                    //   INPUT (Data,Var,Abs)
				    0x95, 0x01,                    //   REPORT_COUNT (1)
				    0x75, 0x06,                    //   REPORT_SIZE (6)
				    0x81, 0x03,                    //   INPUT (Cnst,Var,Abs)
				    0x05, 0x08,                    //   USAGE_PAGE (LEDs)
				    0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
				    0x29, 0x05,                    //   USAGE_MAXIMUM (Kana)
				    0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
				    0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
				    0x95, 0x05,                    //   REPORT_COUNT (5)
				    0x75, 0x01,                    //   REPORT_SIZE (1)
				    0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)
				    0x95, 0x01,                    //   REPORT_COUNT (1)
				    0x75, 0x03,                    //   REPORT_SIZE (3)
				    0x91, 0x03,                    //   OUTPUT (Cnst,Var,Abs)
				  //0xc0                           // END_COLLECTION
		  /* USER CODE END 0 */
		  0xC0    /*     END_COLLECTION	             */		  

按住Ctrl点击USBD_CUSTOM_HID_REPORT_DESC_SIZE跳转配置描述符大小为73

点击左上角锤子进行构建工程,并刷入测试。打开设备管理器观察键盘类是否出现多了一个键盘。如果是进行下一步。如果没有出现观察是否在通用串行总线控制器出现未知的的USB设备(设备描述符请求失败),打开STM32CubeProgrammer进行FullChip Erase,再刷入即可。

构建输入函数

发送函数在usbd_custom_hid_if.c中,函数名称USBD_CUSTOM_HID_SendReport()
接收中断函数在usbd_custom_hid_if.c中,函数名称CUSTOM_HID_OutEvent_FS()

/*
 * key_if.c
 *
 *  Created on: Jan 17, 2025
 *      Author: LLL
 */
#include "key_if.h"

uint8_t report[15]={0};

void ifkey(int command)
{
	switch(command)
	  	{
	  		case 1:report[0]|=0x01;break;//LeftCtrl
	  		case 2:report[0]&=(~0x01);break;
	  		case 3:report[0]|=0x02;break;//LeftShift
	  		case 4:report[0]&=(~0x02);break;
	  		case 5:report[0]|=0x04;break;//LeftAlt
	  		case 6:report[0]&=(~0x04);break;
	  		case 7:report[0]|=0x08;break;//LeftGUI
	  		case 8:report[0]&=(~0x08);break;
	  		case 9:report[0]|=0x10;break;//RightCtrl
	  		case 10:report[0]&=(~0x10);break;
	  		case 11:report[0]|=0x20;break;//RightShift
	  		case 12:report[0]&=(~0x20);break;
	  		case 13:report[0]|=0x40;break;//RightAlt
	  		case 14:report[0]&=(~0x40);break;
	  		case 15:report[0]|=0x80;break;//RightGUI
	  		case 16:report[0]&=(~0x80);break;
	  		case 17:report[2]|=0x01;break;//a按下
	  		case 18:report[2]&=(~0x01);break;//a释放
	  		case 19:report[2]|=0x02;break;//b
	  		case 20:report[2]&=(~0x02);break;
	  		case 21:report[2]|=0x04;break;//c
	  		case 22:report[2]&=(~0x04);break;
	  		case 23:report[2]|=0x08;break;//d
	  		case 24:report[2]&=(~0x08);break;
	  		case 25:report[2]|=0x10;break;//e
	  		case 26:report[2]&=(~0x10);break;
	  		case 27:report[2]|=0x20;break;//f
	  		case 28:report[2]&=(~0x20);break;
	  		case 29:report[2]|=0x40;break;//g
	  		case 30:report[2]&=(~0x40);break;
	  		case 31:report[2]|=0x80;break;//h
	  		case 32:report[2]&=(~0x80);break;
	  		case 33:report[3]|=0x01;break;//i
	  		case 34:report[3]&=(~0x01);break;
	  		case 35:report[3]|=0x02;break;
	  		case 36:report[3]&=(~0x02);break;
	  		case 37:report[3]|=0x04;break;
	  		case 38:report[3]&=(~0x04);break;
	  		case 39:report[3]|=0x08;break;
	  		case 40:report[3]&=(~0x08);break;
	  		case 41:report[3]|=0x10;break;
	  		case 42:report[3]&=(~0x10);break;
	  		case 43:report[3]|=0x20;break;
	  		case 44:report[3]&=(~0x20);break;
	  		case 45:report[3]|=0x40;break;
	  		case 46:report[3]&=(~0x40);break;
	  		case 47:report[3]|=0x80;break;
	  		case 48:report[3]&=(~0x80);break;
	  		case 49:report[4]|=0x01;break;
	  		case 50:report[4]&=(~0x01);break;
	  		case 51:report[4]|=0x02;break;
	  		case 52:report[4]&=(~0x02);break;
	  		case 53:report[4]|=0x04;break;
	  		case 54:report[4]&=(~0x04);break;
	  		case 55:report[4]|=0x08;break;
	  		case 56:report[4]&=(~0x08);break;
	  		case 57:report[4]|=0x10;break;
	  		case 58:report[4]&=(~0x10);break;
	  		case 59:report[4]|=0x20;break;
	  		case 60:report[4]&=(~0x20);break;
	  		case 61:report[4]|=0x40;break;
	  		case 62:report[4]&=(~0x40);break;
	  		case 63:report[4]|=0x80;break;
	  		case 64:report[4]&=(~0x80);break;
	  		case 65:report[5]|=0x01;break;
	  		case 66:report[5]&=(~0x01);break;
	  		case 67:report[5]|=0x02;break;//z
	  		case 68:report[5]&=(~0x02);break;
	  		case 69:report[5]|=0x04;break;//上键盘数字1
	  		case 70:report[5]&=(~0x04);break;
	  		case 71:report[5]|=0x08;break;//2
	  		case 72:report[5]&=(~0x08);break;
	  		case 73:report[5]|=0x10;break;//3
	  		case 74:report[5]&=(~0x10);break;
	  		case 75:report[5]|=0x20;break;//4
	  		case 76:report[5]&=(~0x20);break;
	  		case 77:report[5]|=0x40;break;//5
	  		case 78:report[5]&=(~0x40);break;
	  		case 79:report[5]|=0x80;break;//6
	  		case 80:report[5]&=(~0x80);break;
	  		case 81:report[6]|=0x01;break;//7
	  		case 82:report[6]&=(~0x01);break;
	  		case 83:report[6]|=0x02;break;//8
	  		case 84:report[6]&=(~0x02);break;
	  		case 85:report[6]|=0x04;break;//9
	  		case 86:report[6]&=(~0x04);break;
	  		case 87:report[6]|=0x08;break;//0
	  		case 88:report[6]&=(~0x08);break;
	  		case 89:report[6]|=0x10;break;//enter
	  		case 90:report[6]&=(~0x10);break;
	  		case 91:report[6]|=0x20;break;//esc
	  		case 92:report[6]&=(~0x20);break;
	  		case 93:report[6]|=0x40;break;//backspace
	  		case 94:report[6]&=(~0x40);break;
	  		case 95:report[6]|=0x80;break;//tab
	  		case 96:report[6]&=(~0x80);break;
	  		case 97:report[7]|=0x01;break;//space
	  		case 98:report[7]&=(~0x01);break;
	  		case 99:report[7]|=0x02;break;//-和_
	  		case 100:report[7]&=(~0x02);break;
	  		case 101:report[7]|=0x04;break;//=和+
	  		case 102:report[7]&=(~0x04);break;
	  		case 103:report[7]|=0x08;break;//[和{
	  		case 104:report[7]&=(~0x08);break;
	  		case 105:report[7]|=0x10;break;//]和}
	  		case 106:report[7]&=(~0x10);break;
	  		case 107:report[7]|=0x20;break;//'\'和|
	  		case 108:report[7]&=(~0x20);break;
	  		case 109:report[7]|=0x40;break;//'\'和|
	  		case 110:report[7]&=(~0x40);break;
	  		case 111:report[7]|=0x80;break;//;和:
	  		case 112:report[7]&=(~0x80);break;
	  		case 113:report[8]|=0x01;break;//'和"
	  		case 114:report[8]&=(~0x01);break;
	  		case 115:report[8]|=0x02;break;//`和~
	  		case 116:report[8]&=(~0x02);break;
	  		case 117:report[8]|=0x04;break;//,和<
	  		case 118:report[8]&=(~0x04);break;
	  		case 119:report[8]|=0x08;break;//.和>
	  		case 120:report[8]&=(~0x08);break;
	  		case 121:report[8]|=0x10;break;///和?
	  		case 122:report[8]&=(~0x10);break;
	  		case 123:report[8]|=0x20;break;//大写锁定
	  		case 124:report[8]&=(~0x20);break;
	  		case 125:report[8]|=0x40;break;//F1
	  		case 126:report[8]&=(~0x40);break;
	  		case 127:report[8]|=0x80;break;//F2
	  		case 128:report[8]&=(~0x80);break;
	  		case 129:report[9]|=0x01;break;//F3
	  		case 130:report[9]&=(~0x01);break;
	  		case 131:report[9]|=0x02;break;//F4
	  		case 132:report[9]&=(~0x02);break;
	  		case 133:report[9]|=0x04;break;//F5
	  		case 134:report[9]&=(~0x04);break;
	  		case 135:report[9]|=0x08;break;//F6
	  		case 136:report[9]&=(~0x08);break;
	  		case 137:report[9]|=0x10;break;//F7
	  		case 138:report[9]&=(~0x10);break;
	  		case 139:report[9]|=0x20;break;//F8
	  		case 140:report[9]&=(~0x20);break;
	  		case 141:report[9]|=0x40;break;//F9
	  		case 142:report[9]&=(~0x40);break;
	  		case 143:report[9]|=0x80;break;//F10
	  		case 144:report[9]&=(~0x80);break;
	  		case 145:report[10]|=0x01;break;//F11
	  		case 146:report[10]&=(~0x01);break;
	  		case 147:report[10]|=0x02;break;//F12
	  		case 148:report[10]&=(~0x02);break;
	  		case 149:report[10]|=0x04;break;//PrtSc屏幕截图
	  		case 150:report[10]&=(~0x04);break;
	  		case 151:report[10]|=0x08;break;//ScroIILOCK
	  		case 152:report[10]&=(~0x08);break;
	  		case 153:report[10]|=0x10;break;//Pause
	  		case 154:report[10]&=(~0x10);break;
	  		case 155:report[10]|=0x20;break;//Insert
	  		case 156:report[10]&=(~0x20);break;
	  		case 157:report[10]|=0x40;break;//Home
	  		case 158:report[10]&=(~0x40);break;
	  		case 159:report[10]|=0x80;break;//Pgup
	  		case 160:report[10]&=(~0x80);break;
	  		case 161:report[11]|=0x01;break;//DEL
	  		case 162:report[11]&=(~0x01);break;
	  		case 163:report[11]|=0x02;break;//END
	  		case 164:report[11]&=(~0x02);break;
	  		case 165:report[11]|=0x04;break;//Pgdn
	  		case 166:report[11]&=(~0x04);break;
	  		case 167:report[11]|=0x08;break;//方向键右
	  		case 168:report[11]&=(~0x08);break;
	  		case 169:report[11]|=0x10;break;//方向键左
	  		case 170:report[11]&=(~0x10);break;
	  		case 171:report[11]|=0x20;break;//方向键下
	  		case 172:report[11]&=(~0x20);break;
	  		case 173:report[11]|=0x40;break;//方向键上
	  		case 174:report[11]&=(~0x40);break;
	  		case 175:report[11]|=0x80;break;//小键盘数字锁定
	  		case 176:report[11]&=(~0x80);break;
	  		case 177:report[12]|=0x01;break;//小键盘/
	  		case 178:report[12]&=(~0x01);break;
	  		case 179:report[12]|=0x02;break;//小键盘*
	  		case 180:report[12]&=(~0x02);break;
	  		case 181:report[12]|=0x04;break;//小键盘-
	  		case 182:report[12]&=(~0x04);break;
	  		case 183:report[12]|=0x08;break;//小键盘+
	  		case 184:report[12]&=(~0x08);break;
	  		case 185:report[12]|=0x10;break;//小键盘Enter回车
	  		case 186:report[12]&=(~0x10);break;
	  		case 187:report[12]|=0x20;break;//小键盘数字1
	  		case 188:report[12]&=(~0x20);break;
	  		case 189:report[12]|=0x40;break;//小键盘数字2
	  		case 190:report[12]&=(~0x40);break;
	  		case 191:report[12]|=0x80;break;//小键盘数字3
	  		case 192:report[12]&=(~0x80);break;
	  		case 193:report[13]|=0x01;break;//小键盘数字4
      		case 194:report[13]&=(~0x01);break;
      		case 195:report[13]|=0x02;break;//小键盘数字5
      		case 196:report[13]&=(~0x02);break;
      		case 197:report[13]|=0x04;break;//小键盘数字6
      		case 198:report[13]&=(~0x04);break;
      		case 199:report[13]|=0x08;break;//小键盘数字7
      		case 200:report[13]&=(~0x08);break;
      		case 201:report[13]|=0x10;break;//小键盘数字8
      		case 202:report[13]&=(~0x10);break;
      		case 203:report[13]|=0x20;break;//小键盘数字9
      		case 204:report[13]&=(~0x20);break;
      		case 205:report[13]|=0x40;break;//小键盘数字0
      		case 206:report[13]&=(~0x40);break;
      		case 207:report[13]|=0x80;break;//小键盘数字.
      	}
}
void HIDSend(int command)
{
	ifkey(command);
	USBD_CUSTOM_HID_SendReport(&hUsbDeviceFS, report,sizeof(report));
}

USBD_CUSTOM_HID_SendReport()函数使用了hUsbDeviceFS这个句柄,但是CuBeMX没有自动extern,需要我们手动extern,打开Project\USB_Device\App\usb_device.h,找到第66行,添加extern USBD_HandleTypeDef hUsbDeviceFS;
key_if.c中使用了uint8_t类型的变量,需要include main.h,不然会报错。

之后调用HIDSend(int command)函数即可向电脑发送数据。例如HIDSend(17);表示a按下,HIDSend(18);表示a释放。

参考文章

读懂 HID 报告描述符 (实现全键无冲的键盘 HID 报告描述符)
使用CubeMX 生成 USB从机 Custom HID 工程(STM32F407ZE )

作者:wdjjsn

物联沃分享整理
物联沃-IOTWORD物联网 » 使用Custom HID制作全键无冲的键盘(STM32)

发表回复