CH9828芯片使用

CH9328 是一款串口转 HID 键盘芯片,在电脑上识别为标准的 USB HID 类键盘设备。

应用框图:

通过单片机上通过串口与CH9328键盘芯片进行控制,然后把接收的数据转换成USB HID设备数据发给电脑。

HID设备默认会被电脑识别成键盘。

主要特点:

  • 支持全速USB、兼容USB2.0
  • 支持配置波特率
  • 工作模式切换,适应不同使用场景的键盘
  • 配合单片机,实现键盘功能。

封装:

引脚号 引脚名称 类型 引脚说明
15 VCC 电源 正电源输入端,需要外接 0.1uF 电源退耦电容
8 GND 电源 公共接地端,直接连到 USB 总线的地线
5 V3 电源 在 3.3V 电源电压时连接 VCC 输入外部电源,在 5V 电源电压时外接容量为 0.1uF 退耦电容
6 UD+ USB 信号 直接连到 USB 总线的 D+数据线
7 UD- USB 信号 直接连到 USB 总线的 D-数据线
1 RSTI 输入 外部复位输入,低电平有效,内置上拉电阻
2 TXD 输出 无效引脚,实际未使用
3 RXD 输入 串行数据输入,内置上拉电阻
4 T_LED 输出 串口发送状态输出,高电平有效
9 XI 输入 原 V1.4 以下版本晶体振荡的输入端,需要外接晶体及振荡电容;V1.4 及以后版本已采用内置晶振,该引脚悬空
10 XO 输出 原 V1.4 以下版本晶体振荡的反相输出端,需要外接晶体及振荡电容;V1.4 及以后版本已采用内置晶振,该引脚悬空
14 IO3 双向 用户可自行配置引脚及工作模式配置引脚默认上电后芯片自动检测该引脚电平状态,用于配置芯片的工作模式,USB 配置完成后,可根据需要配置成普通 IO 口使用
13 IO4 双向 用户可自行配置引脚及工作模式配置引脚默认上电后芯片自动检测该引脚电平状态,用于配置芯片的工作模式,USB 配置完成后,可根据需要配置成普通 IO 口使用
12 IO1 双向 用户可自行配置引脚及速度配置引脚默认上电后芯片自动检测该引脚电平状态,用于配置芯片的上传速度(上电时该引脚为高电平则速度配置为普通模式,上电时该引脚为低电平则速度配置为高速模式,高速模式速度大概为普通模式的 1 倍),USB 配置完成后,可根据需要配置成普通 IO 口使用
11 IO2 双向 用户可自行配置引脚及工作模式配置引脚默认上电后芯片自动检测该引脚电平状态,用于配置芯片的工作模式,USB 配置完成后,可根据需要配置成普通 IO 口使用
16 ACT# 输出 USB 配置完成状态输出,低电平有效

工作模式:

场景示例:

8位单片机使用矩阵方式扫描实现USB HID键盘

代码实现:

以下是一个8位单片机实现USB HID键盘的代码,使用的模式3,能够实现全键盘按键事件发送。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// scan.c
// 矩阵键盘扫描表
static const UINT8 __CODE g_u8_scan_table[] =
{
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

// P NUL num1 NUL CTRL NUL Q NUL } - → U T num3 insert J
0x50, 0x00, 0x61, 0x00, 0x11, 0x33, 0x51, 0x00, 0xDD, 0x6D, 0x27, 0x55, 0x54, 0x63, 0x2D, 0x4A, // 0
// , NUL 14NUL NUL NUL NUL Z alt ← pause ↓ B V 131NUL 文档 space
0xDE, 0x00, 0x00, 0x00, 0x00, 0x35, 0x5A, 0x12, 0x25, 0x13, 0x28, 0x42, 0x56, 0x00, 0x5D, 0x20, // 1
// > NUL A NUL NUL NUL X alt NUL ?/ ; N F C 132NUL <,
0xBE, 0x00, 0x41, 0x00, 0x00, 0x3C, 0x58, 0x12, 0x00, 0xBF, 0xBA, 0x4E, 0x46, 0x43, 0x00, 0xBC, // 2
// PgDn NUL CAPS RSHIFT NUL NUL S NUL ↑ PgUp \ H G D 133NUL M
0x22, 0x00, 0x14, 0x10, 0x00, 0x36, 0x53, 0x00, 0x26, 0x21, 0xDC, 0x48, 0x47, 0x44, 0x00, 0x4D, // 3
// L NUL TAB NUL NUL WIN W NUL 45NUL { Enter Y R E 42NUL K
0x4C, 0x00, 0x09, 0x00, 0x00, 0x5B, 0x57, 0x00, 0x00, 0xDB, 0x0D, 0x59, 0x52, 0x45, 0x00, 0x4B, // 4
// O NUL ~ NUL CTRL NUL num2 NUL + num0 Back num7 num5 num4 56NUL I
0x4F, 0x00, 0xC0, 0x00, 0x11, 0x00, 0x62, 0x00, 0x6B, 0x60, 0x08, 0x67, 0x65, 0x64, 0x00, 0x49, // 5
// num9 NUL F1 LSHIFT NUL NUL F2 NUL home F12 NUL num8 num6 F4 NUL F8
0x69, 0x00, 0x70, 0x10, 0x00, 0x00, 0x71, 0x00, 0x24, 0x7B, 0x00, 0x68, 0x66, 0x73, 0x00, 0x77, // 6
// F10 FN ESC NUL NUL NUL F3 NUL prt F11 end F7 F6 F5 NUL F9
0x79, 0x8E, 0x1B, 0x00, 0x00, 0x00, 0x72, 0x00, 0x2A, 0x7A, 0x23, 0x76, 0x75, 0x74, 0x00, 0x78, // 7
// ok ok ok ok ok ok ok ok ok ok ok ok ok ok ok NUL
};


