编程技术分享

分享编程知识,探讨技术创新

0%

简介:使用CH552G制作的3键小键盘,配有改键程序,支持普通键、功能键、多媒体键、组合键、字符串模式。

好的,作为一名高级嵌入式软件开发工程师,很高兴能为您详细解析这个基于CH552G的3键小键盘项目,并阐述其完整的嵌入式系统开发流程和代码架构。正如您所描述的,这个项目旨在打造一个可靠、高效、可扩展的小型键盘平台,涵盖了从需求分析到最终维护升级的各个阶段。
关注微信公众号,提前获取相关推文

项目简介与目标

这个3键小键盘项目,核心目标是利用国产芯片CH552G,设计并实现一个功能丰富、可自定义的迷你键盘。它不仅仅是一个简单的输入设备,更是一个展示完整嵌入式系统开发流程的实践案例。

主要功能特点:

  • 3键设计: 简洁高效,专注于常用快捷操作。
  • CH552G主控: 低成本、高性能、易于开发的国产USB单片机。
  • 多种按键模式:
    • 普通键: 标准键盘按键功能。
    • 功能键: 例如 Ctrl、Shift、Alt 等修饰键。
    • 多媒体键: 音量控制、播放/暂停等。
    • 组合键: 例如 Ctrl+C、Ctrl+V 等快捷键组合。
    • 字符串模式: 一键输入预设字符串。
  • 改键程序: 用户可自定义按键功能,灵活配置。
  • USB HID设备: 模拟标准USB键盘,即插即用,无需额外驱动。

项目开发流程概述

一个完整的嵌入式系统开发流程通常包含以下几个关键阶段:

  1. 需求分析与定义 (Requirements Analysis & Definition)
  2. 系统设计 (System Design)
  3. 硬件设计与选型 (Hardware Design & Selection)
  4. 软件设计与编码 (Software Design & Coding)
  5. 集成与测试 (Integration & Testing)
  6. 验证与确认 (Verification & Validation)
  7. 维护与升级 (Maintenance & Upgrade)

接下来,我们将深入探讨每个阶段,并重点阐述最适合的代码设计架构以及具体的C代码实现。

1. 需求分析与定义 (Requirements Analysis & Definition)

在这个阶段,我们需要明确项目的具体需求,包括功能性需求和非功能性需求。

功能性需求:

  • 按键功能定义: 用户能够自定义每个按键的功能,支持普通键、功能键、多媒体键、组合键、字符串模式。
  • 改键程序: 提供用户友好的改键配置界面(可以是PC软件或板载配置方式)。
  • USB通信: 通过USB接口模拟标准HID键盘,与主机系统进行数据交换。
  • 按键扫描: 可靠地检测按键按下和释放动作。
  • 按键去抖: 消除按键机械抖动带来的误触发。
  • 掉电配置保存: 用户的按键配置信息需要能够掉电保存,下次上电自动加载。

非功能性需求:

  • 可靠性: 系统运行稳定可靠,按键响应准确无误。
  • 高效性: 按键响应速度快,系统资源占用低。
  • 可扩展性: 代码结构清晰,易于扩展和添加新功能。
  • 易维护性: 代码注释清晰,结构模块化,方便后期维护和升级。
  • 低功耗: 尽可能降低功耗,延长使用寿命(如果适用)。
  • 成本效益: 在满足功能需求的前提下,尽量控制成本。

2. 系统设计 (System Design)

系统设计阶段是整个开发流程的核心,它决定了系统的整体架构和实现方式。对于这个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
+-----------------------+  <-- 应用层 (Application Layer)
| 按键功能逻辑 |
| 配置管理 |
+-----------------------+
|
+-----------------------+ <-- 键盘事件处理层 (Key Event Handling Layer)
| 按键事件解析 |
| 组合键/字符串处理 |
+-----------------------+
|
+-----------------------+ <-- 按键扫描与驱动层 (Key Scanning & Driver Layer)
| GPIO驱动 |
| 按键扫描算法 |
| 按键去抖动 |
+-----------------------+
|
+-----------------------+ <-- USB HID驱动层 (USB HID Driver Layer)
| USB协议栈 |
| HID键盘描述符 |
| USB数据传输 |
+-----------------------+
|
+-----------------------+ <-- 硬件抽象层 (HAL - Hardware Abstraction Layer)
| CH552G MCU |
| GPIO配置 |
| 时钟配置 |
| 中断配置 |
+-----------------------+

