单片机
返回首页

STM32移植U8g2图形库——玩转OLED显示

2025-10-24 来源:bilibili

本篇,介绍一下U8g2库如何移植到STM32上,进行OLED的图形显示。


本次的实验硬件为:


STM32:型号为最常见的STM32F103C8T6


OLED:0.96寸OLED,IIC接口(如果是SPI接口,文中也有对应的修改介绍)


1 U8g2简介

U8g2 是一个用于嵌入式设备的单色图形库。U8g2支持单色OLED和LCD,并支持如SSD1306等多种类型的OLED驱动。


U8g2源码的开源库地址:https://github.com/olikraus/u8g2

2 移植步骤

首先下载U8g2的源码,因为STM32主要是使用C语言编程,所以只需关注源码中的C源码部分,即csrc文件夹下的文件。


2.1 精简c源码

U8g2支持多种显示驱动的屏幕,因为源码中也包含了各个驱动对应的文件,为了减小整个工程的代码体积,在移植U8g2时,可以删除一些无用的文件。


2.1.1 去掉无用的驱动文件

这些驱动文件通常是u8x8_d_xxx.c,xxx包括驱动的型号和屏幕分辨率。ssd1306驱动芯片的OLED,使用u8x8_ssd1306_128x64_noname.c这个文件,其它的屏幕驱动和分辨率的文件可以删掉。

2.1.2 精简u8g2_d_setup.c

由于我的OLED是IIC接口,只留一个本次要用到的u8g2_Setup_ssd1306_i2c_128x64_noname_f就好(如果是SPI接口,需要使用u8g2_Setup_ssd1306_128x64_noname_f这个函数),其它的可以删掉或注释掉。


#include "u8g2.h"


/* ssd1306 f */

void u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)

{

 uint8_t tile_buf_height;

 uint8_t *buf;

 u8g2_SetupDisplay(u8g2, u8x8_d_ssd1306_128x64_noname, u8x8_cad_ssd13xx_fast_i2c, byte_cb, gpio_and_delay_cb);

 buf = u8g2_m_16_8_f(&tile_buf_height);

 u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);

}

注意,与这个函数看起来十分相似的函数的有:


u8g2_Setup_ssd1306_128x64_noname_1


u8g2_Setup_ssd1306_128x64_noname_2


u8g2_Setup_ssd1306_128x64_noname_f


u8g2_Setup_ssd1306_i2c_128x64_noname_1


u8g2_Setup_ssd1306_i2c_128x64_noname_2


u8g2_Setup_ssd1306_i2c_128x64_noname_f


其中,前面3个,是给SPI接口的OLED用的,函数最后的数字或字母,代表显示时的buf大小:


1:128字节


2:256字节


f:1024字节


2.1.3 精简u8g2_d_memory.c

由于用到的u8g2_Setup_ssd1306_i2c_128x64_noname_f函数中,只调用了u8g2_m_16_8_f这个函数,所以留下这个函数,其它的函数一定要删掉或注释掉,否则编译时很可能会提示内存不足!!!


#include "u8g2.h"


uint8_t *u8g2_m_16_8_f(uint8_t *page_cnt)

{

 #ifdef U8G2_USE_DYNAMIC_ALLOC

 *page_cnt = 8;

 return 0;

 #else

 static uint8_t buf[1024];

 *page_cnt = 8;

 return buf;

 #endif

}

2.2 编写移植函数


精简源码之后,还需要编写如下的配置函数。


2.2.1 GPIO初始化

对OLED用到的IIC接口进行GPIO的初始化配置:


#define SCL_Pin GPIO_Pin_6

#define SDA_Pin GPIO_Pin_7

#define IIC_GPIO_Port GPIOB

void IIC_Init(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );


GPIO_InitStructure.GPIO_Pin = SCL_Pin|SDA_Pin;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //推挽输出

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

GPIO_Init(IIC_GPIO_Port, &GPIO_InitStructure);

}

如果是SPI接口,则初始化对应的SPI接口即可。


2.2.2 u8x8_gpio_and_delay

这个函数也需要自己写,主要的修改包括:


赋予U8g2相应的延时函数,比如下面的delay_ms和delay_us


为U8g2提供IIC接口的高低电平调用:


U8x8_MSG_GPIO_I2C_CLOCK:IIC的SCL


U8x8_MSG_GPIO_I2C_DATA:IIC的SDA


uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)

