当前位置: 首页 > article >正文

从零构建嵌入式菜单库(一):原型探索——从一段单函数代码开始

从零构建嵌入式菜单库一原型探索——从一段单函数代码开始系列定位这是一套编写教程——我们将一起从零构建一个基于 U8g2 的嵌入式菜单库分析每一步的设计决策、收益与代价。最终产物u8g2_menu一个 3500 行、14 模块、12 示例工程的开源菜单库。前言在一切开始之前2024 年 6 月我面对一块 128×64 的 OLED 屏幕和几个按键。U8g2 已经正常驱动这块屏幕能画线、画圆、显示字符。但仅此而已——没有菜单系统没有页面切换没有任何交互框架。当时的代码大概是这样的// 主循环里直接硬编码u8g2_ClearBuffer(u8g2);u8g2_DrawStr(u8g2,0,10,1. Settings);u8g2_DrawStr(u8g2,0,30,2. About);u8g2_DrawStr(u8g2,0,50,3. Exit);u8g2_SendBuffer(u8g2);每加一个页面就要在主循环里塞一堆if/else上下翻页靠全局变量currentPage来回切——不出三天main.c就变成了意大利面条。我需要一个菜单库。但我不想只是用一个菜单库——我想写一个菜单库并且把这个过程记录下来。知识点预备在阅读本文之前需要先理解几个概念。1.1 U8g2 的绘制模型U8g2 是一个面向帧缓冲的图形库。它不是画一根线屏幕就立刻显示而是ClearBuffer() → [绘制操作] → SendBuffer()所有绘制操作DrawStr、DrawLine、DrawBox 等都作用在一个内存缓冲区上最后调用SendBuffer()一次性推送到屏幕。这带来一个关键约束每一帧的绘制逻辑必须集中完成。1.2 裁剪窗口 (Clip Window)U8g2 提供u8g2_SetClipWindow(u8g2, x0, y0, x1, y1)限制绘制操作只在指定矩形区域内生效。这是实现菜单在固定窗口内滚动的基础。u8g2_SetClipWindow(u8g2,0,0,128,64);// 只在屏幕范围内绘制u8g2_DrawStr(u8g2,0,80,hidden);// 超出裁剪区不会显示u8g2_SetMaxClipWindow(u8g2);// 恢复全屏裁剪1.3 回调函数 (Callback)回调函数就是把函数指针作为参数传递让被调用者在合适的时机回调这个函数。在 C 中这样声明// 声明一个函数指针类型typedefu8g2_uint_t(*menuItem_cb)(u8g2_t*,u8g2_uint_t,u8g2_uint_t,u8g2_uint_t);// 接收这个函数指针voidoled_display_menu(...,menuItem_cb menuItem){totalLengthmenuItem(u8g2,x,y,rowHeight);// 不确定调用的是哪个函数}这就是菜单库框架与业务逻辑解耦的基石。2. 原型代码一段能跑的单函数菜单以下是比仓库第一次正式提交更早的原型。它只有一个函数所有逻辑混在一起但它能跑——这就是一切的开端。charoutBuf[64];#ifndefABS#defineABS(s)((s)0?-(s):(s))#endifu8g2_uint_tposition0;// 目标滚动位置u8g2_uint_tspe3;// 滚动速度u8g2_uint_tmaxCharHeight0;// 最大字符高度u8g2_uint_ttotalLength;// 菜单内容总高度u8g2_uint_twindowHeight0;// 菜单窗口高度// 菜单内容绘制回调u8g2_uint_tmenuItem(u8g2_t*u8g2,u8g2_uint_tx,u8g2_uint_ty,u8g2_uint_trowHeight){sprintf(outBuf,c:%d,count);u8g2_DrawStr(u8g2,x,yrowHeight,outBuf);sprintf(outBuf,t:%d,timer);u8g2_DrawStr(u8g2,x,yrowHeight,outBuf);returny;// 返回最后一行的 Y 坐标}// 垂直滑块条voidu8g2_DrawVSliderBar(u8g2_t*u8g2,u8g2_uint_tx,u8g2_uint_ty,u8g2_uint_tw,u8g2_uint_th,floatschedule){if(schedule1)schedule1;if(schedule0)schedule0;u8g2_DrawVLine(u8g2,xw/2,y,h);u8g2_DrawBox(u8g2,x,yh*0.7*schedule,w,h*0.3);}// 翻页voidpageUp(){if(position)position-maxCharHeight;}voidpageDown(){if(positiontotalLength-windowHeight)positionmaxCharHeight;}// 主绘制函数voidoled_display_menu(u8g2_t*u8g2,u8g2_uint_tx,u8g2_uint_ty,u8g2_uint_tw,u8g2_uint_th,menuItem_cb menuItem){staticu8g2_uint_t_position0;// 当前实际滚动位置staticu8g2_uint_t_rowHeight0;// 当前实际行高if(w10)return;// 第一步设置裁剪窗口u8g2_SetClipWindow(u8g2,x,y,xw-6,yh);// 第二步平滑滚动动画if(ABS(position-_position)spe){if(position_position)_positionspe;if(position_position)_position-spe;}else{_positionposition;}// 第三步行高动画maxCharHeightu8g2_GetMaxCharHeight(u8g2);if(_rowHeightmaxCharHeight)_rowHeight3;if(_rowHeightmaxCharHeight)_rowHeight-1;// 第四步绘制菜单内容totalLengthmenuItem(u8g2,x,y-_position,_rowHeight)_position-y;windowHeighth;// 第五步恢复裁剪u8g2_SetMaxClipWindow(u8g2);// 第六步绘制垂直滑块if(totalLengthh){u8g2_DrawVSliderBar(u8g2,xw-5,y,5,h,(float)_position/(totalLength-h));}}voidoled_display(u8g2_t*u8g2){oled_display_menu(u8g2,0,0,128,32,menuItem);}3. 逐段拆解每一行在做什么3.1 菜单内容回调——“行模型”u8g2_uint_tmenuItem(u8g2_t*u8g2,u8g2_uint_tx,u8g2_uint_ty,u8g2_uint_trowHeight){sprintf(outBuf,c:%d,count);u8g2_DrawStr(u8g2,x,yrowHeight,outBuf);returny;}设计思路把菜单的每一行抽象为按给定 Y 坐标和行高绘制。回调函数不需要知道滚动位置只需要在传入的y坐标上逐行绘制然后返回最后的 Y。totalLength由这个返回值反算。优点简单直观一个函数指针搞定调用者完全控制绑定的上下文变量count、timer等缺点返回 Y 坐标的方式过于原始——如果回调里要绘制不同高度的菜单项调用者得自己算每行间距sprintf每次都要手动拼字符串类型不安全这个行模型后来被重构为menuItem_cb的void返回 u8g2_MenuDrawItemStart/End的包围模式。3.2 平滑滚动动画——追击算法if(ABS(position-_position)spe){if(position_position)_positionspe;if(position_position)_position-spe;}else{_positionposition;}知识点这是一个最简单的线性追击算法。position是目标位置_position是当前实际显示位置。每次调用时_position向position逼近spe个单位。时间轴: t0 t1 t2 t3 t4 目标: 100 100 100 100 100 实际: 0 3 6 9 12 ... 最终追到 100优点计算量极小三次比较 一次加减效果自然——加速启动、减速停止缺点追到目标后就粘住了没有弹性或回弹但这对于菜单来说反而是优点spe是固定步长长距离滚动时速度恒定不够平滑演化最终库中这个逻辑被封装进u8g2_menu_effect_t的run回调支持替换。3.3 行高动画——手写的展开/收起if(_rowHeightmaxCharHeight)_rowHeight3;// 展开if(_rowHeightmaxCharHeight)_rowHeight-1;// 收起更慢这里3和-1的不对称设计是有意的菜单展开要快用户想看到内容收起稍慢留一点视觉残留。缺点3和-1是魔法数字不可配置不可替换。这是原型最需要重构的部分之一。3.4 垂直滑块条——位置映射u8g2_DrawVSliderBar(u8g2,xw-5,y,5,h,(float)_position/(totalLength-h));滑块位置 当前滚动位置 / 可滚动总范围。这是一个归一化到 [0, 1] 的简单映射最终库中保留了这个核心公式。4. 原型暴露的核心问题清单带着这个原型跑了几天后以下问题开始变得无法忍受#问题症状根因1单实例不能同时有两个菜单static全局变量2类型混乱变量修改逻辑散落在回调中没有统一的变量绑定接口3魔法数字3/-1/spe3动画硬编码4无导航子菜单靠全局变量手动管理没有调用链追溯5按键耦合pageUp/pageDown裸函数没有按键抽象层6字符串拼装sprintf(outBuf, ...)没有格式化输出封装7选择器缺失选中的菜单项无视觉反馈没有选择器概念8无法编辑菜单项只能看不能改没有编辑状态管理这 8 个问题就是接下来 6 篇文章要逐个解决的。5. 为什么原型仍然重要原型虽然简陋但它完成了一件最关键的事验证了整个模型可行。✅ 裁剪窗口 回调模型 → 菜单可以滚动✅ 追击算法 → 动画可以平滑✅ 滑块映射 → 滚动位置可视化验证了这三个核心理念之后后续所有的重构——结构体化、模块化、事件化——都是在稳固的地基上盖楼。教训先写一段能跑的原型代码验证核心假设再考虑架构和抽象。过早优化是万恶之源但从不优化是慢性死亡。在下一篇中我们将把这堆全局变量和静态变量搬进一个结构体把单文件拆成多文件建立菜单库的正式架构。下一篇从零构建嵌入式菜单库二架构设计——从函数到结构体从单文件到模块

