文章目錄
- 📁 文件結構建議
- 🔹 `eval.h` 表達式求值頭文件
- 🔹 `eval.c` 表達式求值實現文件(帶詳細注釋)
- 🔹 `ui.h` 界面頭文件
- 🔹 `ui.c` 界面實現文件
- 🔹 `main.c` 主函數入口
- ? 總結
項目效果:
這個項目是一個基于 LVGL(Light and Versatile Graphics Library) 的簡單計算器,實現了基本四則運算(含括號)的表達式求值,支持界面輸入、清除和計算結果顯示功能。
我將把項目分為以下幾個文件進行組織,并添加詳細注釋,便于維護和擴展:
📁 文件結構建議
calculator_project/
├── main.c // 主函數入口
├── ui.c // LVGL 界面實現
├── ui.h // 界面頭文件
├── eval.c // 表達式求值模塊
├── eval.h // 求值函數聲明
└── CMakeLists.txt / Makefile(視構建工具而定)
🔹 eval.h
表達式求值頭文件
#ifndef EVAL_H
#define EVAL_H// 表達式求值主接口
double eval_expression(const char *expr);#endif // EVAL_H
🔹 eval.c
表達式求值實現文件(帶詳細注釋)
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "eval.h"#define STACK_SIZE 64// 數字棧結構
typedef struct {double data[STACK_SIZE];int top;
} NumStack;// 運算符棧結構
typedef struct {char data[STACK_SIZE];int top;
} OpStack;// 數字棧操作
void num_push(NumStack *s, double val) { s->data[++s->top] = val; }
double num_pop(NumStack *s) { return s->data[s->top--]; }// 運算符棧操作
void op_push(OpStack *s, char op) { s->data[++s->top] = op; }
char op_pop(OpStack *s) { return s->data[s->top--]; }
char op_peek(OpStack *s) { return s->data[s->top]; }// 獲取運算符優先級
int precedence(char op) {switch (op) {case '(': return 0;case '+':case '-': return 1;case '*':case '/': return 2;default: return -1;}
}// 執行單個操作符計算
void apply_operator(NumStack *nums, char op) {double b = num_pop(nums);double a = num_pop(nums);double res = 0;switch (op) {case '+': res = a + b; break;case '-': res = a - b; break;case '*': res = a * b; break;case '/': res = (b == 0) ? 0 : a / b; break; // 簡單處理除零}num_push(nums, res);
}// 表達式求值主函數
double eval_expression(const char *expr) {NumStack nums = {.top = -1};OpStack ops = {.top = -1};char token[32];while (*expr) {if (isspace(*expr)) {expr++;} else if (isdigit(*expr) || *expr == '.') {int j = 0;while (isdigit(*expr) || *expr == '.') {token[j++] = *expr++;}token[j] = '\0';num_push(&nums, atof(token)); // 轉為 double 后壓入數字棧} else if (*expr == '(') {op_push(&ops, *expr++);} else if (*expr == ')') {while (ops.top != -1 && op_peek(&ops) != '(') {apply_operator(&nums, op_pop(&ops));}op_pop(&ops); // 彈出 '('expr++;} else if (strchr("+-*/", *expr)) {while (ops.top != -1 && precedence(op_peek(&ops)) >= precedence(*expr)) {apply_operator(&nums, op_pop(&ops));}op_push(&ops, *expr++);} else {expr++; // 非法字符跳過}}while (ops.top != -1) {apply_operator(&nums, op_pop(&ops));}return nums.data[0]; // 返回棧頂值為最終結果
}
🔹 ui.h
界面頭文件
#ifndef UI_H
#define UI_Hvoid create_calculator_ui(void); // 創建 UI 主函數#endif // UI_H
🔹 ui.c
界面實現文件
#include "lvgl.h"
#include "ui.h"
#include "eval.h" // 調用表達式求值
#include <stdio.h>
#include <string.h>static lv_obj_t * ta; // 輸入框全局指針// 按鈕標簽布局
static const char * btnm_map[] = {"7", "8", "9", "/", "\n","4", "5", "6", "*", "\n","1", "2", "3", "-", "\n","0", ".", "=", "+", "\n","(", ")", "C", ""
};// 按鈕點擊事件處理函數
static void btnm_event_cb(lv_event_t * e) {lv_event_code_t code = lv_event_get_code(e);lv_obj_t * obj = lv_event_get_target(e);const char * txt = lv_btnmatrix_get_btn_text(obj, lv_btnmatrix_get_selected_btn(obj));if (code == LV_EVENT_VALUE_CHANGED) {if (strcmp(txt, "=") == 0) {const char * expr = lv_textarea_get_text(ta);double result = eval_expression(expr); // 求值char buf[32];snprintf(buf, sizeof(buf), "%.4f", result); // 保留4位小數lv_textarea_set_text(ta, buf);} else if (strcmp(txt, "C") == 0) {lv_textarea_set_text(ta, ""); // 清空輸入框} else {lv_textarea_add_text(ta, txt); // 添加字符}}
}// 創建計算器界面
void create_calculator_ui(void) {// 容器居中并設置垂直布局lv_obj_t * cont = lv_obj_create(lv_scr_act());lv_obj_set_size(cont, 240, 320);lv_obj_center(cont);lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);// 輸入框ta = lv_textarea_create(cont);lv_obj_set_width(ta, 220);lv_textarea_set_one_line(ta, true);lv_textarea_set_placeholder_text(ta, "0");// 按鈕矩陣lv_obj_t * btnm = lv_btnmatrix_create(cont);lv_btnmatrix_set_map(btnm, btnm_map);lv_obj_set_size(btnm, 220, 200);lv_obj_add_event_cb(btnm, btnm_event_cb, LV_EVENT_ALL, NULL);
}
🔹 main.c
主函數入口
#include "lvgl.h"
#include "ui.h"int main(void) {lv_init(); // 初始化 LVGL// 這里需要根據平臺初始化顯示、輸入設備// lv_port_disp_init();// lv_port_indev_init();create_calculator_ui(); // 創建計算器界面while (1) {lv_timer_handler(); // LVGL 主循環usleep(5000); // 延時 5 ms(可根據平臺調整)}return 0;
}
? 總結
這個項目完整實現了一個簡潔的嵌入式圖形界面計算器:
- 使用 棧結構 + 中綴表達式求值算法 解析和計算表達式;
- 使用 LVGL 圖形庫實現可點擊界面與輸入框;
- 代碼 結構清晰、注釋詳細,適合嵌入式 UI 項目入門;
如果你還需要增加函數功能(比如 pow
, sqrt
等),可以在 eval.c
中擴展 apply_operator
并修改解析器。
計算器界面優化:
void create_calculator_ui(void) {// 設置屏幕背景為淺灰色lv_obj_set_style_bg_color(lv_scr_act(), lv_palette_lighten(LV_PALETTE_GREY, 4), 0);// 創建主容器lv_obj_t * cont = lv_obj_create(lv_scr_act());lv_obj_set_size(cont, 260, 340);lv_obj_center(cont);lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_COLUMN);lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);lv_obj_set_style_pad_all(cont, 10, 0);lv_obj_set_style_radius(cont, 10, 0);lv_obj_set_style_bg_color(cont, lv_color_white(), 0);lv_obj_set_style_bg_opa(cont, LV_OPA_COVER, 0);lv_obj_set_style_shadow_width(cont, 8, 0);// 輸入框ta = lv_textarea_create(cont);lv_obj_set_width(ta, lv_pct(100));lv_obj_set_height(ta, 50);lv_textarea_set_max_length(ta, 128);lv_textarea_set_one_line(ta, false);lv_textarea_set_placeholder_text(ta, "0");lv_textarea_set_align(ta, LV_TEXT_ALIGN_RIGHT);lv_textarea_set_text(ta, "");lv_obj_set_style_text_font(ta, &lv_font_montserrat_20, 0);lv_obj_set_style_radius(ta, 8, 0);lv_obj_set_style_bg_color(ta, lv_palette_lighten(LV_PALETTE_BLUE, 4), 0);lv_obj_set_style_text_color(ta, lv_color_white(), 0);// 按鈕矩陣lv_obj_t * btnm = lv_btnmatrix_create(cont);lv_btnmatrix_set_map(btnm, btnm_map);lv_obj_set_size(btnm, lv_pct(100), 220);lv_obj_add_event_cb(btnm, btnm_event_cb, LV_EVENT_ALL, NULL);// 設置按鈕樣式static lv_style_t style_btn;lv_style_init(&style_btn);lv_style_set_radius(&style_btn, 4);lv_style_set_bg_opa(&style_btn, LV_OPA_COVER);lv_style_set_bg_color(&style_btn, lv_palette_lighten(LV_PALETTE_GREY, 2));lv_style_set_text_font(&style_btn, &lv_font_montserrat_18);lv_obj_add_style(btnm, &style_btn, LV_PART_ITEMS);// 獲取 mapconst char ** map = lv_btnmatrix_get_map(btnm);uint16_t btn_cnt = 0;for (int i = 0; map[i] != NULL; i++) {btn_cnt++;}// 遍歷按鈕設置顏色for (uint16_t i = 0; i < btn_cnt; i++) {const char * label = map[i];if (strcmp(label, "=") == 0) {lv_btnmatrix_set_btn_ctrl(btnm, i, LV_BTNMATRIX_CTRL_CHECKABLE);lv_obj_set_style_bg_color(btnm, lv_palette_main(LV_PALETTE_GREEN), LV_PART_ITEMS | LV_STATE_CHECKED);} else if (strcmp(label, "C") == 0) {lv_btnmatrix_set_btn_ctrl(btnm, i, LV_BTNMATRIX_CTRL_CHECKABLE);lv_obj_set_style_bg_color(btnm, lv_palette_main(LV_PALETTE_RED), LV_PART_ITEMS | LV_STATE_CHECKED);}}
}
運行效果: