U8g2图形库移植

发布时间 2023-04-09 15:57:49作者: 一月一星辰

前言

在最早接触屏幕的时候用到的都是最常见的0.96寸的单色OLED屏幕,当时主要用于智能车PID调参,所以基本上都是使用网上大家通用的代码搞的,后面熟悉了之后用这个屏幕做了个简易的示波器,当时画波形图是自己通过芯片手册上说明自己写的一个GUI接口。后面接触到的屏幕逐步迈向彩屏了,所以对这种单色的OLED屏幕就没太关注过了。最近发现了一个U8g2的图形库正好适用于这种OLED屏幕上,且之前也了解过LVGL的图形库,所以这里就简单移植一下,为以后屏幕使用就不需要自己造轮子了。

U8g2简介

U8g2 是一个用于嵌入式设备的简易图形库,可以在多种 OLED 和 LCD 屏幕上,支持包括 SSD1306 等多种类型的底层驱动,并可以很方便地移植到 Arduino 、树莓派、NodeMCU 和 ARM 上。

U8g2 库同时包含了 U8x8 绘图库,两者的区别为:

  • U8g2 包含各种简单及复杂图形的绘制,并支持各种形式的字体,但需要占用一定单片机的内存作为绘图缓存
  • U8x8 只包含简单的显示文本功能,且只支持简单、定宽的字体。它直接绘制图形,没有缓存功能

U8g2 库的 GitHub 地址为:https://github.com/olikraus/u8g2 ,可以从中获取到源码与文档帮助。

U8g2的移植

本次以将 U8g2 移植到 STM32 单片机与 SSD1306 通过 SPI 驱动的 128x64 OLED 为例,介绍移植的方法。不同单片机和驱动的移植可以参考这一过程,也可以参考 U8g2 的官方移植教程 https://github.com/olikraus/u8g2/wiki/Porting-to-new-MCU-platform

首先下载或克隆 U8g2 的源码,这里主要是使用 C 语言编写,所以只需要用到 csrc 目录下的文件。

下载完成后,将 csrc 目录拷贝或移动到工程目录里,并重命名为合适的目录名例如 u8g2

接下来,需要删除一些无用的代码,并添加底层驱动的代码。

1681024830736

① 删除无用内容

1.去到无用驱动文件

U8g2 的源码为了支持多种设备驱动,包含了许多兼容性的代码。首先,类似 u8x8_d_xxx.c 命名的文件中包含 U8x8 的驱动兼容,文件名包括驱动的型号和屏幕分辨率,因此需要删除无用的驱动文件,只保留当前设备的驱动。

1681025124796

例如:本次使用的是 128x64 的 SSD1306 屏幕,那么只需要保留 u8x8_d_ssd1306_128x64_noname.c 文件,删除其它类似的文件即可。U8g2 支持的所有屏幕驱动可以在 https://github.com/olikraus/u8g2/wiki/u8g2setupc 找到。

2.精简u8g2_d_setup.c

u8g2_d_setup.c 中,只需要保留 u8g2_Setup_ssd1306_128x64_noname_f() 这一个函数即可。注意,该文件内有几个命名类似的函数:

  1. 命名中无 i2c 的是 SPI 接口驱动的函数,含有i2c 的是I2C接口驱动函数,需要根据接口选择;
  2. 以 1 结尾的函数代表使用的缓存空间为 128 字节,以 2 结尾的函数代表使用的缓存为 256字节,类似以 f 结尾的函数代表使用的缓存为 1024 字节。

1681025483257

3.精简u8g2_d_memory.c

它需要根据 u8g2_d_setup.c 中的调用情况决定用到哪些函数。由于 u8g2_Setup_ssd1306_i2c_128x64_noname_f() 函数只用到 u8g2_m_16_8_f() 这一个函数,因此只需要保留它,其余函数全部删除即可。

其它的函数一定要删掉或注释掉,否则编译时很可能会提示内存不足!!

1681025578998

4.精简字体文件

还有一处必要的精简是字体文件 u8x8_fonts.cu8g2_fonts.c ,尤其是 u8g2_fonts.c ,该文件提供了包括汉字在内的几万个文字的多种字体,仅源文件就有 30MB ,编译后占据的内存非常大。

字体类型的变量非常多,建议先复制一个备份后将所有变量删除,之后视情况再添加字体。字体变量的命名大致遵循以下规则:

'' ''

其中:

  • <prefix> 前缀基本上以 u8g2 开头;
  • <name> 字体名,其中可能包含字符大小
  • 各种 <purpose> 含义如下表所示:
名称 描述
t 透明字体形式
h 所有字符等高
m monospace 字体(等宽字体)
8 每一个字符都是 8x8 大小的
  • <charset> 是字体支持的字符集,如下表所示:
名称 描述
f 只包含单字节字符
r 只包含 ASCII 范围为 32~127 的字符
u 只包含 ASCII 范围为 32~95 的字符,即不包括小写英文
n 只包含数字及一些特殊用途字符
... 还包括许多自定义的字符集,例如有一些结尾带 gb2312 或 Chinese 的字体名就包括中文

一般建议只保留需要的字体即可。

② 将修改完的代码添加至工程中

1681025868312

1681025890359

这里我是采用gcc+make,这里展示makefile需要添加的。keil添加方式这里不在展示。

③ 增加回调函数

U8g2 已经包含了 SSD1306 的驱动,只需要添加一个函数 u8x8_gpio_and_delay() 用于模拟时序即可。这里给出软件4-SPI编写的回调函数。

uint8_t u8x8_stm32_gpio_and_delay(u8x8_t* u8x8, uint8_t msg, uint8_t arg_int, void* arg_prt)
{
    switch (msg)
    {
    case U8X8_MSG_GPIO_AND_DELAY_INIT:     
        HAL_Delay(200);           
        break;
    case U8X8_MSG_DELAY_MILLI:
        HAL_Delay(arg_int);
        break;
    case U8X8_MSG_DELAY_NANO: 
        break;
    case U8X8_MSG_DELAY_100NANO:  
        __NOP();  
        break;
    case U8X8_MSG_GPIO_SPI_DATA:
        (arg_int) ? OLED_SDA_SET : OLED_SDA_CLR;
        break;
    case U8X8_MSG_GPIO_SPI_CLOCK:
        (arg_int) ? OLED_CLK_SET : OLED_CLK_CLR;
        break;
    case U8X8_MSG_GPIO_CS:
        (arg_int) ? OLED_CS_SET : OLED_CS_CLR;
        break;
    case U8X8_MSG_GPIO_DC:
        (arg_int) ? OLED_DC_SET : OLED_DC_CLR;
        break;
    case U8X8_MSG_GPIO_RESET:
        (arg_int) ? OLED_RES_SET : OLED_RES_CLR;
        break;
    default:
        return 0;//A message was received which is not implemented, return 0 to indicate an error
    }
    return 1;
}

在.h文件中定义好引脚:

#define OLED_CLK_PIN GPIO_PIN_9
#define OLED_SDA_PIN GPIO_PIN_8
#define OLED_RES_PIN GPIO_PIN_7
#define OLED_DC_PIN GPIO_PIN_6
#define OLED_CS_PIN GPIO_PIN_5

#define OLED_CLK_CLR HAL_GPIO_WritePin(GPIOB, OLED_CLK_PIN, GPIO_PIN_RESET)
#define OLED_CLK_SET HAL_GPIO_WritePin(GPIOB, OLED_CLK_PIN, GPIO_PIN_SET)

#define OLED_SDA_CLR HAL_GPIO_WritePin(GPIOB, OLED_SDA_PIN, GPIO_PIN_RESET)
#define OLED_SDA_SET HAL_GPIO_WritePin(GPIOB, OLED_SDA_PIN, GPIO_PIN_SET)

#define OLED_RES_CLR HAL_GPIO_WritePin(GPIOB, OLED_RES_PIN, GPIO_PIN_RESET)
#define OLED_RES_SET HAL_GPIO_WritePin(GPIOB, OLED_RES_PIN, GPIO_PIN_SET)

#define OLED_DC_CLR HAL_GPIO_WritePin(GPIOB, OLED_DC_PIN, GPIO_PIN_RESET)
#define OLED_DC_SET HAL_GPIO_WritePin(GPIOB, OLED_DC_PIN, GPIO_PIN_SET)

#define OLED_CS_CLR HAL_GPIO_WritePin(GPIOB, OLED_CS_PIN, GPIO_PIN_RESET)
#define OLED_CS_SET HAL_GPIO_WritePin(GPIOB, OLED_CS_PIN, GPIO_PIN_SET)

④ U8g2简单使用

U8g2 的初始化可以参考如下步骤:

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

这里给出可以显示出官方logo代码,移植成功后调用这个函数既可以在屏幕中显示出官方logo图案:

void u8g2_test_demo(u8g2_t* u8g2)
{
    u8g2_FirstPage(u8g2);
    do {
        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");
    } while (u8g2_NextPage(u8g2));
}

官方logo图案:

https://raw.githubusercontent.com/wiki/olikraus/u8g2/ref/u8g2_logo_transparent_orange.png

后记

U8g2移植还是比较容易,后续学习下相关库资源,制作一些比较好看的动画效果。

参考链接: