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


更多推荐

所有评论(0)