编程技术分享

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

0%

简介: 智能键盘是一把我为自己使用需求设计的 多功能、模块化 机械键盘。

好的,作为一名高级嵌入式软件开发工程师,我将深入探讨智能键盘嵌入式系统的软件架构设计,并提供详细的C代码实现。这个项目旨在构建一个可靠、高效、可扩展的智能键盘平台,充分体现从需求分析到最终实现的完整嵌入式系统开发流程。
关注微信公众号,提前获取相关推文

项目背景与需求分析

项目名称: 智能模块化机械键盘

项目目标: 设计并实现一款多功能、高度可定制化的智能机械键盘,满足用户个性化需求,并提供卓越的使用体验。

核心需求:

  1. 模块化设计:

    • 硬件模块化: 支持可插拔的功能模块,如旋钮模块、屏幕模块、扩展按键模块等,用户可以根据需要自由组合。
    • 软件模块化: 软件架构应支持模块化功能扩展,方便添加新的功能模块和特性。
  2. 自定义功能:

    • 按键自定义: 用户可以自定义每个按键的功能,包括标准按键、宏命令、组合键等。
    • 宏编程: 支持复杂的宏命令编程,允许用户录制和编辑宏,实现一键执行复杂操作。
    • 多层键位: 支持多个功能层,通过组合键或切换键快速切换不同的键位配置。
  3. 智能特性:

    • RGB背光控制: 支持全键RGB背光,并提供丰富的灯效模式,用户可自定义颜色和动画效果。
    • 屏幕显示: (可选模块)支持外接屏幕显示,用于显示系统信息、自定义动画、快捷功能等。
    • 音频反馈: (可选模块)支持音频输出,用于按键音效、系统提示音等。
    • 无线连接: (可选)支持蓝牙或2.4GHz无线连接,方便移动设备使用。
  4. 可靠性与高效性:

    • 稳定运行: 系统必须稳定可靠,保证长时间无故障运行。
    • 快速响应: 按键响应速度要快,延迟低,提供流畅的输入体验。
    • 低功耗: 对于无线版本,需要考虑低功耗设计,延长电池续航时间。
  5. 可扩展性与维护升级:

    • 易于扩展: 软件架构应易于扩展新的功能模块和特性。
    • 固件升级: 支持固件在线升级,方便后续功能更新和bug修复。
    • 代码可维护性: 代码结构清晰,模块化设计,方便维护和修改。

系统架构设计

为了满足上述需求,我们选择采用分层模块化架构来设计智能键盘的嵌入式软件系统。这种架构具有良好的可扩展性、可维护性和可靠性,非常适合复杂的嵌入式系统。

架构图:

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
+---------------------+  用户界面层 (可选,如配置工具)
| |
+---------------------+
^
| 配置数据接口
v
+---------------------+ 应用服务层
| 配置管理模块 |
| 按键处理模块 |
| 宏命令引擎 |
| 背光控制模块 |
| 显示驱动模块 |
| 通信管理模块 |
| ... | (其他功能模块)
+---------------------+
^
| 抽象接口
v
+---------------------+ 硬件抽象层 (HAL)
| GPIO 驱动 |
| SPI 驱动 |
| I2C 驱动 |
| USB 驱动 |
| 蓝牙驱动 |
| 显示屏驱动 |
| LED 驱动 |
| 音频驱动 |
| ... | (其他硬件驱动)
+---------------------+
^
| 硬件接口
v
+---------------------+ 硬件层
| 微控制器 (MCU) |
| 键盘矩阵 |
| RGB LED |
| 显示屏 |
| 旋钮 |
| 扩展模块 |
| ... | (其他硬件组件)
+---------------------+

分层架构详解:

  1. 硬件层 (Hardware Layer):

    • 这是系统的最底层,包括微控制器 (MCU)、键盘矩阵、RGB LED、显示屏、旋钮、扩展模块等所有硬件组件。
    • MCU 是系统的核心,负责执行软件代码,控制和协调各个硬件组件。
  2. 硬件抽象层 (HAL - Hardware Abstraction Layer):

    • HAL 层位于硬件层之上,为上层软件提供统一的硬件访问接口。
    • HAL 层封装了底层硬件的细节,使得上层应用代码无需直接操作具体的硬件寄存器,提高了代码的可移植性和可维护性。
    • HAL 层包含各种硬件驱动,如 GPIO 驱动、SPI 驱动、I2C 驱动、USB 驱动、蓝牙驱动、显示屏驱动、LED 驱动、音频驱动等。
  3. 应用服务层 (Application Service Layer):

    • 应用服务层构建在 HAL 层之上,实现智能键盘的核心功能。
    • 这一层包含多个功能模块,每个模块负责一个特定的功能,例如:
      • 配置管理模块: 负责加载、保存和管理键盘的配置数据,包括按键映射、宏定义、背光设置等。
      • 按键处理模块: 负责扫描键盘矩阵,检测按键事件,并根据配置数据解析按键功能。
      • 宏命令引擎: 负责解析和执行宏命令,实现一键执行复杂操作。
      • 背光控制模块: 负责控制 RGB 背光,实现各种灯效模式和自定义颜色。
      • 显示驱动模块: 负责驱动显示屏,显示系统信息、自定义动画等。
      • 通信管理模块: 负责处理 USB 和蓝牙通信,与主机进行数据交互。
      • … (其他功能模块): 可以根据需要添加更多功能模块,例如音频控制模块、传感器模块等。
  4. 用户界面层 (User Interface Layer - 可选):

    • 用户界面层是可选的,可以提供一个图形界面或命令行界面,方便用户配置键盘功能。
    • 用户界面层可以通过 USB 或蓝牙与键盘通信,发送配置命令和接收键盘数据。
    • 例如,可以开发一个 PC 端的配置工具,用户可以通过该工具自定义按键映射、宏命令、背光效果等。

模块化设计策略:

  • 功能模块化: 将键盘的功能分解为独立的模块,每个模块负责一个特定的功能。模块之间通过定义良好的接口进行通信,降低模块之间的耦合度。
  • 接口标准化: 定义清晰的模块接口,包括输入参数、输出参数和返回值,方便模块之间的交互和替换。
  • 配置驱动: 许多功能模块的行为可以通过配置数据来驱动,例如按键映射、宏命令、背光模式等。将配置数据与模块代码分离,提高灵活性和可定制性。
  • 动态加载 (可选): 对于更高级的系统,可以考虑动态加载功能模块,实现更灵活的模块化扩展。

代码实现 (C语言)

以下是智能键盘嵌入式系统核心模块的 C 代码实现示例。由于代码量庞大,这里只提供关键模块的框架和核心代码片段,并进行详细的注释说明。完整的代码实现将超过 3000 行,需要包含所有模块的完整实现以及详细的错误处理、边界条件处理等。

1. hal_gpio.h (HAL GPIO 驱动头文件)

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

#include <stdint.h>
#include <stdbool.h>

// 定义 GPIO 端口和引脚
typedef enum {
GPIO_PORT_A,
GPIO_PORT_B,
GPIO_PORT_C,
// ... 其他端口
GPIO_PORT_MAX
} gpio_port_t;

typedef uint8_t gpio_pin_t;

// 定义 GPIO 方向
typedef enum {
GPIO_DIRECTION_INPUT,
GPIO_DIRECTION_OUTPUT
} gpio_direction_t;

// 定义 GPIO 电平
typedef enum {
GPIO_LEVEL_LOW,
GPIO_LEVEL_HIGH
} gpio_level_t;

// 初始化 GPIO 引脚
void hal_gpio_init(gpio_port_t port, gpio_pin_t pin, gpio_direction_t direction);

// 设置 GPIO 引脚方向
void hal_gpio_set_direction(gpio_port_t port, gpio_pin_t pin, gpio_direction_t direction);

// 设置 GPIO 引脚电平 (仅输出模式有效)
void hal_gpio_set_level(gpio_port_t port, gpio_pin_t pin, gpio_level_t level);

// 读取 GPIO 引脚电平 (仅输入模式有效)
gpio_level_t hal_gpio_get_level(gpio_port_t port, gpio_pin_t pin);

#endif // HAL_GPIO_H

2. hal_gpio.c (HAL GPIO 驱动实现文件 - 示例,需要根据具体 MCU 平台实现)

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
#include "hal_gpio.h"
// 假设使用 STM32 平台,需要包含 STM32 的头文件
#include "stm32fxxx_hal.h" // 替换为实际使用的 STM32 头文件

void hal_gpio_init(gpio_port_t port, gpio_pin_t pin, gpio_direction_t direction) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_TypeDef *GPIOx;

// 根据端口选择 GPIO 组
switch (port) {
case GPIO_PORT_A:
GPIOx = GPIOA;
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
break;
case GPIO_PORT_B:
GPIOx = GPIOB;
__HAL_RCC_GPIOB_CLK_ENABLE(); // 使能 GPIOB 时钟
break;
case GPIO_PORT_C:
GPIOx = GPIOC;
__HAL_RCC_GPIOC_CLK_ENABLE(); // 使能 GPIOC 时钟
break;
// ... 其他端口
default:
return; // 端口无效
}

GPIO_InitStruct.Pin = (1 << pin); // 设置引脚
if (direction == GPIO_DIRECTION_OUTPUT) {
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
} else {
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉输入 (可根据需要选择)
}
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct); // 初始化 GPIO
}

void hal_gpio_set_direction(gpio_port_t port, gpio_pin_t pin, gpio_direction_t direction) {
// ... 实现设置 GPIO 方向的代码 (如果需要动态改变方向)
// 在 STM32 中,通常在初始化时设置方向,运行时不常改变
}

void hal_gpio_set_level(gpio_port_t port, gpio_pin_t pin, gpio_level_t level) {
GPIO_TypeDef *GPIOx;
switch (port) {
case GPIO_PORT_A: GPIOx = GPIOA; break;
case GPIO_PORT_B: GPIOx = GPIOB; break;
case GPIO_PORT_C: GPIOx = GPIOC; break;
// ... 其他端口
default: return;
}

if (level == GPIO_LEVEL_HIGH) {
HAL_GPIO_WritePin(GPIOx, (1 << pin), GPIO_PIN_SET); // 设置高电平
} else {
HAL_GPIO_WritePin(GPIOx, (1 << pin), GPIO_PIN_RESET); // 设置低电平
}
}

gpio_level_t hal_gpio_get_level(gpio_port_t port, gpio_pin_t pin) {
GPIO_TypeDef *GPIOx;
switch (port) {
case GPIO_PORT_A: GPIOx = GPIOA; break;
case GPIO_PORT_B: GPIOx = GPIOB; break;
case GPIO_PORT_C: GPIOx = GPIOC; break;
// ... 其他端口
default: return GPIO_LEVEL_LOW; // 默认返回低电平
}

if (HAL_GPIO_ReadPin(GPIOx, (1 << pin)) == GPIO_PIN_SET) {
return GPIO_LEVEL_HIGH;
} else {
return GPIO_LEVEL_LOW;
}
}

3. key_matrix.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
#ifndef KEY_MATRIX_H
#define KEY_MATRIX_H

#include <stdint.h>
#include <stdbool.h>
#include "hal_gpio.h"

// 定义键盘矩阵的行和列数 (根据实际键盘设计修改)
#define KEY_MATRIX_ROWS 8
#define KEY_MATRIX_COLS 16

// 定义按键状态
typedef enum {
KEY_STATE_RELEASED,
KEY_STATE_PRESSED,
KEY_STATE_HOLD
} key_state_t;

// 定义按键事件结构体
typedef struct {
uint8_t row;
uint8_t col;
key_state_t state;
} key_event_t;

// 初始化键盘矩阵扫描模块
void key_matrix_init(void);

// 扫描键盘矩阵,获取按键事件
void key_matrix_scan(void);

// 获取最新的按键事件 (非阻塞)
bool key_matrix_get_event(key_event_t *event);

#endif // KEY_MATRIX_H

4. key_matrix.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
110
111
112
113
114
115
116
#include "key_matrix.h"
#include "delay.h" // 假设有延时函数

// 定义行和列 GPIO 端口和引脚 (根据实际硬件连接修改)
// 行引脚 (输出)
static const struct {
gpio_port_t port;
gpio_pin_t pin;
} row_pins[KEY_MATRIX_ROWS] = {
{GPIO_PORT_A, 0}, {GPIO_PORT_A, 1}, {GPIO_PORT_A, 2}, {GPIO_PORT_A, 3},
{GPIO_PORT_A, 4}, {GPIO_PORT_A, 5}, {GPIO_PORT_A, 6}, {GPIO_PORT_A, 7}
};

// 列引脚 (输入)
static const struct {
gpio_port_t port;
gpio_pin_t pin;
} col_pins[KEY_MATRIX_COLS] = {
{GPIO_PORT_B, 0}, {GPIO_PORT_B, 1}, {GPIO_PORT_B, 2}, {GPIO_PORT_B, 3},
{GPIO_PORT_B, 4}, {GPIO_PORT_B, 5}, {GPIO_PORT_B, 6}, {GPIO_PORT_B, 7},
{GPIO_PORT_B, 8}, {GPIO_PORT_B, 9}, {GPIO_PORT_B, 10},{GPIO_PORT_B, 11},
{GPIO_PORT_B, 12},{GPIO_PORT_B, 13},{GPIO_PORT_B, 14},{GPIO_PORT_B, 15}
};

// 存储按键状态矩阵 (当前状态)
static key_state_t key_state_matrix[KEY_MATRIX_ROWS][KEY_MATRIX_COLS];

// 存储按键状态矩阵 (上次状态,用于检测状态变化)
static key_state_t last_key_state_matrix[KEY_MATRIX_ROWS][KEY_MATRIX_COLS];

// 按键事件队列 (FIFO)
#define KEY_EVENT_QUEUE_SIZE 16
static key_event_t key_event_queue[KEY_EVENT_QUEUE_SIZE];
static uint8_t key_event_queue_head = 0;
static uint8_t key_event_queue_tail = 0;

void key_matrix_init(void) {
// 初始化行引脚为输出,初始电平为高
for (int i = 0; i < KEY_MATRIX_ROWS; i++) {
hal_gpio_init(row_pins[i].port, row_pins[i].pin, GPIO_DIRECTION_OUTPUT);
hal_gpio_set_level(row_pins[i].port, row_pins[i].pin, GPIO_LEVEL_HIGH);
}

// 初始化列引脚为输入
for (int i = 0; i < KEY_MATRIX_COLS; i++) {
hal_gpio_init(col_pins[i].port, col_pins[i].pin, GPIO_DIRECTION_INPUT);
}

// 初始化按键状态矩阵
for (int i = 0; i < KEY_MATRIX_ROWS; i++) {
for (int j = 0; j < KEY_MATRIX_COLS; j++) {
key_state_matrix[i][j] = KEY_STATE_RELEASED;
last_key_state_matrix[i][j] = KEY_STATE_RELEASED;
}
}

// 初始化事件队列
key_event_queue_head = 0;
key_event_queue_tail = 0;
}

void key_matrix_scan(void) {
for (int i = 0; i < KEY_MATRIX_ROWS; i++) {
// 逐行扫描
hal_gpio_set_level(row_pins[i].port, row_pins[i].pin, GPIO_LEVEL_LOW); // 行输出低电平
delay_us(50); // 稳定延时

for (int j = 0; j < KEY_MATRIX_COLS; j++) {
gpio_level_t col_level = hal_gpio_get_level(col_pins[j].port, col_pins[j].pin);

// 更新按键状态
if (col_level == GPIO_LEVEL_LOW) {
// 检测到按键按下
if (key_state_matrix[i][j] == KEY_STATE_RELEASED) {
key_state_matrix[i][j] = KEY_STATE_PRESSED; // 状态变为按下
} else if (key_state_matrix[i][j] == KEY_STATE_PRESSED) {
key_state_matrix[i][j] = KEY_STATE_HOLD; // 状态变为保持按下
}
} else {
// 按键释放
key_state_matrix[i][j] = KEY_STATE_RELEASED;
}

// 检测按键状态变化,生成按键事件
if (key_state_matrix[i][j] != last_key_state_matrix[i][j]) {
key_event_t event;
event.row = i;
event.col = j;
event.state = key_state_matrix[i][j];

// 将事件添加到事件队列
if (((key_event_queue_tail + 1) % KEY_EVENT_QUEUE_SIZE) != key_event_queue_head) {
key_event_queue[key_event_queue_tail] = event;
key_event_queue_tail = (key_event_queue_tail + 1) % KEY_EVENT_QUEUE_SIZE;
} else {
// 事件队列已满,可以考虑处理错误或丢弃旧事件
// 这里简单忽略新事件
}
}

last_key_state_matrix[i][j] = key_state_matrix[i][j]; // 更新上次状态
}

hal_gpio_set_level(row_pins[i].port, row_pins[i].pin, GPIO_LEVEL_HIGH); // 行恢复高电平
}
}

bool key_matrix_get_event(key_event_t *event) {
if (key_event_queue_head != key_event_queue_tail) {
*event = key_event_queue[key_event_queue_head];
key_event_queue_head = (key_event_queue_head + 1) % KEY_EVENT_QUEUE_SIZE;
return true;
} else {
return false; // 事件队列为空
}
}

5. key_process.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
#ifndef KEY_PROCESS_H
#define KEY_PROCESS_H

#include <stdint.h>
#include <stdbool.h>
#include "key_matrix.h"

// 定义按键码 (可根据需要扩展)
typedef enum {
KEYCODE_NONE = 0x00,
KEYCODE_A = 0x04,
KEYCODE_B = 0x05,
KEYCODE_C = 0x06,
// ... 标准按键码
KEYCODE_FN = 0xE0, // 功能键
KEYCODE_MACRO1 = 0xF0, // 宏命令 1
// ... 自定义按键码
KEYCODE_MAX
} keycode_t;

// 定义按键映射表结构体
typedef struct {
keycode_t keymap[KEY_MATRIX_ROWS][KEY_MATRIX_COLS];
} keymap_t;

// 初始化按键处理模块
void key_process_init(void);

// 处理按键事件,解析按键码
void key_process_event(key_event_t *event);

#endif // KEY_PROCESS_H

6. key_process.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
#include "key_process.h"
#include "config.h" // 假设配置文件

// 默认按键映射表 (可以从配置文件加载)
static keymap_t default_keymap = {
{
{KEYCODE_ESC, KEYCODE_1, KEYCODE_2, KEYCODE_3, KEYCODE_4, KEYCODE_5, KEYCODE_6, KEYCODE_7, KEYCODE_8, KEYCODE_9, KEYCODE_0, KEYCODE_MINUS,KEYCODE_EQUAL,KEYCODE_BACKSPACE,KEYCODE_NONE, KEYCODE_NONE},
{KEYCODE_TAB, KEYCODE_Q, KEYCODE_W, KEYCODE_E, KEYCODE_R, KEYCODE_T, KEYCODE_Y, KEYCODE_U, KEYCODE_I, KEYCODE_O, KEYCODE_P, KEYCODE_LEFTBRACE,KEYCODE_RIGHTBRACE,KEYCODE_BACKSLASH,KEYCODE_NONE, KEYCODE_NONE},
{KEYCODE_CAPSLOCK,KEYCODE_A, KEYCODE_S, KEYCODE_D, KEYCODE_F, KEYCODE_G, KEYCODE_H, KEYCODE_J, KEYCODE_K, KEYCODE_L, KEYCODE_SEMICOLON,KEYCODE_QUOTE, KEYCODE_NONE, KEYCODE_ENTER, KEYCODE_NONE, KEYCODE_NONE},
{KEYCODE_LEFTSHIFT,KEYCODE_Z, KEYCODE_X, KEYCODE_C, KEYCODE_V, KEYCODE_B, KEYCODE_N, KEYCODE_M, KEYCODE_COMMA,KEYCODE_DOT, KEYCODE_SLASH,KEYCODE_RIGHTSHIFT,KEYCODE_NONE, KEYCODE_UP, KEYCODE_NONE, KEYCODE_NONE},
{KEYCODE_LEFTCTRL, KEYCODE_LEFTALT,KEYCODE_SPACE,KEYCODE_SPACE,KEYCODE_SPACE,KEYCODE_SPACE,KEYCODE_SPACE,KEYCODE_SPACE,KEYCODE_SPACE,KEYCODE_SPACE,KEYCODE_SPACE,KEYCODE_RIGHTALT,KEYCODE_RIGHTCTRL,KEYCODE_LEFT, KEYCODE_DOWN, KEYCODE_RIGHT},
{KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE},
{KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE},
{KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE, KEYCODE_NONE}
}
};

static keymap_t *current_keymap; // 当前使用的按键映射表

void key_process_init(void) {
current_keymap = &default_keymap; // 默认使用默认按键映射表
// 可以从配置模块加载用户自定义的按键映射表
}

void key_process_event(key_event_t *event) {
keycode_t keycode = KEYCODE_NONE;

if (event->state == KEY_STATE_PRESSED || event->state == KEY_STATE_HOLD) {
// 获取按键码
keycode = current_keymap->keymap[event->row][event->col];

// 根据按键码执行相应操作
switch (keycode) {
case KEYCODE_A:
case KEYCODE_B:
case KEYCODE_C:
// ... 标准按键处理,例如发送到 USB 报告
// 示例:假设有 usb_hid_send_key(keycode) 函数
// usb_hid_send_key(keycode);
break;

case KEYCODE_FN:
// 功能键处理,例如切换到功能层
// ...
break;

case KEYCODE_MACRO1:
// 宏命令 1 处理,执行预定义的宏
// ...
// macro_engine_execute(MACRO_ID_1); // 假设有宏命令引擎模块
break;

// ... 其他按键码处理
default:
// 未知按键码,忽略
break;
}
} else if (event->state == KEY_STATE_RELEASED) {
// 按键释放事件处理 (例如释放 Shift 键等)
keycode = current_keymap->keymap[event->row][event->col];
// ... 可以根据需要处理按键释放事件
// 示例:usb_hid_release_key(keycode); // 假设有释放按键的函数
}
}

7. macro_engine.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
#ifndef MACRO_ENGINE_H
#define MACRO_ENGINE_H

#include <stdint.h>
#include <stdbool.h>

// 定义宏命令 ID
typedef enum {
MACRO_ID_1,
MACRO_ID_2,
// ... 其他宏命令 ID
MACRO_ID_MAX
} macro_id_t;

// 初始化宏命令引擎
void macro_engine_init(void);

// 加载宏命令配置 (从配置文件或用户界面)
bool macro_engine_load_config(void);

// 执行宏命令
void macro_engine_execute(macro_id_t macro_id);

// 开始宏录制
void macro_engine_start_record(macro_id_t macro_id);

// 停止宏录制
void macro_engine_stop_record(void);

#endif // MACRO_ENGINE_H

8. macro_engine.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
#include "macro_engine.h"
#include "config.h" // 假设配置文件
#include "key_process.h" // 需要使用按键码定义

// 定义宏命令结构体 (示例,需要根据实际需求设计)
typedef struct {
keycode_t commands[256]; // 宏命令序列 (按键码序列)
uint8_t command_count;
} macro_t;

// 存储宏命令配置
static macro_t macro_config[MACRO_ID_MAX];

// 宏录制状态
static bool macro_recording = false;
static macro_id_t recording_macro_id;
static uint8_t recording_command_index;

void macro_engine_init(void) {
macro_engine_load_config(); // 加载宏命令配置
macro_recording = false;
}

bool macro_engine_load_config(void) {
// 从配置文件加载宏命令配置
// ... 读取配置文件,解析宏命令数据,填充 macro_config 数组
// 示例:假设从 config.ini 文件读取宏命令配置

// 示例宏命令 1: "Ctrl + C" (复制)
macro_config[MACRO_ID_1].command_count = 2;
macro_config[MACRO_ID_1].commands[0] = KEYCODE_LEFTCTRL;
macro_config[MACRO_ID_1].commands[1] = KEYCODE_C;

// 示例宏命令 2: "Ctrl + V" (粘贴)
macro_config[MACRO_ID_2].command_count = 2;
macro_config[MACRO_ID_2].commands[0] = KEYCODE_LEFTCTRL;
macro_config[MACRO_ID_2].commands[1] = KEYCODE_V;

return true; // 假设加载成功
}

void macro_engine_execute(macro_id_t macro_id) {
if (macro_id >= MACRO_ID_MAX) return; // ID 无效

macro_t *macro = &macro_config[macro_id];
for (int i = 0; i < macro->command_count; i++) {
// 执行宏命令序列中的每个按键码
keycode_t keycode = macro->commands[i];
// ... 模拟按键按下和释放操作,例如发送 USB HID 报告
// 示例:usb_hid_send_key(keycode); // 按下
// delay_ms(10); // 适当延时
// usb_hid_release_key(keycode); // 释放
}
}

void macro_engine_start_record(macro_id_t macro_id) {
if (macro_recording) return; // 已经在录制中

macro_recording = true;
recording_macro_id = macro_id;
recording_command_index = 0;
// 清空之前的宏命令
macro_config[macro_id].command_count = 0;
}

void macro_engine_stop_record(void) {
macro_recording = false;
}

// 宏录制事件处理 (在按键处理模块中调用)
void macro_engine_record_event(key_event_t *event) {
if (!macro_recording) return;

if (event->state == KEY_STATE_PRESSED) {
keycode_t keycode = KEYCODE_NONE;
// ... 根据按键事件获取按键码 (需要实现按键码转换逻辑)
// keycode = ... ; // 获取按键码

if (recording_command_index < sizeof(macro_config[recording_macro_id].commands) / sizeof(macro_config[recording_macro_id].commands[0])) {
macro_config[recording_macro_id].commands[recording_command_index++] = keycode;
macro_config[recording_macro_id].command_count++;
} else {
// 宏命令序列已满,停止录制或提示错误
macro_engine_stop_record();
// ... 提示用户宏命令过长
}
}
}

9. 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "main.h"
#include "system_init.h" // 系统初始化模块
#include "key_matrix.h"
#include "key_process.h"
#include "macro_engine.h"
#include "usb_hid.h" // 假设有 USB HID 模块
#include "rgb_backlight.h" // 假设有 RGB 背光模块
#include "display_driver.h" // 假设有显示驱动模块
#include "task_scheduler.h" // 任务调度器 (可选,可以使用简单的轮询)
#include "delay.h" // 延时函数

int main(void) {
system_init(); // 系统初始化 (时钟、外设等)
delay_init(); // 延时函数初始化
hal_gpio_init_all(); // 初始化所有 GPIO
key_matrix_init(); // 键盘矩阵初始化
key_process_init(); // 按键处理模块初始化
macro_engine_init(); // 宏命令引擎初始化
usb_hid_init(); // USB HID 初始化
rgb_backlight_init();// RGB 背光初始化
display_driver_init();// 显示驱动初始化
task_scheduler_init();// 任务调度器初始化 (如果使用)

// 启动背光效果
rgb_backlight_set_mode(RGB_MODE_RAINBOW);

// 主循环
while (1) {
key_matrix_scan(); // 扫描键盘矩阵

key_event_t event;
while (key_matrix_get_event(&event)) {
key_process_event(&event); // 处理按键事件
macro_engine_record_event(&event); // 宏录制事件处理
}

usb_hid_task(); // USB HID 任务处理 (例如发送报告)
rgb_backlight_task(); // RGB 背光任务处理 (例如动画效果)
display_driver_task();// 显示驱动任务处理 (例如刷新显示)
task_scheduler_run(); // 任务调度器运行 (如果使用)

// 可以添加其他任务处理,例如蓝牙通信、传感器数据采集等

delay_ms(1); // 适当延时,降低 CPU 占用率
}
}

