首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

嵌入式系統 C 語言螢幕程式設計進階:漢字顯示、時間刷新、動畫與選單架構

     編輯:關於C語言

前言:嵌入式顯示的特殊挑戰

嵌入式系統 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;  // 未找到
}

優勢總結

  • 記憶體極省:只存需要的字,256字僅 8KB vs 完整字庫 1-2MB
  • 搜尋極快:二分查找 8 次比較 vs 線性掃描
  • 結構清晰:內碼與字模綁定,編譯期檢查,易維護
  • 擴展靈活:新增漢字只需在陣列插入,保持有序

二、系統時間顯示:差分刷新策略

每秒更新完整時間字串("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 更強大,可實現單例、工廠等模式)。

三、LCD 動畫:幀緩衝區 + 定時器驅動

本質:靜態畫面快速切換 = 動畫。核心需求:硬體/軟體定時器提供固定幀率(如 20fps = 50ms)。

定時器在嵌入式系統的核心地位

  • 無定時器 → 無時間片輪轉 → 無多任務調度 → 非 RTOS
  • 無定時器 → 無幀率控制 → 無多媒體播放
  • 無定時器 → 無超時重傳 → 無可靠網路協議

合理靈活使用定時器是嵌入式工程師基本功。

雙緩衝動畫框架

/* 幀緩衝區:兩塊完整畫面記憶體 */
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 語言實現「面向對象」核心思想:**資料與操作綁定,透過函數指標實現多態**。

五、模態對話框:MessageBox 實現

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);
    }
}

關鍵設計點

  1. 阻塞語義:簡化上層邏輯,調用處線性等待結果
  2. 類型參數化:單一函數支援多種按鈕組合
  3. 返回值明確:枚舉類型避免魔術數字
  4. 可擴展性:易加入超時自動關閉、預設按鈕、圖示等

總結:嵌入式顯示系統的分層架構建議

┌─────────────────────────────────────┐
│  應用層:業務邏輯、選單流程、對話框  │  ← 本文核心內容
├─────────────────────────────────────┤
│  顯示服務層:字庫管理、時間刷新、動畫  │  ← 可複用組件
├─────────────────────────────────────┤
│  圖形基元層:點/線/矩形/字元/位圖繪製  │  ← 硬體抽象
├─────────────────────────────────────┤
│  驅動層:LCD 控制器、SPI/I2C/8080 總線 │  ← 硬體相關
└─────────────────────────────────────┘

掌握本文五大技巧:

  1. 精簡字庫 + 二分搜尋:記憶體與速度雙贏
  2. 差分刷新 + static 狀態:最小化 LCD 寫入
  3. 雙緩衝 + 定時器驅動:流暢無閃爍動畫
  4. 函數指標表驅動選單:C 語言實現封裝與多態
  5. 模態對話框封裝:統一交互模式,簡化應用邏輯

即可構建專業級嵌入式顯示系統,告別凌亂難維護的螢幕代碼,讓有限資源發揮最大價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved