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 的驱动兼容,文件名包括控制器的型号和屏幕分辨率,因此需要删除无用的文件,只保留当前控制器的文件。例如,本次使用的是 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.c
和 u8g2_fonts.c
,尤其是 u8g2_fonts.c
,该文件提供了包括汉字在内的几万个文字的多种字体,仅源文件就有 30MB ,编译后占据的内存非常大。
字体类型的变量非常多,建议先复制一个备份后将所有变量删除,之后视情况再添加字体。字体变量的命名大致遵循以下规则:
<prefix> '_' <name> '_' <purpose> <charset>
其中:
-
<prefix>
前缀基本上以 u8g2 开头; -
<name>
字体名,其中可能包含字符大小 - 各种
<purpose>
含义如下表所示:
名称 | 描述 |
---|---|
t | 透明字体形式 |
h | 所有字符等高 |
m | monospace 字体(等宽字体) |
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;
}
一个示例:
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:文章来源:https://www.toymoban.com/news/detail-846718.html
[1]: U8g2图形库与STM32移植(I2C,软件与硬件) - 冰封残烛 - 博客园 (cnblogs.com)文章来源地址https://www.toymoban.com/news/detail-846718.html
到了这里,关于【STM32 MCU】使用标准外设库(SPL)移植 u8g2 图形库的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!