LVGL简易计算器实战

news2025/5/14 23:06:31

文章目录

    • 📁 文件结构建议
    • 🔹 `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);
        }
    }
}

运行效果:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2375669.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录算法训练营第60期第三十四天打卡

大家好&#xff0c;我们今天的内容依旧是贪心算法&#xff0c;我们上次的题目主要是围绕多维问题&#xff0c;那种时候我们需要分开讨论&#xff0c;不要一起并发进行很容易顾此失彼&#xff0c;那么我们今天的问题主要是重叠区间问题&#xff0c;又是一种全新的贪心算法思想&a…

关于IDE的相关知识之二【插件推荐】

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于ide插件推荐的相关内容&#xff01…

Axure RP9:列表新增

文章目录 列表新增思路新增按钮操作说明保存新增交互设置列表新增 思路 利用中继器新增行实现列表新增功能 新增按钮操作说明 工具栏中添加新增图标及标签,在图标标签基础上添加热区;对热区添加鼠标单击时交互事件,同步插入如下动作:显示/隐藏动作,设置目标元件为新增窗…

06 mysql之DML

一、什么是DML DML 用于操作数据库中的数据。主要命令包括&#xff1a; INSERT&#xff1a;添加数据SELECT&#xff1a;查询数据UPDATE&#xff1a;修改数据DELETE&#xff1a;删除数据 二、插入数据&#xff08;INSERT&#xff09; 2.1 插入单条记录 -- 插入学生记录&…

【最新版】likeshop连锁点餐系统-PHP版+uniapp前端全开源

一.系统介绍 likeshop外卖点餐系统适用于茶饮类的外卖点餐场景&#xff0c;搭建自己的一点点、奈雪、喜茶点餐系统。 系统基于总部多门店的连锁模式&#xff0c;拥有门店独立管理后台&#xff0c;支持总部定价和门店定价LBS定位点餐&#xff0c;可堂食可外卖。无论运营还是二开…

纯Java实现反向传播算法:零依赖神经网络实战

在深度学习框架泛滥的今天,理解算法底层实现变得愈发重要。反向传播(Backpropagation)作为神经网络训练的基石算法,其实现往往被各种框架封装。本文将突破常规,仅用Java标准库实现完整BP算法,帮助开发者: 1) 深入理解BP数学原理。2) 掌握面向对象的神经网络实现。3) 构建可…

海纳思(Hi3798MV300)机顶盒遇到海思摄像头

海纳思机顶盒遇到海思摄像头&#xff0c;正好家里有个海思Hi3516的摄像头模组开发板&#xff0c;结合机顶盒来做个录像。 准备工作 海纳斯机顶盒摄像机模组两根网线、两个电源、路由器一块64G固态硬盘 摄像机模组和机顶盒都接入路由器的LAN口&#xff0c;确保网络正常通信。 …

Axure应用交互设计:表格跟随菜单移动效果(超长表单)

亲爱的小伙伴,在您浏览之前,烦请关注一下,在此深表感谢!本文如有帮助请订阅 Axure产品经理精品视频课已登录CSDN可点击学习https://edu.csdn.net/course/detail/40420 课程主题:表格跟随菜单移动 主要内容:表格交互设计、动态面板嵌套、拖动时事件、移动动作 应用场景…

7系列 之 I/O标准和终端技术

背景 《ug471_7Series_SelectIO.pdf》介绍了Xilinx 7 系列 SelectIO 的输入/输出特性及逻辑资源的相关内容。 第 1 章《SelectIO Resources》介绍了输出驱动器和输入接收器的电气特性&#xff0c;并通过大量实例解析了各类标准接口的实现。 第 2 章《SelectIO Logic Resource…

github 上的 CI/CD 的尝试

效果 步骤 新建仓库设置仓库的 page 新建一个 vite 的项目&#xff0c;改一下 vite.config.js 中的 base 工作流 在项目的根目录下新建一个 .github/workflows/ci.yml 文件&#xff0c;然后编辑一下内容 name: Build & Deploy Vue 3 Appon:push:branches: [main]permi…

yup 使用 3 - 利用 meta 实现表单字段与表格列的统一结构配置(适配 React Table)

yup 使用 3 - 利用 meta 实现表单字段与表格列的统一结构配置&#xff08;适配 React Table&#xff09; Categories: Tools Last edited time: May 11, 2025 7:45 PM Status: Done Tags: form validation, schema design, yup 本文介绍如何通过 Yup 的 meta() 字段&#xff0…

【OpenCV】imread函数的简单分析

目录 1.imread()1.1 imread()1.2 imread_()1.2.1 查找解码器&#xff08;findDecoder&#xff09;1.2.2 读取数据头&#xff08;JpegDecoder-->readHeader&#xff09;1.2.2.1 初始化错误信息&#xff08;jpeg_std_error&#xff09;1.2.2.2 创建jpeg解压缩对象&#xff08;…

【Linux实践系列】:进程间通信:万字详解共享内存实现通信

&#x1f525; 本文专栏&#xff1a;Linux Linux实践项目 &#x1f338;作者主页&#xff1a;努力努力再努力wz &#x1f4aa; 今日博客励志语录&#xff1a; 人生就像一场马拉松&#xff0c;重要的不是起点&#xff0c;而是坚持到终点的勇气 ★★★ 本文前置知识&#xff1a; …

【笔记】BCEWithLogitsLoss

工作原理 BCEWithLogitsLoss 是 PyTorch 中的一个损失函数&#xff0c;用于二分类问题。 它结合了 Sigmoid 激活函数和二元交叉熵&#xff08;Binary Cross Entropy, BCE&#xff09;损失在一个类中。 这不仅简化了代码&#xff0c;而且通过数值稳定性优化提高了模型训练的效…

关于Go语言的开发环境的搭建

1.Go开发环境的搭建 其实对于GO语言的这个开发环境的搭建的过程&#xff0c;类似于java的开发环境搭建&#xff0c;我们都是需要去安装这个开发工具包的&#xff0c;也就是俗称的这个SDK&#xff0c;他是对于我们的程序进行编译的&#xff0c;不然我们写的这个代码也是跑不起来…

Flutter PIP 插件 ---- 为iOS 重构PipController, Demo界面,更好的体验

接上文 Flutter PIP 插件 ---- 新增PipActivity&#xff0c;Android 11以下支持自动进入PIP Mode 项目地址 PIP&#xff0c; pub.dev也已经同步发布 pip 0.0.3&#xff0c;你的加星和点赞&#xff0c;将是我继续改进最大的动力 在之前的界面设计中&#xff0c;还原动画等体验一…

数据库管理-第325期 ADG Failover后该做啥(20250513)

数据库管理325期 2025-05-13 数据库管理-第325期 ADG Failover后该做啥&#xff08;20250513&#xff09;1 故障处置2 恢复原主库3 其他操作总结 数据库管理-第325期 ADG Failover后该做啥&#xff08;20250513&#xff09; 作者&#xff1a;胖头鱼的鱼缸&#xff08;尹海文&a…

SQLi-Labs 第21-24关

Less-21 http://127.0.0.1/sqli-labs/Less-21/ 1&#xff0c;抓个请求包看看 分析分析cookie被base64URL编码了&#xff0c;解码之后就是admin 2&#xff0c;那么这个网站的漏洞利用方式也是和Less-20关一样的&#xff0c;只是攻击语句要先base64编码&#xff0c;再URL编码&…

PVE WIN10直通无线网卡蓝牙

在 Proxmox VE (PVE) 中直通 Intel AC3165 无线网卡的 **蓝牙模块**&#xff08;通常属于 USB 设备&#xff0c;而非 PCIe 设备&#xff09;需要特殊处理&#xff0c;因为它的蓝牙部分通常通过 USB 连接&#xff0c;而 Wi-Fi 部分才是 PCIe 设备。以下是详细步骤&#xff1a; …

第六节第二部分:抽象类的应用-模板方法设计模式

模板方法设计模式的写法 建议使用final关键字修饰模板方法 总结 代码&#xff1a; People(父类抽象类) package com.Abstract3; public abstract class People {/*设计模板方法设计模式* 1.定义一个模板方法出来*/public final void write(){System.out.println("\t\t\t…