【STM32】U8G2图形库应用--菜单设计与开发
一、 前言
在掌握U8G2图形库移植的基础上,本文进一步探讨其在STM32上的应用:实现多级菜单界面,分享多种菜单架构的设计思路与代码实现。

二、系统介绍
1.项目实现的功能
(1)一级菜单使用U8G2图形库中的列表功能,垂直方向滑动选择。
(2)二级菜单采用自定义列表菜单,分别是垂直和水平方向滑动选择,交互体验更丰富且提升操作灵活性。
(3)实现长文本垂直方向循环滚动效果,在有限屏幕空间内完整展示信息。
(4)使用U8G2图形库的API,实现对屏幕对比度调节、中英文字体切换、画面旋转、反显模式等功能,满足多样化显示需求。
2.项目硬件组成
(1)主控芯片:该项目使用的是STM32F103RCT6。
(2)OLED显示屏:本模块搭载的是SSD1306驱动芯片,采用IIC接口,分辨率是128*64。
(3)按键:机械式轻触开关。
三、项目软件开发
1.初始化
系统启动后,首先完成 IIC 通信接口和对U8G2初始化,再对按键GPIO的初始化(具体移植细节和初始化请参阅前篇文章【STM32 + SSD1306 OLED】U8G2图形库移植)。

重点解析按键中断设计,由于一级主菜单采用U8G2图形提供的列表功能,其逻辑包含无限循环( for(;;))。若使用传统轮询方式检测按键,将导致无法识别按键响应。因此,本方案摒弃轮询机制,将按键配置为外部中断(EXTI)模式。当用户触发按键时,硬件立即中断当前显示任务并跳转至中断服务程序,从而实现菜单交互的实时响应,确保在复杂图形刷新背景下依然保持流畅的操作体验。
c
/*---------------------- 按键初始化 ----------------------*/
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// GPIOB
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); /* 使能PORTB时钟 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 |GPIO_Pin_14; /* PB9、14 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 设置成上拉输入 */
GPIO_Init(GPIOB, &GPIO_InitStructure); /* 初始化GPIOB */
// GPIOC
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC| RCC_APB2Periph_AFIO, ENABLE); /* 使能PORTC口时钟 */
BKP_TamperPinCmd(DISABLE); /* 关闭入侵检测功能,也就是PC13,可以当普通IO使用 */
BKP_ITConfig(DISABLE); /* 这样就可以当输出用。 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_10; /* PC0、1、2、10、13 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 设置成上拉输入 */
GPIO_Init(GPIOC, &GPIO_InitStructure); /* 初始化GPIOC */
}
/*---------------------- 外部中断初始化 ----------------------*/
void EXTIX_Init(void)
{
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE); /* 外部中断,需要使能AFIO时钟 */
KEY_Init();
// GPIOB.9 中断线以及中断初始化配置
GPIO_EXTILineConfig(GPIO_PortSourceGPIOB,GPIO_PinSource9);
EXTI_InitStructure.EXTI_Line = EXTI_Line9; /* 第9条中断线 */
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; /* 下降沿触发 */
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure); /* 根据EXTI_InitStruct中指定的参数初始化外设EXTI寄存器 */
... /* 其他脚位同样配置 */
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; /* 使能按键所在的外部中断通道 */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; /* 抢占优先级2 */
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00; /* 子优先级0 */
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; /* 使能外部中断通道 */
NVIC_Init(&NVIC_InitStructure); /* 根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 */
... /* 其他脚位同样配置 */
}
外部中断初始化过程中,需特别注意:
(1)由于PC13默认复用为侵入检测功能,必须先行禁用该功能,方可将其配置为普通GPIO。
(2)此外,同一条中断线只能连接相同编号引脚的其中一个端口。如EXTI_Line9只能配置对应GPIO引脚(PA9、PB9、PC9...)其中一个端口,若对多个同编号引脚重复配置同一中断线,后配置的会覆盖前配置的,导致前面配置的中断功能失效。
2.开机显示动画
静态显示侧重于图形与文字的固定绘制,而条形进度条的动态实现的原理是绘制不同尺寸的实心矩形图形。按时间间隔不断更新实心矩形的宽度,利用视觉暂留效应形成平滑的进度增长动画,其中间隔时长决定了进度条的填充速率。
c
/*----------------------- 进度条显示 ----------------------*/
void Display_Progress_Bar(u8g2_t *u8g2, uint8_t speed)
{
uint8_t pos_height = 3;
uint8_t pos_x;
for (pos_x = 0; pos_x <= 126; pos_x += speed)
{
u8g2_DrawBox(u8g2, pos_x, u8g2_GetDisplayHeight(u8g2)-pos_height, speed, pos_height);
u8g2_SendBuffer(u8g2);
//delay_ms(nms); /* 可根据是实际增加,用于控制填充速度 */
}
}
/*----------------------- 开机动画显示 ----------------------*/
void Display_Poweron_Animation(void)
{
u8g2_ClearBuffer(&u8g2);
u8g2_DrawXBMP(&u8g2,0,16,32,32,BMP_WECHAT); /* 传入箭头标志图片 */
u8g2_DrawXBMP(&u8g2,32,20,24,24,BMP_SEARCH); /* 传入游戏图标图片 */
u8g2_DrawRFrame(&u8g2,33,19,95,26,5); /* 绘制带圆角空心框 */
u8g2_SetFont(&u8g2, u8g2_font_spleen8x16_mf); /* 设置字体格式 */
u8g2_DrawStr(&u8g2, 54,37,"YouziTech");
Display_Progress_Bar(&u8g2, 5); /* 进度条 */
}
其中涉及的几个函数:
(1)U8G2图形库提供了四种矩形绘制函数:u8g2_DrawFrame用于绘制直角空心矩形,u8g2_DrawRFrame用于绘制圆角空心矩形;u8g2_DrawBox用于绘制直角实心矩形,u8g2_DrawRBox则用于绘制圆角实心矩形。
在使用带圆角函数(DrawRFrame或DrawRBox)时,需特别注意参数约束:圆角取值的两倍必须小于矩形的宽度和高度。若超出,将导致图形渲染异常或形状变异。
(2)u8g2_GetDisplayHeight该函数用于获取显示屏的高度,同理u8g2_GetDisplayWeigh函数是获取显示屏的宽度。
3.列表式菜单
U8G2图形库提供的u8g2_UserInterfaceSelectionList函数用于快速构建选择列表界面,其中参数配置如下:
●结构体。
●菜单标题:填入字符串,包括中英文等。
●默认选中项:0为第0个菜单项位置。
●列表选项:各选项间使用换行符(\n)分隔。
c
menu_page_select = u8g2_UserInterfaceSelectionList(&u8g2,"M E N U",0,"Game\nSetting\nAbout\n...");

除了选择列表,还提供其他用户界面交互的的函数,可根据实际需求使用:
●u8g2_UserInterfaceMessage:用于显示多行文本以及多个按钮,适用于操作反馈和重要信息提示等。
●u8g2_UserInterfaceInputValue:用于数值输入界面,便于用户调整参数。
c
u8g2_UserInterfaceMessage("Title1", "Title2", "Title3", " OK \n Cancel ");
u8g2_UserInterfaceInputValue("Select Voltage", "DAC= ", &v, 0, 5, 1, " V");
U8G2图形库提供的原函数包含不同状态的参数配置、缓冲刷新以及事件处理三部分。此处重点阐述事件处理,响应上下滑动选择动作,调整选中项索引(加1或减1),并在参数变更后触发界面重绘,从而实现菜单选项的切换。
c
uint8_t u8g2_UserInterfaceSelectionList(u8g2_t *u8g2, const char *title, uint8_t start_pos, const char *sl)
{
u8sl_t u8sl;
u8g2_uint_t yy;
uint8_t event;
// ---u8sl参数的调整
u8g2_uint_t line_height = u8g2_GetAscent(u8g2) - u8g2_GetDescent(u8g2)+MY_BORDER_SIZE;
uint8_t title_lines = u8x8_GetStringLineCnt(title);
uint8_t display_lines;
if ( start_pos > 0 ) /* issue 112 */
start_pos--; /* issue 112 */
if ( title_lines > 0 ) /* 获取sl总共多少个列表,并分配一页多少个列表 */
{
display_lines = (u8g2_GetDisplayHeight(u8g2)-3) / line_height;
u8sl.visible = display_lines;
u8sl.visible -= title_lines;
}
else
{
display_lines = u8g2_GetDisplayHeight(u8g2) / line_height;
u8sl.visible = display_lines;
}
u8sl.total = u8x8_GetStringLineCnt(sl);
u8sl.first_pos = 0;
u8sl.current_pos = start_pos;
if ( u8sl.current_pos >= u8sl.total )
u8sl.current_pos = u8sl.total-1;
if ( u8sl.first_pos+u8sl.visible <= u8sl.current_pos )
u8sl.first_pos = u8sl.current_pos-u8sl.visible+1;
u8g2_SetFontPosBaseline(u8g2);
for(;;)
{
// ---图形的显示
u8g2_FirstPage(u8g2);
do
{
yy = u8g2_GetAscent(u8g2);
if ( title_lines > 0 )
{
yy += u8g2_DrawUTF8Lines(u8g2, 0, yy, u8g2_GetDisplayWidth(u8g2), line_height, title); /* 显示title */
u8g2_DrawHLine(u8g2, 0, yy-line_height- u8g2_GetDescent(u8g2) + 1, u8g2_GetDisplayWidth(u8g2)); /* 显示title与sl间的横杆 */
yy += 3;
}
u8g2_DrawSelectionList(u8g2, &u8sl, yy, sl); /* 显示sl字体+实心矩形框 */
} while( u8g2_NextPage(u8g2) );
#ifdef U8G2_REF_MAN_PIC
return 0;
#endif
// ---事件的获取与处理
for(;;)
{
event = u8x8_GetMenuEvent(u8g2_GetU8x8(u8g2));
if ( event == U8X8_MSG_GPIO_MENU_SELECT )
return u8sl.current_pos+1; /* +1, issue 112 */
else if ( event == U8X8_MSG_GPIO_MENU_HOME )
return 0; /* issue 112: return 0 instead of start_pos */
else if ( event == U8X8_MSG_GPIO_MENU_NEXT || event == U8X8_MSG_GPIO_MENU_DOWN )
{
u8sl_Next(&u8sl);
break;
}
else if ( event == U8X8_MSG_GPIO_MENU_PREV || event == U8X8_MSG_GPIO_MENU_UP )
{
u8sl_Prev(&u8sl);
break;
}
}
}
}
或者根据实际需求将事件处理这部分修改成自定义函数。
c
if(key_value!=0)
{
if(key_value==KEY_DOWN_PRESSED4) // 菜单选择 pos+1,不退出函数
{
if(u8sl.current_pos<3)
u8sl_Next(&u8sl);
key_value=0;
break;
}
else if(key_value==KEY_UP_PRESSED1) //菜单选择 pos-1,不退出函数
{
if(u8sl.current_pos>0)
u8sl_Prev(&u8sl);
key_value=0;
break;
}
else if(key_value==KEY_CONFIRM_PRESSED5) //确定,退出函数
{
if(u8sl.current_pos<3)
{
key_value=0;
return u8sl.current_pos;
}
}
key_value=0;
}
4.自定义列表式菜单
(1)水平方向
实现图片选项的水平滚动交互,选中框保持屏幕中央固定。选中框采用带阴影的空心方框样式。