// uart.c
// modifier bit 定义(参考 HID 协议)
#define MOD_LEFT_CTRL 0x01
#define MOD_LEFT_SHIFT 0x02
#define MOD_LEFT_ALT 0x04
#define MOD_LEFT_GUI 0x08
#define MOD_RIGHT_CTRL 0x10
#define MOD_RIGHT_SHIFT 0x20
#define MOD_RIGHT_ALT 0x40
#define MOD_RIGHT_GUI 0x80
#define MOD_RIGHT_FN 0x80
UINT8 g_modifier;
typedef struct {
UINT8 scan_code; // 你的扫描键值(从 g_u8_scan_table[] 得到)
UINT8 modifier; // 对应的 modifier 值
UINT8 hid_code; // 对应的 HID keycode 值
} scan_hid_map_t;

// 完整映射表(节选常见键 + 修饰键 + 数字 + 功能键 + 符号)
static const scan_hid_map_t scan_to_hid_map[] = {
// 字母 A-Z
{ 0x41, 0x00, 0x04 }, { 0x42, 0x00, 0x05 }, { 0x43, 0x00, 0x06 }, { 0x44, 0x00, 0x07 },
{ 0x45, 0x00, 0x08 }, { 0x46, 0x00, 0x09 }, { 0x47, 0x00, 0x0A }, { 0x48, 0x00, 0x0B },
{ 0x49, 0x00, 0x0C }, { 0x4A, 0x00, 0x0D }, { 0x4B, 0x00, 0x0E }, { 0x4C, 0x00, 0x0F },
{ 0x4D, 0x00, 0x10 }, { 0x4E, 0x00, 0x11 }, { 0x4F, 0x00, 0x12 }, { 0x50, 0x00, 0x13 },
{ 0x51, 0x00, 0x14 }, { 0x52, 0x00, 0x15 }, { 0x53, 0x00, 0x16 }, { 0x54, 0x00, 0x17 },
{ 0x55, 0x00, 0x18 }, { 0x56, 0x00, 0x19 }, { 0x57, 0x00, 0x1A }, { 0x58, 0x00, 0x1B },
{ 0x59, 0x00, 0x1C }, { 0x5A, 0x00, 0x1D },

// 字母区数字 1-0
{ 0x61, 0x00, 0x1E }, { 0x62, 0x00, 0x1F }, { 0x63, 0x00, 0x20 }, { 0x64, 0x00, 0x21 },
{ 0x65, 0x00, 0x22 }, { 0x66, 0x00, 0x23 }, { 0x67, 0x00, 0x24 }, { 0x68, 0x00, 0x25 },
{ 0x69, 0x00, 0x26 }, { 0x60, 0x00, 0x27 },

// 控制与符号键
{ 0x0D, 0x00, 0x28 }, // Enter
{ 0x1B, 0x00, 0x29 }, // Esc
{ 0x08, 0x00, 0x2A }, // Backspace
{ 0x09, 0x00, 0x2B }, // Tab
{ 0x20, 0x00, 0x2C }, // Space
{ 0x6D, 0x00, 0x2D }, // -
{ 0x6B, 0x00, 0x2E }, // +
{ 0xDB, 0x00, 0x2F }, // {
{ 0xDD, 0x00, 0x30 }, // }
{ 0xDC, 0x00, 0x31 }, // \ (反斜杠)
{ 0xBA, 0x00, 0x33 }, // ;
{ 0xDE, 0x00, 0x34 }, // '
{ 0xC0, 0x00, 0x35 }, // `
{ 0xBC, 0x00, 0x36 }, // ,
{ 0xBE, 0x00, 0x37 }, // .
{ 0xBF, 0x00, 0x38 }, // /

// 功能键 F1-F12
{ 0x70, 0x00, 0x3A }, { 0x71, 0x00, 0x3B }, { 0x72, 0x00, 0x3C },
{ 0x73, 0x00, 0x3D }, { 0x74, 0x00, 0x3E }, { 0x75, 0x00, 0x3F },
{ 0x76, 0x00, 0x40 }, { 0x77, 0x00, 0x41 }, { 0x78, 0x00, 0x42 },
{ 0x79, 0x00, 0x43 }, { 0x7A, 0x00, 0x44 }, { 0x7B, 0x00, 0x45 },

// 系统控制与方向键
{ 0x2A, 0x00, 0x46 }, // PrintScreen
// { 0x47, 0x00, 0x47 }, // Scroll Lock
{ 0x13, 0x00, 0x48 }, // Pause
{ 0x2D, 0x00, 0x49 }, // Insert
{ 0x24, 0x00, 0x4A }, // Home
{ 0x21, 0x00, 0x4B }, // Page Up
{ 0x4C, 0x00, 0x4C }, // Delete
{ 0x23, 0x00, 0x4D }, // End
{ 0x22, 0x00, 0x4E }, // Page Down
{ 0x27, 0x00, 0x4F }, // →
{ 0x25, 0x00, 0x50 }, // ←
{ 0x28, 0x00, 0x51 }, // ↓
{ 0x26, 0x00, 0x52 }, // ↑

// 数字小键盘区
// { 0x53, 0x00, 0x53 }, // Num Lock
// { 0x54, 0x00, 0x54 }, // /
// { 0x55, 0x00, 0x55 }, // *
// { 0x56, 0x00, 0x56 }, // -
// { 0x57, 0x00, 0x57 }, // +
// { 0x58, 0x00, 0x58 }, // Enter
// { 0x59, 0x00, 0x59 }, { 0x5A, 0x00, 0x5A }, { 0x5B, 0x00, 0x5B },
// { 0x5C, 0x00, 0x5C }, { 0x5D, 0x00, 0x5D }, { 0x5E, 0x00, 0x5E },
// { 0x5F, 0x00, 0x5F }, { 0x60, 0x00, 0x60 }, { 0x61, 0x00, 0x61 },
// { 0x62, 0x00, 0x62 }, { 0x63, 0x00, 0x63 }, // 小数点
{ 0x5D, 0x00, 0x65 }, // 文档键
{ 0x14, 0x00, 0x39 }, // CAPS
// 修饰键
{ 0x11, MOD_LEFT_CTRL, 0x00 }, // 左Ctrl
{ 0x10, MOD_LEFT_SHIFT, 0x00 }, // 左Shift
{ 0x12, MOD_LEFT_ALT, 0x00 }, // 左Alt
{ 0x5B, MOD_LEFT_GUI, 0x00 }, // 左Win
{ 0x11, MOD_RIGHT_CTRL, 0x00 }, // 右Ctrl
{ 0x10, MOD_RIGHT_SHIFT, 0x00 }, // 右Shift
{ 0x12, MOD_RIGHT_ALT, 0x00 }, // 右Alt
{ 0x5B, MOD_RIGHT_GUI, 0x00 }, // 右Win
{ 0x8E, MOD_RIGHT_FN, 0x00 }, // FN
};



