本帖最后由 uant 于 2023-2-6 10:44 编辑
U8g2是一个优秀的图形显示库,支持很多屏幕,U8g2可以简化显示的处理难度,并且支持丰富的功能。因支持的型号众多,这里就不一一列出了,如果需要了解所支持的屏幕,可以访问以下地址查看: #屏幕支持列表
https://github.com/olikraus/u8g2/wiki/u8g2setupcpp
本文将讲解U8g2移植到Keil5的过程,屏幕是OLED SSD1306&SH1106进行移植,这两个芯片的移植方式基本上是一样的。 讲解前先来说下我的环境: # 系统 window 11
# 开发工具 Keil 5.36
# 开发板 国民技术的 N32WB031_STB_V1.3
下面就开始讲解移植过程: 一、获取支持文件: 访问 https://github.com/olikraus/u8g2 ,下载代码库,可以直接下载zip包或者直接使用命令 git clone https://github.com/olikraus/u8g2 克隆一份。 如果是压缩包请解压,文件结构与github上所见一致。 这里,我们需要移植到Keil5中,所以需要使用他的C版本,C版本文件都在 csrc 目录下,我们将目录拷到我们自己的工程目录下,这里将 csrc 目录改名为 u8g2。到这里第一步就算完成了,后续就是要进行相关配置。 二、配置过程 1、编译环境配置: 先将u8g2目录加入到keil5的引用路径中,以便系统可以找到所需的头文件(这里图片演示仅做简单展示,正常会用keil5都知道步聚吧): 接下来就是创建一个目录,将需要的 *.c 文件加入到项目中,我们并不需要将所有的都加到项目中,不然就会提示空间不足了,所以先来了解下各个不同文件代表的是什么使用 u8g2_xxx 都是显示处理相关的文件,所以都需要导入
u8x8_d_xxx 都是驱动文件,所以需要按需导入
我们显示显示屏是SSD1306,分辨率是128*64,
所以需要将 u8x8_d_ssd1306_128x64_noname.c文件加入到工程中,
其它 不含u8x8_d_的文件也都加到工程中
2、文件精简配置: 因我们的MCU Flash都是非常紧张的,所以还要进一步对相关文件精简,不然会浪费额外的空间。 第一步,精简 u8g2_d_memory.c,为了方便操作,我们先可以将所有的函数注释掉,就像这样: 我们可以看到这里是所有支持型号初始化配置的地方,我们小小的几十k或几百k的Flash可扛不住,所以这里只需要打开我们自己型号的就可以了 # 将SSD 1306 128*64 的初始化函数,取消注释,进行启用
/* sh1106 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);
}
# 将SH1106 128*64 的初始化函数,取消注释,进行启用
/* sh1106 f */
void u8g2_Setup_sh1106_i2c_64x32_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_sh1106_64x32, u8x8_cad_ssd13xx_fast_i2c, byte_cb, gpio_and_delay_cb);
buf = u8g2_m_8_4_f(&tile_buf_height);
u8g2_SetupBuffer(u8g2, buf, tile_buf_height, u8g2_ll_hvline_vertical_top_lsb, rotation);
}
我们可以往慢慢翻找,也可以直接搜索对应的函数名快速定位
第二步,配置 u8g2_d_memory.c 文件 同样的,把所有的函数先注释掉,这时我们先保存编译一下,看看缺少哪些函数,再相应的启用即可(之前搜索资料时看到别人是这样做的,发现这个方法还真不错) # 最后我们需要的函数是以下两个,如果你不是这两个,取消掉对应函数的注释就可以了
uint8_t *u8g2_m_16_8_1(uint8_t *page_cnt)
{
#ifdef U8G2_USE_DYNAMIC_ALLOC
*page_cnt = 1;
return 0;
#else
static uint8_t buf[128;
*page_cnt = 1;
return buf;
#endif
}
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
}
第三步,配置 u8g2_fonts.c 文件 这个是字库文件,我下载过来的文件查看了下,好家伙,有35万行,这就得几百K空间啊,我们可怜的flash可真吃不下,所以呢!全部注释掉,哈哈!然后看看哪些我们需要,就把对应的字库取消注释就好了。 你可能会说,我也不知道要用哪些字库啊,怎么办呢?这个也好办,U8g2提供了一个可以在线查看样式的地址,我们访问以下网址就可以看到样式了,需要哪个就把哪个的注释取消就可以啦。 # 字体预览页面
https://github.com/olikraus/u8g2/wiki/fntlistall
3、编写驱动文件 以上配置好就需要配置自己的驱动文件了,这里使用的是GPIO直接驱动I2C,这个会比较简单一些。这里我们创建好驱动文件,分别命名为: u8g2_drv.h 和 u8g2_drv.c,接下来就是编写内容了 u8g2_drv.h 文件 #ifndef U8G2_DRV_H_
#define U8G2_DRV_H_
#ifdef __cplusplus
extern "C" {
#endif
#include "main.h"
#include "u8g2.h"
// OLED配置
// 使用的引脚 SCL=PB4 SDA=PB3
#define OLED_SCL_RCC RCC_APB2_PERIPH_GPIOB
#define OLED_SDA_RCC RCC_APB2_PERIPH_GPIOB
#define OLED_SCL_PORT GPIOB
#define OLED_SDA_PORT GPIOB
#define OLED_SCL_PIN GPIO_PIN_4
#define OLED_SDA_PIN GPIO_PIN_3
#define OLED_SCL_Clr GPIO_ResetBits(OLED_SCL_PORT,OLED_SCL_PIN)//SCL
#define OLED_SCL_Set GPIO_SetBits(OLED_SCL_PORT,OLED_SCL_PIN)
#define OLED_SDA_Clr GPIO_ResetBits(OLED_SDA_PORT,OLED_SDA_PIN)//SDA
#define OLED_SDA_Set GPIO_SetBits(OLED_SDA_PORT,OLED_SDA_PIN)
// I2C初始化
void IIC_Init(void);
uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
void u8g2Init(u8g2_t *u8g2);
// 显示测试
void draw(u8g2_t *u8g2);
#ifdef __cplusplus
}
#endif
#endif
u8g2_drv.c 文件
#include "u8g2_drv.h"
#define delay_us delay_n_us
#define delay_ms delay_n_ms
void IIC_Init(void)
{
GPIO_InitType GPIO_InitStructure;
RCC_EnableAPB2PeriphClk(OLED_SCL_RCC | OLED_SDA_RCC, ENABLE);
GPIO_InitStructure.GPIO_Speed = GPIO_SPEED_HIGH;
GPIO_InitStructure.GPIO_Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.GPIO_Alternate = GPIO_DC_HIGH;
GPIO_InitStructure.Pin = OLED_SCL_PIN;
GPIO_InitPeripheral(OLED_SCL_PORT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = OLED_SDA_PIN;
GPIO_InitPeripheral(OLED_SDA_PORT, &GPIO_InitStructure);
}
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)
{
OLED_SCL_Set;
}
else if(arg_int == 0)
{
OLED_SCL_Clr;
}
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)
{
OLED_SDA_Set;
}
else if(arg_int == 0)
{
OLED_SDA_Clr;
}
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 u8g2Init(u8g2_t *u8g2)
{
u8g2_Setup_sh1106_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);
}
void draw(u8g2_t *u8g2)
{
u8g2_ClearDisplay(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");
}
4、实际显示测试 至此,移植过程已经全部完成了,我们可以在main 函数中添加测试代码了,首先肯定是要先引入我们编写的驱动文件 u8g2_drv.h,以下是测代码内容: #include "main.h"
#include "u8g2_drv.h"
int main(void)
{
IIC_Init();
u8g2_t u8g2;
u8g2Init(&u8g2);
u8g2_FirstPage(&u8g2);
do
{
draw(&u8g2);
} while (u8g2_NextPage(&u8g2));
while(1)
{
}
}
来看看实际的效果吧! 总结,整个移植过程其实非常简单,主要还是配置以减少内存的占用为主,毕竟U8g2用于Arduino中比较多,像之前的ESP8266和ESP32,这些模块封装的Flash都有4M,所以足够U8g2的开销,但如果不外置Flash情况下,考虑比较多的还是减少空间的占用了。最后测试显示中文显示不出来,目前还没找到具体的原因,如果之后解决了再更新吧! 三、生成自己的字库 在正常显示之后,大家应该就会考虑如何生成自己的字库了吧!其实这部分已经有人写成了现成的工具,这里就直接贴出原作者的链接了,至于流程想要了解的话可以搜索下:“U8g2字体生成”。 # 字库生成工具原作者地址,这里感谢下
https://oshwhub.com/article/Easy-u8g2-font-generate-tools
最后,将示例程序上传到了网盘,需要可以自己下载: 链接: https://pan.baidu.com/s/1-tU8tW526h0Mb7Evog_s6Q?pwd=gv9w 提取码: gv9w
本文也同步发在了B站,欢迎大家关注 https://www.bilibili.com/read/cv21546269?spm_id_from=333.999.0.0
|
共1人点赞
|