各层功能描述:

  • 硬件抽象层 (HAL): 这是最底层,直接与CH552G硬件打交道。它提供了一系列API,用于配置和控制MCU的硬件资源,例如GPIO端口的初始化、时钟配置、中断配置等。HAL层的目的是将硬件细节抽象出来,使上层软件可以独立于具体的硬件平台。
  • USB HID驱动层: 负责实现USB HID协议,模拟USB键盘设备。它包括USB协议栈的实现、HID键盘描述符的定义、以及USB数据的接收和发送。这一层确保了小键盘能够被主机系统识别为标准的USB键盘,并进行数据通信。
  • 按键扫描与驱动层: 负责驱动GPIO端口,扫描按键状态,并进行按键去抖处理。它将原始的按键输入信号转换为稳定的按键事件,供上层使用。这一层是硬件和软件之间的桥梁,确保了按键输入的可靠性和准确性。
  • 键盘事件处理层: 接收来自按键扫描层的按键事件,并进行解析和处理。它负责识别普通键、功能键、组合键和字符串模式,并将按键事件转换为相应的键盘动作。例如,将 “Ctrl + C” 的组合键事件转换为复制操作。
  • 应用层: 这是最高层,负责实现具体的按键功能逻辑和配置管理。它接收来自键盘事件处理层的键盘动作,并根据用户的配置,生成最终的USB键盘报告,发送给主机系统。同时,应用层还负责管理用户的按键配置信息,并提供配置接口(例如通过串口或USB)。

3. 硬件设计与选型 (Hardware Design & Selection)

硬件设计主要围绕CH552G MCU展开,并根据项目需求选择合适的外部器件。

核心器件:

  • 主控芯片:CH552G - 选择CH552G的原因是其内置USB控制器,简化了USB键盘的实现,且成本较低,易于开发。
  • 按键: 3个独立按键,根据手感和寿命需求选择合适的按键开关。
  • LED指示灯(可选): 用于指示键盘状态,例如配置模式、连接状态等。
  • 晶振电路: 为CH552G提供稳定的时钟源。
  • 复位电路: 保证系统可靠复位。
  • USB接口: Micro USB或Type-C接口,用于连接主机和供电。
  • 电源管理: 简单的5V转3.3V稳压电路,为CH552G供电。
  • EEPROM或Flash(可选): 用于存储用户配置信息,例如按键映射表。CH552G本身带有Flash,可以考虑使用内部Flash模拟EEPROM。

硬件连接示意图(简化):

1
2
3
4
5
6
7
8
9
+-----------------+      +--------------+      +--------------+
| 按键 1 |------| GPIO Pin X |------| CH552G |----USB---> 主机
+-----------------+ +--------------+ | |
+-----------------+ +--------------+ | |
| 按键 2 |------| GPIO Pin Y |------| |
+-----------------+ +--------------+ | |
+-----------------+ +--------------+ | |
| 按键 3 |------| GPIO Pin Z |------| |
+-----------------+ +--------------+ +--------------+

4. 软件设计与编码 (Software Design & Coding)

软件设计阶段是根据系统架构,详细设计每个模块的功能和接口,并进行C代码编写。

4.1 硬件抽象层 (HAL) 代码实现 (hal.h & hal.c)

hal.h (头文件,定义HAL层接口)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef HAL_H
#define HAL_H

#include "ch552g.h"

// GPIO配置
void HAL_GPIO_Init(uint8_t pin, uint8_t mode); // 配置GPIO引脚模式 (输入/输出/上拉/下拉)
void HAL_GPIO_WritePin(uint8_t pin, uint8_t value); // 写GPIO引脚 (高/低电平)
uint8_t HAL_GPIO_ReadPin(uint8_t pin); // 读GPIO引脚电平

// 时钟配置 (根据需要添加,CH552G默认时钟配置可能已足够)
// void HAL_Clock_Init(uint32_t frequency);

// 中断配置 (根据需要添加,例如按键中断,USB中断)
// void HAL_NVIC_EnableIRQ(uint8_t irq_channel);
// void HAL_NVIC_DisableIRQ(uint8_t irq_channel);

