Skip to content

u8g2

一.u8g2是什么

u8g2是一个开源的C/C++库,用于在嵌入式系统中显示文本和图形。它支持多种显示器类型,只能用于单色显示器,包括OLED、LCD、LED矩阵等,并且可以在多种微控制器上运行,包括Arduino、ESP8266、ESP32等。

u8g2库的主要特点包括:

  • 支持多种显示器类型和分辨率
  • 支持多种字体和字符集
  • 内置了各种型号的显示的初始化及驱动代码
  • 提供了丰富的字体和字符集,无需自己取模.
  • 移植非常简单,逻辑也很清晰.

二.u8g2如何移植?

1. 下载源码?

https://github.com/olikraus/u8g2 ,在这个地址下载得到其中的源代码 ,我们只需其中的 csrc 目录里面的文件,其它的文件都不需要.

2. 清除不必要的驱动文件

u8g2中已经包含的所有可以支持的显示屏的驱动文件, 每一种显示屏都有对应的驱动文件.我们需要将不需要的驱动文件全部删除,以避免冲突. 这些驱动文件的文件名都有一个共同的特征.

  • 如: u8x8_d_ssd1305.c 这个文件,所有的驱动文件都是以 u8x8_d_开头的.后面就是芯片的名称. 或者u8x8_d_ssd1306_128x64_noname.c 这个文件名就是指的 ssd1306驱动芯片的,尺寸大小为 128x64 的 noname 表示不是专指的而是通用的驱动程序.由于ssd1306的显示屏非常多,所以名称就相对复杂一些.
  • 除了不需要的驱动文件外,其它文件一个都不要删除.都是有用的.
  • 同时要注意的:u8x8_d_setup.c文件以及u8g2_d_memory.c 这两个文件是显示屏驱动的配置文件,这两个文件是我们需要的两个重要文件,不能删除.

最后是这个样子的

3. 修改驱动配置文件u8g2_d_setup.c

假如我的屏幕是ssd1306芯片的,借助于i2c来驱动的 128*64分辨率的屏幕. 那么我们可以找到下面几个函数

c
// 未指定 i2c 的,是指的spi接口中的函数
void u8g2_Setup_ssd1306_128x64_noname_1(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
void u8g2_Setup_ssd1306_128x64_noname_2(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
void u8g2_Setup_ssd1306_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
// 指定了 i2c 的,是指的使用i2c接口的函数
void u8g2_Setup_ssd1306_i2c_128x64_noname_1(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
void u8g2_Setup_ssd1306_i2c_128x64_noname_2(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb)
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)

对于ssd1306屏幕有8个page. 所以其刷新也有三种方式.

  • 最后一个字符的_1,表示: 单缓冲,需要一页page画一页page, 对RAM要求最低
  • 最后一个字符的_2,表示: 双缓冲模式. 两个缓冲转流工作, 刷新速度更快一些.
  • 最后一个字符的_f,表示: 全缓冲模式, 8个page全部缓冲,占RAM最大,但是刷新速度最快.
  • 建议直接选择_f

需要根据名称必须判断,不要弄错了.
只需要保留一个函数,其它所有的代码全部删除.

c
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);
}

4. 修改u8g2_d_memory.c的内存分配文件

在上面的函数中,有一个函数 u8g2_m_16_8_f 这个函数位于 u8g2_d_memory.c 文件,我们需要修改这个文件.
方法也很简单,直接找到这个函数u8g2_m_16_8_f,其它所有的函数,全部删除.

c
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_Setup_ssd1306_i2c_128x64_noname_f 这个函数时:

c
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)

时,需要指定两个回调函数: u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb 所以这两个回调函数,需要写在自己的项目处

5. 编写回调函数

1. 编写函数名

在自己的项目目录中, 创建一个 oled.c 文件,我们要在这个函数中,编写三个函数.其它两个是上面的回调函数. 还有一个是void u8g2Init(u8g2_t *u8g2)这个函数用于调用 u8g2_Setup_ssd1306_i2c_128x64_noname_f 来将设置的回调函数配置上去.

c
uint8_t u8x8_byte_stm32_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,void *arg_ptr){

}

uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,void *arg_ptr){

}


void u8g2Init(u8g2_t *u8g2){
  //配置回调函数
  u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8x8_byte_stm32_hw_i2c,u8g2_gpio_and_delay_stm32); 
  //设置I2C地址(8位地址格式)
  u8g2_SetI2CAddress(u8g2, OLED_ADDRESS<<1);

  u8g2_InitDisplay(u8g2); // 根据所选的芯片进行初始化工作,初始化完成后,显示器处于关闭状态
  u8g2_SetPowerSave(u8g2, 0); // 打开显示器
  u8g2_ClearBuffer(u8g2);     // 清除缓冲区
}

我们来看一下uint8_t u8x8_byte_stm32_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,void *arg_ptr)这个函数中的具体内容

