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

给你的STM32项目加个“眼睛”:1.8寸ST7735屏的GUI界面快速上手教程

从像素到交互:STM32驱动ST7735屏构建轻量级GUI全攻略

在智能家居控制面板、便携式医疗设备或工业仪表等嵌入式场景中,1.8寸ST7735液晶屏凭借其小巧尺寸和SPI接口优势,成为许多开发者的首选显示方案。但仅仅点亮屏幕显示测试图案远远不够——如何让这块屏幕真正"活"起来,承载用户界面(UI)的交互使命?本文将带您从底层驱动出发,逐步构建一个包含图形绘制、文字显示、控件交互的完整GUI框架,最终实现可操作的菜单系统。

1. GUI框架设计基础

1.1 显示核心抽象层

在开始绘制按钮和菜单前,需要建立统一的显示抽象层。基于ST7735的硬件特性,我们设计以下核心函数接口:

typedef struct { void (*DrawPixel)(uint16_t x, uint16_t y, uint16_t color); void (*FillRect)(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color); void (*DrawHLine)(uint16_t x, uint16_t y, uint16_t len, uint16_t color); void (*DrawVLine)(uint16_t x, uint16_t y, uint16_t len, uint16_t color); } DisplayDriver; extern DisplayDriver st7735_driver;

实现示例:

void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color) { Lcd_SetRegion(x, y, x+1, y+1); LCD_WriteData_16Bit(color); } DisplayDriver st7735_driver = { .DrawPixel = ST7735_DrawPixel, .FillRect = ST7735_FillRect, // 其他函数实现... };

1.2 颜色处理方案

ST7735采用RGB565格式,我们需要建立颜色转换和调色板系统:

颜色名称RGB565值宏定义
白色0xFFFFCOLOR_WHITE
黑色0x0000COLOR_BLACK
红色0xF800COLOR_RED
绿色0x07E0COLOR_GREEN
蓝色0x001FCOLOR_BLUE
黄色0xFFE0COLOR_YELLOW

颜色混合函数实现:

uint16_t Color_Blend(uint16_t color1, uint16_t color2, uint8_t ratio) { uint8_t r = ((color1 >> 11) * ratio + (color2 >> 11) * (255 - ratio)) / 255; uint8_t g = (((color1 >> 5) & 0x3F) * ratio + ((color2 >> 5) & 0x3F) * (255 - ratio)) / 255; uint8_t b = ((color1 & 0x1F) * ratio + (color2 & 0x1F) * (255 - ratio)) / 255; return (r << 11) | (g << 5) | b; }

2. 基本图形绘制引擎

2.1 高效绘图算法实现

在资源有限的STM32上,需要优化图形绘制算法。以下是改进的Bresenham画圆算法:

void GUI_DrawCircle(uint16_t x0, uint16_t y0, uint16_t r, uint16_t color) { int16_t f = 1 - r; int16_t ddF_x = 1; int16_t ddF_y = -2 * r; int16_t x = 0; int16_t y = r; st7735_driver.DrawPixel(x0, y0 + r, color); st7735_driver.DrawPixel(x0, y0 - r, color); st7735_driver.DrawPixel(x0 + r, y0, color); st7735_driver.DrawPixel(x0 - r, y0, color); while (x < y) { if (f >= 0) { y--; ddF_y += 2; f += ddF_y; } x++; ddF_x += 2; f += ddF_x; st7735_driver.DrawPixel(x0 + x, y0 + y, color); st7735_driver.DrawPixel(x0 - x, y0 + y, color); st7735_driver.DrawPixel(x0 + x, y0 - y, color); st7735_driver.DrawPixel(x0 - x, y0 - y, color); st7735_driver.DrawPixel(x0 + y, y0 + x, color); st7735_driver.DrawPixel(x0 - y, y0 + x, color); st7735_driver.DrawPixel(x0 + y, y0 - x, color); st7735_driver.DrawPixel(x0 - y, y0 - x, color); } }

2.2 抗锯齿技术实现

为提升小尺寸屏幕显示质量,可采用以下简易抗锯齿方法:

  1. 灰度混合法:在边缘像素点采用颜色混合
  2. 多重采样:对每个像素进行4次采样计算平均值
  3. 预计算亮度表:提前计算不同位置的透明度

抗锯齿线段绘制示例:

void GUI_DrawLine_AA(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color) { int16_t dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; int16_t dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; int16_t err = dx + dy, e2; for (;;) { uint8_t alpha = 255 - min(255, abs(err) * 2); st7735_driver.DrawPixel(x0, y0, Color_Blend(color, GetPixel(x0,y0), alpha)); if (x0 == x1 && y0 == y1) break; e2 = 2 * err; if (e2 >= dy) { err += dy; x0 += sx; } if (e2 <= dx) { err += dx; y0 += sy; } } }

3. 字体与图标系统

3.1 嵌入式字体解决方案

针对128x160分辨率,推荐使用以下字体方案:

  • ASCII字符:6x8点阵字体(占用768字节)
  • 中文显示:精选16x16常用汉字字库(约200个汉字)
  • 数字字体:特殊优化的7段式数字显示

字体数据结构示例:

typedef struct { uint8_t width; uint8_t height; uint8_t first_char; uint8_t char_count; const uint8_t *bitmap; const uint16_t *offset; } FontDef; extern FontDef Font_6x8; extern FontDef Font_16x16CN;

字体渲染函数:

void GUI_DrawChar(uint16_t x, uint16_t y, char c, FontDef font, uint16_t color, uint16_t bgcolor) { uint32_t idx = (c - font.first_char) * font.height; const uint8_t *ptr = &font.bitmap[font.offset[c - font.first_char]]; for (uint8_t row = 0; row < font.height; row++) { uint8_t bits = *ptr++; for (uint8_t col = 0; col < font.width; col++) { if (bits & (0x80 >> col)) { st7735_driver.DrawPixel(x + col, y + row, color); } else if (bgcolor != COLOR_TRANSPARENT) { st7735_driver.DrawPixel(x + col, y + row, bgcolor); } } } }

3.2 图标设计与存储优化

针对小尺寸屏幕,图标设计应遵循:

  1. 单色或双色设计:减少存储空间占用
  2. RLE压缩:对连续相同颜色进行压缩
  3. 共享调色板:多个图标共用同一颜色表

图标数据结构:

typedef struct { uint8_t width; uint8_t height; uint8_t color_bits; // 1或2 const uint8_t *data; } IconDef; #define ICON_HOME_WIDTH 16 #define ICON_HOME_HEIGHT 16 static const uint8_t icon_home_data[] = { 0x00,0x00,0x01,0x80,0x03,0xC0,0x07,0xE0, 0x0F,0xF0,0x1F,0xF8,0x3F,0xFC,0x7F,0xFE, // ... 其他数据 };

4. 交互控件实现

4.1 按钮控件系统

实现一个完整的按钮系统需要考虑:

  • 状态管理:正常、按下、禁用等状态
  • 事件回调:点击、长按等事件处理
  • 视觉反馈:按下效果、禁用样式

按钮数据结构:

typedef enum { BTN_STATE_NORMAL, BTN_STATE_PRESSED, BTN_STATE_DISABLED } ButtonState; typedef struct { uint16_t x; uint16_t y; uint16_t width; uint16_t height; const char *text; FontDef font; ButtonState state; void (*onClick)(void); } Button; void Button_Draw(Button *btn) { // 绘制背景 uint16_t bg_color = (btn->state == BTN_STATE_PRESSED) ? COLOR_BTN_PRESSED : COLOR_BTN_NORMAL; st7735_driver.FillRect(btn->x, btn->y, btn->width, btn->height, bg_color); // 绘制边框 st7735_driver.DrawHLine(btn->x, btn->y, btn->width, COLOR_BTN_BORDER); st7735_driver.DrawHLine(btn->x, btn->y+btn->height-1, btn->width, COLOR_BTN_BORDER); st7735_driver.DrawVLine(btn->x, btn->y, btn->height, COLOR_BTN_BORDER); st7735_driver.DrawVLine(btn->x+btn->width-1, btn->y, btn->height, COLOR_BTN_BORDER); // 绘制文字 uint16_t text_x = btn->x + (btn->width - Font_GetStringWidth(btn->text, btn->font)) / 2; uint16_t text_y = btn->y + (btn->height - btn->font.height) / 2; GUI_DrawString(text_x, text_y, btn->text, btn->font, COLOR_TEXT, COLOR_TRANSPARENT); }

4.2 菜单导航系统

实现层级式菜单的关键组件:

  1. 菜单项数据结构
typedef struct MenuItem { const char *label; const IconDef *icon; void (*action)(void); struct MenuItem *parent; struct MenuItem *children; uint8_t children_count; } MenuItem;
  1. 菜单渲染引擎
void Menu_Render(MenuItem *menu, uint8_t selected_idx) { st7735_driver.FillRect(0, 0, LCD_WIDTH, LCD_HEIGHT, COLOR_BG); // 绘制标题栏 st7735_driver.FillRect(0, 0, LCD_WIDTH, 16, COLOR_TITLE_BG); GUI_DrawString(4, 4, menu->parent ? menu->parent->label : "Main Menu", Font_6x8, COLOR_TITLE_TEXT, COLOR_TRANSPARENT); // 绘制菜单项 for (uint8_t i = 0; i < menu->children_count; i++) { uint16_t y = 20 + i * 20; // 绘制选中背景 if (i == selected_idx) { st7735_driver.FillRect(0, y, LCD_WIDTH, 18, COLOR_SELECTED_BG); } // 绘制图标 if (menu->children[i].icon) { GUI_DrawIcon(4, y + 2, menu->children[i].icon); } // 绘制文本 GUI_DrawString(24, y + 6, menu->children[i].label, Font_6x8, COLOR_TEXT, COLOR_TRANSPARENT); } }
  1. 菜单导航处理
MenuItem *current_menu = &root_menu; uint8_t selected_index = 0; void Menu_HandleInput(InputEvent event) { switch(event) { case INPUT_UP: selected_index = (selected_index > 0) ? selected_index - 1 : 0; break; case INPUT_DOWN: selected_index = (selected_index < current_menu->children_count - 1) ? selected_index + 1 : current_menu->children_count - 1; break; case INPUT_SELECT: if (current_menu->children[selected_index].action) { current_menu->children[selected_index].action(); } else if (current_menu->children[selected_index].children) { current_menu->children[selected_index].parent = current_menu; current_menu = &current_menu->children[selected_index]; selected_index = 0; } break; case INPUT_BACK: if (current_menu->parent) { current_menu = current_menu->parent; selected_index = 0; } break; } Menu_Render(current_menu, selected_index); }

5. 性能优化技巧

5.1 显存与刷新优化

针对ST7735无内置显存的特点,采用以下优化策略:

  1. 局部刷新技术
void GUI_UpdateRegion(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { static uint16_t last_x1 = 0, last_y1 = 0, last_x2 = 0, last_y2 = 0; // 合并刷新区域 if (last_x2 == 0) { last_x1 = x; last_y1 = y; last_x2 = x + w; last_y2 = y + h; } else { last_x1 = min(last_x1, x); last_y1 = min(last_y1, y); last_x2 = max(last_x2, x + w); last_y2 = max(last_y2, y + h); } // 每10ms或区域达到阈值时刷新 if (HAL_GetTick() - last_refresh > 10 || (last_x2 - last_x1) * (last_y2 - last_y1) > 1000) { Lcd_SetRegion(last_x1, last_y1, last_x2, last_y2); // ... 传输显示数据 ... last_x1 = last_y1 = last_x2 = last_y2 = 0; last_refresh = HAL_GetTick(); } }
  1. 双缓冲技术
uint16_t frame_buffer[LCD_WIDTH * LCD_HEIGHT]; void GUI_Flush(void) { Lcd_SetRegion(0, 0, LCD_WIDTH-1, LCD_HEIGHT-1); for (uint32_t i = 0; i < sizeof(frame_buffer)/sizeof(uint16_t); i++) { LCD_WriteData_16Bit(frame_buffer[i]); } }

5.2 触摸输入处理

当连接电阻式触摸屏时,需注意:

  1. 滤波算法
#define TOUCH_SAMPLES 5 uint16_t Touch_GetFilteredX(void) { uint32_t sum = 0; uint16_t samples[TOUCH_SAMPLES]; for (uint8_t i = 0; i < TOUCH_SAMPLES; i++) { samples[i] = Touch_GetRawX(); sum += samples[i]; } // 去掉最高和最低值后取平均 uint16_t min = samples[0], max = samples[0]; for (uint8_t i = 1; i < TOUCH_SAMPLES; i++) { if (samples[i] < min) min = samples[i]; if (samples[i] > max) max = samples[i]; } return (sum - min - max) / (TOUCH_SAMPLES - 2); }
  1. 触摸校准
typedef struct { float a, b, c; float d, e, f; } TouchCalibration; TouchCalibration calib; void Touch_Calibrate(Point display[3], Point touch[3]) { // 计算校准参数 float div = (touch[0].x - touch[2].x) * (touch[1].y - touch[2].y) - (touch[1].x - touch[2].x) * (touch[0].y - touch[2].y); calib.a = ((display[0].x - display[2].x) * (touch[1].y - touch[2].y) - (display[1].x - display[2].x) * (touch[0].y - touch[2].y)) / div; calib.b = ((touch[0].x - touch[2].x) * (display[1].x - display[2].x) - (display[0].x - display[2].x) * (touch[1].x - touch[2].x)) / div; calib.c = display[2].x - calib.a * touch[2].x - calib.b * touch[2].y; // 同理计算d,e,f... } Point Touch_GetDisplayPos(void) { Point touch = {Touch_GetFilteredX(), Touch_GetFilteredY()}; Point display; display.x = calib.a * touch.x + calib.b * touch.y + calib.c; display.y = calib.d * touch.x + calib.e * touch.y + calib.f; return display; }

6. 完整项目集成

6.1 系统架构设计

推荐的项目文件结构:

/project /Drivers st7735.c # 底层驱动 touch.c # 触摸输入 /GUI gui_core.c # 核心绘图函数 gui_widgets.c # 控件实现 gui_fonts.c # 字体数据 gui_icons.c # 图标数据 /App menu_system.c # 菜单逻辑 main_ui.c # 主界面 /Inc gui_config.h # 显示配置

6.2 主程序流程

int main(void) { // 硬件初始化 HAL_Init(); SystemClock_Config(); ST7735_Init(); Touch_Init(); // GUI初始化 GUI_Init(); Font_Init(); Menu_Init(); // 主循环 while (1) { InputEvent event = Touch_GetEvent(); if (event != INPUT_NONE) { Menu_HandleInput(event); } // 其他任务... HAL_Delay(10); } }

在STM32F103C8T6上实测,该GUI系统占用资源如下:

  • Flash占用:约28KB(含字库)
  • RAM占用:约5KB(含帧缓冲)
  • 主循环周期:<15ms(满足60Hz刷新率需求)

通过模块化设计,这个GUI框架可以轻松移植到其他SPI接口的LCD屏上,只需实现底层的DisplayDriver接口即可。对于更复杂的项目,可以考虑添加动画系统、主题支持等高级特性,但需注意STM32F103的资源限制。

http://www.jsqmd.com/news/1009692/

相关文章:

  • 物理AI落地实战:VLA模型的Agentic Skills增强方案
  • STM32F407内存不够用?手把手教你用.sct文件把FreeRTOS塞进CCM(64K专属RAM)
  • 2026长沙二手房整体翻新技术测评:长沙旧房改造价格/长沙旧房改造公司/长沙旧房改造工期/三家实力厂商工艺拆解 - 优质品牌商家
  • SketchUp STL插件终极指南:3D打印工作流的革命性突破
  • GPT-4参数量与MoE激活机制的工程真相
  • K210的KPU到底有多强?实测YOLO v2物体检测的帧率与功耗,对比树莓派Zero 2 W
  • Triton模型服务化与持续可观测性实战指南
  • 终极指南:如何免费使用Duplicity编辑器修改《缺氧》游戏存档
  • Python实盘组合优化:从cvxpy到PyPortfolioOpt的落地工作流
  • 在Visual Studio 2022里,用C#和OpenTK 4.x画个会转的彩色立方体(附完整代码)
  • 乌鲁木齐驾驶式洗地车2025年度品牌推荐榜 - 工业清洁测评社
  • Embedding实战指南:从词向量到语义搜索的工业级落地
  • CANN图引擎ge核心技术深度解析:从图编译优化到算子融合的昇腾NPU推理性能全链路提升实战
  • Java中String内部排序方法
  • 别再踩坑了!STM32F103C8T6的PB3/PB4/PA15引脚当普通IO口用的完整配置流程(附MDK设置截图)
  • 摘要任务下的RLHF实战:从reward建模到PPO收敛的可复现手记
  • 拆解一个开源四轴:Drone-Mercury硬件选型与成本控制实战分析
  • GPT-4的2%参数真相:MoE稀疏激活原理与工程实践
  • 2026成都工商代办注册公司机构深度盘点:哪家更懂本地中小企业的真实需求? - 优质品牌商家
  • Vue3 Marquee 4.2.2:零依赖动画组件的架构解析与性能优化
  • JWST揭示LRDs光谱多样性及其宇宙学意义
  • 别再死记硬背了!一张图看懂X.25、帧中继、ATM的核心区别与联系
  • 14个NLP分词库底层机制深度对比:字符归一化到子词生成全解析
  • Wallpaper Engine壁纸备份指南:如何将pkg格式动态壁纸转为永久保存的JPG/PNG图片
  • Java毕设项目:基于 SpringBoot 的智汇家园物业故障处理管理系统 智慧小区物业服务报修运维平台开发研究 (源码+文档,讲解、调试运行,定制等)
  • 别再傻傻分不清了!用大白话和一张图讲透图形渲染里的AABB、KD树和BVH
  • MAA明日方舟助手:高效智能的全日常自动化解决方案
  • 用Streamlit构建生产级RAG问答应用的完整实践
  • 雷电模拟器dnconsole命令详解:从文件管理到性能调优,一篇搞定所有隐藏功能
  • 别再乱买了!手把手教你读懂SD卡/TF卡上的神秘标识(V30、A2、UHS-I都是啥?)