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

2023-11-17

前言

这个系列的文章属于是为了一碟醋包了一顿饺子系列,起因是看到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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【ESP-IDF】2.ESP32C3移植u8g2显示库驱动OLED 的相关文章

  • php安装部署及优化

    目录 PHP源码编译 php启动与nginx整合 php功能模块的扩展 php添加memcache功能模块 构建Nginx高速缓存 tomcat结合memcache PHP源码编译 https www php net 下载软件包 安装解压工
  • 【下资源】全网独家首发2014传智播客三层架构及餐饮管理系统项目

    核心技术课程 三层架构原理 手写三层 自己动手代码生成器 商业级代码生成器 三层架构应用案例 NPOI MD5 WinForm高级应用 常用WinForm相关设计模式 数据库设计工具PowerDesigner高级应用 源代码管理 团队配合做

随机推荐

  • 【更新中…】Matlab simulink建模与仿真

    本文为学习笔记 视频来源 https www bilibili com video BV1L7411a7uL Matlab simulink建模与仿真 1 初始simulink 1 1 simulink简介 1 1 1 matlab与sim
  • error: static assertion failed: Type is not registered, please use the Q_DECLARE_METATYPE macro to m

    error static assertion failed Type is not registered please use the Q DECLARE METATYPE macro to m 解决方案 报错信息如下 调用了类的静态函数导
  • 【docker】CMD ENTRYPOINT 区别 终极解读!

    昨天用Dockerfile来启动mongodb的集群 启动参数 replSet死活没执行 最后就决定研究一哈cmd和entrypoint 但是上网看了一些资料个人觉得讲的不好 还是没有说出根本的东西 决定自己研究并且整理一哈 首先上dock
  • Linux系统下使用socat将串口映射到TCP服务器端口

    首先需要安装socat 安装方法即是 apt get install socat 或 yum install socat 然后使用以下命令进行映射 socat TCP LISTEN 8899 fork reuseaddr FILE dev
  • 华为OD机试 - 可以组成网络的服务器(Java)

    题目描述 在一个机房中 服务器的位置标识在 n m 的整数矩阵网格中 1 表示单元格上有服务器 0 表示没有 如果两台服务器位于同一行或者同一列中紧邻的位置 则认为它们之间可以组成一个局域网 请你统计机房中最大的局域网包含的服务器个数 输入
  • Java进阶知识

    今天分享有关java方面的知识 Paradigm 除了Java语言基础 通常在每种语言中还有很多paradigm 这些paradigm往往是衡量老鸟和新手的地方 比如函数命名 异常处理 泛型等等 下面用异常处理的两种类型来说明 笔者见过很多
  • JVM知识点

    JVM知识点 概念 java内存模型 线程私有 内存溢出和内存泄漏 线程共享区域 存在GC 类加载 类加载机制 双亲委派模型 垃圾回收GC 概念 如何判断一个对象是垃圾 有两种算法 1 引用计数算法 2 可达性分析算法 JVM采取 垃圾回收
  • 最大连续子序列和,以及开始、结束下标(Java)

    对一个有n个元素的数组 求最大的连续子数组的和 并求其开始 结束下标 数组的元素必然有正数也有负数才有意义 如果全是正数 那最大的子数组就是本身 如果全部为负数 那最大子数组就是空数组 例如下面的数组 其最大子数组序列和为187 子数组为X
  • k8s之nginx-ingress做tcp或udp的4层网络负载

    检查nginx ingress是否开启tcp udp转发 test test02 ingress kubectl get pod n ingress nginx o yaml grep i configmap configmap POD N
  • SpringBoot多数据源配置

    Druid 可以说是国内使用最广泛的数据源连接池产品
  • 华为校招机试题-MVP争夺战-2023年

    题目描述 在星球争霸篮球赛对抗赛中 强大的宇宙战队 希望每个人都能拿到MVP MVP的条件是 单场最高分得分获得者 可以并列 所以宇宙战队决定在比赛中 尽可能让更多的队员上场 且让所有有得分的队员得分都相同 然而比赛过程中的每一分钟的得分都
  • jsvc

    boltapp localhost apphome home boltapp apphome jsvc help Usage jsvc options class args Where options include help help s
  • python自动生成编号

    model 编号自增字段 class Bh BaseModel key models CharField null True max length 128 verbose name 唯一值 db index True unique True
  • 前端获取本地ip地址

    在某些场合的情况下 后台可能需要前端电脑的ip 因为每台电脑的ip不一样 所有需要动态获取 翻翻网上写的很多 里面其实是很坑的 因为都是在调用闭包函数 所以执行起来是没有任何问题的 但是 你页面想拿的时候 你是没法拿到的 下面就一vue 为
  • Ubuntu18.04 谷歌浏览器安装教程

    Ubuntu 经验笔记 Ubuntu18 04 谷歌浏览器安装教程 1 测试环境 2 安装步骤 Ubuntu18 04 谷歌浏览器安装教程 1 测试环境 系统版本 Ubuntu 18 04 安装时间 2021年7月4日 2 安装步骤 启动终
  • 区块链学习笔记(八)——应用之有机大米的一生

    区块链学习笔记 八 应用之有机大米的一生 前言 一 张三申请加入村里合作社的有机大米农业区块链项目 二 大米种植全程上链 三 有机大米收获出售过程上链 总结 前言 其实区块链的现实应用很多 我们用有机大米种植销售为例来看看它的应用 麻将四人
  • 设备怎样开启位置服务器,开启设备服务器

    开启设备服务器 内容精选 换一换 使用远程登录方式连接登录Windows云服务器时出现如下错误 此计算机无法连接到远程计算机 服务端安全组3389端口未开启 检查云服务器端口配置 服务端防火墙关闭 检查防火墙配置是否正常远程桌面连接配置不正
  • BMP存储方式

    BMP存储像素值的方式为从下至上 从左至右 紧随着文件头存储的字节为图像最下一行的数值 从左下角开始依次存储 22 22 22 23 为图像左下角像素的数值 依次向右存储 最后一行扫描完后 紧接着存储上一行 最后一个byte存储的是图像右上
  • Java中位数

    中位数 输入数组长度n 和n个数 输出这n个数的中位数 当结果为小数时向下取整 输入用例 1 1 输出用例 1 输入用例 2 3 3 输出用例 3 输入用例 5 5 3 1 2 4 输出用例 3 import java util Scann
  • 【ESP-IDF】2.ESP32C3移植u8g2显示库驱动OLED

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