概述
本文是使用HAL库的USB驱动
因为官方cubeMX生成的hal库做组合设备时过于繁琐所以这里使用某大神的插件,可以集成在cubeMX里自动生成组合设备
有小bug会覆盖生成文件里自己写的内容,所以生成一次后注意保存
插件安装
下载地址 https://github.com/alambe94/I-CUBE-USBD-Composite/releases/tag/V01.00.03
下载pack文件
打开cubeMX

点击这个 之后选择下载的文件 安装

出现这个即为安装成功

生成代码
打开USB 设为设备模式,打开中断

注意设置时钟树
USB需要较为精确的时钟 建议用外部晶振

选择库文件

目前打勾的这俩必选
其余根据需要选择

根据需要选择,注意要在上步开启的库文件中选

在初始化后加入这个函数
MX_USB_DEVICE_Init();

可以去
usbd_desc.c
里设置VID/PID 某些名称等参数,不同设备有些许不同

虚拟串口(CDC)
概述
串口名是由PC的驱动来决定的,没法在STM32端设置
设置
打开这个
可以在这里设置虚拟的串口数量
注意一个串口要占用2个IN端点和一个OUT端点

也可以设置
AL94.I-CUBE-USBD-COMPOSITE_conf.h
文件中的_USBD_CDC_ACM_COUNT
设置虚拟的CDC串口数量
发送
开始发送
类型 | 名称 | 功能 |
---|---|---|
uint8_t |
ch |
通道 |
uint8_t * |
Buf |
缓冲区地址 |
uint16_t |
Len |
发送数量 |
uint8_t |
输出 | 已经发送的数量 |
c
uint8_t CDC_Transmit(uint8_t ch, uint8_t *Buf, uint16_t Len)
发送完成
类型 | 名称 | 功能 |
---|---|---|
uint8_t |
cdc_ch |
通道 |
uint8_t * |
Buf |
缓冲区地址 |
uint32_t |
Len |
发送数量 |
uint8_t |
epnum |
端点号 |
uint8_t |
错误码 |
c
int8_t CDC_TransmitCplt(uint8_t cdc_ch, uint8_t *Buf, uint32_t *Len, uint8_t epnum)
接收
类型 | 名称 | 功能 |
---|---|---|
uint8_t |
cdc_ch |
通道 |
uint8_t * |
Buf |
缓冲区地址 |
uint32_t |
Len |
发送数量 |
int8_t |
错误码 |
接收到数据会自动调用这函数
c
int8_t CDC_Receive(uint8_t cdc_ch, uint8_t *Buf, uint32_t *Len)
在这个函数里调用这俩句,来接收下个数据包
c
USBD_CDC_SetRxBuffer(cdc_ch, &hUsbDevice, &Buf[0]);
USBD_CDC_ReceivePacket(cdc_ch, &hUsbDevice);
控制函数
类型 | 名称 | 功能 |
---|---|---|
uint8_t |
cdc_ch |
通道 |
uint8_t |
cmd |
命令类型 |
uint8_t * |
pbuf |
命令缓冲区 |
uint16_t |
length |
长度 |
int8_t |
错误码 |
c
int8_t CDC_Control(uint8_t cdc_ch, uint8_t cmd, uint8_t *pbuf, uint16_t length)
在
cmd
为CDC_SET_LINE_CODING
时收到来自主机的命令
具体内容生成的函数中有注释
人体工学设备(HID)
概述
全部使用自定义HID设备
根据不同设备设置描述符即可
HID间的复合直接复制就行
eg:鼠标+键盘 直接把鼠标的描述符和键盘的描述符写到一起即可
建立工程


设置
在
usbd_customhid.h
中
名称 | 功能 |
---|---|
CUSTOM_HID_STR_DESC |
HID描述 |
CUSTOM_HID_EPIN_SIZE |
输入缓冲大小(一般设为64) |
CUSTOM_HID_EPOUT_SIZE |
输出缓冲大小(一般设为64) |
USBD_CUSTOMHID_OUTREPORT_BUF_SIZE |
HID缓冲区(一般设为64) |
CUSTOM_HID_FS_BINTERVAL |
包间隔时间 |
在
usbd_custom_hid_if.c
中
CUSTOM_HID_ReportDesc
设置HID描述符USBD_CUSTOM_HID_REPORT_DESC_SIZE
同时也要设置配置符大小