#endif // HAL_H

hal.c (源文件,实现HAL层接口)

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
#include "hal.h"

// GPIO配置
void HAL_GPIO_Init(uint8_t pin, uint8_t mode) {
if (pin < 8) { // P1口
if (mode == GPIO_MODE_INPUT) {
P1_DIR &= ~(1 << pin); // 设置为输入
} else if (mode == GPIO_MODE_OUTPUT) {
P1_DIR |= (1 << pin); // 设置为输出
} // ... 其他模式,例如上拉、下拉
} else if (pin >= 8 && pin < 16) { // P3口 (假设按键接在P1和P3)
uint8_t p3_pin = pin - 8;
if (mode == GPIO_MODE_INPUT) {
P3_DIR &= ~(1 << p3_pin);
} else if (mode == GPIO_MODE_OUTPUT) {
P3_DIR |= (1 << p3_pin);
}
}
// ... 其他端口配置
}

void HAL_GPIO_WritePin(uint8_t pin, uint8_t value) {
if (pin < 8) { // P1口
if (value) {
P1 |= (1 << pin); // 输出高电平
} else {
P1 &= ~(1 << pin); // 输出低电平
}
} else if (pin >= 8 && pin < 16) { // P3口
uint8_t p3_pin = pin - 8;
if (value) {
P3 |= (1 << p3_pin);
} else {
P3 &= ~(1 << p3_pin);
}
}
// ... 其他端口写操作
}

uint8_t HAL_GPIO_ReadPin(uint8_t pin) {
if (pin < 8) { // P1口
return (P1 & (1 << pin)) ? 1 : 0; // 读取P1引脚电平
} else if (pin >= 8 && pin < 16) { // P3口
uint8_t p3_pin = pin - 8;
return (P3 & (1 << p3_pin)) ? 1 : 0;
}
return 0; // 默认返回0
}

// ... 其他HAL层函数实现,例如时钟配置、中断配置等 (根据项目需要添加)

4.2 按键扫描与驱动层代码实现 (keypad_driver.h & keypad_driver.c)

keypad_driver.h

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
#ifndef KEYPAD_DRIVER_H
#define KEYPAD_DRIVER_H

#include "hal.h"
#include "stdint.h"

// 按键定义
#define KEY_PIN_1 P1_0 // 示例:按键1连接到P1.0
#define KEY_PIN_2 P1_1 // 示例:按键2连接到P1.1
#define KEY_PIN_3 P1_2 // 示例:按键3连接到P1.2

typedef enum {
KEY_STATE_RELEASED,
KEY_STATE_PRESSED,
KEY_STATE_LONG_PRESSED // 长按状态 (可选)
} KeyState;

typedef enum {
KEY_EVENT_NONE,
KEY_EVENT_PRESS_SHORT,
KEY_EVENT_PRESS_LONG, // 长按事件 (可选)
KEY_EVENT_RELEASE
} KeyEvent;

typedef struct {
KeyState state;
KeyEvent event;
uint32_t press_time; // 按键按下时间 (用于长按检测)
} KeyInfo;

// 初始化按键驱动
void Keypad_Init(void);
// 按键扫描任务 (需要在主循环或定时器中断中周期性调用)
void Keypad_ScanTask(void);
// 获取按键事件
KeyEvent Keypad_GetKeyEvent(uint8_t key_index);
// 清除按键事件标志
void Keypad_ClearKeyEvent(uint8_t key_index);

#endif // KEYPAD_DRIVER_H

keypad_driver.c

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
#include "keypad_driver.h"
#include "delay.h" // 假设使用了延时函数,CH552G可以使用定时器实现更精确的延时

#define KEY_DEBOUNCE_DELAY_MS 20 // 按键去抖延时 (20ms)
#define KEY_LONG_PRESS_TIME_MS 500 // 长按时间阈值 (500ms)

KeyInfo key_info[3]; // 存储3个按键的状态信息