相关文章:

从零构建嵌入式菜单库(一):原型探索——从一段单函数代码开始

从零构建嵌入式菜单库(一):原型探索——从一段单函数代码开始 系列定位:这是一套编写教程——我们将一起从零构建一个基于 U8g2 的嵌入式菜单库,分析每一步的设计决策、收益与代价。 最终产物:u8g2_menu&am…...

电气设备、工业炉行业企业官网模板资源整理

做工业类企业网站的开发和设计时,很多人都会遇到一个痛点:行业适配的官网模板太少,要么风格老旧,要么和电气设备、工业炉这类硬核行业的调性不符,从零开发又耗时耗力。 今天就结合自己的建站经验,给大家整…...

【197期】视频一键转图文笔记

这期分享一个自己一直在用的视频转图文笔记工具,把视频文件和对应的字幕文件拖进去,一键就能生成详细的图文笔记。目前自媒体平台上的文章基本都靠这个流程来出,不用另外再写一遍,效率高了很多。使用方式很简单,把视频…...

FPGA微振动视频欧拉放大测量【附程序】

✨ 长期致力于微振动测量、欧拉视频放大、频率估计、Gabor、FPGA研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (1)改进线性欧拉视频放大算法的FPGA流水…...

Motrix WebExtension:浏览器下载加速的终极解决方案

