使用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