文章目录
- 📁 文件结构建议
- 🔹 `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_H
void 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);
// 获取 map
const 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);
}
}
}
运行效果: