【ESP-IDF】2.ESP32C3移植u8g2显示库驱动OLED

前言

这个系列的文章属于是为了一碟醋包了一顿饺子系列,起因是看到tb上某家店的ESP32C3开发板才9.9包邮。想着研究一下,把手头有个用Arduino UNO实现的项目升级一下,于是就有了这个系列。

ESP32C3的简介:

2020 年末,乐鑫推出安全、低功耗、低成本的 RISC-V MCU ESP32-C3。ESP32-C3 是一款安全稳定、低功耗、低成本的物联网芯片,搭载 RISC-V 32 位单核处理器,支持 2.4 GHz Wi-Fi 和 Bluetooth 5 (LE),为物联网产品提供行业领先的射频性能、完善的安全机制和丰富的内存资源。

环境信息

硬件

合宙ESP32C3开发板
中景园0.91寸128x32OLED显示屏SSD1306驱动(IIC版)

软件

ESP-IDF v4.4.4

驱动移植

1、获取&裁剪u8g2源码

1.1、u8g2的源码可以从github下载:https://github.com/olikraus/u8g2
在这里插入图片描述
1.2、解压&提取压缩文件中的csrc目录(u8g2支持c和c++,这里使用c的,复制csrc目录就行了)
在这里插入图片描述
1.3、把不需要的文件删除
除了自己器件外的其他"u8x8_d_器件名.c"的文件都删除,我的屏幕是0.91寸的oled(屏幕驱动SSD1306),所以除了"u8x8_d_ssd1306_128x32.c"其他都删除。可以创建个文件夹(u8g2)整理成如下结构(把.c跟.h分开):

u8g2
    ├─include
    │      mui.h
    │      mui_u8g2.h
    │      u8g2.h
    │      u8x8.h
    │
    └─src
            mui.c
            mui_u8g2.c
            u8g2_bitmap.c
            u8g2_box.c
            u8g2_buffer.c
            u8g2_button.c
            u8g2_circle.c
            u8g2_cleardisplay.c
            u8g2_d_memory.c
            u8g2_d_setup.c
            u8g2_font.c
            u8g2_fonts.c
            u8g2_hvline.c
            u8g2_input_value.c
            u8g2_intersection.c
            u8g2_kerning.c
            u8g2_line.c
            u8g2_ll_hvline.c
            u8g2_message.c
            u8g2_polygon.c
            u8g2_selection_list.c
            u8g2_setup.c
            u8log.c
            u8log_u8g2.c
            u8log_u8x8.c
            u8x8_8x8.c
            u8x8_byte.c
            u8x8_cad.c
            u8x8_capture.c
            u8x8_debounce.c
            u8x8_display.c
            u8x8_d_ssd1306_128x32.c
            u8x8_fonts.c
            u8x8_gpio.c
            u8x8_input_value.c
            u8x8_message.c
            u8x8_selection_list.c
            u8x8_setup.c
            u8x8_string.c
            u8x8_u16toa.c
            u8x8_u8toa.c

1.4、把不需要的设备驱动初始化代码删除
删除 u8g2_d_setup.c中不必要的内容,只保留u8g2_Setup_ssd1306_i2c_128x32_univision_f
完成后u8g2_d_setup.c就剩下如下的内容(为了防止不同版本u8g2接口有改动,这边贴图只是示意要删成这样,别照抄图片中的代码):
在这里插入图片描述
这里类似的屏幕通信接口函数还有这些(这边只列举几个):

u8g2_Setup_ssd1306_128x32_univision_f
u8g2_Setup_ssd1306_i2c_128x32_univision_1
u8g2_Setup_ssd1306_i2c_128x32_univision_2
u8g2_Setup_ssd1306_i2c_128x32_univision_f
u8g2_Setup_ssd1306_i2c_128x32_winstar_f

其中,名字里面不带i2c的函数是SPI接口,函数最后的数字或字母,代表显示时的buf大小:

  • 1 :128字节
  • 2 :256字节
  • f :1024字节

winstar跟univision可以理解成不同的驱动(我这款屏幕用univision正常,winstar会花屏)

1.5、把不需要的内存申请代码删除
删除 u8g2_d_memory.c中不必要的内容,只保留 u8g2_m_16_4_f函数(就是上一步函数体里面调用的)
在这里插入图片描述
由于用到的u8g2_Setup_ssd1306_i2c_128x32_univision_f函数中,只调用了u8g2_m_16_4_f这个函数,所以留下这个函数,其它的函数一定要删掉或注释掉,因为这里面的函数分配了大量的静态内存,会导致内存耗尽。

2、添加u8g2的component

2.1、在main目录的同级目录下,创建u8g2的component
在这里插入图片描述

idf create-component -C components/ u8g2

成功后会多出来一个文件夹:
在这里插入图片描述
在这里插入图片描述
2.2、按照1.3的目录结构把精简后的u8g2库文件放进来:
替换、覆盖同名文件。注意u8g2目录下有个u8g2.c,把它也移动到./src目录下面,整理好的目录结构如下:

PS C:\Espressif\frameworks\esp-idf-v4.4.4\examples\get-started\sample_project\components\u8g2> tree /F
C:.
│  CMakeLists.txt
│
├─include
│      mui.h
│      mui_u8g2.h
│      u8g2.h
│      u8x8.h
│
└─src
        mui.c
        mui_u8g2.c
        u8g2.c
        u8g2_bitmap.c
        u8g2_box.c
        u8g2_buffer.c
        u8g2_button.c
        u8g2_circle.c
        u8g2_cleardisplay.c
        u8g2_d_memory.c
        u8g2_d_setup.c
        u8g2_font.c
        u8g2_fonts.c
        u8g2_hvline.c
        u8g2_input_value.c
        u8g2_intersection.c
        u8g2_kerning.c
        u8g2_line.c
        u8g2_ll_hvline.c
        u8g2_message.c
        u8g2_polygon.c
        u8g2_selection_list.c
        u8g2_setup.c
        u8log.c
        u8log_u8g2.c
        u8log_u8x8.c
        u8x8_8x8.c
        u8x8_byte.c
        u8x8_cad.c
        u8x8_capture.c
        u8x8_debounce.c
        u8x8_display.c
        u8x8_d_ssd1306_128x32.c
        u8x8_fonts.c
        u8x8_gpio.c
        u8x8_input_value.c
        u8x8_message.c
        u8x8_selection_list.c
        u8x8_setup.c
        u8x8_string.c
        u8x8_u16toa.c
        u8x8_u8toa.c

2.3 添加文件到编译工具链
在u8g2目录下新建component.mk文件,写入以下内容:

COMPONENT_SRCDIRS:=.
COMPONENT_ADD_INCLUDEDIRS:=include

修改CmakeLists.txt,把里面替换为如下内容:
TODO:后续看看能不能改成通配符,这边是把所有的文件硬编码到CmakeLists

idf_component_register(SRCS "src/mui.c"
"src/mui_u8g2.c"
"src/u8g2_bitmap.c"
"src/u8g2_box.c"
"src/u8g2_buffer.c"
"src/u8g2_button.c"
"src/u8g2.c"
"src/u8g2_circle.c"
"src/u8g2_cleardisplay.c"
"src/u8g2_d_memory.c"
"src/u8g2_d_setup.c"
"src/u8g2_font.c"
"src/u8g2_fonts.c"
"src/u8g2_hvline.c"
"src/u8g2_input_value.c"
"src/u8g2_intersection.c"
"src/u8g2_kerning.c"
"src/u8g2_line.c"
"src/u8g2_ll_hvline.c"
"src/u8g2_message.c"
"src/u8g2_polygon.c"
"src/u8g2_selection_list.c"
"src/u8g2_setup.c"
"src/u8log.c"
"src/u8log_u8g2.c"
"src/u8log_u8x8.c"
"src/u8x8_8x8.c"
"src/u8x8_byte.c"
"src/u8x8_cad.c"
"src/u8x8_capture.c"
"src/u8x8_debounce.c"
"src/u8x8_display.c"
"src/u8x8_d_ssd1306_128x32.c"
"src/u8x8_fonts.c"
"src/u8x8_gpio.c"
"src/u8x8_input_value.c"
"src/u8x8_message.c"
"src/u8x8_selection_list.c"
"src/u8x8_setup.c"
"src/u8x8_string.c"
"src/u8x8_u16toa.c"
"src/u8x8_u8toa.c"
INCLUDE_DIRS "include")

做完这些,需要运行 idf.py reconfigure, 才可以在下次 idf.py build时生效component
在这里插入图片描述

3、实现u8g2依赖的功能接口

3.1、在u8g2.c中实现接口
u8g2通过switch的方式,访问需要的各种平台资源,这一点设计的很巧妙,也很灵活。
u8x8_gpio_and_delay提供平台的延时和gpio资源,u8x8_byte_i2c实现iic操作接口。

uint8_t u8x8_gpio_and_delay(u8x8_t *u8x8, uint8_tmsg, uint8_targ_int, void *arg_ptr)
uint8_t u8x8_byte_i2c(u8x8_t *u8x8, uint8_tmsg, uint8_targ_int, void *arg_ptr)

这里先给出一个实现好的示例:

/*
 * @Author: xmprocat
 * @Date: 2023-02-19 22:33:18
 * @LastEditors: xmprocat
 * @LastEditTime: 2023-02-20 23:04:36
 * @Description: 
 */
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c.h"
#include "u8g2.h"
#include "u8x8.h"

#define I2C_SCL_IO 5
#define I2C_SDA_IO 4
#define ACK_CHECK_EN 0x1                        /*!< I2C master will check ack from slave*/
#define WRITE_BIT I2C_MASTER_WRITE              /*!< I2C master write */


static const char *TAG = "u8g2";

i2c_config_t i2c_config = {
    .mode = I2C_MODE_MASTER,             // 主机模式
    .sda_io_num = I2C_SDA_IO,               // sda i引脚编号
    .scl_io_num = I2C_SCL_IO,               // scl 引脚编号
    .sda_pullup_en = GPIO_PULLUP_ENABLE, // 上拉使能
    .scl_pullup_en = GPIO_PULLUP_ENABLE, // 上拉使能
    .master.clk_speed = 1000000          // 100k
};

static void _oled_i2c_init(void)
{
    i2c_param_config(I2C_NUM_0, &i2c_config);                // 配置参数初始化,此函数内部就是将i2c_config 中的相关参数 填入到i2c[i2c_num_0] 结构体中。
    i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0); // 初始化配置以外的所有相关参数,将配置写入寄存器
}

void esp32_i2c_write(uint8_t addr, uint32_t idx, uint8_t *data)
{
    i2c_cmd_handle_t handler = i2c_cmd_link_create();
    i2c_master_start(handler);
    i2c_master_write_byte(handler, addr | WRITE_BIT, ACK_CHECK_EN);
    i2c_master_write(handler, data, idx, 2);
    i2c_master_stop(handler);
    i2c_master_cmd_begin(I2C_NUM_0, handler, 100 / portTICK_RATE_MS);
    i2c_cmd_link_delete(handler);
}

// u8g2用到的系统资源
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:
        _oled_i2c_init();           //调用iic初始化
	    break;
    case U8X8_MSG_DELAY_MILLI:
        vTaskDelay(arg_int);
        break;
    default:
        return 0;
    }
    return 1;
}
// u8g2用到的显示屏控制接口
uint8_t u8x8_byte_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr)
{
    static uint8_t buffer[32]; /* u8g2/u8x8 will never send more than 32 bytes between START_TRANSFER and END_TRANSFER */
    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 */
        break;
    case U8X8_MSG_BYTE_SET_DC:
        /* ignored for i2c */
        break;
    case U8X8_MSG_BYTE_START_TRANSFER:
        buf_idx = 0;
        break;
    case U8X8_MSG_BYTE_END_TRANSFER:
        esp32_i2c_write(u8x8_GetI2CAddress(u8x8), buf_idx, buffer);
        break;
    default:
        return 0;
    }
    return 1;
}

void u8g2Init(u8g2_t *u8g2)
{
    u8g2_Setup_ssd1306_i2c_128x32_univision_f(u8g2, U8G2_R0, u8x8_byte_i2c, u8x8_gpio_and_delay); // 初始化 u8g2 结构体
    u8g2_InitDisplay(u8g2);                                                                    // 根据所选的芯片进行初始化工作,初始化完成后,显示器处于关闭状态
    u8g2_SetPowerSave(u8g2, 0);                                                                // 打开显示器
    u8g2_ClearBuffer(u8g2);
    u8g2_SendBuffer(u8g2);		// 清屏
    ESP_LOGI(TAG, "u8g2 init done");
}

注意:我的这套方案采用的是硬件IIC,所以u8x8_gpio_and_delay函数中的

U8X8_MSG_DELAY_I2C
U8X8_MSG_GPIO_I2C_CLOCK
U8X8_MSG_GPIO_I2C_DATA
  • 带有Delay字样的是延时相关东西,在case后面补充一些相应的延时函数,这些延时函数是用来模拟时序的,硬件IIC的话时序不需要软件控制,不管也行。
  • 带有GPIO的是操控设备可能需要的一些引脚,如果用u8g2库里自带的软件模拟IIC或者软件模拟SPI函数去写设备的话,这些库自带的函数就会调用这里设置的电平函数,可以认为我们就是在这里告诉了u8g2库要怎么操作这个单片机的引脚或者延时。由于我们用的是硬件IIC,所以这里不用管也行。
  • 带有MENU的是一些操控菜单的按键引脚啥的,如果用到菜单控制才去设置。

补充:由于ESP32与ESP32C3的硬件架构不同,以下通过用内联汇编的方式实现了毫秒级以下延时的代码在ESP32C3上无法正常使用(硬件IIC本身就不需要,但是有很多教程都写了这个就提一嘴)

static __inline void delay_clock(int ts)
{
    uint32_t start, curr;
    __asm__ __volatile__("rsr %0, ccount" : "=r"(start));
    do
    __asm__ __volatile__("rsr %0, ccount" : "=r"(curr));
    while (curr - start <= ts);
}
#define delay_us(val)       delay_clock(240*val)
#define delay_100ns(val)    delay_clock(24*val)

4、效果展示

跨文件调用函数记得在.h里面声明

/*
 * @Author: xmprocat
 * @Date: 2023-02-19 20:09:31
 * @LastEditors: xmprocat
 * @LastEditTime: 2023-02-20 23:26:53
 * @Description: 
 */
#include "ssd1306_oled.h"
#include <stdio.h>
#include "esp_log.h"
#include "driver/i2c.h"
#include "u8g2.h"
#include "u8x8.h"

static const char *TAG = "ssd1306_oled";

void ssd1306_init(void)
{
    u8g2_t u8g2;

    u8g2Init(&u8g2);

    u8g2_SetFont(&u8g2, u8g2_font_7x13_mr); // 设置英文字体
    u8g2_DrawStr(&u8g2, 0, 13, "IP:255.255.255.255");
    u8g2_DrawStr(&u8g2, 0, 32, "FreeRTOS#2023@Miao");
    u8g2_SendBuffer(&u8g2);		// 一定要发送buffer
    
}

在这里插入图片描述
需要完整工程的话私信吧

参考文献

https://zhuanlan.zhihu.com/p/587171646
https://blog.csdn.net/qq_43862401/article/details/121809470

  • 9
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
ESP32驱动SPI屏幕是通过使用SPI通信协议来与屏幕进行通信和控制的过程。ESP32是一款集成了Wi-Fi和蓝牙功能的微控制器,具有丰富的通信接口和强大的处理能力,因此可以很好地驱动SPI屏幕。 首先,需要在ESP32上配置SPI硬件接口。ESP32的开发环境通常提供了相关的和函数,可以方便地配置和控制SPI接口。我们需要设置SPI的工作模式、时钟频率等参数,以适配特定的SPI屏幕。 接下来,我们需要根据SPI屏幕的通信协议来编写驱动程序。SPI屏幕通常有一套特定的指令集,用于控制屏幕的显示、清除、写入数据等操作。我们需要了解这些指令并将其编码到驱动程序中。通过发送这些指令和数据,可以控制屏幕的各种显示效果,如文字、图像等。 在驱动程序中,还需要实现数据的传输和接收。在SPI通信中,数据通过主设备(即ESP32)和从设备(即屏幕)之间的SPI总线传输。我们需要调用相关的函数来发送和接收数据,以实现与屏幕的通信。 最后,我们需要将屏幕的显示数据发送到驱动程序中。数据可以来自于ESP32的内部存储器或外部设备。我们需要将数据格式化为SPI屏幕可以接受的格式,并通过SPI接口发送到屏幕上进行显示。 总的来说,ESP32驱动SPI屏幕的过程涉及到硬件配置、指令编码、数据传输等多个步骤。通过正确编写驱动程序,我们可以控制和显示各种内容在SPI屏幕上,实现丰富的用户界面和交互体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值