STM32移植U8g2图形库:让OLED显示变得更加精彩

之前的文章,介绍过ESP8266在Arduino IDE环境中使用U8g2库,实现OLED上的各种图形显示。

本篇,介绍一下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字节
  • f1024字节
  • 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接口进行类似的修改即可。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32移植U8g2图形库:让OLED显示变得更加精彩

    发表回复