APIs
发送数据
类型 | 名称 | 功能 |
---|---|---|
USBD_HandleTypeDef * |
pdev |
USB句柄 |
uint8_t * |
report |
缓冲区 |
uint16_t |
len |
数据长度 |
uint8_t |
错误码 |
c
uint8_t USBD_CUSTOM_HID_SendReport(USBD_HandleTypeDef *pdev,uint8_t *report, uint16_t len);
接收数据回调
usbd_custom_hid_if.c
类型 | 名称 | 功能 |
---|---|---|
uint8_t |
event_idx |
|
uint8_t |
state |
|
uint8_t |
错误码 |
c
int8_t CUSTOM_HID_OutEvent(uint8_t event_idx, uint8_t state)
在内部调用以获取数据
c
USBD_CUSTOM_HID_HandleTypeDef *hhid = (USBD_CUSTOM_HID_HandleTypeDef *)HZ_DAP_USB_Handle.pClassData_HID_Custom;
// hhid->Report_buf;
USBD_CUSTOM_HID_ReceivePacket(&HZ_DAP_USB_Handle);
鼠标
鼠标的配置描述符
c
0x05, 0x01,
0x09, 0x02,
0xa1, 0x01,
0x85, 0x02, // 报告ID (2)
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, 0x03,
0x95, 0x03,
0x75, 0x08,
0x05, 0x01,
0x09, 0x30,
0x09, 0x31,
0x09, 0x38,
0x15, 0x81,
0x25, 0x7f,
0x81, 0x06,
0xc0,
0xc0
需要发送的命令 含义
位置 | 功能 |
---|---|
Bit0 | 报告ID |
Bit1[0] | 左键(0未按1按下) |
Bit1[1] | 右键(0未按1按下) |
Bit1[2] | 中键(0未按1按下) |
Bit2 | x轴(正右负左 -127~127) |
Bit3 | y轴(正下负上 -127~127) |
Bit4 | 滚动(正上负下 -127~127) |
这个函数是自己封装的
c
extern USBD_HandleTypeDef hUsbDevice;
/**
* @brief 控制鼠标
* @param key_l 左键(仅bit0 0未按1按下)
* @param key_r 右键(仅bit0 0未按1按下)
* @param key_m 中键(仅bit0 0未按1按下)
* @param x x轴(正右负左 -127~127)
* @param y y轴(正下负上 -127~127)
* @param ec 滚动(正上负下 -127~127)
* @author HZ12138
* @date 2025-03-28 20:17:39
*/
void HZ_Mouse_set(uint8_t key_l, uint8_t key_r, uint8_t key_m, int8_t x, int8_t y, int8_t ec)
{
uint8_t buf[5];
key_l &= 0x01;
key_r &= 0x01;
key_m &= 0x01;
buf[0] = 0x02; // 报告ID 鼠标是0x02
buf[1] = (key_m << 2) | (key_r << 1) | (key_l << 0);
buf[2] = (uint8_t)x;
buf[3] = (uint8_t)y;
buf[4] = (uint8_t)ec;
USBD_CUSTOM_HID_SendReport(&hUsbDevice, buf, 5);
}
键盘
描述符
0x85后面跟的是报告ID 0保留
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
0x09, 0x06, // USAGE (Keyboard)
0xa1, 0x01, // COLLECTION (Application)
0x85, 0x01, // 报告ID (1)
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, 0x65, // LOGICAL_MAXIMUM (101)
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)
0xc0 // END_COLLECTION
数据内容
Bit2~8 发送的是按下的键号 如果不按下写0即可
位置 | 功能 |
---|---|
Bit0 | 报告ID |
Bit1[0] | 左CTRL(0未按1按下) |
Bit1[1] | 左SHIFT(0未按1按下) |
Bit1[2] | 左ALT(0未按1按下) |
Bit1[3] | 左GUI(0未按1按下) |
Bit1[4] | 右CTRL(0未按1按下) |
Bit1[5] | 右SHIFT(0未按1按下) |
Bit1[6] | 右ALT(0未按1按下) |
Bit1[7] | 右GUI(0未按1按下) |
Bit2 | 保留(0x00) |
Bit3 | 按键1 |
Bit4 | 按键2 |
Bit5 | 按键3 |
Bit6 | 按键4 |
Bit7 | 按键5 |
Bit8 | 按键6 |
封装的发送按键函数和单个按键按下函数
c
extern USBD_HandleTypeDef hUsbDevice;
/**
* @brief 发送按键
* @param keys 按键
* @param key_num 数量最大6个
* @param ctrl_l 如名(0未按1按下)
* @param shift_l 如名(0未按1按下)
* @param alt_l 如名(0未按1按下)
* @param gui_l 如名(0未按1按下)
* @param ctrl_r 如名(0未按1按下)
* @param shift_r 如名(0未按1按下)
* @param alt_r 如名(0未按1按下)
* @param gui_r 如名(0未按1按下)
* @author HZ12138
* @date 2025-03-28 22:52:09
*/
void HZ_KeyBoard_set(uint8_t *keys, uint8_t key_num,
uint8_t ctrl_l, uint8_t shift_l, uint8_t alt_l, uint8_t gui_l,
uint8_t ctrl_r, uint8_t shift_r, uint8_t alt_r, uint8_t gui_r)
{
uint8_t buf[9];
ctrl_l &= 0x01;
shift_l &= 0x01;
alt_l &= 0x01;
gui_l &= 0x01;
ctrl_r &= 0x01;
shift_r &= 0x01;
alt_r &= 0x01;
gui_r &= 0x01;
buf[0] = 0x01;
buf[1] = (gui_r << 7) | (alt_r << 6) | (shift_r << 5) | (ctrl_r << 4) |
(gui_l << 3) | (alt_l << 2) | (shift_l << 1) | (ctrl_l << 0);
buf[2] = 0x00;
for (int i = 0; i < key_num; i++)
buf[3 + i] = keys[i];
USBD_CUSTOM_HID_SendReport(&hUsbDevice, buf, 9);
}
/**
* @brief 发送单个按键
* @param key 按键值
* @author HZ12138
* @date 2025-03-28 22:52:12
*/
void HZ_KeyBoard_one_key(uint8_t key)
{
uint8_t temp[6];
temp[0] = key;
HZ_KeyBoard_set(temp, 6, 0, 0, 0, 0, 0, 0, 0, 0);
}
键码对应表,使用HID码
按键名称 | HID码 | 虚拟键码 |
---|---|---|
ESC | 41 [0X29] | 27 [0x1B] |
F1 | 58 [0X3a] | 112 [0x70] |
F2 | 59 [0X3b] | 113 [0x71] |
F3 | 60 [0X3c] | 114 [0x72] |
F4 | 61 [0X3d] | 115 [0x73] |
F5 | 62 [0X3e] | 116 [0x74] |
F6 | 63 [0X3f] | 117 [0x75] |
F7 | 64 [0X40] | 118 [0x76] |
F8 | 65 [0X41] | 119 [0x77] |
F9 | 66 [0X42] | 120 [0x78] |
F10 | 67 [0X43] | 121 [0x79] |
F11 | 68 [0X44] | 122 [0x7A] |
F12 | 69 [0X45] | 123 [0x7B] |
Esc | 41 [0X29] | 27 [0x1B] |
Back (回退) | 42 [0X2a] | 8 [0x08] |
Tab | 43 [0X2b] | 9 [0x09] |
CapLck (大小写) | 57 [0X39] | 20 [0x14] |
Enter (回车) | 40 [0X28] | 13 [0x0D] |
Space (空格) | 44 [0X2c] | 32 [0x20] |
Scroll | 71 [0X47] | 145 [0x91] |
Pause(暂停) | 72 [0X48] | 19 [0x13] |
Insert (插入) | 73 [0X49] | 45 [0x2D] |
PrintScr (截屏) | 70 [0X46] | 44 [0x2C] |
Delete (删除) | 76 [0X4c] | 46 [0x2E] |
Home (首页) | 74 [0X4a] | 36 [0x24] |
End (结尾) | 77 [0X4d] | 35 [0x23] |
PageUp (上一页) | 75 [0X4b] | 33 [0x21] |
PageDn (下一页) | 78 [0X4e] | 34 [0x22] |
Left (左) | 80 [0X50] | 37 [0x25] |
Up (上) | 82 [0X52] | 38 [0x26] |
Right (右) | 79 [0X4f] | 39 [0x27] |
Down (下) | 81 [0X51] | 40 [0x28] |
Num0 (小键盘) | 98 [0X62] | 96 [0x60] |
Num1 (小键盘) | 89 [0X59] | 97 [0x61] |
Num2 (小键盘) | 90 [0X5a] | 98 [0x62] |
Num3 (小键盘) | 91 [0X5b] | 99 [0x63] |
Num4 (小键盘) | 92 [0X5c] | 100 [0x64] |
Num5 (小键盘) | 93 [0X5d] | 101 [0x65] |
Num6 (小键盘) | 94 [0X5e] | 102 [0x66] |
Num7 (小键盘) | 95 [0X5f] | 103 [0x67] |
Num8 (小键盘) | 96 [0X60] | 104 [0x68] |
Num9 (小键盘) | 97 [0X61] | 105 [0x69] |
NumAdd (加号) | 87 [0X57] | 107 [0x6B] |
NumSub (减号) | 86 [0X56] | 109 [0x6D] |
NumMult (乘号) | 85 [0X55] | 106 [0x6A] |
NumDiv (除号) | 84 [0X54] | 111 [0x6F] |
NumDecim (点) | 99 [0X63] | 110 [0x6E] |
NumLock (数字锁定键) | 83 [0X53] | 144 [0x90] |
Ctrl | 1 [0X01] | 17 [0x11] |
LCtrl (左CTR) | 1 [0X01] | 162 [0xA2] |
RCtrl | 16 [0X10] | 163 [0xA3] |
Shift | 2 [0X02] | 16 [0x10] |
LShift | 2 [0X02] | 160 [0xA0] |
RShift | 32 [0X20] | 161 [0xA1] |
Alt | 4 [0X04] | 18 [0x12] |
LAlt | 4 [0X04] | 164 [0xA4] |
RAlt | 64 [0X40] | 165 [0xA5] |
WIN | 8 [0X08] | 91 [0x5B] |
LWIN | 8 [0X08] | 91 [0x5B] |
RWIN | 128 [0X80] | 92 [0x5C] |
A | 4 [0X04] | 65 [0x41] |
B | 5 [0X05] | 66 [0x42] |
C | 6 [0X06] | 67 [0x43] |
D | 7 [0X07] | 68 [0x44] |
E | 8 [0X08] | 69 [0x45] |
F | 9 [0X09] | 70 [0x46] |
G | 10 [0X0a] | 71 [0x47] |
H | 11 [0X0b] | 72 [0x48] |
I | 12 [0X0c] | 73 [0x49] |
J | 13 [0X0d] | 74 [0x4A] |
K | 14 [0X0e] | 75 [0x4B] |
L | 15 [0X0f] | 76 [0x4C] |
M | 16 [0X10] | 77 [0x4D] |
N | 17 [0X11] | 78 [0x4E] |
O | 18 [0X12] | 79 [0x4F] |
P | 19 [0X13] | 80 [0x50] |
Q | 20 [0X14] | 81 [0x51] |
R | 21 [0X15] | 82 [0x52] |
S | 22 [0X16] | 83 [0x53] |
T | 23 [0X17] | 84 [0x54] |
U | 24 [0X18] | 85 [0x55] |
V | 25 [0X19] | 86 [0x56] |
W | 26 [0X1a] | 87 [0x57] |
X | 27 [0X1b] | 88 [0x58] |
Y | 28 [0X1c] | 89 [0x59] |
Z | 29 [0X1d] | 90 [0x5A] |
0 | 39 [0X27] | 48 [0x30] |
1 | 30 [0X1e] | 49 [0x31] |
2 | 31 [0X1f] | 50 [0x32] |
3 | 32 [0X20] | 51 [0x33] |
4 | 33 [0X21] | 52 [0x34] |
5 | 34 [0X22] | 53 [0x35] |
6 | 35 [0X23] | 54 [0x36] |
7 | 36 [0X24] | 55 [0x37] |
8 | 37 [0X25] | 56 [0x38] |
9 | 38 [0X26] | 57 [0x39] |
大容量存储(MSC)
建立工程