显示前清空选项列表区域缓冲,将图片内容与固定选中框写入缓冲。并调整图片当前坐标到达目标位置,持续执行上述重绘流程以实现动画过渡。
c
/*--------------------- 设置页面菜单显示 --------------------*/
void Display_Setting_List(void)
{
u8g2_ClearBuffer(&u8g2);
// 显示标题
OLED_Display_Normal_or_Reverse(); /* 正反显设置 */
u8g2_DrawXBMP(&u8g2,0,0,24,24,BMP_SETTING);
if(screen_language == LANGUAGE_ENGLISH)
u8g2_DrawStr(&u8g2, 30,14,"SETTING");
else
u8g2_DrawUTF8(&u8g2,30,14,"设置");
u8g2_DrawHLine(&u8g2,30,19,98);
// 显示菜单内容
for(uint8_t i=0;i<SETTING_SELECT_MAX;i++)
{
u8g2_DrawXBMP(&u8g2,setting_page_x+38*i, setting_page_y,24,24,setting_pic[i]);
}
u8g2_DrawButtonFrame(&u8g2,56,52,U8G2_BTN_BW1|U8G2_BTN_SHADOW1,15,6,6); /* 选中方框显示 */
u8g2_SendBuffer(&u8g2);
// 调整x坐标改变到目标坐标数值
Value_Move(&setting_page_x, &setting_page_x_target, setting_page_speed);
}
根据移动方向对图片数据索引进行左移或右移一位的逻辑更新。
c
//---------------------------------设置页面选项设置:右移动---------------------------------//
if(key_value==KEY_RIGHT_PRESSED3)
{
// x坐标重置
setting_page_x = -24;
setting_page_x_target = 14;
// 图片数组:数据移动
if(setting_page_dir == SETTING_DIR_RIGHT)
Right_Rotate_Array(setting_pic,SETTING_SELECT_MAX);
setting_page_dir = SETTING_DIR_RIGHT;
// 设置页面选项数值
if(setting_page_select>0)
setting_page_select--;
else
setting_page_select = SETTING_SELECT_MAX-1;
key_value=0;
}
//---------------------------------设置页面选项设置:左移动---------------------------------//
else if(key_value==KEY_LEFT_PRESSED2)
{
setting_page_x = 14;
setting_page_x_target = -24;
if(setting_page_dir == SETTING_DIR_LEFT)
Left_Rotate_Array(setting_pic,SETTING_SELECT_MAX);
setting_page_dir = SETTING_DIR_LEFT;
setting_page_select++;
if(setting_page_select >= SETTING_SELECT_MAX)
setting_page_select = 0;
key_value=0;
}
其中Value_Move函数是将当前数值平滑过渡至目标数值。
c
/*------------ 调整移动变量的数值到目标数值 ------------*/
int Value_Move(int16_t *value, int16_t *value_target, uint8_t speed)
{
/* 绝对值大于speed+1则步幅为speed,否则步幅为1:实现快速逼近然后减速 */
speed = abs(*value_target - *value) > (speed+1) ? speed : 1;
if(*value < *value_target)
*value += speed;
else if (*value > *value_target)
*value -= speed;
else return 0; /* 变量数值已调整为目标数值时,返回0 */
return 1;
}
除了图片还可以使用字符串代替图片作为选择项。去掉原有的选中框元素,转而通过字体大小差异来区分选中状态。利用u8g2_DrawStr/u8g2_DrawUTF8函数在两侧以正常尺寸渲染未选中项,同时使用u8g2_DrawStrX2/u8g2_DrawUTF8X2函数在中央区域以双倍尺寸渲染当前选中项,通过字体放大效果突出显示当前选项。
(2)垂直方向
实现字符串选项的垂直滚动交互,选中框随当前选项动态移动并自适应调整尺寸。选中框采用圆角实心样式,其填充色与字符串绘制色相反。

