STM32 MCU使用SPL库移植u8g2图形库教程

STM32: port u8g2 library using Standard Peripheral Library

MCU STM32F103C8T6
Module 0.96 inch OLED with SSD1306
Library Standard peripheral library

1.删除不需要的文件

Porting to new MCU platform · olikraus/u8g2 Wiki (github.com)

为了减小编译后的大小,我们需要删除或精简以下的文件

U8g2 的源码为了支持多种控制器(controller),包含了许多兼容性的代码。首先,类似 u8x8_d_xxx.c 命名的文件中包含 U8x8 的驱动兼容,文件名包括控制器的型号和屏幕分辨率,因此需要删除无用的文件,只保留当前控制器的文件。例如,本次使用的是 128×64 OLED with SSD1306 controller,那么只需要保留 u8x8_d_ssd1306_128x64_noname.c 文件,删除其它类似的文件即可。

u8g2_d_setup.c

删去其他函数,只保留 u8g2_Setup_ssd1306_i2c_128x64_noname_f()

根据 u8g2/doc/faq.txt at master · olikraus/u8g2 (github.com),我们可以知道 F 意为 full buffer mode,需要 RAM 大小 1024 bytes

u8g2_d_memory.c

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

Fonts

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

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

<prefix> '_' <name> '_' <purpose> <charset>

其中:

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

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

    2.实现 callback 函数

    官方提供了 GPIO and Delay callback 函数模板

    uint8_t u8x8_gpio_and_delay_template(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
    {
      switch(msg)
      {
        case U8X8_MSG_GPIO_AND_DELAY_INIT:	// called once during init phase of u8g2/u8x8
          break;							// can be used to setup pins
        case U8X8_MSG_DELAY_NANO:			// delay arg_int * 1 nano second
          break;    
        case U8X8_MSG_DELAY_100NANO:		// delay arg_int * 100 nano seconds
          break;
        case U8X8_MSG_DELAY_10MICRO:		// delay arg_int * 10 micro seconds
          break;
        case U8X8_MSG_DELAY_MILLI:			// delay arg_int * 1 milli second
          break;
        case U8X8_MSG_DELAY_I2C:				// arg_int is the I2C speed in 100KHz, e.g. 4 = 400 KHz
          break;							// arg_int=1: delay by 5us, arg_int = 4: delay by 1.25us
        case U8X8_MSG_GPIO_D0:				// D0 or SPI clock pin: Output level in arg_int
        //case U8X8_MSG_GPIO_SPI_CLOCK:
          break;
        case U8X8_MSG_GPIO_D1:				// D1 or SPI data pin: Output level in arg_int
        //case U8X8_MSG_GPIO_SPI_DATA:
          break;
        case U8X8_MSG_GPIO_D2:				// D2 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D3:				// D3 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D4:				// D4 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D5:				// D5 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D6:				// D6 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_D7:				// D7 pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_E:				// E/WR pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_CS:				// CS (chip select) pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_DC:				// DC (data/cmd, A0, register select) pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_RESET:			// Reset pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_CS1:				// CS1 (chip select) pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_CS2:				// CS2 (chip select) pin: Output level in arg_int
          break;
        case U8X8_MSG_GPIO_I2C_CLOCK:		// arg_int=0: Output low at I2C clock 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
          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;
    }
    

    一个示例:

    void HW_I2C_Init(void)
    {
    
    	RCC_APB2PeriphClockCmd(I2C_RCC_APBx_GPIOx, ENABLE);
    
    	GPIO_InitTypeDef GPIO_InitStructure;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    	GPIO_InitStructure.GPIO_Pin = SCL_Pin | SDA_Pin;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(I2C_GPIOx, &GPIO_InitStructure);
    
    	RCC_APB1PeriphClockCmd(I2C_RCC_APBx_I2Cx, ENABLE);
    
    	I2C_InitTypeDef I2C_InitStructure;
    	I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
    	I2C_InitStructure.I2C_ClockSpeed = 400000;
    	I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
    	I2C_InitStructure.I2C_Ack = I2C_Ack_Disable;
    	I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    	I2C_InitStructure.I2C_OwnAddress1 = 0x00;
    	I2C_Init(I2Cx, &I2C_InitStructure);
    
    	I2C_Cmd(I2Cx, ENABLE);
    }
    
    
    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_AND_DELAY_INIT: // called once during init phase of u8g2/u8x8
        SW_I2C_Init();
        break;                   // can be used to setup pins
      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(1);                // 1us = 500kHz, just for SW I2C
        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 == 0)
        {
          SW_MCU_SCL(Bit_RESET);
        }
        else
        {
          SW_MCU_SCL(Bit_SET);
        }
        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 == 0)
        {
          SW_MCU_SDA(Bit_RESET);
        }
        else
        {
          SW_MCU_SDA(Bit_SET);
        }
        break; // arg_int=1: Input dir with pullup high for I2C data pin
      default:
        u8x8_SetGPIOResult(u8x8, 1); // default return value
        break;
      }
      return 1;
    }
    

    使用

    u8g2_t u8g2; // a structure which will contain all the data for one display
    ...
    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_Setup_ssd1306_128x64_noname_f() 函数,该函数的4个参数,其含义为:

  • u8g2 :需要配置的 U8g2 结构体
  • rotation :配置屏幕是否要旋转,默认使用 U8G2_R0 即可
  • byte_cb :传输字节的方式,这里使用软件 I2C 驱动,因此使用 U8g2 提供的 u8x8_byte_sw_i2c() 函数。如果是硬件 I2C 的话,可以参照编写自己的函数
  • gpio_and_delay_cb :提供给软件模拟 I2C 的 GPIO 输出和延时,使用之前编写的配置函数 u8x8_gpio_and_delay()
  • 使用硬件 I2C,则需要实现 u8x8_byte_hw_i2c(),官方文档给的模板实现后无法正常工作。
    在此我自己实现,并解决了问题。

    uint8_t u8x8_byte_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
    {
      uint8_t *data = (uint8_t *)arg_ptr;
      uint8_t data_length = arg_int;
      uint8_t retry = 0;
      switch (msg)
      {
      case U8X8_MSG_BYTE_INIT:
        /* add your custom code to init i2c subsystem */
        HW_I2C_Init();
        break;
      case U8X8_MSG_BYTE_START_TRANSFER:
        while (I2C_GetFlagStatus(I2C1, I2C_FLAG_BUSY) && retry < 200)
        {
          retry++;
        }
        retry = 0;
        I2C_GenerateSTART(I2C1, ENABLE);
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT) && retry < 200)
        {
          retry++;
        }
        retry = 0;
        I2C_Send7bitAddress(I2C1, u8x8_GetI2CAddress(u8x8), I2C_Direction_Transmitter);
        while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) && retry < 200)
        {
          retry++;
        }
        break;
      case U8X8_MSG_BYTE_SEND:
        for (int i = 0; i < data_length; i++)
        {
          I2C_SendData(I2C1, data[i]);
          while (I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE) == RESET)
          {
          }
        }
        break;
      case U8X8_MSG_BYTE_END_TRANSFER:
        I2C_GenerateSTOP(I2C1, ENABLE);
        break;
      default:
        return 0;
      }
      return 1;
    }
    

    使用

    u8g2_t u8g2; // a structure which will contain all the data for one display
    ...
    u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_hw_i2c, u8x8_gpio_and_delay);  		// 初始化 u8g2 结构体
    u8g2_InitDisplay(u8g2);     // 根据所选的芯片进行初始化工作,初始化完成后,显示器处于关闭状态
    u8g2_SetPowerSave(u8g2, 0); // 打开显示器
    

    Reference:

    [1]: U8g2图形库与STM32移植(I2C,软件与硬件) – 冰封残烛 – 博客园 (cnblogs.com)

    作者:ShiinaKaze

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 MCU使用SPL库移植u8g2图形库教程

    发表评论