STM32移植U8G2

STM32 移植 U8G2

u8g2 (Universal 8bit Graphics Library version2 的缩写)是用于嵌入式设备的单色图形库,可以在单色屏幕中绘制 GUI。u8g2 内部附带了例如 SSD13xx,ST7xx 等很多 OLED,LCD 驱动。内置多种不同大小和风格的字体,可以显示中文,其次就是图形程序实现线,框,圆等图形绘制。

1. 建立裸机工程

在移植 u8g2 之前先创建 STM32F103 的 Keil5 工程模板(如何搭建 Keil 模板这里不多介绍),用来编译 STM32 驱动源码和 u8g2 源码。这也是支持 u8g2 开发的一个单片机运行环境,并且调试好屏幕驱动,确保屏幕初始化成功。

屏幕驱动只是为了验证屏幕可行性,移植后方便排除因屏幕不显示的问题,u8g2 附带屏幕驱动程序,受支持的屏幕可以使用里面的驱动程序 。整体移植操作和芯片无关,所以无论什么芯片移植方法都相同。

2. 下载源码

在 Github 官网搜索 u8g2 进行下载,或通过 https://github.com/olikraus/u8g2 链接下载,u8g2 没有发行版,所以直接下载 master 分支源码,下载后可以得到一个 u8g2-master.zip 这样的源码压缩包,如下图。

请添加图片描述

3. u8g2 文件概览

u8g2 源码主要需要使用 cppsrc/csrc/ 这两个文件夹。分别为 u8g2 对 C++ 兼容支持库,u8g2 源码文件夹

请添加图片描述

cppsrc 对 C++ 兼容支持,方便把 u8g2 移植到 C++ 应用。cppsrc 用 C++ 类简单封装了 u8g2 普通操作函数(实际还是依赖 csrc),如下所示,把 u8g2 移植到 C++ 应用才需要这部分代码。

/*u8g2_line.c */
void U8G2::drawLine(u8g2_uint_t x1, u8g2_uint_t y1, u8g2_uint_t x2, u8g2_uint_t y2) 
{
    u8g2_DrawLine(&u8g2, x1, y1, x2, y2);
}

其他就是一些说明文档,辅助工具程序,版本变更信息,LICENSE 等等,这部分保留 LICENSE(开源许可) 其余可以删除精简工程。

4. 移植 u8g2

在 Keil 项目管理器新建一个 u8g2 文件夹,添加 u8g2 目录 u8g2/csrc/ 下所有的.c 文件。即使 u8g2 移植到 C++ 应用,这部分也是需要的(u8g2 C++ 部分实际还是依赖 csrc 的源码)。

请添加图片描述

添加 u8g2 的头文件目录 u8g2/csrc/,即 u8g2 源码和头文件在同一个目录。

5. 驱动函数选择

csrc 目录下屏幕驱动文件以 u8x8_d_xx_yy_zz.c 格式命名,其中 xx 芯片型号,yy 屏幕分辨率,zz 识别名称,zz 不是一定的,可以为空或 noname。

例如:SSD1312 芯片驱动,128x64 分辨率的 OLED,使用的驱动文件为 u8x8_d_ssd1312_128x64_noname.c

驱动配置函数通常和文件同名,在驱动文件中找到同名函数即可,比如这里就是:

uint8_t u8x8_d_ssd1312_128x64_noname(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)

注意:驱动文件和驱动函数也可能不对应,例如 ssd1312 的驱动函数可能定义在 ssd1306 的驱动文件,这是 u8g2 为了把相近屏幕的驱动函数统一到一个文件。

如果要减小代码量,其它型号屏幕驱动和分辨率对应的 .c 驱动文件可以删除。

6. u8g2_d_setup.c

csrc 目录下找到这个文件,文件包含各种屏幕的配置函数,实上选择的屏幕驱动函数就会被这些配置函数调用。

屏幕配置函数以 u8g2_Setup_xx_yy_zz_gg_[1/2/f].c 命名,具体字段解释如下:

xx 芯片型号,ssd1306,sh1106 等等。

yy 通信方式,识不是一定的,i2c 表示为 i2c 接口,如果没有则表示 spi 通信。

zz 屏幕分辨率,128x64,128x80 等等。

gg 识别名称,gg 不是一定的,可以为 noname 也可以为空。

[1/2/f] 显存 BUF 大小,1:128字节,2:256 字节,f:1024 字节。

例如:SSD1312 芯片驱动,128x64 分辨率 SPI 通信的 OLED,希望使用 1024 字节显存,使用的配置函数为 u8g2_Setup_ssd1312_128x64_noname_f

/*ssd1312 f*/
void u8g2_Setup_ssd1312_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_ssd1312_128x64_noname, u8x8_cad_001, 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);
}

可以看到上方函数调用了 u8x8_d_ssd1312_128x64_noname(),如果要减小代码量,其它的配置函数可以删除或注释,只留下选择的即可。

7. u8g2_d_memory.c

csrc 目录下找到 u8g2_d_memory.c,文件里面是 u8g2 对显存的定义。在屏幕配置函数中,只调用了 u8g2_m_16_8_f(),所以如果编译 u8g2 时如果提示内存不足,除此之外其它显存函数可以删除或注释。

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
}

8. 对接屏幕驱动

把屏幕相关的硬件控制对接给 u8g2,需要我们实现 GPIO 控制和数据发送这 2 个统一驱动函数,函数原型如下,函数名称可以自定义:

uint8_t (*u8x8_msg_cb)(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);

最终通过注册的的方式提供给 u8g2。

8.1 对接 GPIO

把屏幕的 GPIO 控制函数按照用途对接到指定位置,比如 SPI 屏幕要对接 CSDCRESET 对应的 GPIO。

uint8_t u8x8_stm32_gpio_and_delay(u8x8_t *u8x8, 
    uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    /**STM32 supports HW SPI, 
    Remove unused cases like U8X8_MSG_DELAY_XXX & 
    U8X8_MSG_GPIO_XXX */
    switch (msg) {
    case U8X8_MSG_GPIO_AND_DELAY_INIT:
        /*Insert codes for initialization*/
        break;

    case U8X8_MSG_DELAY_MILLI:
        /* ms Delay */
        sleep_ms(arg_int);
        break;

#ifdef _USE_SPI
        /*SPI Interface*/
    case U8X8_MSG_GPIO_CS:
        /*Insert codes for SS pin control */
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, arg_int);
        break;

    case U8X8_MSG_GPIO_DC:
        /*Insert codes for DC pin control */
        HAL_GPIO_WritePin(GPIOD, GPIO_PIN_2, arg_int);
        break;

    case U8X8_MSG_GPIO_RESET:
        /*Insert codes for RST pin control*/
        HAL_GPIO_WritePin(GPIOB, GPIO_PIN_3, arg_int);
        break;

    case U8X8_MSG_GPIO_SPI_CLOCK:
        /*Insert codes for CLOCK pin control */
        break;

    case U8X8_MSG_GPIO_SPI_DATA:
        /*Insert codes for DATA pin control */
        break;

#endif /*_USE_SPI*/

#ifdef _USE_I2C
    case U8X8_MSG_GPIO_I2C_CLOCK:
        /*Insert codes for CLOCK pin control */
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, arg_int);
        break;
    case U8X8_MSG_GPIO_I2C_DATA:
        /*Insert codes for CLOCK pin control */
        HAL_GPIO_WritePin(GPIOA, GPIO_PIN_6, arg_int);
        break;
#endif /*_USE_I2C*/
    }
    return 1;
}

如果使用 GPIO 模拟时序通信还要对接模拟通信 GPIO,比如软件模拟 I2C 还要对接 SCLSDA 对应 GPIO,同时还要对接延时函数,u8g2 才能模拟时序。

8.2 对接数据

把屏幕的数据发送函数对接到指定位置,如果屏幕使用 SPI 接口,U8X8_MSG_BYTE_SEND 对应的是 SPI 数据发送函数,可以参考如下写法:

uint8_t u8x8_byte_stm32_hw_spi(u8x8_t *u8x8, 
    uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    HAL_StatusTypeDef _res = HAL_ERROR;

    switch (msg) {
    case U8X8_MSG_BYTE_SEND:
        /*Insert codes to transmit data*/
        _res = HAL_SPI_Transmit(&hspi3, arg_ptr, arg_int, TX_TIMEOUT);
        if (_res != HAL_OK) return 0;
        break;

    case U8X8_MSG_BYTE_INIT:
        /*Insert codes to begin 
        SPI transmission*/
        break;

    case U8X8_MSG_BYTE_SET_DC:
        /*Control DC pin, U8X8_MSG_GPIO_DC 
        will be called*/
        u8x8_gpio_SetDC(u8x8, arg_int);
        break;

    case U8X8_MSG_BYTE_START_TRANSFER:
        /* Select slave, U8X8_MSG_GPIO_CS will be called */
        u8x8_gpio_SetCS(u8x8, 
            u8x8->display_info->chip_enable_level);
        sleep_ms(2);
        break;

    case U8X8_MSG_BYTE_END_TRANSFER:
        sleep_ms(2);
        /* Insert codes to end SPI transmission */
        u8x8_gpio_SetCS(u8x8, u8x8->display_info->chip_disable_level);
        break;
    }
    return 1;
}

如果屏幕使用 I2C 接口,U8X8_MSG_BYTE_SEND 对应的是 I2C 数据发送函数,可以参考如下写法:

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] = {0};
    static uint8_t buf_idx = 0;
    uint8_t * data = NULL;

    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*/
        break;

    case U8X8_MSG_BYTE_SET_DC:
        break;

    case U8X8_MSG_BYTE_START_TRANSFER:
        buf_idx = 0;
        break;

    case U8X8_MSG_BYTE_END_TRANSFER:
        HAL_I2C_Master_Transmit(_I2C_DEV, (DEV_ADDR << 1), buffer, buf_idx, TX_TIMEOUT);
        break;
    }
    return 1;
}

8.3 初始化外设

如果数据发送是通过单片机外设硬件实现实现的,初始化 u8g2 前先初始化外设和对应的 GPIO,例如这里初始化 SPI 接口,还有相应 GPIO。

void oled_spi3_init(void)
{

  /* USER CODE BEGIN SPI3_Init 0 */

  /* USER CODE END SPI3_Init 0 */

  /* USER CODE BEGIN SPI3_Init 1 */

  /* USER CODE END SPI3_Init 1 */
  hspi3.Instance = SPI3;
  hspi3.Init.Mode = SPI_MODE_MASTER;
  hspi3.Init.Direction = SPI_DIRECTION_2LINES;
  hspi3.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi3.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi3.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi3.Init.NSS = SPI_NSS_SOFT;
  hspi3.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi3.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi3.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi3.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi3.Init.CRCPolynomial = 10;
  if (HAL_SPI_Init(&hspi3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI3_Init 2 */

  /* USER CODE END SPI3_Init 2 */
}

8.4 初始化 u8g2

u8g2 没有对象初始化函数,自定义 u8g2 对象初始化函数,函数名称可以自定义,在初始化函数调用显示初始化及对应的屏幕配置函数,如下。

void u8g2_init(u8g2_t *u8g2)
{
    u8g2_Setup_ssd1312_128x64_noname_f(u8g2, U8G2_R2, 
        u8x8_byte_stm32_hw_spi, u8x8_stm32_gpio_and_delay);

    u8g2_SetPowerSave(u8g2, 1);
    u8g2_InitDisplay(u8g2);
    u8g2_ClearDisplay(u8g2);
    u8g2_ClearBuffer(u8g2);
    u8g2_SetPowerSave(u8g2, 0);
}

可以看到这个就是前面讲解屏幕的配置函数,如下。

void u8g2_Setup_ssd1312_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb);

该函数的 4 个参数含义:

(1) u8g2,指定待初始化的 u8g2 对象,即 u8g2 结构体。
(2) rotation,配置屏幕旋转方向,支持 U8G2_R0,U8G2_R1,U8G2_R2,U8G2_R3 四种旋转方向。
(3) byte_cb,注册数据通信发送函数。
(4) gpio_and_delay_cb:注册屏幕 GPIO 控制函数。

8.5 总结

把对接屏幕驱动用到的这些自定义函数统一编写到一个文件中,文件可自行命名,例如命名为 u8g2_stm32.c,并添加到 u8g2 之外的用户目录。

9. 测试函数

移植完成后编写应用程序测试 u8g2 是否正常,调用 u8g2 绘图函数在显存绘制内容,在 main 函数调用初始化 SPI 和 u8g2,注意在主循环中调用 u8g2 显存发送函数 u8g2_SendBuffer 将显存内容更新至屏幕,在此附上 u8g2 测试程序。

#include "u8g2.h"

static u8g2_t u8g2 = {0};

void draw(u8g2_t * u8g2_p)
{
  u8g2_SetFontMode(u8g2_p, 1);
  u8g2_SetFontDirection(u8g2_p, 0);
  u8g2_SetFont(u8g2_p, u8g2_font_inb24_mf);
  u8g2_DrawStr(u8g2_p, 0, 20, "U");

  u8g2_SetFontDirection(u8g2_p, 1);
  u8g2_SetFont(u8g2_p, u8g2_font_inb30_mn);
  u8g2_DrawStr(u8g2_p, 21, 8, "8");

  u8g2_SetFontDirection(u8g2_p, 0);
  u8g2_SetFont(u8g2_p, u8g2_font_inb24_mf);
  u8g2_DrawStr(u8g2_p, 51, 30, "g");
  u8g2_DrawStr(u8g2_p, 67, 30,"\xb2");

  u8g2_DrawHLine(u8g2_p, 2, 35, 47);
  u8g2_DrawHLine(u8g2_p, 3, 36, 47);
  u8g2_DrawVLine(u8g2_p, 45, 32, 12);
  u8g2_DrawVLine(u8g2_p, 46, 33, 12);

  u8g2_SetFont(u8g2_p, u8g2_font_4x6_tr);
  u8g2_DrawStr(u8g2_p, 1, 54, 
    "github.com/olikraus/u8g2");
}

void main()
{
  oled_spi_init();
  u8g2_init(&u8g2);

  for (;;) {
    u8g2_ClearBuffer(&u8g2);
    draw(&u8g2);
    u8g2_SendBuffer(&u8g2);
  }
}

10. 移植 C++ 版本

移植 C++ 版本基础步骤和前面小节介绍的步骤相同,除此之外现在继续在 Keil 项目管理器再新建一个 u8g2cpp 文件夹,然后添加 u8g2 目录 u8g2/cppsrc/ 下所有的 .cpp 文件。

请添加图片描述

10.1 u8g2 继承类

自定义 u8g2 对象初始化类,继承 U8G2 不是必须的。可以把类定义在 U8g2lib.h 文件中,或其他文件,类名称可以自定义。在类的构造函数调用对应的屏幕配置函数,例如调用 ssd1312 屏幕配置。

void u8g2_Setup_ssd1312_128x64_noname_f(u8g2_t *u8g2, const u8g2_cb_t *rotation, u8x8_msg_cb byte_cb, u8x8_msg_cb gpio_and_delay_cb);

再自定义初始化函数 SSD1312::Init(),调用 u8g2 显示初始化来初始化 u8g2。

extern "C" uint8_t u8x8_stm32_gpio_and_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
extern "C" uint8_t u8x8_byte_stm32_hw_spi(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);

class SSD1312 : public U8G2 {
private:

public: 
  SSD1312(const u8g2_cb_t *rotation = U8G2_R2) : U8G2() {
    u8g2_Setup_ssd1312_128x64_noname_f(&u8g2, rotation, u8x8_byte_stm32_hw_spi, u8x8_stm32_gpio_and_delay);
  }

  bool Init() {
    /**note: call to u8x8_utf8_init is not required here, this is done in the setup procedures before*/
    setPowerSave(1); initDisplay(); clearDisplay(); clearBuffer(); setPowerSave(0);
    return 1;
  }
};

10.2 测试程序

调用 SSD1312 类对象方法,编写应用程序测试 u8g2 是否正常,调用 u8g2 绘图函数在显存绘制内容,初始化 u8g2 对象,在主循环中调用 sendBuffer 将显存内容更新至屏幕。

#include "U8g2lib.h"

SSD1312 oled;

void draw(SSD1312 * oled_p)
{
  oled_p->setFontMode(1);
  oled_p->setFontDirection(0);
  oled_p->setFont(u8g2_font_inb24_mf);
  oled_p->drawStr(0, 20, "U");

  oled_p->setFontDirection(1);
  oled_p->setFont(u8g2_font_inb30_mn);
  oled_p->drawStr(21, 8, "8");

  oled_p->setFontDirection(0);
  oled_p->setFont(u8g2_font_inb24_mf);
  oled_p->drawStr(51, 30, "g");
  oled_p->drawStr(67, 30,"\xb2");

  oled_p->drawHLine(2, 35, 47);
  oled_p->drawHLine(3, 36, 47);
  oled_p->drawVLine(45, 32, 12);
  oled_p->drawVLine(46, 33, 12);

  oled_p->setFont(u8g2_font_4x6_tr);
  oled_p->drawStr(1, 54, 
    "github.com/olikraus/u8g2");
}

void main()
{
  oled.Init();

  for (;;) {
    oled.clearBuffer();
    draw(&oled);
    oled.sendBuffer();
  }
}

详细查看:

https://blog.csdn.net/qq_51183186/article/details/120348070

你好!对于将 U8g2 移植STM32 上,你需要按照以下步骤进行操作: 1. 首先,确保你已经安装了适当的开发环境,包括 STM32CubeMX 和 STM32Cube HAL 库。如果没有,请先下载和安装它们。 2. 打开 STM32CubeMX,并创建一个新的工程。选择适合你的 STM32 芯片型号,并配置所需的引脚和外设。 3. 在 "Middleware" 部分中,找到 "Graphics" 并选择 "U8g2"。 4. 根据你的需求选择所需的显示屏控制器和接口类型。U8g2 库支持多种不同的控制器和接口。 5. 在 "Configuration" 选项中,配置 U8g2 库的其他参数,比如显示屏分辨率、字体等。 6. 生成代码并打开工程。 7. 在生成的代码中,找到 "main.c" 文件,并添加以下代码来初始化 U8g2 库: ```c #include "u8g2.h" u8g2_t u8g2; int main(void) { // 初始化代码 u8g2_Setup_<Controller>_<Interface>_u8g2(&u8g2, <rotation>, <u8x8_byte_fn>, <u8x8_gpio_and_delay_fn>); u8g2_InitDisplay(&u8g2); // 其他初始化代码 while (1) { // 显示内容更新代码 } } ``` 注意替换 `<Controller>` 和 `<Interface>` 为你选择的控制器和接口类型。`<rotation>` 是显示屏旋转角度,`<u8x8_byte_fn>` 和 `<u8x8_gpio_and_delay_fn>` 是用于与硬件通信的函数指针。 8. 在 `while (1)` 循环中,你可以使用 U8g2 库提供的函数来更新显示屏上的内容。 这只是一个简单的示例,你可能还需要根据具体需求进行其他配置和修改。请参考 U8g2 的文档和示例代码来了解更多细节。希望对你有帮助!如果你有其他问题,请随时提问。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值