嵌入式系統 LCD 螢幕程式設計面臨獨特約束:資源受限(記憶體、CPU)、無作業系統圖形子系統、即時性要求、功耗敏感。本文針對漢字處理、時間顯示優化、動畫實現、選單架構、模態對話框五大核心場景,提供經實戰驗證的 C 語言解決方案。
GB2312/GBK 字庫按區位序排列:每區 94 字,區號 1-94,位號 1-94。
/* 16×16 點陣,每字 32 位元組 */
uint32_t font_offset(uint16_t qu, uint16_t wei) {
return ((qu - 1) * 94 + (wei - 1)) * 32; // 位元組偏移
}
/* 使用:從 FLASH 讀取 32 位元組到緩衝區 */
void get_gbk_font(uint16_t gbk_code, uint8_t *buf) {
uint16_t qu = gbk_code >> 8;
uint16_t wei = gbk_code & 0xFF;
flash_read(FONT_BASE + font_offset(qu, wei), buf, 32);
}
嵌入式產品通常只需幾十至幾百個漢字(微波爐、空調、手持設備)。完整字庫浪費寶貴 FLASH。
/* 字模結構:內碼 + 32位元組點陣 */
typedef struct {
uint16_t unicode; // Unicode/GBK 內碼
uint8_t bitmap[32]; // 16×16 點陣
} font16x16_t;
/* 字庫陣列:按內碼升序排列,支援二分搜尋 */
#define CHINESE_CHAR_NUM 256
const font16x16_t chinese_fonts[CHINESE_CHAR_NUM] = {
{ 0x4E1A, { 0x04,0x40, 0x04,0x40, ... } }, // "業"
{ 0x4E2D, { 0x01,0x00, 0x01,0x00, ... } }, // "中"
{ 0x4E91, { 0x00,0x00, 0x00,0x30, ... } }, // "雲"
{ 0x4EF6, { 0x10,0x40, 0x1A,0x40, ... } }, // "件"
// ... 更多漢字
};
/* 二分搜尋取字模:O(log N) */
const uint8_t *find_font(uint16_t code) {
int lo = 0, hi = CHINESE_CHAR_NUM - 1;
while (lo <= hi) {
int mid = (lo + hi) >> 1;
if (chinese_fonts[mid].unicode == code)
return chinese_fonts[mid].bitmap;
else if (chinese_fonts[mid].unicode < code)
lo = mid + 1;
else
hi = mid - 1;
}
return NULL; // 未找到
}
每秒更新完整時間字串("HH:MM:SS")極其浪費:LCD 寫入慢、CPU 佔用高、閃爍明顯。關鍵觀察:秒變頻率最高,分/時極低。
void display_time(void) {
static uint8_t last_hour = 0xFF, last_min = 0xFF, last_sec = 0xFF;
uint8_t now_hour = get_sys_hour();
uint8_t now_min = get_sys_min();
uint8_t now_sec = get_sys_sec();
if (now_hour != last_hour) {
lcd_show_number(HOUR_X, HOUR_Y, now_hour, 2); // 只更新時
last_hour = now_hour;
}
if (now_min != last_min) {
lcd_show_number(MIN_X, MIN_Y, now_min, 2); // 只更新分
last_min = now_min;
}
if (now_sec != last_sec) {
lcd_show_number(SEC_X, SEC_Y, now_sec, 2); // 只更新秒
last_sec = now_sec;
}
}
void timer_1hz_callback(void) { // RTC 秒中斷或軟體定時器
static bool colon_on = true;
lcd_show_char(COLON_X, COLON_Y, colon_on ? ':' : ' ');
colon_on = !colon_on;
}
static 區域變數 在函數間保持狀態,不佔用全域命名空間,是 C 語言實現「類靜態成員」的關鍵技巧(C++ 中 static 更強大,可實現單例、工廠等模式)。
本質:靜態畫面快速切換 = 動畫。核心需求:硬體/軟體定時器提供固定幀率(如 20fps = 50ms)。
合理靈活使用定時器是嵌入式工程師基本功。
/* 幀緩衝區:兩塊完整畫面記憶體 */
uint8_t frame_buf[2][LCD_WIDTH * LCD_HEIGHT / 8]; // 假設單色 LCD,每位元一像素
volatile uint8_t active_buf = 0; // 顯示中緩衝區
volatile uint8_t draw_buf = 1; // 繪圖中緩衝區
/* 定時器中斷:交換緩衝區指標,觸發 LCD DMA 刷新 */
void lcd_vsync_isr(void) {
active_buf ^= 1;
draw_buf ^= 1;
lcd_dma_start(frame_buf[active_buf], LCD_SIZE);
}
/* 主迴圈/繪圖任務:在 draw_buf 繪製下一幀 */
void animation_task(void) {
while (1) {
wait_vsync(); // 等待緩衝區可寫(信號量/事件組)
clear_frame(frame_buf[draw_buf]);
draw_current_frame(frame_buf[draw_buf]); // 繪製邏輯
signal_frame_ready(); // 通知 VSYNC 可交換
}
}
傳統 switch-case 選單處理:
/* 反面教材:每新增選單項需修改多處 switch */
void on_ok_key(void) {
switch (current_focus) {
case MENU1: menu1_on_ok(); break;
case MENU2: menu2_on_ok(); break;
// ...
}
}
void on_cancel_key(void) { /* 同樣的 switch */ }
/* 選單項結構:資料 + 操作 */
typedef struct {
const char *text; // 顯示文字
uint8_t x, y; // 顯示座標
void (*on_ok)(void); // OK 鍵處理函數
void (*on_cancel)(void); // CANCEL 鍵處理函數
void (*on_enter)(void); // 進入選單項初始化
void (*on_exit)(void); // 離開選單項清理
} menu_item_t;
/* 具體選單項實現 */
void menu1_on_ok(void) { /* ... */ }
void menu1_on_cancel(void) { /* ... */ }
void menu2_on_ok(void) { /* ... */ }
/* ... */
/* 選單表:靜態常數,編譯期固化,放入 FLASH (.rodata) */
const menu_item_t menu_table[] = {
{ "Menu 1", 0, 0, menu1_on_ok, menu1_on_cancel, NULL, NULL },
{ "Menu 2", 0, 16, menu2_on_ok, menu2_on_cancel, NULL, NULL },
{ "Menu 3", 0, 32, menu3_on_ok, menu3_on_cancel, NULL, NULL },
{ "Menu 4", 0, 48, menu4_on_ok, menu4_on_cancel, NULL, NULL },
};
#define MENU_COUNT (sizeof(menu_table)/sizeof(menu_table[0]))
// 當前焦點索引
static uint8_t current_focus = 0;
/* 按鍵處理極度簡化 */
void on_ok_key(void) {
if (menu_table[current_focus].on_ok)
menu_table[current_focus].on_ok();
}
void on_cancel_key(void) {
if (menu_table[current_focus].on_cancel)
menu_table[current_focus].on_cancel();
}
/* 焦點切換 */
void focus_next(void) {
if (menu_table[current_focus].on_exit) menu_table[current_focus].on_exit();
current_focus = (current_focus + 1) % MENU_COUNT;
if (menu_table[current_focus].on_enter) menu_table[current_focus].on_enter();
update_focus_display();
}
| 維度 | Switch-Case | 函數指標表 |
|---|---|---|
| 新增選單項 | 修改 3+ 處 switch | 只需在陣列新增一項 |
| 程式碼耦合 | 高(集中在大函數) | 低(各項獨立實現) |
| 記憶體佈局 | 代碼分散 | 表在 .rodata,函數在 .text |
| 擴展性 | 差 | 優(支援動態註冊/插件) |
| 物件導向對應 | 過程式 | 封裝 + 多態 (虛函數表雛形) |
這正是 C 語言實現「面向對象」核心思想:**資料與操作綁定,透過函數指標實現多態**。
Windows 經典 MessageBox 是模態對話框典範:阻塞調用者,等待用戶回應。嵌入式同樣需要。
typedef enum {
MSG_TYPE_OK = 0, // 僅「確定」
MSG_TYPE_OK_CANCEL = 1 // 「確定」+「取消」
} msg_type_t;
typedef enum {
KEY_OK = 0,
KEY_CANCEL = 1
} key_result_t;
/* 模態對話框:阻塞直到用戶按鍵 */
key_result_t message_box(const char *msg, msg_type_t type) {
uint8_t key = 0xFF;
lcd_clear(); // 清屏
lcd_draw_rect(0, 0, LCD_W-1, LCD_H-1); // 外框
lcd_show_string(10, 20, msg); // 訊息文字
/* 根據類型繪製按鈕 */
if (type == MSG_TYPE_OK) {
lcd_show_string(50, 80, "[ OK ]");
} else {
lcd_show_string(20, 80, "[ OK ]");
lcd_show_string(90, 80, "[ CANCEL ]");
}
/* 事件循環:阻塞等待有效按鍵 */
while (key != KEY_OK && key != KEY_CANCEL) {
key = get_system_key(); // 非阻塞輪詢或阻塞等待
// 可加入按鍵去抖、長按檢測等
}
/* 返回結果 */
return (key == KEY_OK) ? KEY_OK : KEY_CANCEL;
}
/* 使用範例 */
void delete_file_confirm(const char *filename) {
char buf[64];
snprintf(buf, sizeof(buf), "Delete %s?", filename);
if (message_box(buf, MSG_TYPE_OK_CANCEL) == KEY_OK) {
fs_delete(filename);
message_box("Deleted!", MSG_TYPE_OK);
}
}
┌─────────────────────────────────────┐
│ 應用層:業務邏輯、選單流程、對話框 │ ← 本文核心內容
├─────────────────────────────────────┤
│ 顯示服務層:字庫管理、時間刷新、動畫 │ ← 可複用組件
├─────────────────────────────────────┤
│ 圖形基元層:點/線/矩形/字元/位圖繪製 │ ← 硬體抽象
├─────────────────────────────────────┤
│ 驅動層:LCD 控制器、SPI/I2C/8080 總線 │ ← 硬體相關
└─────────────────────────────────────┘
掌握本文五大技巧:
即可構建專業級嵌入式顯示系統,告別凌亂難維護的螢幕代碼,讓有限資源發揮最大價值。