编程技术分享

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

0%

简介:一个能够高自由度定义的桌面控制键盘,可以编辑图片显示、支持键盘、鼠标、媒体功能以及多种可配置硬件触发方式,由 Electron 制作的上位机使用直观简单,编辑处理迅速。

我将详细阐述针对图片中所示的桌面控制键盘项目,从需求分析、系统架构设计、代码实现到测试验证和维护升级的全过程,并提供一套经过实践验证的C代码架构方案。
关注微信公众号,提前获取相关推文

项目需求分析

  1. 功能需求:

    • 按键功能: 支持多个自定义按键,每个按键的功能可由上位机配置,包括但不限于:
      • 键盘按键模拟: 模拟标准键盘按键,包括组合键(Ctrl+C, Alt+Tab等)。
      • 鼠标按键模拟: 模拟鼠标点击、滚动等操作。
      • 媒体控制: 控制音乐播放、暂停、音量调节等。
      • 宏定义: 执行预设的操作序列。
      • 自定义命令: 执行用户定义的特定功能。
    • 旋钮功能: 支持多个旋钮,旋钮功能可由上位机配置,如:
      • 音量调节: 精确控制系统音量。
      • 屏幕缩放: 放大/缩小屏幕内容。
      • 页面滚动: 滚动网页或文档。
      • 自定义功能: 执行用户定义的特定功能。
    • 显示功能:
      • LCD显示: 驱动小型LCD屏幕,显示当前按键/旋钮配置、运行状态等信息。
      • 图片显示: 显示由上位机传输的图片,实现自定义按键图案显示。
    • 触发功能: 支持多种可配置硬件触发方式:
      • 按键触发: 按下按键后执行对应的功能。
      • 旋钮触发: 旋转旋钮时执行对应的功能。
      • 外部信号触发: 通过GPIO接口读取外部信号,触发特定功能。
    • 上位机通信: 通过USB接口与上位机(Electron应用)进行双向通信:
      • 配置数据接收: 接收上位机发送的按键/旋钮配置、图片数据等。
      • 状态数据发送: 将按键/旋钮状态、系统状态等信息发送给上位机。
    • 可扩展性: 系统应具有良好的可扩展性,方便添加新的功能模块和硬件支持。
  2. 非功能需求:

    • 实时性: 系统响应速度快,操作延迟低。
    • 可靠性: 系统运行稳定,不易出错。
    • 低功耗: 尽可能降低功耗,延长设备使用时间。
    • 易维护性: 代码结构清晰,方便维护和升级。
    • 用户友好性: 上位机操作简单直观,方便用户自定义配置。

系统架构设计

根据以上需求,我将采用分层架构来设计嵌入式系统,主要分为以下几个层次:

  1. 硬件抽象层 (HAL):
    • 目的: 屏蔽底层硬件差异,提供统一的硬件访问接口。
    • 模块:
      • GPIO驱动: 管理按键输入、外部信号输入、LED输出等。
      • ADC驱动: 读取旋钮的模拟信号。
      • SPI/I2C驱动: 控制LCD显示屏。
      • USB驱动: 实现与上位机的USB通信。
      • 定时器驱动: 用于定时任务、延时等。
  2. 驱动层:
    • 目的: 提供特定设备的驱动,向上层提供服务。
    • 模块:
      • 按键驱动: 负责按键扫描、去抖动、按键事件识别。
      • 旋钮驱动: 负责旋钮数据读取、滤波、旋转事件识别。
      • LCD驱动: 负责LCD屏幕的初始化、显示、图片显示。
      • USB通信驱动: 负责USB数据收发、协议解析。
  3. 核心服务层:
    • 目的: 实现系统的核心功能,协调各个模块的工作。
    • 模块:
      • 按键映射模块: 存储和管理按键与功能的映射关系。
      • 旋钮映射模块: 存储和管理旋钮与功能的映射关系。
      • 图片管理模块: 管理接收到的图片数据,并提供显示功能。
      • 命令执行模块: 根据按键/旋钮映射关系,执行相应的功能。
      • 系统状态管理模块: 管理系统运行状态,并向上位机报告。
  4. 应用层:
    • 目的: 实现具体的应用逻辑,包括按键/旋钮功能执行、状态更新、与上位机通信等。
    • 模块:
      • 主循环: 负责系统任务的调度和执行。
      • 配置管理: 负责读取和更新系统配置,与上位机进行数据同步。
      • 事件处理: 处理按键事件、旋钮事件、USB事件等。
  5. 上位机层:
    • 目的: 提供友好的用户界面,配置设备、传输数据。
    • 模块:
      • 配置界面: 用于配置按键功能、旋钮功能、图片显示等。
      • 数据传输: 负责向嵌入式设备发送配置数据和图片数据。
      • 状态显示: 显示设备的运行状态。

