【STM32】U8G2图形库应用--菜单设计与开发

【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」 ,获取完整代码!

相关推荐
2301_805962931 小时前
ESP32 使用 PlatformIO 编译点灯程序
stm32·esp32
Silicore_Emma1 小时前
芯谷科技—D55126 漏电保护器专用集成电路
嵌入式硬件·新能源充电桩·芯谷科技·漏电保护器·高性能cmos漏电保护器·智能断路器/物联网配电·家用漏电保护
国科安芯2 小时前
商业航天级抗辐照全双工RS-485/RS-422收发器ASM491S2Y的技术特性与应用研究
运维·网络·单片机·嵌入式硬件·安全·架构·安全性测试
国科安芯2 小时前
ASP7A84AS高精度抗辐照线性稳压器技术特性与应用分析
单片机·嵌入式硬件·安全·架构
say_fall2 小时前
模拟量输入输出技术超详细知识点总结
linux·开发语言·嵌入式硬件·学习·php
恶魔泡泡糖2 小时前
stm32F103C8T6标准库串口发送之发送字节2
stm32·单片机·嵌入式硬件
fffzd3 小时前
STM32:时钟树与时钟源
单片机·嵌入式硬件·嵌入式软件·时钟树·时钟源
嵌入式小站3 小时前
STM32 零基础可移植教程 22:SPI 入门,先读一个外部 Flash
stm32·单片机·嵌入式硬件
崇山峻岭之间3 小时前
单片机USB 鼠标键盘实验
单片机·嵌入式硬件·计算机外设