关键技术和方法:

  1. 分层模块化架构: 提高代码可维护性、可扩展性和可移植性。
  2. 硬件抽象层 (HAL): 隔离硬件差异,方便代码移植到不同的 MCU 平台。
  3. 键盘矩阵扫描: 高效检测按键事件,降低硬件成本。
  4. 按键去抖动: 通过软件或硬件方法消除按键抖动,提高按键识别的准确性。 (代码示例中未显式加入去抖动,实际项目中需要添加,可以使用延时去抖动或更复杂的算法)
  5. 事件驱动模型: 按键事件触发相应的处理函数,提高系统响应速度和效率。
  6. 宏命令引擎: 实现复杂的宏命令编程,满足用户自定义需求.
  7. RGB 背光控制: 实现丰富的灯效模式和自定义颜色,提升键盘的视觉效果。
  8. USB HID 通信: 模拟 USB HID 键盘设备,与主机进行标准键盘协议通信。
  9. 固件在线升级 (DFU): 方便后续功能更新和 bug 修复。(代码示例中未包含 DFU 功能,实际项目中需要添加)
  10. 配置管理: 将配置数据与代码分离,提高灵活性和可定制性。可以使用配置文件、EEPROM、Flash 等存储配置数据。

测试与验证:

  • 单元测试: 针对每个模块进行单元测试,验证模块功能的正确性。例如,测试 GPIO 驱动、键盘矩阵扫描、按键处理、宏命令引擎等模块。
  • 集成测试: 将各个模块集成在一起进行测试,验证模块之间的协同工作是否正常。
  • 系统测试: 进行完整的系统测试,包括功能测试、性能测试、稳定性测试、兼容性测试等。
  • 用户测试: 邀请用户进行实际使用测试,收集用户反馈,不断改进产品。
  • 自动化测试: 可以使用自动化测试工具进行一些重复性的测试,提高测试效率和覆盖率。

维护与升级:

  • 模块化设计: 模块化设计使得维护和升级更加容易,可以独立修改和替换某个模块,而不会影响其他模块。
  • 固件在线升级 (DFU): 通过 DFU 功能,可以方便地进行固件升级,修复 bug、添加新功能。
  • 版本控制: 使用版本控制系统 (如 Git) 管理代码,方便代码维护和版本管理。
  • 日志记录: 添加日志记录功能,方便调试和问题排查。
  • 用户反馈渠道: 建立用户反馈渠道,收集用户反馈,及时修复 bug 和改进产品。

总结:

这个智能模块化机械键盘项目展示了一个完整的嵌入式系统开发流程,从需求分析、架构设计到代码实现、测试验证和维护升级。采用分层模块化架构,结合 HAL 硬件抽象层、键盘矩阵扫描、按键处理、宏命令引擎、RGB 背光控制、USB HID 通信等关键技术,构建了一个可靠、高效、可扩展的智能键盘平台。提供的 C 代码示例虽然只是框架和片段,但展示了核心模块的设计思路和实现方法。实际项目中需要根据具体的硬件平台和功能需求进行详细的实现和完善。整个系统设计注重模块化、可配置性、可维护性和可升级性,为用户提供高度自定义和卓越使用体验的智能键盘产品。

后续扩展方向:

  • 无线连接: 添加蓝牙或 2.4GHz 无线连接功能,支持移动设备使用。
  • 音频反馈: 增加音频输出模块,实现按键音效、系统提示音等。
  • 屏幕显示模块: 完善屏幕显示模块驱动,支持更丰富的显示内容和用户交互。
  • 传感器模块: 集成环境光传感器、温度传感器等,实现更多智能功能。
  • 云端互联: 连接云端服务,实现云端配置同步、固件升级、数据分析等功能。
  • 更高级的宏编程: 支持更复杂的宏命令语法、脚本语言、图形化宏编辑器等。
  • 个性化定制服务: 提供用户自定义硬件模块、软件功能、外观设计等个性化定制服务。

希望这份详细的架构设计和代码示例能够帮助您理解智能键盘嵌入式系统的开发过程。 请记住,实际的嵌入式系统开发是一个复杂而迭代的过程,需要不断学习、实践和改进。

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