{

   switch (msg)

   {

   case U8X8_MSG_DELAY_100NANO: // delay arg_int * 100 nano seconds

       __NOP();

       break;

   case U8X8_MSG_DELAY_10MICRO: // delay arg_int * 10 micro seconds

       for (uint16_t n = 0; n < 320; n++)

       {

           __NOP();

       }

       break;

   case U8X8_MSG_DELAY_MILLI: // delay arg_int * 1 milli second

       delay_ms(1);

       break;

   case U8X8_MSG_DELAY_I2C: // arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz

       delay_us(5);

       break;                    // arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us

   case U8X8_MSG_GPIO_I2C_CLOCK: // arg_int=0: Output low at I2C clock pin

 if(arg_int == 1)

 {

  GPIO_SetBits(IIC_GPIO_Port, SCL_Pin);

 }

 else if(arg_int == 0)

 {

  GPIO_ResetBits(IIC_GPIO_Port, SCL_Pin);

 }

       break;                    // arg_int=1: Input dir with pullup high for I2C clock pin

   case U8X8_MSG_GPIO_I2C_DATA:  // arg_int=0: Output low at I2C data pin

       if(arg_int == 1)

 {

  GPIO_SetBits(IIC_GPIO_Port, SDA_Pin);

 }

 else if(arg_int == 0)

 {

  GPIO_ResetBits(IIC_GPIO_Port, SDA_Pin);

 }

       break;                    // arg_int=1: Input dir with pullup high for I2C data pin

   case U8X8_MSG_GPIO_MENU_SELECT:

       u8x8_SetGPIOResult(u8x8, /* get menu select pin state */ 0);

       break;

   case U8X8_MSG_GPIO_MENU_NEXT:

       u8x8_SetGPIOResult(u8x8, /* get menu next pin state */ 0);

       break;

   case U8X8_MSG_GPIO_MENU_PREV:

       u8x8_SetGPIOResult(u8x8, /* get menu prev pin state */ 0);

       break;

   case U8X8_MSG_GPIO_MENU_HOME:

       u8x8_SetGPIOResult(u8x8, /* get menu home pin state */ 0);

       break;

   default:

       u8x8_SetGPIOResult(u8x8, 1); // default return value

       break;

   }

   return 1;

}

如果是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: //SPI - CLK

           lcd_sclk(arg_int);

           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: //SPI - CS

           lcd_cs((uint8_t)arg_int);

       case U8X8_MSG_GPIO_DC:

           lcd_dc((uint8_t)arg_int); //SPI - MISO

           break;

       case U8X8_MSG_GPIO_RESET:

           break;

   }

   return 1;

}

可以看出,对于IIC与SPI接口,只有分别进行对应的配置即可。


2.2.3 u8g2Init

U8g2的初始化,需要调用下面这个u8g2_Setup_ssd1306_128x64_noname_f函数,该函数的4个参数含义:


u8g2:传入的U8g2结构体


U8G2_R0:默认使用U8G2_R0即可(用于配置屏幕是否要旋转)


u8x8_byte_sw_i2c:使用软件IIC驱动,该函数由U8g2源码提供


u8x8_gpio_and_delay:就是上面我们写的配置函数


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);

}

2.2.4 显示测试函数


使用U8g2提供的测试函数,用于查看显示效果


void draw(u8g2_t *u8g2)

{

   u8g2_SetFontMode(u8g2, 1); /*字体模式选择*/

   u8g2_SetFontDirection(u8g2, 0); /*字体方向选择*/

   u8g2_SetFont(u8g2, u8g2_font_inb24_mf); /*字库选择*/

   u8g2_DrawStr(u8g2, 0, 20, "U");


   u8g2_SetFontDirection(u8g2, 1);

   u8g2_SetFont(u8g2, u8g2_font_inb30_mn);

   u8g2_DrawStr(u8g2, 21,8,"8");


   u8g2_SetFontDirection(u8g2, 0);

   u8g2_SetFont(u8g2, u8g2_font_inb24_mf);

   u8g2_DrawStr(u8g2, 51,30,"g");

   u8g2_DrawStr(u8g2, 67,30,"xb2");


   u8g2_DrawHLine(u8g2, 2, 35, 47);

   u8g2_DrawHLine(u8g2, 3, 36, 47);

   u8g2_DrawVLine(u8g2, 45, 32, 12);

   u8g2_DrawVLine(u8g2, 46, 33, 12);


   u8g2_SetFont(u8g2, u8g2_font_4x6_tr);

   u8g2_DrawStr(u8g2, 1,54,"github.com/olikraus/u8g2");

}

2.3 源码加入到MDK编译


在一个STM32的基础例程上进行修改。


2.3.1添加u8g2源码到工程

左侧工程目录添加U8g2源码,然后再添加U8g2的头文件搜寻目录,如下:

2.3.2 主函数

主函数中,首先是IIC的初始化和U8g2的初始化,然后就可以测试U8g2的图形显示功能了:


#include "delay.h"

#include "sys.h"

#include "u8g2.h"


int main(void)

{

delay_init();

IIC_Init();


   u8g2_t u8g2;

u8g2Init(&u8g2);


while(1)

{

      u8g2_FirstPage(&u8g2);

      do

      {

  draw(&u8g2);

      } while (u8g2_NextPage(&u8g2));

   }

}



3 测试效果

4 总结

本篇介绍了如何将U8g2图形库移植到STM32中,其中主要的修改包括:


精简源码中的u8g2_d_setup.c和u8g2_d_memory.c


OLED所用IIC接口的GPIO初始化


编写u8x8_gpio_and_delay和u8g2Init


其中,u8g2_d_memory.c文件一定要去掉无用的函数,否则编译时会提示内存不足;对于SPI接口的OLED,参考IIC接口进行类似的修改即可。


进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦四级调频发射机

  • 500W MOS场效应管电源逆变器,12V转110V/220V

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外开关

  • 12V转110V/220V 500W逆变器

  • DS1669数字电位器

    相关电子头条文章