// 初始化按键驱动
void Keypad_Init(void) {
// 初始化按键GPIO为输入模式,并配置上拉电阻 (如果硬件需要)
HAL_GPIO_Init(KEY_PIN_1, GPIO_MODE_INPUT_PULLUP);
HAL_GPIO_Init(KEY_PIN_2, GPIO_MODE_INPUT_PULLUP);
HAL_GPIO_Init(KEY_PIN_3, GPIO_MODE_INPUT_PULLUP);

// 初始化按键状态
for (uint8_t i = 0; i < 3; i++) {
key_info[i].state = KEY_STATE_RELEASED;
key_info[i].event = KEY_EVENT_NONE;
key_info[i].press_time = 0;
}
}

// 按键扫描任务
void Keypad_ScanTask(void) {
static uint32_t last_scan_time = 0;
uint32_t current_time = GetTickCount(); // 假设有获取系统Tick计数的函数 GetTickCount()

if (current_time - last_scan_time < KEY_DEBOUNCE_DELAY_MS) { // 扫描间隔,避免频繁扫描
return;
}
last_scan_time = current_time;

// 扫描按键1
KeyState current_key1_state = (HAL_GPIO_ReadPin(KEY_PIN_1) == 0) ? KEY_STATE_PRESSED : KEY_STATE_RELEASED; // 低电平有效 (根据硬件连接决定)
KeyProcess(0, current_key1_state); // 处理按键1状态

// 扫描按键2
KeyState current_key2_state = (HAL_GPIO_ReadPin(KEY_PIN_2) == 0) ? KEY_STATE_PRESSED : KEY_STATE_RELEASED;
KeyProcess(1, current_key2_state); // 处理按键2状态

// 扫描按键3
KeyState current_key3_state = (HAL_GPIO_ReadPin(KEY_PIN_3) == 0) ? KEY_STATE_PRESSED : KEY_STATE_RELEASED;
KeyProcess(2, current_key3_state); // 处理按键3状态
}

// 按键状态处理函数
void KeyProcess(uint8_t key_index, KeyState current_state) {
if (current_state != key_info[key_index].state) { // 按键状态发生变化
if (current_state == KEY_STATE_PRESSED) { // 按键按下
delay_ms(KEY_DEBOUNCE_DELAY_MS); // 去抖延时
if ((HAL_GPIO_ReadPin((key_index == 0) ? KEY_PIN_1 : ((key_index == 1) ? KEY_PIN_2 : KEY_PIN_3)) == 0)) { // 再次确认按键状态
key_info[key_index].state = KEY_STATE_PRESSED;
key_info[key_index].event = KEY_EVENT_PRESS_SHORT; // 初始认为是短按
key_info[key_index].press_time = GetTickCount(); // 记录按下时间
}
} else { // 按键释放
delay_ms(KEY_DEBOUNCE_DELAY_MS); // 去抖延时
if ((HAL_GPIO_ReadPin((key_index == 0) ? KEY_PIN_1 : ((key_index == 1) ? KEY_PIN_2 : KEY_PIN_3)) != 0)) { // 再次确认按键状态
key_info[key_index].state = KEY_STATE_RELEASED;
key_info[key_index].event = KEY_EVENT_RELEASE;

// 长按检测 (可选)
if (key_info[key_index].event != KEY_EVENT_PRESS_LONG && (GetTickCount() - key_info[key_index].press_time) >= KEY_LONG_PRESS_TIME_MS) {
key_info[key_index].event = KEY_EVENT_PRESS_LONG;
}
}
}
} else if (current_state == KEY_STATE_PRESSED) { // 按键持续按下状态,检测是否长按 (可选)
if (key_info[key_index].event == KEY_EVENT_PRESS_SHORT && (GetTickCount() - key_info[key_index].press_time) >= KEY_LONG_PRESS_TIME_MS) {
key_info[key_index].event = KEY_EVENT_PRESS_LONG;
}
}
}

// 获取按键事件
KeyEvent Keypad_GetKeyEvent(uint8_t key_index) {
return key_info[key_index].event;
}

// 清除按键事件标志
void Keypad_ClearKeyEvent(uint8_t key_index) {
key_info[key_index].event = KEY_EVENT_NONE;
}

4.3 键盘事件处理层代码实现 (key_event_handler.h & key_event_handler.c)

key_event_handler.h

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
#ifndef KEY_EVENT_HANDLER_H
#define KEY_EVENT_HANDLER_H

#include "keypad_driver.h"
#include "usb_hid_driver.h" // 假设USB HID驱动层头文件

// 定义按键功能类型
typedef enum {
KEY_FUNC_NORMAL,
KEY_FUNC_FUNCTION,
KEY_FUNC_MEDIA,
KEY_FUNC_COMBO,
KEY_FUNC_STRING
} KeyFunctionType;

// 定义按键功能配置结构体 (可扩展,根据实际功能需求)
typedef struct {
KeyFunctionType type;
uint8_t keycode1; // 普通键/功能键/多媒体键 键值
uint8_t keycode2; // 组合键的第二个键值 (例如 Ctrl+C 中的 'C')
char* string_value; // 字符串模式的字符串值
} KeyFunctionConfig;

// 初始化键盘事件处理层
void KeyEvent_Handler_Init(void);
// 处理按键事件,生成USB键盘报告
void KeyEvent_Process(void);
// 加载按键配置 (从EEPROM/Flash 或默认配置)
void KeyEvent_LoadConfig(void);
// 保存按键配置 (到EEPROM/Flash)
void KeyEvent_SaveConfig(void);
// 获取当前按键配置 (用于配置程序)
KeyFunctionConfig* KeyEvent_GetConfig(uint8_t key_index);
// 设置按键配置 (用于配置程序)
void KeyEvent_SetConfig(uint8_t key_index, KeyFunctionConfig config);

#endif // KEY_EVENT_HANDLER_H

key_event_handler.c

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
#include "key_event_handler.h"
#include "usb_hid_driver.h" // 假设USB HID驱动层头文件
#include "eeprom_driver.h" // 假设EEPROM驱动层头文件 (如果使用EEPROM存储配置)
#include "string.h" // 字符串操作

// 默认按键配置 (示例)
KeyFunctionConfig default_key_config[3] = {
{KEY_FUNC_NORMAL, KEYCODE_A, 0, NULL}, // 按键1: 'A'
{KEY_FUNC_FUNCTION, KEYCODE_CTRL_LEFT, 0, NULL}, // 按键2: 左Ctrl
{KEY_FUNC_MEDIA, KEYCODE_MEDIA_VOLUME_UP, 0, NULL} // 按键3: 音量+
};

KeyFunctionConfig current_key_config[3]; // 当前按键配置

// 初始化键盘事件处理层
void KeyEvent_Handler_Init(void) {
KeyEvent_LoadConfig(); // 加载配置
}

// 处理按键事件
void KeyEvent_Process(void) {
for (uint8_t i = 0; i < 3; i++) {
KeyEvent event = Keypad_GetKeyEvent(i);
if (event != KEY_EVENT_NONE) {
Keypad_ClearKeyEvent(i); // 清除事件标志

if (event == KEY_EVENT_PRESS_SHORT) { // 短按事件
HandleKeyEvent(i, KEY_EVENT_PRESS_SHORT);
} else if (event == KEY_EVENT_RELEASE) { // 释放事件
HandleKeyEvent(i, KEY_EVENT_RELEASE);
} // ... 处理长按事件 (如果支持)
}
}
}

// 处理单个按键事件,生成USB键盘报告
void HandleKeyEvent(uint8_t key_index, KeyEvent event) {
KeyFunctionConfig* config = &current_key_config[key_index];

if (event == KEY_EVENT_PRESS_SHORT) { // 按键按下
switch (config->type) {
case KEY_FUNC_NORMAL:
USB_HID_Keyboard_SendReport(config->keycode1, 0); // 发送普通键按下报告
break;
case KEY_FUNC_FUNCTION:
USB_HID_Keyboard_SendReport(0, config->keycode1); // 发送功能键按下报告 (修饰键)
break;
case KEY_FUNC_MEDIA:
USB_HID_Keyboard_SendMediaReport(config->keycode1); // 发送多媒体键报告
break;
case KEY_FUNC_COMBO:
// 发送组合键按下报告 (例如 Ctrl+C)
USB_HID_Keyboard_SendReport(config->keycode2, config->keycode1); // 先发送修饰键,再发送普通键
break;
case KEY_FUNC_STRING:
// 发送字符串 (需要分多次发送,模拟键盘输入)
USB_HID_Keyboard_SendString(config->string_value);
break;
default:
break;
}
} else if (event == KEY_EVENT_RELEASE) { // 按键释放
switch (config->type) {
case KEY_FUNC_NORMAL:
USB_HID_Keyboard_SendReport(0, 0); // 发送普通键释放报告 (所有键释放)
break;
case KEY_FUNC_FUNCTION:
USB_HID_Keyboard_SendReport(0, 0); // 发送功能键释放报告 (所有键释放)
break;
case KEY_FUNC_MEDIA:
USB_HID_Keyboard_SendMediaReport(0); // 发送多媒体键释放报告
break;
case KEY_FUNC_COMBO:
USB_HID_Keyboard_SendReport(0, 0); // 组合键释放 (所有键释放)
break;
case KEY_FUNC_STRING:
// 字符串模式不需要释放事件
break;
default:
break;
}
}
}

