新闻详情

新闻详情

首页 / 资讯中心 / 详情

告别点阵图:用STM32+ILI9341实现你的第一个简易GUI界面(附Proteus仿真文件)

发布时间:2026/9/9 23:21:55来源:尧图网络
告别点阵图:用STM32+ILI9341实现你的第一个简易GUI界面(附Proteus仿真文件)
从像素到界面STM32ILI9341构建轻量级GUI的实战指南在嵌入式开发中图形用户界面(GUI)往往是提升产品交互体验的关键一环。对于已经掌握ILI9341液晶屏基础显示的开发者而言如何利用简单的画点、画线函数构建出实用的界面组件是一个值得深入探讨的话题。本文将带你从底层绘图函数出发逐步实现按钮、进度条、数据图表等常见UI元素最终完成一个温度监控仪表盘的完整案例。1. GUI基础构建模块解析1.1 核心绘图函数优化原始代码中提供的GUI_DrawPoint和LCD_Fill等函数是构建GUI的基石但在实际应用中我们需要对这些基础函数进行优化和扩展// 优化后的画点函数增加边界检查 void GUI_DrawPoint_Safe(uint16_t x, uint16_t y, uint16_t color) { if(x LCD_WIDTH || y LCD_HEIGHT) return; LCD_SetCursor(x, y); LCD_DrawPoint_16Bit(color); } // 带透明度处理的区域填充 void LCD_Fill_Alpha(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, uint16_t color, uint8_t alpha) { uint16_t i, j; uint16_t width ex - sx 1; uint16_t height ey - sy 1; LCD_SetWindows(sx, sy, ex-1, ey-1); for(i 0; i height; i) { for(j 0; j width; j) { if(alpha 255) { LCD_WR_DATA(color); } else { uint16_t bg_color LCD_ReadPoint(sxj, syi); uint16_t mixed Color_Blend(color, bg_color, alpha); LCD_WR_DATA(mixed); } } } LCD_SetWindows(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1); }提示在实际项目中建议将常用颜色定义为宏如#define COLOR_BACKGROUND 0xFFFF方便统一管理和修改。1.2 基本几何图形绘制基于基础函数我们可以构建更高级的绘图功能抗锯齿直线算法改进原始LCD_DrawLine函数实现更平滑的线条圆角矩形绘制结合直线和圆弧绘制实现现代UI常见的圆角效果多边形填充使用扫描线算法实现任意多边形的填充// 圆角矩形绘制实现 void GUI_DrawRoundRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t radius, uint16_t color) { // 绘制四条直线边 LCD_DrawLine(x1radius, y1, x2-radius, y1); // 上边 LCD_DrawLine(x1radius, y2, x2-radius, y2); // 下边 LCD_DrawLine(x1, y1radius, x1, y2-radius); // 左边 LCD_DrawLine(x2, y1radius, x2, y2-radius); // 右边 // 绘制四个圆角 gui_circle(x1radius, y1radius, color, radius, 0); // 左上 gui_circle(x2-radius, y1radius, color, radius, 0); // 右上 gui_circle(x1radius, y2-radius, color, radius, 0); // 左下 gui_circle(x2-radius, y2-radius, color, radius, 0); // 右下 }2. UI组件设计与实现2.1 按钮控件开发按钮是交互界面中最基础的组件一个完整的按钮实现需要考虑以下要素视觉状态正常、按下、禁用等不同状态的外观触摸反馈点击效果和状态转换事件处理点击回调函数的注册和执行typedef struct { uint16_t x, y, width, height; uint16_t normal_color, pressed_color; uint8_t radius; char* text; void (*onClick)(void); uint8_t is_pressed; } Button; void Button_Draw(Button* btn) { uint16_t color btn-is_pressed ? btn-pressed_color : btn-normal_color; // 绘制圆角矩形背景 LCD_Fill(btn-x, btn-y, btn-xbtn-width, btn-ybtn-height, color); GUI_DrawRoundRect(btn-x, btn-y, btn-xbtn-width, btn-ybtn-height, btn-radius, COLOR_BLACK); // 计算文字居中位置 uint16_t text_x btn-x (btn-width - strlen(btn-text)*8)/2; uint16_t text_y btn-y (btn-height - 16)/2; LCD_ShowString(text_x, text_y, 16, (uint8_t*)btn-text, 0); } uint8_t Button_CheckTouch(Button* btn, uint16_t touch_x, uint16_t touch_y) { if(touch_x btn-x touch_x btn-xbtn-width touch_y btn-y touch_y btn-ybtn-height) { btn-is_pressed 1; Button_Draw(btn); return 1; } return 0; }2.2 进度条实现技巧进度条是数据可视化的重要组件实现时需要考虑平滑动画使用插值算法实现数值变化的平滑过渡多种样式直线型、圆形、分段式等不同表现形式文本显示在进度条上叠加百分比数值typedef struct { uint16_t x, y, width, height; uint16_t min_value, max_value; uint16_t current_value; uint16_t bg_color, fg_color; uint8_t is_vertical; } ProgressBar; void ProgressBar_Update(ProgressBar* bar, uint16_t new_value) { // 限制数值范围 if(new_value bar-min_value) new_value bar-min_value; if(new_value bar-max_value) new_value bar-max_value; // 计算填充比例 float ratio (float)(new_value - bar-min_value) / (bar-max_value - bar-min_value); // 绘制背景 LCD_Fill(bar-x, bar-y, bar-xbar-width, bar-ybar-height, bar-bg_color); // 绘制进度 if(bar-is_vertical) { uint16_t fill_height bar-height * ratio; LCD_Fill(bar-x, bar-ybar-height-fill_height, bar-xbar-width, bar-ybar-height, bar-fg_color); } else { uint16_t fill_width bar-width * ratio; LCD_Fill(bar-x, bar-y, bar-xfill_width, bar-ybar-height, bar-fg_color); } // 绘制边框 GUI_DrawRectangle(bar-x, bar-y, bar-xbar-width, bar-ybar-height); // 显示百分比文本 char percent_str[10]; sprintf(percent_str, %d%%, (int)(ratio*100)); uint16_t text_x bar-x (bar-width - strlen(percent_str)*8)/2; uint16_t text_y bar-y (bar-height - 16)/2; LCD_ShowString(text_x, text_y, 16, (uint8_t*)percent_str, 1); }3. 数据可视化实战3.1 简易曲线图实现在嵌入式设备上实现数据曲线显示需要考虑内存和性能限制数据缓冲使用环形缓冲区存储历史数据坐标变换将实际数值映射到屏幕坐标动态更新实现平滑的曲线滚动效果#define GRAPH_WIDTH 240 #define GRAPH_HEIGHT 120 #define GRAPH_MARGIN 20 #define MAX_DATA_POINTS 100 typedef struct { uint16_t x, y; // 图表位置 uint16_t width, height; // 图表尺寸 uint16_t bg_color; // 背景色 uint16_t line_color; // 线条颜色 float min_val, max_val; // 数值范围 float values[MAX_DATA_POINTS]; // 数据缓冲区 uint8_t data_count; // 当前数据点数 } LineGraph; void LineGraph_Init(LineGraph* graph) { memset(graph-values, 0, sizeof(graph-values)); graph-data_count 0; } void LineGraph_AddValue(LineGraph* graph, float value) { // 移动现有数据 if(graph-data_count MAX_DATA_POINTS) { memmove(graph-values, graph-values1, sizeof(float)*(MAX_DATA_POINTS-1)); graph-data_count MAX_DATA_POINTS - 1; } // 添加新数据 graph-values[graph-data_count] value; // 更新数值范围 if(value graph-min_val) graph-min_val value; if(value graph-max_val) graph-max_val value; } void LineGraph_Draw(LineGraph* graph) { // 绘制背景和坐标轴 LCD_Fill(graph-x, graph-y, graph-xgraph-width, graph-ygraph-height, graph-bg_color); GUI_DrawRectangle(graph-x, graph-y, graph-xgraph-width, graph-ygraph-height); // 绘制网格线 for(uint16_t i 1; i 5; i) { uint16_t y graph-y graph-height - i*graph-height/5; LCD_DrawLine(graph-x, y, graph-xgraph-width, y, COLOR_LIGHTGRAY); } // 绘制数据曲线 if(graph-data_count 2) return; float val_range graph-max_val - graph-min_val; if(val_range 0) val_range 1; // 避免除零 for(uint8_t i 1; i graph-data_count; i) { uint16_t x1 graph-x (i-1)*graph-width/(MAX_DATA_POINTS-1); uint16_t y1 graph-y graph-height - (uint16_t)((graph-values[i-1]-graph-min_val)/val_range*graph-height); uint16_t x2 graph-x i*graph-width/(MAX_DATA_POINTS-1); uint16_t y2 graph-y graph-height - (uint16_t)((graph-values[i]-graph-min_val)/val_range*graph-height); LCD_DrawLine(x1, y1, x2, y2, graph-line_color); } }3.2 仪表盘设计要点温度监控仪表盘需要结合多种UI元素模拟表盘使用圆弧和指针表示当前值数字显示清晰显示精确数值状态指示使用颜色变化表示不同温度区间typedef struct { uint16_t center_x, center_y; uint16_t radius; float min_temp, max_temp; float current_temp; uint16_t needle_color; uint16_t dial_color; } TemperatureDial; void TemperatureDial_Draw(TemperatureDial* dial) { // 绘制外圆 gui_circle(dial-center_x, dial-center_y, dial-dial_color, dial-radius, 0); // 绘制刻度 for(int angle -120; angle 120; angle 30) { float rad angle * M_PI / 180.0; uint16_t x1 dial-center_x (dial-radius-10) * cos(rad); uint16_t y1 dial-center_y (dial-radius-10) * sin(rad); uint16_t x2 dial-center_x dial-radius * cos(rad); uint16_t y2 dial-center_y dial-radius * sin(rad); LCD_DrawLine(x1, y1, x2, y2, COLOR_BLACK); // 刻度标签 float temp dial-min_temp (angle 120) * (dial-max_temp - dial-min_temp) / 240.0; char temp_str[10]; sprintf(temp_str, %.0f, temp); x1 dial-center_x (dial-radius-25) * cos(rad) - 10; y1 dial-center_y (dial-radius-25) * sin(rad) - 8; LCD_ShowString(x1, y1, 16, (uint8_t*)temp_str, 1); } // 绘制指针 float ratio (dial-current_temp - dial-min_temp) / (dial-max_temp - dial-min_temp); float angle -120 ratio * 240; // -120°到120° float rad angle * M_PI / 180.0; uint16_t x1 dial-center_x dial-radius * 0.2 * cos(rad M_PI); uint16_t y1 dial-center_y dial-radius * 0.2 * sin(rad M_PI); uint16_t x2 dial-center_x dial-radius * 0.8 * cos(rad); uint16_t y2 dial-center_y dial-radius * 0.8 * sin(rad); LCD_DrawLine(x1, y1, x2, y2, dial-needle_color); LCD_DrawLine(x11, y1, x21, y2, dial-needle_color); LCD_DrawLine(x1, y11, x2, y21, dial-needle_color); // 中心点 gui_circle(dial-center_x, dial-center_y, COLOR_RED, 5, 1); // 显示当前温度值 char temp_str[20]; sprintf(temp_str, %.1f C, dial-current_temp); uint16_t text_x dial-center_x - strlen(temp_str)*8/2; uint16_t text_y dial-center_y dial-radius/2; LCD_ShowString(text_x, text_y, 16, (uint8_t*)temp_str, 1); }4. Proteus仿真与性能优化4.1 Proteus仿真配置技巧在Proteus中仿真STM32驱动ILI9341时需要注意以下配置要点元件选择STM32F103C8或根据实际项目选择ILI9341 LCD模块必要的电阻电容连接方式SPI接口连接SCK, MOSI, CS, DC, RESET背光控制引脚连接仿真参数CPU频率设置为72MHz与实际硬件一致启用足够的内存用于帧缓冲注意Proteus中的ILI9341模型可能响应速度比实际硬件慢建议在代码中添加适当的延时。4.2 内存与性能优化策略嵌入式GUI开发常受限于资源以下优化方法值得考虑部分刷新只更新屏幕上发生变化的部分而非全屏刷新缓冲策略双缓冲技术如果内存允许脏矩形标记更新绘制优化避免浮点运算使用定点数代替预计算常用数值使用查表法替代实时计算// 使用查表法优化三角函数计算 const int16_t sin_table[91] { 0, 18, 36, 54, 71, 89, 107, 125, 143, 160, 178, 195, 213, 230, 247, 265, 282, 299, 316, 333, // ... 省略部分数据 1000 }; int16_t fast_sin(int16_t angle) { angle % 360; if(angle 0) angle 360; if(angle 90) return sin_table[angle]; if(angle 180) return sin_table[180-angle]; if(angle 270) return -sin_table[angle-180]; return -sin_table[360-angle]; } int16_t fast_cos(int16_t angle) { return fast_sin(angle 90); }4.3 完整案例温度监控界面结合前述组件我们可以构建一个完整的温度监控界面typedef struct { TemperatureDial dial; LineGraph graph; Button btn_up, btn_down; float target_temp; uint8_t mode; // 0自动, 1手动 } TemperatureUI; void TemperatureUI_Init(TemperatureUI* ui) { // 初始化仪表盘 ui-dial.center_x 120; ui-dial.center_y 120; ui-dial.radius 100; ui-dial.min_temp 10.0; ui-dial.max_temp 40.0; ui-dial.current_temp 25.0; ui-dial.needle_color COLOR_RED; ui-dial.dial_color COLOR_CYAN; // 初始化曲线图 ui-graph.x 0; ui-graph.y 200; ui-graph.width 240; ui-graph.height 80; ui-graph.bg_color COLOR_WHITE; ui-graph.line_color COLOR_BLUE; ui-graph.min_val 10.0; ui-graph.max_val 40.0; LineGraph_Init(ui-graph); // 初始化按钮 ui-btn_up.x 180; ui-btn_up.y 50; ui-btn_up.width 50; ui-btn_up.height 30; ui-btn_up.normal_color COLOR_GREEN; ui-btn_up.pressed_color COLOR_DARKGREEN; ui-btn_up.radius 5; ui-btn_up.text ; ui-btn_up.is_pressed 0; ui-btn_down.x 180; ui-btn_down.y 90; ui-btn_down.width 50; ui-btn_down.height 30; ui-btn_down.normal_color COLOR_RED; ui-btn_down.pressed_color COLOR_DARKRED; ui-btn_down.radius 5; ui-btn_down.text -; ui-btn_down.is_pressed 0; ui-target_temp 25.0; ui-mode 0; } void TemperatureUI_Update(TemperatureUI* ui, float current_temp) { // 更新当前温度 ui-dial.current_temp current_temp; LineGraph_AddValue(ui-graph, current_temp); // 绘制所有组件 LCD_Fill(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1, COLOR_BACKGROUND); TemperatureDial_Draw(ui-dial); LineGraph_Draw(ui-graph); Button_Draw(ui-btn_up); Button_Draw(ui-btn_down); // 显示模式信息 char mode_str[20]; sprintf(mode_str, Mode: %s, ui-mode ? Manual : Auto); LCD_ShowString(10, 10, 16, (uint8_t*)mode_str, 1); // 显示目标温度 char target_str[20]; sprintf(target_str, Target: %.1f C, ui-target_temp); LCD_ShowString(10, 30, 16, (uint8_t*)target_str, 1); }
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