在显示前计算菜单区域的可显示行数,以此确定起始索引及选中框的Y轴目标坐标。随后清空选项区域缓冲,更新选中框参数,并将菜单内容与选中框写入缓冲区,最终完成菜单画面的绘制。
c
//--- 游戏菜单:英文名称 ---//
const char *Game_Name_en[]=
{
{"-Dino"},
{"-Snake"},
{"-Sokoban"},
{"-..."},
};
//--- 游戏菜单:中文名称(注意中文字数为2的倍数) ---//
const char *Game_Name_ch[]=
{
{"-棘龙避障"},
{"-盘蛇夺食"},
{"-移箱破局"},
{"-..."},
};
/*---------------------- 游戏菜单列表显示 ----------------------*/
void Display_Game_List()
{
uint8_t i_temp;
game_page_y = GAME_POS_Y;
// 获取菜单显示行数
game_page_display_lines = (u8g2_GetDisplayHeight(&u8g2) - (GAME_POS_Y - GAME_FONT_HEIGHT)) / GAME_FONT_HEIGHT;
// 清除标题以下区域缓冲
OLED_Clear_Area_Buffer(0,25,127,63);
// 判断选择的菜单名是否超出显示行数
if(game_page_display_lines <= game_page_select)
i_temp = game_page_select - game_page_display_lines +1; /* 超过显示行:数菜单往后移 */
else
i_temp = 0; /* 未超过显示行:数菜单从第0位显示 */
// 显示菜单名字
for (uint8_t i = i_temp ; i < GAME_SELECT_MAX; i++)
{
if(screen_language == LANGUAGE_ENGLISH)
u8g2_DrawStr(&u8g2,game_page_x,game_page_y,Game_Name_en[i]);
else
u8g2_DrawUTF8(&u8g2,game_page_x,game_page_y,Game_Name_ch[i]);
game_page_y += GAME_FONT_HEIGHT; /* 下个菜单的y坐标 */
}
// 调整选中方框:
u8g2_SetDrawColor(&u8g2,2); /* 异或模式:选中框与框内文字反色、底层与其他文字反色 */
// --- 获取菜单每个条目的目标宽度(+6:选中框左右两边往外扩3格)
if(screen_language == LANGUAGE_ENGLISH)
game_page_frame_weight_target = u8g2_GetUTF8Width(&u8g2,Game_Name_en[game_page_select]) + 6;
else
game_page_frame_weight_target = u8g2_GetUTF8Width(&u8g2,Game_Name_ch[game_page_select]) + 6;
// --- 获取选中框的目标Y坐标(+2:选中框整体往下移2行)
if(game_page_display_lines <= game_page_select)
game_page_frame_y_target = GAME_POS_Y + GAME_FONT_HEIGHT*game_page_display_lines - GAME_FONT_HEIGHT*2 + 2; /* 超过显示行 */
else
game_page_frame_y_target = GAME_POS_Y + GAME_FONT_HEIGHT*game_page_select - GAME_FONT_HEIGHT + 2; /* 未超过显示行 */
// --- 调整选中框Y坐标数值
Value_Move(&game_page_frame_y, &game_page_frame_y_target, GAME_FONT_HEIGHT / 4);
// --- 调整选中框宽度数值
Value_Move(&game_page_frame_weight, &game_page_frame_weight_target, game_page_frame_weight_target / 4);
// --- 画方框
u8g2_DrawRBox(&u8g2,1, game_page_frame_y, game_page_frame_weight, GAME_FONT_HEIGHT, 3);
u8g2_SendBuffer(&u8g2);
}
其中U8G2图形库仅支持全屏清缓冲,而OLED_Clear_Area_Buffer函数是自定义清除缓冲,该函数通过绘制实心矩形覆盖指定区域以清除缓冲,并在操作完成后恢复原有绘制颜色,从而实现局部刷新。
c
/*---------- 清部分区域缓冲(带正反显示模式) ----------*/
void OLED_Clear_Area_Buffer(uint8_t start_x,uint8_t start_y,uint8_t width,uint8_t height)
{
if(screen_reverse == DISPLAY_REVERSE)
{
u8g2_SetDrawColor(&u8g2, 1); /*先绘图白色实心矩形(实现区域清缓冲)*/
u8g2_DrawBox(&u8g2,start_x,start_y,width,height);
u8g2_SetDrawColor(&u8g2, 0); /*再恢复绘图颜色为黑色*/
}
else
{
u8g2_SetDrawColor(&u8g2, 0); /*先绘图黑色实心矩形(实现区域清缓冲)*/
u8g2_DrawBox(&u8g2,start_x,start_y,width,height);
u8g2_SetDrawColor(&u8g2, 1); /*再恢复绘图颜色为白色*/
}
}
5.循环滚动字符串
实现字符串在垂直方向上的循环滚动显示效果。