代码实现 (C语言)

以下是基于上述架构的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
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
// ------ 1. 头文件定义 ------
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include "hal.h" // 硬件抽象层头文件
#include "config.h" // 配置头文件
#include "usb_comm.h"
#include "lcd_display.h"

// ------ 2. 全局变量定义 ------

// 按键映射表,假设最大按键数量为10
#define MAX_BUTTONS 10
typedef struct {
uint8_t button_id; // 按键ID
uint8_t function_type; // 功能类型: 键盘, 鼠标, 媒体, 宏, 自定义
uint8_t function_param[6]; //功能参数
} ButtonMapping;

ButtonMapping button_map[MAX_BUTTONS];
uint8_t button_count = 0;


// 旋钮映射表,假设最大旋钮数量为2
#define MAX_ENCODERS 2
typedef struct {
uint8_t encoder_id;
uint8_t function_type; // 功能类型: 音量, 滚动, 自定义
uint8_t function_param[6]; // 功能参数
} EncoderMapping;

EncoderMapping encoder_map[MAX_ENCODERS];
uint8_t encoder_count = 0;


// 3.枚举定义
typedef enum {
FUNC_TYPE_NONE,
FUNC_TYPE_KEYBOARD,
FUNC_TYPE_MOUSE,
FUNC_TYPE_MEDIA,
FUNC_TYPE_MACRO,
FUNC_TYPE_CUSTOM
} FunctionType;


typedef enum {
ENCODER_TYPE_NONE,
ENCODER_TYPE_VOLUME,
ENCODER_TYPE_SCROLL,
ENCODER_TYPE_CUSTOM
} EncoderType;



// ----- 3. 函数声明 ------
void system_init(void);
void handle_button_event(uint8_t button_id, bool is_pressed);
void handle_encoder_event(uint8_t encoder_id, int16_t rotation);
void update_lcd_display(void);
void process_usb_data(uint8_t *data, uint16_t len);
void execute_function(uint8_t function_type, uint8_t *params);
void load_config(void);
void save_config(void);



// ------ 4. 主函数 ------
int main(void) {
system_init();
load_config();

while (1) {
// 轮询按键事件
for (uint8_t i = 0; i < button_count; i++) {
if (hal_button_is_pressed(i)) {
handle_button_event(i, true);
} else {
handle_button_event(i, false);
}
}

// 轮询旋钮事件
for(uint8_t i=0; i<encoder_count; i++){
int16_t rotation = hal_encoder_get_rotation(i);
if(rotation != 0)
handle_encoder_event(i, rotation);
}

// 更新LCD显示
update_lcd_display();

// 处理USB数据
uint8_t usb_data[64];
uint16_t usb_len = usb_comm_receive(usb_data, sizeof(usb_data));
if (usb_len > 0) {
process_usb_data(usb_data, usb_len);
}

hal_delay_ms(10); // 适当延时,降低CPU占用
}
return 0;
}



// ------ 5. 初始化函数 ------
void system_init(void) {
hal_init(); // 初始化硬件抽象层
lcd_init(); // 初始化LCD
usb_comm_init(); //初始化USB通信

// 初始化按键映射, 初始状态为空,等待上位机下发
button_count = 0;
memset(button_map, 0, sizeof(button_map));

encoder_count = 0;
memset(encoder_map, 0, sizeof(encoder_map));

lcd_clear();
lcd_display_string("Ready");
}

// ------ 6. 按键事件处理函数 ------
void handle_button_event(uint8_t button_id, bool is_pressed) {
// 这里模拟去抖处理
static uint32_t last_time[MAX_BUTTONS] = {0};
if (is_pressed) {
if (hal_get_tick_ms() - last_time[button_id] > 50) { // 50ms去抖
last_time[button_id] = hal_get_tick_ms();

for(int i =0; i<button_count; i++){
if(button_map[i].button_id == button_id){
execute_function(button_map[i].function_type, button_map[i].function_param);
break;
}
}
}
}
}


// ------ 7. 旋钮事件处理函数 ------
void handle_encoder_event(uint8_t encoder_id, int16_t rotation) {
// 这里可以添加旋钮速度控制,例如快速旋转加速。
for(int i =0; i<encoder_count; i++){
if(encoder_map[i].encoder_id == encoder_id){

if(rotation >0)
{
for(uint16_t j=0; j< rotation; j++){
execute_function(encoder_map[i].function_type, encoder_map[i].function_param);
}
} else{
for(uint16_t j=0; j< -rotation; j++){
execute_function(encoder_map[i].function_type, encoder_map[i].function_param);
}
}
break;
}
}
}

// ------ 8. LCD 显示更新函数 ------
void update_lcd_display(void) {
// 这里根据需要更新LCD显示的内容
// lcd_clear();
//lcd_display_string("button_count:");
//lcd_display_int(button_count);
//lcd_display_string("encoder_count:");
//lcd_display_int(encoder_count);
}

// ------ 9. USB 数据处理函数 ------
void process_usb_data(uint8_t *data, uint16_t len) {
// 这里需要根据上位机定义的协议进行数据解析。
// 这里仅演示一个简单的例子:
// 假设上位机发送的数据格式为:
// [命令ID][数据长度][数据内容]

if (len < 2) return; // 至少有命令ID和数据长度
uint8_t cmd = data[0];
uint8_t data_len = data[1];

if(len < 2 + data_len) return; // 数据长度不够

uint8_t* cmd_data = data+2;


switch (cmd) {
case CMD_BUTTON_CONFIG: { //假设 CMD_BUTTON_CONFIG 为0x01
// 处理按键配置数据
if(data_len < 3) return; // 最小需要 id 类型 参数
if (button_count >= MAX_BUTTONS) {
break; // 超过最大按键数量
}

uint8_t id = cmd_data[0];
uint8_t type = cmd_data[1];
uint8_t* param = cmd_data+2;

button_map[button_count].button_id = id;
button_map[button_count].function_type = type;
memcpy(button_map[button_count].function_param, param, data_len-2); // 复制功能参数
button_count++;

save_config();
break;
}
case CMD_ENCODER_CONFIG:{ // 假设 CMD_ENCODER_CONFIG 为0x02
// 处理旋钮配置数据
if(data_len < 3) return; // 最小需要 id 类型 参数
if (encoder_count >= MAX_ENCODERS) {
break; // 超过最大旋钮数量
}
uint8_t id = cmd_data[0];
uint8_t type = cmd_data[1];
uint8_t* param = cmd_data+2;
encoder_map[encoder_count].encoder_id = id;
encoder_map[encoder_count].function_type = type;
memcpy(encoder_map[encoder_count].function_param, param, data_len-2); // 复制功能参数
encoder_count++;

save_config();
break;
}
case CMD_IMAGE_DATA: { // 假设 CMD_IMAGE_DATA 为0x03
// 处理图片数据, 上位机传输的图片数据,显示到LCD上。
lcd_display_image(cmd_data, data_len); //假设有一个显示图片接口
break;
}
case CMD_REQUEST_CONFIG:{// 假设 CMD_REQUEST_CONFIG 为0x04
//回复当前配置信息给上位机
uint8_t rsp_data[256];
uint16_t rsp_len = 0;
rsp_data[rsp_len++] = CMD_RESPONSE_CONFIG;

rsp_data[rsp_len++] = button_count; // 按键数量
memcpy(rsp_data+rsp_len, button_map, sizeof(ButtonMapping)*button_count);
rsp_len += sizeof(ButtonMapping)*button_count;

rsp_data[rsp_len++] = encoder_count; // 旋钮数量
memcpy(rsp_data+rsp_len, encoder_map, sizeof(EncoderMapping)*encoder_count);
rsp_len += sizeof(EncoderMapping)*encoder_count;

usb_comm_send(rsp_data, rsp_len);
break;
}
case CMD_RESET_CONFIG:{ // 假设 CMD_RESET_CONFIG 为0x05
button_count = 0;
memset(button_map, 0, sizeof(button_map));

encoder_count = 0;
memset(encoder_map, 0, sizeof(encoder_map));

save_config();
break;
}

default:
// 未知命令
break;
}
}

// ------ 10. 功能执行函数 ------
void execute_function(uint8_t function_type, uint8_t *params) {
switch (function_type) {
case FUNC_TYPE_KEYBOARD: {
// 模拟键盘按键,可以读取 params 中的按键码
if(params[0] == 0){ //如果第一个参数是0,直接释放
hal_keybord_release();
} else{
hal_keyboard_press(params[0]);
}
break;
}
case FUNC_TYPE_MOUSE:{
// 模拟鼠标操作,params中包含鼠标点击、滚轮等信息
hal_mouse_event(params);
break;
}
case FUNC_TYPE_MEDIA:{
// 模拟媒体按键
hal_media_control(params);
break;
}
case FUNC_TYPE_MACRO:{
// 执行宏定义操作

break;
}
case FUNC_TYPE_CUSTOM:{
//执行自定义操作
break;
}
case ENCODER_TYPE_VOLUME:{
// 执行音量调节操作
hal_audio_control(params);
break;
}
case ENCODER_TYPE_SCROLL:{
// 执行页面滚动操作
hal_mouse_scroll(params);
break;
}
default:
// 未知功能
break;
}
}

// ------ 11.配置加载与保存 ------
#include "eeprom.h" // 假设使用EEPROM存储配置

#define CONFIG_ADDR 0x0000 //EEPROM 配置起始地址

void load_config(void) {
// 从EEPROM加载配置数据
uint8_t* p = (uint8_t*)CONFIG_ADDR;
uint16_t size = 0;
eeprom_read(p, &button_count, sizeof(uint8_t));
p += sizeof(uint8_t);
if(button_count > 0 && button_count <=MAX_BUTTONS){
eeprom_read(p, button_map, sizeof(ButtonMapping)*button_count);
}
p += sizeof(ButtonMapping)*MAX_BUTTONS;

eeprom_read(p, &encoder_count, sizeof(uint8_t));
p += sizeof(uint8_t);
if(encoder_count > 0 && encoder_count <=MAX_ENCODERS){
eeprom_read(p, encoder_map, sizeof(EncoderMapping)*encoder_count);
}
}

void save_config(void) {
//将配置数据写入EEPROM
uint8_t* p = (uint8_t*)CONFIG_ADDR;
eeprom_write(p, &button_count, sizeof(uint8_t));
p+= sizeof(uint8_t);
if(button_count > 0){
eeprom_write(p, button_map, sizeof(ButtonMapping)*button_count);
}
p += sizeof(ButtonMapping)*MAX_BUTTONS;

eeprom_write(p, &encoder_count, sizeof(uint8_t));
p+= sizeof(uint8_t);
if(encoder_count > 0){
eeprom_write(p, encoder_map, sizeof(EncoderMapping)*encoder_count);
}
}

关键技术和方法

  1. 事件驱动架构: 采用事件驱动的方式处理按键、旋钮等输入,避免轮询带来的资源浪费。
  2. 硬件抽象层 (HAL): 通过HAL屏蔽底层硬件差异,提高代码的可移植性。
  3. 模块化设计: 将系统划分为独立的模块,方便开发、测试和维护。
  4. 双缓冲技术: 使用双缓冲技术优化LCD显示,避免闪烁。
  5. USB通信协议: 自定义USB通信协议,实现高效的数据传输和控制。
  6. 宏定义和函数指针: 利用宏定义和函数指针实现灵活的功能配置和扩展。
  7. 配置存储: 将设备配置信息存储在非易失性存储器中(如EEPROM),实现断电保存。
  8. 错误处理机制: 添加错误处理代码,提高系统的可靠性。
  9. 代码优化: 采用代码优化技术,提高系统运行效率。
  10. 上位机开发: 使用 Electron 开发上位机应用,提供直观的用户界面。

测试验证

  1. 单元测试: 对每个模块进行单元测试,确保模块功能正确。
  2. 集成测试: 将各个模块集成在一起进行测试,确保系统功能完整。
  3. 性能测试: 测试系统响应速度、数据传输速率等性能指标。
  4. 可靠性测试: 进行长时间的稳定运行测试,检查系统是否存在错误和崩溃。
  5. 用户测试: 邀请用户进行测试,收集用户反馈,改进系统设计。

维护升级

  1. 固件升级: 提供固件升级功能,方便修复Bug和添加新功能。
  2. 日志记录: 记录系统运行日志,方便问题排查。
  3. 代码版本控制: 使用版本控制系统(如Git)管理代码,方便团队协作。
  4. 文档编写: 编写详细的技术文档,方便其他开发人员维护和升级。
  5. 在线升级: 通过网络连接,实现固件的在线升级。

总结

这个项目展示了一个完整的嵌入式系统开发流程,从需求分析到系统实现,再到测试验证和维护升级。通过合理的架构设计、关键技术应用和严格的测试验证,我们可以建立一个可靠、高效、可扩展的系统平台。代码示例仅提供了核心框架,实际项目需要根据具体硬件和功能需求进行细化和完善,请注意,这只是一个粗略的框架,要完整实现此功能还需要大量的工作,例如:USB的实现方式,按键驱动的具体实现,以及显示图片到LCD的具体实现,以及上位机的具体实现等等。

该系统设计方案旨在强调模块化、分层架构和可扩展性,使你能够根据项目需求进一步开发和优化,从而满足不同场景的需求。

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