STM32F103与u8g2库打造0.96寸OLED动态菜单系统实战在嵌入式设备的人机交互设计中菜单系统是最常见的交互方式之一。传统的静态菜单往往显得生硬呆板而加入流畅的动画效果不仅能提升用户体验还能让产品更具专业感。本文将带你从零开始基于STM32F103C8T6和0.96寸IIC OLED屏利用u8g2图形库实现一个支持多级切换动画的菜单系统。1. 硬件准备与环境搭建1.1 所需硬件组件要完成这个项目你需要准备以下硬件STM32F103C8T6最小系统板俗称蓝莓派Cortex-M3内核72MHz主频64KB Flash20KB RAM丰富的GPIO和外设接口0.96寸OLED显示屏分辨率128x64驱动芯片SSD1306接口I2C仅需4根线其他配件3个轻触按键用于菜单导航杜邦线若干USB转TTL模块用于调试1.2 开发环境配置我们使用STM32CubeIDE作为开发环境以下是具体配置步骤安装STM32CubeIDE# 在Linux下安装示例 sudo apt install ./en.st-stm32cubeide_1.11.0_20220325_1055_amd64.deb_bundle.sh创建新工程选择STM32F103C8Tx芯片配置时钟树为72MHz启用I2C1外设OLED使用u8g2库移植// 在CubeMX中配置I2C后添加u8g2库文件 #include u8g2.h // u8g2初始化代码 u8g2_t u8g2; U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset*/ U8X8_PIN_NONE);提示u8g2库支持多种显示控制器确保选择正确的初始化函数SSD1306对应型号。2. u8g2图形库深度解析2.1 u8g2库架构与特性u8g2是一个功能强大的单色图形库具有以下特点多控制器支持支持SSD1306、SH1106等多种OLED驱动芯片多接口兼容I2C、SPI、8080并行接口等丰富的绘图API// 常用绘图函数示例 u8g2_DrawBox(u8g2, x, y, w, h); // 绘制实心矩形 u8g2_DrawCircle(u8g2, x, y, r, opt); // 绘制圆 u8g2_DrawXBMP(u8g2, x, y, w, h, bitmap); // 显示XBM格式位图2.2 性能优化技巧在STM32F103上使用u8g2需要注意以下性能优化点缓冲区策略全缓冲模式U8G2_R0需要1KB RAM分页模式节省内存但刷新较慢字体选择// 推荐使用内置字体避免自定义字体占用过多Flash u8g2_SetFont(u8g2, u8g2_font_6x10_tf); // 6x10像素字体 u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols); // Unicode符号字体刷新优化// 局部刷新而非全屏刷新 u8g2_SetClipWindow(u8g2, x0, y0, x1, y1); u8g2_SendBuffer(u8g2);3. 菜单系统设计与实现3.1 菜单数据结构设计我们采用分层式菜单结构使用状态机模型管理菜单状态typedef struct { char *text; // 菜单项文本 uint8_t icon[128]; // 32x32图标数据 void (*action)(void); // 点击动作函数 MenuItem *child; // 子菜单数组 MenuItem *parent; // 父菜单 } MenuItem; // 示例菜单定义 MenuItem mainMenu[] { {Voltage, icon_voltage, NULL, voltageSubMenu, NULL}, {Current, icon_current, NULL, currentSubMenu, NULL}, {Settings, icon_settings, NULL, settingsMenu, NULL} };3.2 动画效果实现原理我们实现了两种核心动画效果进入动画渐显uint8_t ui_come(void) { int len 8 * u8g2_GetBufferTileHeight(u8g2) * u8g2_GetBufferTileWidth(u8g2); uint8_t *p u8g2_GetBufferPtr(u8g2); for(int i0; ilen; i) { p[i] p[i] (rand()%0xff) c_come_temp; } c_come_temp - 2; return (c_come_temp 0) ? 0 : 1; }退出动画渐隐uint8_t ui_disapper(void) { // 类似ui_come但反向处理 uc_disapper_temp 2; return (uc_disapper_temp 8) ? 0 : 1; }3.3 多级菜单切换逻辑菜单导航通过三个按键控制按键功能对应处理函数OK键进入子菜单/确认menu_switch()LEFT键同级左移/减menu_left()RIGHT键同级右移/加menu_right()状态转换示意图主菜单 → 按OK → 一级菜单 → 按OK → 二级菜单 ↑____________| ↑______|4. 完整工程实现与调试4.1 工程文件结构├── Core/ │ ├── Src/ │ │ ├── main.c # 主循环 │ │ ├── stm32f1xx_it.c # 中断处理 │ ├── Inc/ ├── Drivers/ ├── Middlewares/ ├── u8g2/ # u8g2库 ├── User/ │ ├── gui/ │ │ ├── gui_task.c # 主任务调度 │ │ ├── gui_menu.c # 菜单实现 │ │ ├── icon.h # 图标资源 │ ├── key/ # 按键处理 │ ├── oled/ # OLED驱动4.2 关键代码片段菜单任务调度void gui_main_task(void) { static uint8_t last_state 0; // 状态变化时触发动画 if(last_state ! current_state) { start_animation(); last_state current_state; } // 执行当前状态的显示函数 menu_items[current_state].show_func(); // 处理按键 handle_key_events(); }图标显示优化void show_icon_with_shadow(uint8_t x, uint8_t y, const uint8_t *icon) { // 绘制阴影 u8g2_SetDrawColor(u8g2, 0); u8g2_DrawXBM(u8g2, x1, y1, 32, 32, icon); // 绘制实际图标 u8g2_SetDrawColor(u8g2, 1); u8g2_DrawXBM(u8g2, x, y, 32, 32, icon); }4.3 常见问题解决显示闪烁问题确保在完成所有绘制操作后再调用u8g2_SendBuffer()适当增加I2C时钟频率不超过400kHz内存不足# 在Makefile中优化编译选项 CFLAGS -ffunction-sections -fdata-sections LDFLAGS -Wl,--gc-sections按键抖动处理// 简单的软件消抖 uint8_t read_key() { static uint8_t count 0; uint8_t raw HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin); if(raw 0) { if(count 255) count; return (count 10) ? 1 : 0; } else { count 0; return 0; } }5. 进阶优化与扩展5.1 菜单配置工具化为了便于维护我们可以将菜单结构转移到JSON配置文件中{ menus: [ { id: main, items: [ { text: Voltage, icon: voltage.xbm, action: submenu, target: voltage_menu } ] } ] }然后使用Python脚本自动生成C代码def generate_menu_code(json_file): with open(json_file) as f: data json.load(f) output // Auto-generated menu code\n for menu in data[menus]: output fMenuItem {menu[id]}[] {{\n for item in menu[items]: output f {{\{item[text]}\, {item[icon]}, NULL, {item.get(target, NULL)}, NULL}},\n output };\n return output5.2 3D视觉效果实现通过简单的阴影和视差效果可以增强菜单的立体感void draw_3d_menu_item(uint8_t index) { // 背景阴影 u8g2_SetDrawColor(u8g2, 0); u8g2_DrawRBox(u8g2, item_x[index]2, item_y[index]2, ITEM_W, ITEM_H, 3); // 前景项目 u8g2_SetDrawColor(u8g2, 1); u8g2_DrawRBox(u8g2, item_x[index], item_y[index], ITEM_W, ITEM_H, 3); // 高光效果 u8g2_SetDrawColor(u8g2, 2); u8g2_DrawHLine(u8g2, item_x[index]2, item_y[index]2, ITEM_W-4); }5.3 与RTOS集成在FreeRTOS中创建专门的任务处理GUIvoid gui_task(void *params) { u8g2_InitDisplay(u8g2); u8g2_SetPowerSave(u8g2, 0); while(1) { TickType_t last_wake xTaskGetTickCount(); // 处理GUI更新 gui_update(); // 精确控制帧率20FPS vTaskDelayUntil(last_wake, pdMS_TO_TICKS(50)); } } // 在main中创建任务 xTaskCreate(gui_task, GUI, 512, NULL, 2, NULL);6. 实际应用案例将这套菜单系统应用在智能家居控制器上我们可以实现环境监测界面实时显示温湿度曲线历史数据查询设备控制面板void control_light() { static uint8_t brightness 50; u8g2_DrawStr(u8g2, 10, 20, Light Control); u8g2_DrawHBox(u8g2, 10, 30, brightness, 10); if(key RIGHT_KEY) brightness 5; if(key LEFT_KEY) brightness - 5; set_light_brightness(brightness); }系统设置菜单WiFi配置时间设置屏幕校准在开发气象站项目时这套菜单系统特别适合用来展示多层次的天气数据。通过添加简单的图表绘制功能可以直观显示温度变化趋势void draw_temp_chart(float *temps, uint8_t count) { float max -100, min 100; // 计算极值 for(int i0; icount; i) { if(temps[i] max) max temps[i]; if(temps[i] min) min temps[i]; } // 绘制坐标轴 u8g2_DrawHLine(u8g2, 10, 50, 100); u8g2_DrawVLine(u8g2, 10, 10, 40); // 绘制曲线 for(int i1; icount; i) { uint8_t x1 10 (i-1)*10; uint8_t y1 50 - (uint8_t)((temps[i-1]-min)*30/(max-min)); uint8_t x2 10 i*10; uint8_t y2 50 - (uint8_t)((temps[i]-min)*30/(max-min)); u8g2_DrawLine(u8g2, x1, y1, x2, y2); } }
用STM32F103和u8g2库,给你的0.96寸OLED屏做个带切换动画的菜单(附完整工程)
STM32F103与u8g2库打造0.96寸OLED动态菜单系统实战在嵌入式设备的人机交互设计中菜单系统是最常见的交互方式之一。传统的静态菜单往往显得生硬呆板而加入流畅的动画效果不仅能提升用户体验还能让产品更具专业感。本文将带你从零开始基于STM32F103C8T6和0.96寸IIC OLED屏利用u8g2图形库实现一个支持多级切换动画的菜单系统。1. 硬件准备与环境搭建1.1 所需硬件组件要完成这个项目你需要准备以下硬件STM32F103C8T6最小系统板俗称蓝莓派Cortex-M3内核72MHz主频64KB Flash20KB RAM丰富的GPIO和外设接口0.96寸OLED显示屏分辨率128x64驱动芯片SSD1306接口I2C仅需4根线其他配件3个轻触按键用于菜单导航杜邦线若干USB转TTL模块用于调试1.2 开发环境配置我们使用STM32CubeIDE作为开发环境以下是具体配置步骤安装STM32CubeIDE# 在Linux下安装示例 sudo apt install ./en.st-stm32cubeide_1.11.0_20220325_1055_amd64.deb_bundle.sh创建新工程选择STM32F103C8Tx芯片配置时钟树为72MHz启用I2C1外设OLED使用u8g2库移植// 在CubeMX中配置I2C后添加u8g2库文件 #include u8g2.h // u8g2初始化代码 u8g2_t u8g2; U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset*/ U8X8_PIN_NONE);提示u8g2库支持多种显示控制器确保选择正确的初始化函数SSD1306对应型号。2. u8g2图形库深度解析2.1 u8g2库架构与特性u8g2是一个功能强大的单色图形库具有以下特点多控制器支持支持SSD1306、SH1106等多种OLED驱动芯片多接口兼容I2C、SPI、8080并行接口等丰富的绘图API// 常用绘图函数示例 u8g2_DrawBox(u8g2, x, y, w, h); // 绘制实心矩形 u8g2_DrawCircle(u8g2, x, y, r, opt); // 绘制圆 u8g2_DrawXBMP(u8g2, x, y, w, h, bitmap); // 显示XBM格式位图2.2 性能优化技巧在STM32F103上使用u8g2需要注意以下性能优化点缓冲区策略全缓冲模式U8G2_R0需要1KB RAM分页模式节省内存但刷新较慢字体选择// 推荐使用内置字体避免自定义字体占用过多Flash u8g2_SetFont(u8g2, u8g2_font_6x10_tf); // 6x10像素字体 u8g2_SetFont(u8g2, u8g2_font_unifont_t_symbols); // Unicode符号字体刷新优化// 局部刷新而非全屏刷新 u8g2_SetClipWindow(u8g2, x0, y0, x1, y1); u8g2_SendBuffer(u8g2);3. 菜单系统设计与实现3.1 菜单数据结构设计我们采用分层式菜单结构使用状态机模型管理菜单状态typedef struct { char *text; // 菜单项文本 uint8_t icon[128]; // 32x32图标数据 void (*action)(void); // 点击动作函数 MenuItem *child; // 子菜单数组 MenuItem *parent; // 父菜单 } MenuItem; // 示例菜单定义 MenuItem mainMenu[] { {Voltage, icon_voltage, NULL, voltageSubMenu, NULL}, {Current, icon_current, NULL, currentSubMenu, NULL}, {Settings, icon_settings, NULL, settingsMenu, NULL} };3.2 动画效果实现原理我们实现了两种核心动画效果进入动画渐显uint8_t ui_come(void) { int len 8 * u8g2_GetBufferTileHeight(u8g2) * u8g2_GetBufferTileWidth(u8g2); uint8_t *p u8g2_GetBufferPtr(u8g2); for(int i0; ilen; i) { p[i] p[i] (rand()%0xff) c_come_temp; } c_come_temp - 2; return (c_come_temp 0) ? 0 : 1; }退出动画渐隐uint8_t ui_disapper(void) { // 类似ui_come但反向处理 uc_disapper_temp 2; return (uc_disapper_temp 8) ? 0 : 1; }3.3 多级菜单切换逻辑菜单导航通过三个按键控制按键功能对应处理函数OK键进入子菜单/确认menu_switch()LEFT键同级左移/减menu_left()RIGHT键同级右移/加menu_right()状态转换示意图主菜单 → 按OK → 一级菜单 → 按OK → 二级菜单 ↑____________| ↑______|4. 完整工程实现与调试4.1 工程文件结构├── Core/ │ ├── Src/ │ │ ├── main.c # 主循环 │ │ ├── stm32f1xx_it.c # 中断处理 │ ├── Inc/ ├── Drivers/ ├── Middlewares/ ├── u8g2/ # u8g2库 ├── User/ │ ├── gui/ │ │ ├── gui_task.c # 主任务调度 │ │ ├── gui_menu.c # 菜单实现 │ │ ├── icon.h # 图标资源 │ ├── key/ # 按键处理 │ ├── oled/ # OLED驱动4.2 关键代码片段菜单任务调度void gui_main_task(void) { static uint8_t last_state 0; // 状态变化时触发动画 if(last_state ! current_state) { start_animation(); last_state current_state; } // 执行当前状态的显示函数 menu_items[current_state].show_func(); // 处理按键 handle_key_events(); }图标显示优化void show_icon_with_shadow(uint8_t x, uint8_t y, const uint8_t *icon) { // 绘制阴影 u8g2_SetDrawColor(u8g2, 0); u8g2_DrawXBM(u8g2, x1, y1, 32, 32, icon); // 绘制实际图标 u8g2_SetDrawColor(u8g2, 1); u8g2_DrawXBM(u8g2, x, y, 32, 32, icon); }4.3 常见问题解决显示闪烁问题确保在完成所有绘制操作后再调用u8g2_SendBuffer()适当增加I2C时钟频率不超过400kHz内存不足# 在Makefile中优化编译选项 CFLAGS -ffunction-sections -fdata-sections LDFLAGS -Wl,--gc-sections按键抖动处理// 简单的软件消抖 uint8_t read_key() { static uint8_t count 0; uint8_t raw HAL_GPIO_ReadPin(KEY_GPIO_Port, KEY_Pin); if(raw 0) { if(count 255) count; return (count 10) ? 1 : 0; } else { count 0; return 0; } }5. 进阶优化与扩展5.1 菜单配置工具化为了便于维护我们可以将菜单结构转移到JSON配置文件中{ menus: [ { id: main, items: [ { text: Voltage, icon: voltage.xbm, action: submenu, target: voltage_menu } ] } ] }然后使用Python脚本自动生成C代码def generate_menu_code(json_file): with open(json_file) as f: data json.load(f) output // Auto-generated menu code\n for menu in data[menus]: output fMenuItem {menu[id]}[] {{\n for item in menu[items]: output f {{\{item[text]}\, {item[icon]}, NULL, {item.get(target, NULL)}, NULL}},\n output };\n return output5.2 3D视觉效果实现通过简单的阴影和视差效果可以增强菜单的立体感void draw_3d_menu_item(uint8_t index) { // 背景阴影 u8g2_SetDrawColor(u8g2, 0); u8g2_DrawRBox(u8g2, item_x[index]2, item_y[index]2, ITEM_W, ITEM_H, 3); // 前景项目 u8g2_SetDrawColor(u8g2, 1); u8g2_DrawRBox(u8g2, item_x[index], item_y[index], ITEM_W, ITEM_H, 3); // 高光效果 u8g2_SetDrawColor(u8g2, 2); u8g2_DrawHLine(u8g2, item_x[index]2, item_y[index]2, ITEM_W-4); }5.3 与RTOS集成在FreeRTOS中创建专门的任务处理GUIvoid gui_task(void *params) { u8g2_InitDisplay(u8g2); u8g2_SetPowerSave(u8g2, 0); while(1) { TickType_t last_wake xTaskGetTickCount(); // 处理GUI更新 gui_update(); // 精确控制帧率20FPS vTaskDelayUntil(last_wake, pdMS_TO_TICKS(50)); } } // 在main中创建任务 xTaskCreate(gui_task, GUI, 512, NULL, 2, NULL);6. 实际应用案例将这套菜单系统应用在智能家居控制器上我们可以实现环境监测界面实时显示温湿度曲线历史数据查询设备控制面板void control_light() { static uint8_t brightness 50; u8g2_DrawStr(u8g2, 10, 20, Light Control); u8g2_DrawHBox(u8g2, 10, 30, brightness, 10); if(key RIGHT_KEY) brightness 5; if(key LEFT_KEY) brightness - 5; set_light_brightness(brightness); }系统设置菜单WiFi配置时间设置屏幕校准在开发气象站项目时这套菜单系统特别适合用来展示多层次的天气数据。通过添加简单的图表绘制功能可以直观显示温度变化趋势void draw_temp_chart(float *temps, uint8_t count) { float max -100, min 100; // 计算极值 for(int i0; icount; i) { if(temps[i] max) max temps[i]; if(temps[i] min) min temps[i]; } // 绘制坐标轴 u8g2_DrawHLine(u8g2, 10, 50, 100); u8g2_DrawVLine(u8g2, 10, 10, 40); // 绘制曲线 for(int i1; icount; i) { uint8_t x1 10 (i-1)*10; uint8_t y1 50 - (uint8_t)((temps[i-1]-min)*30/(max-min)); uint8_t x2 10 i*10; uint8_t y2 50 - (uint8_t)((temps[i]-min)*30/(max-min)); u8g2_DrawLine(u8g2, x1, y1, x2, y2); } }