当前位置:   article > 正文

【STM32 MCU】使用标准外设库(SPL)移植 u8g2 图形库

【STM32 MCU】使用标准外设库(SPL)移植 u8g2 图形库

STM32: port u8g2 library using Standard Peripheral Library

MCUSTM32F103C8T6
Module0.96 inch OLED with SSD1306
LibraryStandard peripheral library

1.删除不需要的文件

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

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

U8g2 的源码为了支持多种控制器(controller),包含了许多兼容性的代码。首先,类似 u8x8_d_xxx.c 命名的文件中包含 U8x8 的驱动兼容,文件名包括控制器的型号和屏幕分辨率,因此需要删除无用的文件,只保留当前控制器的文件。例如,本次使用的是 128x64 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>
  • 1

其中:

  • <prefix> 前缀基本上以 u8g2 开头;
  • <name> 字体名,其中可能包含字符大小
  • 各种 <purpose> 含义如下表所示:
名称描述
t透明字体形式
h所有字符等高
mmonospace 字体(等宽字体)
8每一个字符都是 8x8 大小的
  • <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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68

一个示例:

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

使用

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); // 打开显示器
  • 1
  • 2
  • 3
  • 4
  • 5

这里需要调用之前保留的 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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

使用

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); // 打开显示器
  • 1
  • 2
  • 3
  • 4
  • 5

Reference:

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/312479
推荐阅读
相关标签
  

闽ICP备14008679号