c
uint8_t u8x8_byte_stm32_hw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,void *arg_ptr){

  /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and
   * END_TRANSFER */
  static uint8_t buffer[32];
  static uint8_t buf_idx;
  uint8_t *data;

  switch (msg) {
  case U8X8_MSG_BYTE_SEND:// 发送数据
    data = (uint8_t *)arg_ptr;
    while (arg_int > 0) {
      buffer[buf_idx++] = *data;
      data++;
      arg_int--;
    }
    break;
  case U8X8_MSG_BYTE_INIT: //初始化硬件环境
    /* add your custom code to init i2c subsystem */
    // MX_I2C1_Init(); // 注释掉,因为已经在main中初始化
    break;
  case U8X8_MSG_BYTE_START_TRANSFER:// 开始传输
    buf_idx = 0;
    break;
  case U8X8_MSG_BYTE_END_TRANSFER:// 结束传输
    // HAL库使用7位地址,需要左移1位转换为8位地址
    if (HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDRESS<<1, buffer, buf_idx, 1000) != HAL_OK)
      return 0;
    break;
  case U8X8_MSG_BYTE_SET_DC:// 设置DC引脚
    // 对于I2C,DC状态已经在CAD层通过控制字节处理
    // 这里不需要做任何操作
    break;
  default:
    return 0;
  }
  return 1;
}

2.分析一下 u8x8_byte_stm32_hw_i2c的回调函数

在这个函数中,实际上就是处理u8g2请求的各种消息.我们只需要根据这些消息,和对应的参数,来将数据处理好,就可以了. 结合我们的I2C硬件处理方式,我们只需要将这些消息处理好主可以了.包括:

  • case U8X8_MSG_BYTE_SEND// 当u8g2请求发送消息时. 其参数是要发送的消息arg_ptr和消息的长度arg_int
  • case U8X8_MSG_BYTE_INIT //初始化硬件环境,只需要初始i2c的硬件就可以了.MX_I2C1_Init();
  • case U8X8_MSG_BYTE_START_TRANSFER// 开始传输进行数据传输
  • case U8X8_MSG_BYTE_END_TRANSFER// 等待结束传输数据传输完成,return 0表示传输完成
  • case U8X8_MSG_BYTE_SET_DC// 设置DC引脚,对于i2c来讲,并没有dc这个引脚,也不需要这个消息,所以直接break即可,但是对于spi接口屏幕就需要处理了. 根据这些消息处理好了,就可以完成了屏幕的驱动.

3. 不需要自己来进行屏幕的初始化序列

咱们都知道一个屏幕在使用之前,必须要进行一次初始化序列,屏幕才能正常工作.但是在这个u8g2中,并不需要我们自己来编写屏幕的初始化序列. 因为u8g2会根据 型号 自动去处理这个初始化序列.

3.完成u8g2_gpio_and_delay_stm32函数

c

uint8_t u8g2_gpio_and_delay_stm32(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int,void *arg_ptr)
  {
    switch (msg) {
    case U8X8_MSG_DELAY_100NANO: // 延时100纳秒
      __NOP();
      break;
    case U8X8_MSG_DELAY_10MICRO: // 延时10微秒
      for (uint16_t n = 0; n < 320; n++) {
        __NOP();
      }
      break;
    case U8X8_MSG_DELAY_MILLI: // 延时arg_int毫秒
      HAL_Delay(arg_int);
      break;
    // 硬件I2C不需要以下延时和GPIO控制
    // case U8X8_MSG_DELAY_I2C: // 延时I2C时钟 - 硬件I2C不需要
    // case U8X8_MSG_GPIO_I2C_CLOCK: // 输出I2C时钟 - 硬件I2C不需要
    // case U8X8_MSG_GPIO_I2C_DATA: // 输出I2C数据 - 硬件I2C不需要
    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;
  }

这个函数是为u8g2提供一些必要的延时函数.也是软件模拟i2c的引脚电平控制的地方. 我们这里只需要完成延时函数的代码就可以了.

  • case U8X8_MSG_DELAY_100NANO: 延时100ns
  • case U8X8_MSG_DELAY_10MICRO: 延时10us
  • case U8X8_MSG_DELAY_MILLI: 延长 x ms , x的值取值为arg_int 的值
  • U8X8_MSG_DELAY_I2C : I2C的操作延时,我们使用硬件控制,这里不需要处理
  • case U8X8_MSG_GPIO_I2C_CLOCK: // 输出I2C时钟 - 硬件I2C不需要
  • case U8X8_MSG_GPIO_I2C_DATA: // 输出I2C数据 - 硬件I2C不需要
  • case U8X8_MSG_GPIO_MENU_* // 这几个是用于处理按键操作的,我们这里不需要,直接使用默认的函数即可.

6. 将u8g2的代码添加至系统中

要将全部的c代码文件和头文件目录都要编译到系统中去,我们使用cmakefile.txt来编写,就非常简单了

cmake
# 遍历u8g2目录,将所有源文件添加到项目中
file(GLOB_RECURSE u8g2_sources "u8g2/*.c")
# Add sources to executable
target_sources(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user sources here
    ${u8g2_sources}
    Core/Src/oled.c

)
# Add include paths
target_include_directories(${CMAKE_PROJECT_NAME} PRIVATE
    # Add user defined include paths
    u8g2
)

三.u8g2如何使用?

这里直接给出一个使用示例

c
void draw(u8g2_t *u8g2)
{
	u8g2_ClearBuffer(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");
	 u8g2_SendBuffer(u8g2);
}
void main()
{
  u8g2_t u8g2;
  u8g2Init(&u8g2);
  draw(&u8g2);
  while(1);
}

四.全部示例源代码

这里的代码是使用ssd1306芯片的的I2C的驱动 u8g2的主要源码?

五.使用方法