一、前言


基于STM32平台移植U8G2图形库,实现OLED多种单色图形界面的显示。本文提供详细的移植教程,并汇总了开发过程中常见问题的解决策略,助力开发者快速上手。



二、认识U8G2图形库


使用U8G2库驱动OLED相比于直接用IIC/SPI等通信方式更具有优势,主要体现在如下方面:

(1)跨平台且移植性强:同一套U8G2代码可在ESP32、STM32、Arduino等不同平台运行,仅需要调整初始化参数,极大提升了项目的移植效率。

(2)降低开发复杂度:如IIC通信控制需要处理时序、发送命令或数据等代码,涉及不少寄存器操作或运算,而U8G2封装了这些复杂逻辑,且提供不同驱动芯片(如SH1107、UC1701等)的绘图函数(如u8g2_DrawStr、u8g2_DrawGlyph等)和多种字体(中、英文等)。

(3)提供多种缓冲模式:全款冲、页缓冲和U8X8纯文本模式,可根据单片机的RAM大小选择合适模式。



三、移植流程


1.硬件组成


(1)主控芯片:使用的是STM32F103RCT6。

(2)OLED显示屏:本模块搭载的是SSD1306驱动芯片,分辨率是128*64,默认采用4线制SPI总线接口。实际应用切换为IIC接口,需进行如下硬件调整:将模块上的电阻R3移至R1位置,R8位置使用0欧电阻短路,同时将CS片选引脚、DC数据/命令引脚接地,RES复位引脚接电源拉高。

在这里插入图片描述

STM32与OLED的引脚连接方式如下。

在这里插入图片描述

2.移植前准备工作


(1)在GitHub平台搜索U8G2并下载最新源码。

在这里插入图片描述

(2)准备需要移植的基于STM32的工程源码。


3.U8G2源文件精简与优化


通过移除未引用的文件及死代码,消除无效编译代码,从而减小构建体积并提升编译效率。

(1)STM32使用的是C语言编程,则只需要csrc文件夹内的文件。鉴于OLED屏幕采用SSD1306驱动且分辨率为128x64,应仅保留u8x8_d_ssd1306_128x64_noname.c文件,其余所有不匹配的u8x8_d_*.c驱动文件均可删除。

在这里插入图片描述

(2)先确认OLED屏幕接口类型(IIC或SPI),随后在u8g2_d_setup.c文件中仅保留对应的函数(IIC接口保留u8g2_Setup_ssd1306_i2c_128x64_noname_f,SPI接口保留u8g2_Setup_ssd1306_128x64_noname_f),并将其余无关函数屏蔽或删除。

在这里插入图片描述

函数后缀含义及选型建议:
_f:对应1024字节缓冲区,适用于全缓冲模式;
_1:对应128字节缓冲区,适用于页缓冲模式;
_2:对应256字节缓冲区,刷新速度下降,但稳定性提升。

选型策略:STM32资源充裕时,推荐优先使用_f(1024字节)以获最佳性能;若资源受限,可根据实际需求选择_1或_2。

(3)在u8g2_d_memory.c中根据前步选择的缓冲区类型(_f/_1/2),仅保留对应的u8g2_m_16_8*(_f/_1/_2)系列函数,屏蔽或删除其余无关函数。

在这里插入图片描述

(4)添加以下函数完成硬件接口配置

//IIC接口
#define    SCL_Pin 	GPIO_Pin_1
#define    SDA_Pin 	GPIO_Pin_2
#define    SCL_GPIO_Port 	GPIOA
#define    SDA_GPIO_Port	 GPIOA

uint8_t u8x8_gpio_and_delay(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr)
{
	switch(msg)
		{
			case U8X8_MSG_DELAY_MILLI:			//Function with a delay of  'arg_int' ms 
				delay_ms(arg_int);
			break;
		
			case U8X8_MSG_DELAY_10MICRO:		//Function with a delay of 10us
				delay_us(10);
			break;
		
			case U8X8_MSG_DELAY_100NANO:		//Function with a delay of 100ns
				__NOP();
			break;
			
			case U8X8_MSG_GPIO_I2C_CLOCK:
				if (arg_int) 
					GPIO_SetBits(SCL_GPIO_Port, SCL_Pin);		//SCL=1
				else 
					GPIO_ResetBits(SCL_GPIO_Port, SCL_Pin);  	//SCL=0
			break;
			
			case U8X8_MSG_GPIO_I2C_DATA:
				if (arg_int) 
					GPIO_SetBits(SDA_GPIO_Port, SDA_Pin);		//SDA=1
				else 
					GPIO_ResetBits(SDA_GPIO_Port, SDA_Pin);  	//SDA=0
			break;		
						
			default:
				return 0; 	// Received an unimplemented message. Returning 0 indicates an error.
	}
	return 1; 				// The command has been successfully processed.
}
//SPI接口
uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    switch (msg)
    {
        case U8X8_MSG_GPIO_SPI_DATA:
            lcd_sdin((uint8_t)arg_int); 	//SPI - MOSI	
            break;

        case U8X8_MSG_GPIO_SPI_CLOCK: 	
            lcd_sclk(arg_int);		//SPI - CLK
            break;

        case U8X8_MSG_GPIO_AND_DELAY_INIT:
            oled_init(); 			//OLED初始化
            Delay(1);
            break;

        case U8X8_MSG_DELAY_MILLI:
            Delay(arg_int); 		//延时
            break;

        case U8X8_MSG_GPIO_CS: 	
            lcd_cs((uint8_t)arg_int);		//SPI - CS

        case U8X8_MSG_GPIO_DC:
            lcd_dc((uint8_t)arg_int); 	//SPI - MISO
            break;

        case U8X8_MSG_GPIO_RESET:
            break;
            
        default:
			return 0; 
    }
    return 1;
}

4.编写代码


按以下步骤配置函数。

(1)添加修改过的U8G2源码到工程并添加头文件路径。

(2)对硬件引脚初始化:对IIC接口进行GPIO口初始化配置,对应SPI接口也是如此。

void IIC_Init(void)
{
    GPIO_InitTypeDef  GPIO_InitStructure;  	      
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE );    
    GPIO_InitStructure.GPIO_Pin 	= GPIO_Pin_2|GPIO_Pin_1;
    GPIO_InitStructure.GPIO_Mode 	= GPIO_Mode_Out_PP; 	 //推挽输出
    GPIO_InitStructure.GPIO_Speed 	= GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}

(3)对U8G2初始化:调用 u8g2_Setup_ssd1306_128x64_noname_f 函数时,需根据需求配置旋转角度、通信接口等参数。其中参数U8G2_R0、R1、R2、R3分别对应0、90、180、270度;IIC 模式使用 u8x8_byte_sw_i2c,SPI 模式则需在底层驱动文件(u8x8_byte.c)中选取对应的回调函数参数。

void u8g2Init(u8g2_t *u8g2)
{
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_sw_i2c, u8x8_gpio_and_delay);  // 初始化 u8g2 结构体
    u8g2_InitDisplay(u8g2); 		// 根据所选的芯片进行初始化工作,初始化完成后,显示器处于关闭状态
    u8g2_SetPowerSave(u8g2, 0);	// 打开显示器
    u8g2_ClearBuffer(u8g2);
}

(4)可在此阶段添加显示测试功能,以下为参考示例的思路:初始化后配置字体格式,后续无需重复配置,调用U8G2函数绘制画面并写入缓存以完成显示开机画面;随后通过逐字显示与逐字清除字符串的循环操作,实现动画效果。

//主函数
u8g2_t u8g2;  //u8g2结构体
int main(void)
{
    //SystemInit();		//初始化RCC 设置系统主频为72MHZ   SYSCLK_FREQ_72MHz
    delay_init();	   	//延时初始化	
    IIC_Init();		//液晶屏初始化
    u8g2Init(&u8g2);
    Dsp_Startup(); 		//开机显示界面
    delay_ms(200);
    while(1)
    {
    	Dsp_Animation();				
    }
}

//开机显示函数
void Dsp_Startup(void)
{
    u8g2_ClearBuffer(&u8g2);  //清缓冲
    //u8g2_SetFontMode(&u8g2, 1);	//设置字体模式,1启用,0禁用(透明模式)
    //u8g2_SetFontDirection(&u8g2, 0);	//设置字体方向     
    u8g2_SetFont(&u8g2, u8g2_font_wqy12_t_gb2312); //设置字体格式
    u8g2_DrawUTF8(&u8g2,6,12,"你好,很高兴认识你!"); //中文显示
	
    u8g2_DrawXBM(&u8g2,0,27,32,32,wechat);	//图片显示
    u8g2_DrawXBM(&u8g2,32,30,24,24,search);	//图片显示
    u8g2_DrawFrame(&u8g2,33,30,95,26);		//方框显示  u8g2_DrawRFrame 带圆角矩形框
    u8g2_SendBuffer(&u8g2); //把要显示的信息写入缓冲
}

//逐字显示字符函数
//x、y:字符首坐标地址
//str:字符内容
//weight:字符宽度(配置的字体格式)
//delay:延时时间
void Print_String(u8g2_t *u8g2, uint8_t x, uint8_t y, uint8_t* str,uint8_t weight,uint16_t delay) 
{
    uint8_t str_len;
 
    str_len = strlen(str);
    for(uint8_t i = 0; i < str_len ; i++)
    {
    	u8g2_DrawGlyph(u8g2, x + weight * i, y, str[i]);
    	u8g2_SendBuffer(u8g2);
    	delay_ms(delay); 
  }
}

//字符逐字清除
//weight:绘制矩形宽度
//delay:延时时间
void Clear_String(u8g2_t *u8g2, uint8_t weight, uint16_t delay)
{
    int clear_x = 119; //绘制矩形首坐标x轴地址
    int clear_height = 16;   //绘制矩形高度
    int x;
  
    u8g2_SetDrawColor(u8g2, 0); 	//设置绘图颜色为黑色   
    for (x = clear_x ; x >= 54; x -= weight) 
    {
        u8g2_DrawBox(u8g2, x, 32, weight, clear_height );
        u8g2_SendBuffer(u8g2);
        delay_ms(delay);
    }   
    u8g2_SetDrawColor(u8g2, 1); 	// 恢复默认绘图颜色
}

//动画显示函数
void Dsp_Animation(void)
{
    u8g2_SetFont(&u8g2, u8g2_font_spleen8x16_mf); //设置字体格式(若更改字体格式需重新设置)
    Print_String(&u8g2, 55, 48, "YouziTech", 8, 50);	//逐字打印字符
    Clear_String(&u8g2, 8, 50); 			//逐字从右向左清除字符
}

图形显示的实现主要三个步骤:清空缓冲区;绘制内容到缓冲区;发送缓冲区数据。若不清除缓冲区,u8g2_SendBuffer会将上一帧的残留数据与当前内容叠加发送,导致图形重影、闪屏或显示异常。



四、问题优化方案


1.内存不足报错


经过精简U8G2源代码后还存在内存不足现象,还可以通过以下方法解决:

(1)提升Keil的优化等级,设为非0值以减少代码体积。

在这里插入图片描述

(2)屏蔽u8g2_fonts.c和u8g2_font.c文件中没有用到的字体,仅保留项目实际使用的字体(如u8g2_font_spleen8x16_mf)。

在这里插入图片描述

(3)使用更少字节的缓冲模式,如全缓冲改成页缓冲,初始化u8g2_m_16_8_f改成_1或_2后缀。


2.中文显示乱码、缺失或不显示


(1)在Keil软件打开Configuration窗口,在Editor页面将Encoding选项改为UTF-8,再将代码文件保存为UTF-8

在这里插入图片描述

在这里插入图片描述

(2)显示缺失可能没有使用全缓冲模式,可以改成全缓冲模式修改显示内容或位置

(3)检测使用的字体格式是否支持中文字或特殊字体,确保字体文件中包含UTF-8编码中文字符集



五、总结


U8G2图形库支持STM32等多平台移植,通过源文件中函数操作实现快速开发。使用前需确认屏幕为单色驱动芯片分辨率在库支持范围内,并核实通信接口。此外,精简和优化源码对解决内存不足及减小代码体积至关重要。

在这里插入图片描述




在这里插入图片描述

关注「YouziTech」 ,获取更多内容
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