前言
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: 測試結果 如果對你又幫助,麻煩點個贊,加個關注