通过动态调整Y轴目标坐标实现内容滚动,并在字符串超出滚动显示区域时重置坐标以形成循环效果。显示流程如下:首先清空全屏缓冲,接着在滚动区域内绘制滚动内容;随后清除该区域外的冗余显示,最后写入标题内容至缓冲区,完成最终画面的绘制。
c
const char *About_Name_en[]=
{
{"Name:MenuDemo"},
{"Ver:V1.0"},
{"Prod:YouziTech"},
};
const char *About_Name_ch[]=
{
{"名字:MenuDemo"},
{"版本:V1.0"},
{"出品:YouziTech"},
};
void Display_About_Scroll(void)
{
// 调整Y坐标
if(Value_Move(&about_page_y, &about_page_y_target, 1)==0)
{
about_page_y = ABOUT_POS_Y; /* 重置Y坐标 */
about_page_y_target = ABOUT_POS_Y_TARGET;
}
u8g2_ClearBuffer(&u8g2);
OLED_Display_Normal_or_Reverse(); /* 正反显设置 */
// 显滚动内容
if(screen_language == LANGUAGE_ENGLISH)
{
for(uint8_t i=0;i<ABOUT_LINE_MAX;i++)
u8g2_DrawStr(&u8g2 , (u8g2_GetDisplayWidth(&u8g2)-u8g2_GetStrWidth(&u8g2,About_Name_en[i]))/2 , about_page_y +ABOUT_FONT_HEIGHT*i , About_Name_en[i] );
}
else
{
for(uint8_t i=0;i<ABOUT_LINE_MAX;i++)
u8g2_DrawUTF8(&u8g2 , (u8g2_GetDisplayWidth(&u8g2)-u8g2_GetUTF8Width(&u8g2,About_Name_ch[i]))/2 , about_page_y +ABOUT_FONT_HEIGHT*i , About_Name_ch[i] );
}
// 清除移动文字出现在上方区域缓冲
OLED_Clear_Area_Buffer(0,0,127,31);
// 显示标题内容
u8g2_DrawXBMP(&u8g2,0,0,24,24,BMP_ABOUT);
if(screen_language == LANGUAGE_ENGLISH)
u8g2_DrawStr(&u8g2, 30,14,"ABOUT");
else
u8g2_DrawUTF8(&u8g2,30,14,"关于");
u8g2_DrawHLine(&u8g2,30,19,98);
u8g2_SendBuffer(&u8g2);
}
6.设置屏幕参数
(1)亮度设置
使用U8G2图形库提供的u8g2_SetContrast函数调节对比度从而控制屏幕亮度。对比度参数范围为0~255。