LoopX:长周期Agent任务的控制平面与状态调度系统 2026/9/10 6:35:40

LoopX:长周期Agent任务的控制平面与状态调度系统

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Django图书管理系统实战:模型设计、Admin定制与生产部署 2026/9/10 6:35:40

Django图书管理系统实战:模型设计、Admin定制与生产部署

简介:这是一份面向高校计算机专业本科生的Django毕业设计实战项目,完整实现了一个功能完备的图书管理系统,兼顾基础管理与智能推荐能力,适用于课程设计、毕设开发与Web全栈入门实践。资源包共2000个文件,约10MB&#x…

阅读更多 →
MATLAB实现InSAR全链路仿真:从SLC建模到形变反演 2026/9/10 6:35:40

MATLAB实现InSAR全链路仿真:从SLC建模到形变反演

简介:本资源是一套面向遥感图像处理学习者与InSAR初学者的MATLAB实战代码包,聚焦干涉合成孔径雷达(InSAR)全流程仿真与算法实现,助力掌握地表形变监测、高程反演等核心应用。资源共19个文件,含12个核心.m脚…

阅读更多 →
数学可视化工具选型指南:按场景挑工具,一张表看完 2026/9/10 6:35:40

数学可视化工具选型指南:按场景挑工具,一张表看完