// 加载按键配置 (从EEPROM/Flash 或默认配置)
void KeyEvent_LoadConfig(void) {
// 尝试从EEPROM加载配置 (假设EEPROM_ReadConfig 函数存在)
if (EEPROM_ReadConfig(current_key_config, sizeof(current_key_config)) == EEPROM_OK) {
// 加载成功,使用EEPROM配置
} else {
// 加载失败,使用默认配置
memcpy(current_key_config, default_key_config, sizeof(default_key_config));
}
}

// 保存按键配置 (到EEPROM/Flash)
void KeyEvent_SaveConfig(void) {
EEPROM_WriteConfig(current_key_config, sizeof(current_key_config)); // 假设EEPROM_WriteConfig 函数存在
}

// 获取当前按键配置
KeyFunctionConfig* KeyEvent_GetConfig(uint8_t key_index) {
return &current_key_config[key_index];
}

// 设置按键配置
void KeyEvent_SetConfig(uint8_t key_index, KeyFunctionConfig config) {
current_key_config[key_index] = config;
}

4.4 USB HID驱动层代码实现 (usb_hid_driver.h & usb_hid_driver.c)

这部分代码会比较复杂,涉及到USB协议栈的实现和HID键盘描述符的定义。由于篇幅限制,这里只提供关键代码框架和思路。

usb_hid_driver.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#ifndef USB_HID_DRIVER_H
#define USB_HID_DRIVER_H

#include "ch552g.h"
#include "stdint.h"

// USB HID键盘描述符 (HID Report Descriptor) - 定义键盘报告格式
extern const uint8_t USB_HID_KeyboardDescriptor[];

// 初始化USB HID驱动
void USB_HID_Init(void);
// USB处理任务 (需要在主循环中周期性调用)
void USB_HID_Task(void);
// 发送键盘报告 (普通键/功能键)
void USB_HID_Keyboard_SendReport(uint8_t keycode, uint8_t modifier);
// 发送多媒体键报告
void USB_HID_Keyboard_SendMediaReport(uint16_t media_keycode);
// 发送字符串 (模拟键盘输入)
void USB_HID_Keyboard_SendString(const char* str);

// USB事件处理回调函数 (例如 USB连接/断开事件)
void USB_EventHandler(uint8_t event);

#endif // USB_HID_DRIVER_H

usb_hid_driver.c (部分代码框架,需要根据CH552G USB库和HID协议规范进行详细实现)

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
#include "usb_hid_driver.h"
#include "usb.h" // CH552G USB库头文件 (假设)

// USB HID键盘描述符 (HID Report Descriptor)
const uint8_t USB_HID_KeyboardDescriptor[] = {
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) ; Modifier byte
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x08, // REPORT_SIZE (8)
0x81, 0x03, // INPUT (Cnst,Var,Abs) ; Reserved byte
0x95, 0x05, // REPORT_COUNT (5)
0x75, 0x01, // REPORT_SIZE (1)
0x05, 0x08, // USAGE_PAGE (LEDs)
0x19, 0x01, // USAGE_MINIMUM (LED Num Lock)
0x29, 0x05, // USAGE_MAXIMUM (LED Kana)
0x91, 0x02, // OUTPUT (Data,Var,Abs) ; LED report
0x95, 0x01, // REPORT_COUNT (1)
0x75, 0x03, // REPORT_SIZE (3)
0x91, 0x03, // OUTPUT (Cnst,Var,Abs) ; LED report padding
0x95, 0x06, // REPORT_COUNT (6)
0x75, 0x08, // REPORT_SIZE (8)
0x05, 0x07, // USAGE_PAGE (Keyboard)
0x19, 0x00, // USAGE_MINIMUM (Reserved (no event indicated))
0x29, 0x65, // USAGE_MAXIMUM (Keyboard Application)
0x00, 0x00, // LOGICAL_MINIMUM (0)
0x46, 0x65, 0x00, // LOGICAL_MAXIMUM (101)
0x81, 0x00, // INPUT (Data,Ary,Abs) ; Key arrays (6 bytes)
0xC0 // END_COLLECTION
};

// USB报告缓冲区 (Keyboard Report)
typedef struct {
uint8_t modifiers; // 修饰键 (Ctrl, Shift, Alt, Win)
uint8_t reserved; // 保留字节
uint8_t keycodes[6]; // 按键码 (最多6个同时按下)
} KeyboardReport;

KeyboardReport usb_report;

// 初始化USB HID驱动
void USB_HID_Init(void) {
// 初始化CH552G USB控制器 (参考CH552G USB库例程)
USB_Init(); // 假设USB_Init 函数存在于CH552G USB库中

// 注册USB事件处理回调函数
USB_SetEventHandler(USB_EventHandler); // 假设USB_SetEventHandler 函数存在
}

// USB处理任务 (需要在主循环中周期性调用)
void USB_HID_Task(void) {
USB_Poll(); // 轮询USB事件 (参考CH552G USB库例程)
}

// 发送键盘报告 (普通键/功能键)
void USB_HID_Keyboard_SendReport(uint8_t keycode, uint8_t modifier) {
memset(&usb_report, 0, sizeof(usb_report)); // 清空报告
usb_report.modifiers = modifier; // 设置修饰键
if (keycode) {
usb_report.keycodes[0] = keycode; // 设置按键码 (只发送一个按键)
}
USB_SendReport((uint8_t*)&usb_report, sizeof(usb_report)); // 发送USB报告 (假设USB_SendReport 函数存在)
}

// 发送多媒体键报告 (需要定义多媒体键码,参考HID Usage Tables)
void USB_HID_Keyboard_SendMediaReport(uint16_t media_keycode) {
// ... 实现发送多媒体键报告的逻辑 (可能需要使用不同的HID报告格式)
// 这部分实现会比较复杂,需要查阅HID Usage Tables文档,并根据CH552G USB库进行适配
}

// 发送字符串 (模拟键盘输入)
void USB_HID_Keyboard_SendString(const char* str) {
// ... 实现发送字符串的逻辑,需要将字符串拆分成单个字符,并模拟按键按下和释放的过程
// 这部分实现也比较复杂,需要考虑字符编码、Shift键处理等
}

// USB事件处理回调函数
void USB_EventHandler(uint8_t event) {
switch (event) {
case USB_EVENT_CONNECTED:
// USB连接事件处理
break;
case USB_EVENT_DISCONNECTED:
// USB断开事件处理
break;
// ... 其他USB事件处理
default:
break;
}
}

4.5 应用层代码实现 (main.c)

main.c

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
#include "ch552g.h"
#include "hal.h"
#include "keypad_driver.h"
#include "key_event_handler.h"
#include "usb_hid_driver.h"
#include "delay.h" // 假设使用了延时函数

void main() {
SystemClockInit(); // 初始化系统时钟 (CH552G库函数)
Delay_Init(); // 初始化延时函数

HAL_GPIO_Init(P1_7, GPIO_MODE_OUTPUT); // 示例:LED指示灯连接到P1.7,初始化为输出

Keypad_Init(); // 初始化按键驱动
KeyEvent_Handler_Init(); // 初始化键盘事件处理层
USB_HID_Init(); // 初始化USB HID驱动

while (1) {
Keypad_ScanTask(); // 按键扫描任务
KeyEvent_Process(); // 键盘事件处理
USB_HID_Task(); // USB处理任务
// ... 其他应用逻辑,例如 LED 指示灯控制等

HAL_GPIO_WritePin(P1_7, !HAL_GPIO_ReadPin(P1_7)); // 示例:LED 闪烁,指示系统运行
delay_ms(500); // 延时 500ms
}
}

5. 集成与测试 (Integration & Testing)

在完成各个模块的代码编写后,需要进行集成测试,验证模块之间的协同工作是否正常。测试可以分为单元测试和系统测试。

  • 单元测试: 针对每个模块进行独立测试,例如测试按键扫描模块的去抖动效果、USB HID驱动层能否正确发送键盘报告等。
  • 系统测试: 将所有模块集成在一起,进行整体功能测试,验证整个小键盘系统是否能够按照需求正常工作。测试内容包括:
    • 按键功能测试:验证普通键、功能键、多媒体键、组合键、字符串模式是否工作正常。
    • 改键程序测试:验证改键程序能否正确配置按键功能,配置信息能否正确保存和加载。
    • USB兼容性测试:在不同的操作系统和主机上测试USB连接和键盘功能是否正常。
    • 可靠性测试:进行长时间运行测试,验证系统的稳定性。

测试方法:

  • 代码走查: 仔细检查代码逻辑,查找潜在的错误。
  • 仿真器调试: 使用CH552G的仿真器进行代码调试,观察变量值和程序运行流程。
  • 硬件测试: 将代码烧录到实际的CH552G小键盘硬件上进行测试。
  • 自动化测试(可选): 编写自动化测试脚本,提高测试效率和覆盖率。

6. 验证与确认 (Verification & Validation)

验证 (Verification) 是指“我们是否正确地构建了产品?”,即确保系统实现符合设计规范。
确认 (Validation) 是指“我们是否构建了正确的产品?”,即确保系统满足用户需求。

在这个阶段,需要从用户的角度出发,验证小键盘是否满足用户的实际使用需求。例如,用户是否能够方便地自定义按键功能?小键盘是否能够提高工作效率?用户体验是否良好?

验证方法:

  • 用户测试: 邀请用户试用小键盘,收集用户反馈。
  • 需求回顾: 再次回顾需求文档,确认系统是否满足所有需求。
  • 性能测试: 测试按键响应速度、功耗等性能指标是否满足要求。

7. 维护与升级 (Maintenance & Upgrade)

软件开发是一个持续迭代的过程,在产品发布后,还需要进行维护和升级。

维护内容:

  • Bug修复: 修复用户反馈的bug。
  • 性能优化: 优化代码,提高系统性能。
  • 安全更新: 修复安全漏洞(如果存在)。

升级内容:

  • 新功能添加: 根据用户需求或市场变化,添加新的功能。
  • 硬件升级适配: 如果硬件平台升级,需要适配新的硬件平台。
  • 软件架构优化: 随着项目发展,可能需要对软件架构进行优化,提高可扩展性和可维护性。

维护与升级策略:

  • 版本控制: 使用Git等版本控制工具管理代码,方便代码维护和版本迭代。
  • 模块化设计: 采用模块化设计,方便模块的独立升级和替换。
  • 文档维护: 及时更新代码注释和文档,方便维护人员理解代码。
  • 用户反馈渠道: 建立用户反馈渠道,及时收集用户意见和bug报告。
  • 固件升级机制: 设计固件升级机制,方便用户在线升级固件。

总结

这个基于CH552G的3键小键盘项目,从需求分析到维护升级,展示了一个完整的嵌入式系统开发流程。通过采用分层架构,将系统划分为硬件抽象层、USB HID驱动层、按键扫描与驱动层、键盘事件处理层和应用层,实现了模块化、可扩展和易维护的代码结构。

提供的C代码示例,涵盖了各个层次的关键功能实现,包括GPIO驱动、按键扫描、去抖动、USB HID协议栈、键盘报告生成、按键配置管理等。当然,完整的USB HID驱动层代码实现会更加复杂,需要参考CH552G USB库和HID协议规范进行详细设计和编码。

在实际开发过程中,还需要根据具体的硬件连接和功能需求,对代码进行调整和完善。同时,充分的测试和验证是确保系统可靠性和稳定性的关键环节。

希望这份详细的解答能够帮助您理解嵌入式系统开发流程和代码架构设计,并为您的实际项目开发提供参考。这个3键小键盘项目虽然简单,但它蕴含了嵌入式系统开发的精髓,通过实践这个项目,您可以深入理解嵌入式软件开发的各个方面,为未来更复杂的嵌入式系统开发打下坚实的基础。

欢迎关注我的其它发布渠道