前言
U8g2 是一个用于嵌入式设备的单色图形库。U8g2支持单色OLED和LCD,并支持如SSD1306 SSD1315等多种类型的OLED驱动,几乎市面上常见都支持。
U8g2源码 download:https://github.com/olikraus/u8g2
1:环境
ESP32 S3(ESP32-S3-DevKitC-1 44pin)
vscdoe+idf5.4.1
2: 在vscode 创建个工程 模块helloworld就行
3:增加组件
idf.py create-component -C components/u8g2
会创建u8g2 目录 里面有还有include目录(u8g2.h)于u8g2.c cmakefile
1>把 u8g2-master\csrc
.h 文件拷贝到 工程/components/u8g2/include 4个文件 u8g2.h覆盖就好
u8g2.c 移动到src 目录
2> 删除多余文件
u8x8_d_*****.c 这里只保留u8x8_d_ssd1306_128x64_noname.c 与 u8x8_d_ssd1315_128x64_noname.c
其他都删除了,这里保留2个,可能还会用到1315 所以先保留下来
3>u8g2_d_setup.c
以128*64 为例
_f 全缓冲(Full) 128×64÷8 = 1024 字节 一次性渲染全屏幕,速度最快 内存充足的设备(如 ESP32)
_1 单页缓冲(Page 1) 128×8÷8 = 128 字节 逐页渲染,需循环处理 8 页 内存紧张的设备(如 Arduino Uno)
_2 双页缓冲(Page 2) 128×16÷8 = 256 字节 逐页渲染,减少页切换频率 内存有限但需稍快速度
只保留了2个函数
/* ssd1306 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);
}
/* ssd1315 f */
void u8g2_Setup_ssd1315_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_ssd1315_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
只保留上面2个函数用到的 函数(默认情况下 全是静态变量,内存如果足够大,这个文件可以不修改,全保留)
uint8_t *u8g2_m_16_8_f(uint8_t *page_cnt)
{#ifdef U8G2_USE_DYNAMIC_ALLOC*page_cnt = 8;return 0;#elsestatic uint8_t buf[1024];*page_cnt = 8;return buf;#endif
}
5>修改 components/u8g2的cmakefile
#idf_component_register(SRCS "u8g2.c"
# INCLUDE_DIRS "include")
# 定义变量,存储所有 .c 文件路径
file(GLOB MY_C_FILES "src/*.c") # "src/" 是目标目录,"*.c" 匹配所有 .c 文件
## 递归匹配 "src" 目录及所有子目录下的 .c 文件
#file(GLOB_RECURSE MY_C_FILES "src/*.c")
# 在 ESP-IDF 中,将文件添加到组件编译(如果是 main 组件)
idf_component_register(SRCS ${MY_C_FILES} # 通过变量引用所有 .c 文件INCLUDE_DIRS "include" # 包含目录(根据实际情况修改)
)
# 排除 "src/test" 目录下的 .c 文件
#file(GLOB_RECURSE MY_C_FILES
# "src/*.c"
# PATTERN "src/test/*" EXCLUDE # 排除指定路径
#)
6>其他配置
4:上main.c
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/i2c_master.h"
#include "driver/gpio.h"
#include "u8g2.h"
#include "driver/i2c.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include <xtensa/xtruntime.h>
#include "esp_log.h"// 日志标签
static const char *TAG = "OLED_EXAMPLE";// 引脚定义(SCL: GPIO9, SDA: GPIO10)
#define BOARD_I2C_SDA 47
#define BOARD_I2C_SCL 21#define OLED_SCL_GPIO BOARD_I2C_SCL
#define OLED_SDA_GPIO BOARD_I2C_SDA
// 可选:LED引脚(如果需要)
#define LED_GPIO 38 //or 48 需要锡焊下,这里就免了// 函数声明
void OLED_W_SCL(uint8_t BitValue);
void OLED_W_SDA(uint8_t BitValue);
void OLED_GPIO_Init(void);
void led_init(void);
void LED(uint8_t state);
void LED_TOGGLE(void);// 微秒级延时(ESP32内置函数)
#define ets_delay_us(us) ({ \uint32_t _count = (us) * (XTAL_CLK_FREQ / 1000000) / 2; \while (_count--) { \__asm__ __volatile__("nop"); \} \
})// 自定义延时函数(纳秒级,近似)
void ns_delay(int ns) {// 1ns约对应XTAL时钟的1/40(ESP32默认40MHz XTAL)int nop_count = ns / 25; for (int i = 0; i < nop_count; i++) {__asm__ __volatile__("nop");}
}// U8g2的GPIO和延时回调函数
uint8_t u8g2_gpio_and_delay_esp32(U8X8_UNUSED u8x8_t *u8x8, U8X8_UNUSED uint8_t msg, U8X8_UNUSED uint8_t arg_int, U8X8_UNUSED void *arg_ptr) {switch(msg) {case U8X8_MSG_DELAY_MILLI: // 毫秒级延时vTaskDelay(pdMS_TO_TICKS(arg_int));break;case U8X8_MSG_DELAY_10MICRO: // 10微秒延时(修正原10ms错误)ets_delay_us(10);break;case U8X8_MSG_DELAY_100NANO: // 100纳秒延时ns_delay(100);break;case U8X8_MSG_GPIO_I2C_CLOCK: // I2C时钟线控制OLED_W_SCL(arg_int);break;case U8X8_MSG_GPIO_I2C_DATA: // I2C数据线控制OLED_W_SDA(arg_int);break;default:return 0; // 未实现的消息}return 1; // 成功处理
}// 绘制圆形任务
void draw_circle_fun(u8g2_t* u8g2) {u8g2_ClearBuffer(u8g2); // 清空缓冲区// 1. 绘制完整圆形轮廓(圆心(64,32),半径20)u8g2_DrawCircle(u8g2, 64, 32, 20, U8G2_DRAW_ALL);// 2. 绘制填充圆形(圆心(30,30),半径15)//u8g2_DrawFilledEllipse(u8g2, 30, 30, 15, U8G2_DRAW_ALL);u8g2_DrawFilledEllipse(u8g2, // U8g2实例30, 30, // 中心坐标(x0=64, y0=32),对应128x64屏幕的中心15, 15, // rx=40(水平半径),ry=20(垂直半径)U8G2_DRAW_ALL // 绘制完整椭圆);// 3. 绘制部分圆弧(右上象限,圆心(100,30),半径10)u8g2_DrawCircle(u8g2, 100, 30, 10, U8G2_DRAW_UPPER_RIGHT);u8g2_DrawBox(u8g2, 100, 30,20,20);u8g2_SendBuffer(u8g2); // 将缓冲区内容发送到屏幕vTaskDelay(pdMS_TO_TICKS(1000)); // 每秒刷新一次}void app_main(void) {esp_err_t ret;// 初始化NVS(非易失性存储)
// 需要调用 nvs_flash_init() 的场景
// 如果你的应用程序使用了以下功能,必须初始化 NVS:
// WiFi / 蓝牙功能:WiFi 的连接凭证(SSID、密码)、蓝牙的配对信息等默认存储在 NVS 中,不初始化会导致 WiFi / 蓝牙无法正常工作。
// OTA 升级:OTA 相关的状态信息(如当前固件版本、升级标志)需要 NVS 存储。
// 分区表中的 NVS 分区:默认的 ESP-IDF 分区表包含 nvs 分区(用于用户数据)和 nvs_keys 分区(用于加密密钥),若需使用这些分区存储自定义数据(如设备配置),必须初始化。
// 其他依赖 NVS 的组件:如 esp_http_client 的缓存、esp_https_ota 的状态记录等,底层可能依赖 NVS。ret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());ret = nvs_flash_init();}ESP_ERROR_CHECK(ret);// 初始化LED(可选,用于调试)led_init();// 初始化OLED引脚(SCL/SDA)OLED_GPIO_Init();// 初始化U8g2u8g2_t u8g2;// 配置SSD1306 128x64,全屏缓冲,软件I2Cu8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8x8_byte_sw_i2c, // 软件I2C发送函数u8g2_gpio_and_delay_esp32); // 回调函数u8g2_InitDisplay(&u8g2); // 初始化显示器u8g2_SetPowerSave(&u8g2, 0); // 唤醒显示器u8g2_ClearBuffer(&u8g2); // 清空缓冲区// 绘制十字线u8g2_DrawLine(&u8g2, 0, 0, 127, 63);u8g2_DrawLine(&u8g2, 127, 0, 0, 63);u8g2_SendBuffer(&u8g2); // 发送缓冲区数据到屏幕// 主循环while (1) {LED_TOGGLE(); // 闪烁LED表示程序运行vTaskDelay(pdMS_TO_TICKS(500)); // 延时500msdraw_circle_fun(&u8g2);}
}// 控制SCL引脚电平
void OLED_W_SCL(uint8_t BitValue) {gpio_set_level(OLED_SCL_GPIO, BitValue);ets_delay_us(2); // 小延时保证I2C时序稳定
}// 控制SDA引脚电平
void OLED_W_SDA(uint8_t BitValue) {gpio_set_level(OLED_SDA_GPIO, BitValue);ets_delay_us(2); // 小延时保证I2C时序稳定
}// 初始化OLED的SCL和SDA引脚(开漏输出+上拉)
void OLED_GPIO_Init(void) {gpio_config_t gpio_init_struct = {.intr_type = GPIO_INTR_DISABLE, // 禁用中断.mode = GPIO_MODE_OUTPUT_OD, // 开漏输出(I2C必需).pull_up_en = GPIO_PULLUP_ENABLE, // 使能上拉(I2C无外部上拉时).pull_down_en = GPIO_PULLDOWN_DISABLE,.pin_bit_mask = (1ULL << OLED_SCL_GPIO) | (1ULL << OLED_SDA_GPIO) // 配置SCL和SDA引脚};gpio_config(&gpio_init_struct);// 初始化为高电平(释放总线)OLED_W_SCL(1);OLED_W_SDA(1);vTaskDelay(pdMS_TO_TICKS(10)); // 等待引脚稳定
}// 初始化LED引脚(可选,用于调试)
void led_init(void) {gpio_config_t gpio_init_struct = {.intr_type = GPIO_INTR_DISABLE,.mode = GPIO_MODE_OUTPUT,.pull_up_en = GPIO_PULLUP_DISABLE,.pull_down_en = GPIO_PULLDOWN_DISABLE,.pin_bit_mask = 1ULL << LED_GPIO};gpio_config(&gpio_init_struct);LED(0); // 初始关闭
}// 控制LED状态
void LED(uint8_t state) {gpio_set_level(LED_GPIO, state);
}// 翻转LED状态
void LED_TOGGLE(void) {uint8_t current = gpio_get_level(LED_GPIO);gpio_set_level(LED_GPIO, !current);
}
主要是2个回调函数
主要是 回调函数 u8x8_msg_cb,u8x8_msg_cb
typedef uint8_t (*u8x8_msg_cb)(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr);
u8x8_msg_cb 可以用uint8_t u8x8_byte_sw_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void arg_ptr) //现成的
/ ssd1306 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)
u8g2_Setup_ssd1306_i2c_128x64_noname_f(
&u8g2, // U8g2 上下文结构体,保存配置和状态
U8G2_R0, // 屏幕旋转方向(U8G2_R0:不旋转;U8G2_R180:翻转180度等)
u8g2_esp32_i2c_byte_cb, // I2C 字节发送回调(底层实现 I2C 写操作)
u8g2_esp32_gpio_and_delay_cb // GPIO 控制和延时回调(处理复位、延时)
);
5: 测试结果 如果对你又帮助,麻烦点个赞,加个关注