数学可视化工具选型指南:按场景挑工具,一张表看完 【免费下载链接】awesome-math A curated list of awesome mathematics resources 项目地址: https://gitcode.com/GitHub_Trending/aw/awesome-math 公式和符号堆在一起时,抽象概念很…

阅读更多 →
在裸机 Rust 中实现 `log` crate 日志记录:以 comprehensive-rust 应用处理器篇的 `Log` trait 实战为例 2026/9/10 6:35:40

在裸机 Rust 中实现 `log` crate 日志记录:以 comprehensive-rust 应用处理器篇的 `Log` trait 实战为例

在裸机 Rust 中实现 log crate 日志记录:以 comprehensive-rust 应用处理器篇的 Log trait 实战为例 【免费下载链接】comprehensive-rust This is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust. 项…

阅读更多 →
Nx nx import 实战指南:将外部仓库与 Git 历史完整迁入 Monorepo 2026/9/10 6:32:39

Nx nx import 实战指南:将外部仓库与 Git 历史完整迁入 Monorepo

Nx nx import 实战指南:将外部仓库与 Git 历史完整迁入 Monorepo 【免费下载链接】nx The Monorepo Platform that amplifies both developers and AI agents. Nx optimizes your builds, scales your CI, and fixes failed PRs automatically. Ship in half the ti…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