Motrix WebExtension:浏览器下载加速的终极解决方案 【免费下载链接】motrix-webextension A browser extension for the Motrix Download Manager and its forks 项目地址: https://gitcode.com/gh_mirrors/mo/motrix-webextension 在当今数字时代&#xff…...

扩展卡尔曼滤波锂电池SOC估算【附代码】

✨ 长期致力于锂离子电池、SOC估算、锂离子电池建模、EKF算法研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (1)二阶RC等效电路建模与温度自适应参数修正…...

基于ChatGPT与飞书开放平台构建企业级智能聊天机器人实践指南

1. 项目概述:当ChatGPT遇上飞书,打造你的专属智能工作伙伴 最近在折腾一个挺有意思的项目,叫“chatgpt-for-chatbot-feishu”。简单来说,这就是一个桥梁,一个能让OpenAI的ChatGPT模型,直接接入到飞书&…...

面向科学计算Agent的Harness数值稳定性校验

面向科学计算Agent的Harness数值稳定性校验关键词:科学计算Agent、Harness框架、数值稳定性校验、数值误差溯源、Agent-数值系统交互、可复现科学、边界条件自动化测试摘要:随着大语言模型(LLM)与多模态AI的崛起,科学计…...

浏览器扩展实现AI提示词高效管理:从模板变量到工作流优化

1. 项目概述与核心价值最近在折腾AI工具链,发现一个痛点:每次和ChatGPT、Claude或者本地部署的大模型对话时,那些精心调试好的提示词(Prompt)总是散落在各个聊天窗口里,要么就是得手动复制粘贴,…...

FSearch终极指南:如何在Linux上实现秒级文件搜索

FSearch终极指南:如何在Linux上实现秒级文件搜索 【免费下载链接】fsearch A fast file search utility for Unix-like systems based on GTK3 项目地址: https://gitcode.com/gh_mirrors/fs/fsearch 还在为Linux系统中查找文件而烦恼吗?FSearch是…...

GitHub合规自动化:法律条款代码化与开源许可证检查实践

1. 项目概述:当法律条款遇上代码仓库最近在折腾一个挺有意思的项目,叫Clause-Logic/exoclaw-github。光看名字,你可能会有点懵——“Clause-Logic”听起来像是法律或合同条款的逻辑分析,“exoclaw”这个组合词有点科幻感&#xff…...

Altium Designer20 从零到一:新手必备的安装与核心功能上手指南

1. Altium Designer20安装全攻略 第一次接触Altium Designer20(简称AD20)时,我和大多数电子设计新手一样,面对这个专业软件既兴奋又忐忑。记得当时为了完成课程设计,我在宿舍折腾了整整一个下午才搞定安装。现在回想起…...

腾讯云秒杀活动是什么?2026年最新参与指南(附抢购技巧)

腾讯云秒杀活动是什么?怎么参与?本文将详细解析腾讯云秒杀活动规则、参与入口、抢购技巧及备选方案,助力大家低成本开启云端之旅! 一、活动介绍 腾讯云秒杀活动是腾讯云官方推出的限量限时抢购活动,主打高性价比的轻量…...

我的嵌入式项目踩坑记:用STM32的输入捕获功能给自制旋转编码器“把脉”

我的嵌入式项目踩坑记:用STM32的输入捕获功能给自制旋转编码器“把脉” 去年参加电子设计竞赛时,我遇到了一个棘手的问题——自制的旋转编码器信号抖动严重,导致电机转速测量误差高达15%。作为一名嵌入式开发者,这种精度显然无法接…...

Windows Cleaner终极方案:5分钟告别C盘爆红,系统性能飙升200%

Windows Cleaner终极方案:5分钟告别C盘爆红,系统性能飙升200% 【免费下载链接】WindowsCleaner Windows Cleaner——专治C盘爆红及各种不服! 项目地址: https://gitcode.com/gh_mirrors/wi/WindowsCleaner Windows Cleaner是一款专为W…...

在Unity中实现四旋翼飞行器的串级PID姿态控制

1. 为什么需要串级PID控制 四旋翼飞行器的姿态控制一直是无人机开发中的核心难题。我刚开始用Unity做飞行器仿真时,发现简单的单级PID控制器在应对突发气流扰动时,飞行器总是会出现明显的振荡和超调。有一次测试中,飞行器甚至因为过度修正导致…...

UI-TARS桌面版:用自然语言控制计算机的智能GUI助手

UI-TARS桌面版:用自然语言控制计算机的智能GUI助手 【免费下载链接】UI-TARS-desktop The Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra 项目地址: https://gitcode.com/GitHub_Trending/ui/UI-TARS-desktop …...

终极Photoshop图层批量导出指南:如何用免费脚本提升10倍工作效率

终极Photoshop图层批量导出指南:如何用免费脚本提升10倍工作效率 【免费下载链接】Photoshop-Export-Layers-to-Files-Fast This script allows you to export your layers as individual files at a speed much faster than the built-in script from Adobe. 项目…...

从巨头并购看FPGA技术演进与国产破局之路

1. 从两起世纪并购看FPGA的宿命与价值2015年,英特尔以167亿美元吞下Altera;2022年情人节,AMD用全股票交易正式将赛灵思(Xilinx)收入囊中。这两起震动半导体行业的并购案,表面看是巨头在“买公司”&#xff…...

为什么你需要Markdown Viewer:浏览器中预览Markdown文件的终极解决方案

为什么你需要Markdown Viewer:浏览器中预览Markdown文件的终极解决方案 【免费下载链接】markdown-viewer Markdown Viewer / Browser Extension 项目地址: https://gitcode.com/gh_mirrors/ma/markdown-viewer 还在为浏览器无法直接打开.md文件而烦恼吗&…...

STM32嵌入式开发入门:从硬件配置到项目实战的完整学习路径

1. 项目概述:从零到一,如何构建你的STM32知识体系很多刚接触嵌入式开发的朋友,拿到一块STM32开发板,看着满屏的英文手册和复杂的库函数,第一反应往往是“从哪开始?”。这感觉就像面对一座零件齐全但没图纸的…...

从LED驱动到Arduino编程:电子入门实战指南与避坑技巧

1. 从一颗LED到智能世界:我的电子入门实践心路很多朋友第一次接触电子制作,可能都是从让一颗LED亮起来开始的。我至今还记得十几年前,第一次用一节5号电池直接点亮一颗红色发光二极管时的那种兴奋。但很快,问题就来了:…...

航空发电机综合测试系统设计【附代码】

✨ 长期致力于航空发电机、测试系统、控制方法、LabVIEW研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (1)设计直流拖动调速系统的双闭环自适应模糊PID控…...

手工打造柔性LED眼罩:从SMD焊接入门到可穿戴电路实践

1. 项目概述:从零打造你的赛博格之眼如果你和我一样,对《银翼杀手》里那些闪烁着冷光的义眼,或是赛博朋克美学中标志性的发光装饰着迷,那么亲手制作一个属于自己的LED眼罩,绝对是一次令人兴奋的旅程。这不仅仅是一个酷…...

MEMS传感器机械臂姿态检测【附代码】

✨ 长期致力于MEMS传感器、机械臂、惯性测量单元、数据融合、姿态检测系统研究工作,擅长数据搜集与处理、建模仿真、程序编写、仿真设计。 ✅ 专业定制毕设、代码 ✅ 如需沟通交流,点击《获取方式》 (1)设计基于ICM20948的惯性测量…...

Akebi-GC游戏辅助工具:5个核心模块深度解析与实战应用指南

Akebi-GC游戏辅助工具:5个核心模块深度解析与实战应用指南 【免费下载链接】Akebi-GC (Fork) The great software for some game that exploiting anime girls (and boys). 项目地址: https://gitcode.com/gh_mirrors/ak/Akebi-GC Akebi-GC是一款专为特定游戏…...

从理论到ONNX:手把手带你拆解pytorch_quantization量化YOLOv7的每一个Tensor变化

从理论到ONNX:手把手拆解YOLOv7量化中的Tensor演变 量化技术正在重塑计算机视觉模型的部署格局。当我们将YOLOv7这样的复杂检测模型从FP32压缩到INT8时,每一个卷积核、每一层激活值的细微变化都可能影响最终检测框的坐标精度。本文将以手术刀般的精确度&…...

解锁专业阅读体验:Chrome本地Markdown文件智能渲染解决方案

解锁专业阅读体验:Chrome本地Markdown文件智能渲染解决方案 【免费下载链接】markdownReader markdownReader is a extention for chrome, used for reading markdown file. 项目地址: https://gitcode.com/gh_mirrors/ma/markdownReader 你是否曾经在Chrome…...

为什么你的旁遮普语语音听起来像“机械诵经”?ElevenLabs隐藏参数`stability=0.35`+`similarity_boost=0.72`调优公式首次披露

更多请点击: https://intelliparadigm.com 第一章:旁遮普语语音合成的“机械诵经”现象本质 当旁遮普语(Gurmukhi script)文本被输入主流TTS系统时,常出现一种高度重复、节奏僵硬、缺乏韵律起伏的输出效果——业内戏称…...

【ElevenLabs卡纳达文语音实战指南】:2024年唯一经生产环境验证的7步本地化部署方案

更多请点击: https://intelliparadigm.com 第一章:ElevenLabs卡纳达文语音技术概览与生产价值定位 ElevenLabs 作为全球领先的文本转语音(TTS)平台,自2023年Q4起正式支持卡纳达语(Kannada)&…...