/**
* @brief 发送按键事件
* @param modifier 修饰符(见MOD_*定义)
* @param key_code 键码(见KeyCode枚举)
* @param is_press 是否按下(0=释放,1=按下)
*/
void send_key_event(UINT8 modifier, UINT8 key_code, UINT8 is_press) {
UINT8 i;
UINT8 byte_to_send;

for (i = 0; i < 8; i++) {
// 默认所有数据为 0
byte_to_send = 0;

// 如果是按键按下事件,设置第 0 和第 2 字节
if (is_press) {
if (i == 0) {
byte_to_send = modifier;
} else if (i == 2) {
byte_to_send = key_code;
}
}

core_uart1_print_char(byte_to_send);
}

}

// 键码映射函数
// 主转换函数
void scan_code_to_hid(UINT8 scan_code, UINT8 *modifier, UINT8 *hid_code) {
int i = 0;
*modifier = 0;
*hid_code = 0;

for (i = 0; i < sizeof(scan_to_hid_map) / sizeof(scan_to_hid_map[0]); ++i) {
if (scan_to_hid_map[i].scan_code == scan_code) {
*modifier = scan_to_hid_map[i].modifier;
*hid_code = scan_to_hid_map[i].hid_code;
return;
}
}

// 未匹配
*modifier = 0;
*hid_code = 0;
}

/**
* @brief 发送单个按键(带修饰符)
* @param event
* @param ascii ascii字符
*/
VOID send_key_to_uart(UINT8 event, UINT8 ascii) {
UINT8 key_code = 0;
UINT8 modifier = 0;
scan_code_to_hid(ascii, &modifier, &key_code);
LOG1("Send Key: ", key_code);
// LOG1("Modifier: ", modifier);
// LOG1("Event: ", event);



if(event== MAKE_EVENT) {

if(key_code == 0) {
g_modifier = modifier;
}

send_key_event(g_modifier, key_code, 1);
} else if(event == BREAK_EVENT) {
if(key_code == 0)
{
g_modifier = 0;
}
// 如果是释放事件,发送修饰符和按键码
send_key_event(0, 0, 0);
} else if(event == REPEAT_EVENT) {

if(key_code == 0) {
g_modifier = modifier;
return;
}
else
{
// 如果是按下事件,发送修饰符和按键码
send_key_event(g_modifier, key_code, 1);
}
}

}

问题

待续……