uant 发表于 2023-2-6 10:45

N32WB031移植U8g2过程

本帖最后由 uant 于 2023-2-6 10:44 编辑

       @21小跑堂 @安小芯       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]
查看完整版本: N32WB031移植U8g2过程