设置
usbd_msc.h
中的MSC_MEDIA_PACKET
要设为扇区(sector)大小w25Qxx为4096 SD卡为512
STORAGE_LUN_NBR
为虚拟磁盘卷数量 一般把一个设备设为一个卷 设置这个可以虚拟出多个磁盘
usbd_storage_if.c
的STORAGE_BLK_NBR
为最小操作单元数量(一般写扇区数量)
usbd_storage_if.c
的STORAGE_BLK_SIZ
为最小操作单元大小 (一般写扇区大小) 单位(Byte)这俩相乘即可得到总大小 单位(Byte)
注意这俩的blk所指的块与FLASH的不同,也是我写成最小操作单元的原因
可以修改这个最后三项来更改显示名称
usbd_storage_if.c
的STORAGE_Inquirydata

APIs
存储读取(必写)
描述 | 名称 | 功能 |
---|---|---|
uint8_t |
lun |
卷标 |
uint8_t * |
buf |
缓冲区 |
uint32_t |
blk_addr |
最小操作单元起始地址(*STORAGE_BLK_SIZ 后得到Byte起始地址 ) |
uint16_t |
blk_len |
最小操作单元的数量(*STORAGE_BLK_SIZ 后得到Byte数量 ) |
int8_t |
输出 | 错误码 |
在
usbd_storage_if.c
中需要根据自己内容填写
c
int8_t STORAGE_Read(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
存储写入(必写)
描述 | 名称 | 功能 |
---|---|---|
uint8_t |
lun |
卷标 |
uint8_t * |
buf |
缓冲区 |
uint32_t |
blk_addr |
最小操作单元起始地址(*STORAGE_BLK_SIZ 后得到Byte起始地址 ) |
uint16_t |
blk_len |
最小操作单元的数量(*STORAGE_BLK_SIZ 后得到Byte数量 ) |
int8_t |
输出 | 错误码 |
在
usbd_storage_if.c
中需要根据自己内容填写
c
int8_t STORAGE_Write(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)