该进度条与前文版本的主要区别在于取消了动画填充效果,并增加了一层与进度条颜色相反的底层背景,以形成阴影视觉效果。
c
// 调整亮度(对比度)
if(screen_contrast<250)
screen_contrast += 25;
else
screen_contrast=25;
u8g2_SetContrast(&u8g2,screen_contrast);
// 绘制调节亮度进度条:
// --- 正常显示模式 [底层尺寸: +4: 进度条左右两边往外扩2格。 -2:进度条上下两边往外扩2格。 ]
if(screen_reverse == DISPLAY_NORMAL)
{
u8g2_SetDrawColor(&u8g2, 0); /*先绘制黑色底层*/
u8g2_DrawRBox(&u8g2,(u8g2_GetDisplayWidth(&u8g2)-(BRIGHTNESS_PBAR_WIDTH+4))/2 ,BRIGHTNESS_PBAR_Y-2 ,BRIGHTNESS_PBAR_WIDTH+4,BRIGHTNESS_PBAR_HEIGHT+4 ,3);
u8g2_SetDrawColor(&u8g2, 1); /*再绘制颜色为白色的进度条*/
u8g2_DrawRFrame(&u8g2,(u8g2_GetDisplayWidth(&u8g2)-BRIGHTNESS_PBAR_WIDTH)/2 ,BRIGHTNESS_PBAR_Y ,BRIGHTNESS_PBAR_WIDTH ,BRIGHTNESS_PBAR_HEIGHT ,3);
u8g2_DrawRBox(&u8g2,(u8g2_GetDisplayWidth(&u8g2)-BRIGHTNESS_PBAR_WIDTH)/2 ,BRIGHTNESS_PBAR_Y ,screen_contrast/25*8 ,BRIGHTNESS_PBAR_HEIGHT ,3);
}
// --- 反显模式
else
{
u8g2_SetDrawColor(&u8g2, 1); /*先绘制白色底层*/
u8g2_DrawRBox(&u8g2,(u8g2_GetDisplayWidth(&u8g2)-(BRIGHTNESS_PBAR_WIDTH+4))/2 ,BRIGHTNESS_PBAR_Y-2 ,BRIGHTNESS_PBAR_WIDTH+4 ,BRIGHTNESS_PBAR_HEIGHT+4 ,3);
u8g2_SetDrawColor(&u8g2, 0); /*再绘制颜色为黑色的进度条*/
u8g2_DrawRFrame(&u8g2,(u8g2_GetDisplayWidth(&u8g2)-BRIGHTNESS_PBAR_WIDTH)/2 ,BRIGHTNESS_PBAR_Y ,BRIGHTNESS_PBAR_WIDTH ,BRIGHTNESS_PBAR_HEIGHT ,3);
u8g2_DrawRBox(&u8g2,(u8g2_GetDisplayWidth(&u8g2)-BRIGHTNESS_PBAR_WIDTH)/2 ,BRIGHTNESS_PBAR_Y ,screen_contrast/25*8 ,BRIGHTNESS_PBAR_HEIGHT ,3);
}
u8g2_SendBuffer(&u8g2);
(2)语言设置
调用U8G2图形库的u8g2_SetFont函数设定字体格式后,若后续无需变更,则无需在每次绘制字符串前重复设置。

c
// 中英文标志位切换
screen_language ^= 0x01;
// Chinese模式
if(screen_language == LANGUAGE_CHINESE)
{
u8g2_SetFont(&u8g2, u8g2_font_wqy16_t_gb2312a); /*设置字体格式*/
OLED_Clear_Area_Buffer(30,0,86,14); /*清SETTING字符串区域款冲*/
u8g2_DrawUTF8(&u8g2,30,14,"设置");
}
// English模式
else
{
u8g2_SetFont(&u8g2, u8g2_font_spleen8x16_mf);
OLED_Clear_Area_Buffer(30,0,86,14);
u8g2_DrawStr(&u8g2, 30,14,"SETTING");
}
u8g2_SendBuffer(&u8g2);
(3)翻转设置
调用U8G2图形库的u8g2_SetFlipMode函数可设置屏幕翻转模式(参数0为关闭,1为启用)。更改该模式后需重绘整个显示内容;若未修改缓冲区数据,仅调用 u8g2_SendBuffer 即可刷新原有画面。

c
// 翻转标志位切换
screen_rotate_degree ^= 0x01;
// 设置翻转模式
u8g2_SetFlipMode(&u8g2,screen_rotate_degree);
// 翻转模式后要重新绘制整个显示(由于只有下部分区域被清除,需要添加缓冲后再SendBuffer)
u8g2_DrawXBMP(&u8g2,14, setting_page_y,24,24,BMP_REVERSE);
u8g2_DrawXBMP(&u8g2,14+38,setting_page_y,24,24,BMP_ROTATE);
u8g2_DrawXBMP(&u8g2,14+76,setting_page_y,24,24,BMP_BRIGHTNESS);
// 选中方框显示
u8g2_DrawButtonFrame(&u8g2,56,52,U8G2_BTN_BW1|U8G2_BTN_SHADOW1,15,6,6);
u8g2_SendBuffer(&u8g2);
(4)反显设置
U8G2图形库未提供直接的反显配置函数,但可利用其单色屏特性(1为亮/白,0为暗/黑)自定义反显逻辑。

通过u8g2_SetDrawColor控制绘制颜色:正常显示时设为白色(1)绘制内容;反显时,先以白色(1)填充背景,再切换为黑色(0)绘制内容,从而实现视觉反转。注意,此自定义逻辑未应用于u8g2_UserInterfaceSelectionList等内置函数。
c
/*------------ 正常显示与反显:绘制颜色切换 ------------*/
void OLED_Display_Normal_or_Reverse(void)
{
// 正常显示
if(screen_reverse == DISPLAY_NORMAL)
u8g2_SetDrawColor(&u8g2, 1); /* 设置绘图颜色为白色 */
// 反显
else
{
u8g2_SetDrawColor(&u8g2, 1); /* 先用白色绘制底层 */
u8g2_DrawBox(&u8g2,0,0,u8g2_GetDisplayWidth(&u8g2),u8g2_GetDisplayHeight(&u8g2));
u8g2_SetDrawColor(&u8g2, 0); /* 再设置绘图颜色为黑色 */
}
}
四、总结
U8G2图形显示流程主要包含三步:清空缓冲区、绘制内容至缓冲区、发送缓冲区数据以刷新屏幕。若不清除缓冲区,u8g2_SendBuffer会将上一帧的残留数据与当前内容叠加发送,导致图形重影、闪屏或显示异常。由于每次刷新前均需执行清缓冲操作,在处理高频或密集动态内容时,需重点评估其刷新效率是否满足实时性要求。
该库功能完备,不仅支持基础几何图形(如线条、圆形)和 UI 控件(如按钮)的绘制,还集成了字体管理及字符串度量功能,并提供裁剪区域限制以优化渲染范围。建议通过实际开发深入探索这些特性,从而构建出更复杂、高效的显示效果。

关注「YouziTech」 ,获取完整代码!