本案例使用u8g2的软件包,stm32f407,0.96寸oled显示屏,at24c04。rtthread版本4.1.1
增加软件包u8g2,该软件包可以直接支持SSD1306的0.96寸的oled显示屏

修改配置:打开RT THread settings 配置u8g2软件包

| 配置项 | 开关 / 填写值 | 核心说明 |
|---|---|---|
| U8G2 总开关 | ✅ 开启 | 启用 u8g2 软件包 |
| 使用硬件 SPI | ❌ 关闭 | 我们用 I2C 接口,不需要 SPI |
| 使用硬件 I2C | ✅ 必须开启(核心!) | 名字有误导性,真实含义是「启用 RT-Thread 标准 I2C 设备接口」,哪怕你用的是软件模拟 I2C,也必须开这个 |
| ↳ i2c device name | 填入 i2c1 |
和你board.h里注册的 I2C 设备名完全一致,和 AT24C04 共用同一个 I2C 总线 |
| Use C++ | ❌ 关闭 | 我们用 C 语言开发,不需要 C++ 支持 |
| U8G2 Examples | ❌ 里面所有示例全部关闭 | 避免示例代码和你自己写的main.c/oled.c编译冲突,我们自己写,不用它的示例 |
| Version | 保持latest |
不用修改,用最新稳定版即可 |
为什么用软件 I2C,还要开使用硬件 I2c?
这个开关的名字有极强的误导性,本质:
-
它不是控制 "用不用硬件 I2C 外设" ,而是控制 u8g2 要不要对接RT-Thread 标准的 I2C 设备驱动框架。
-
只要你在 RT-Thread 里注册了名为
i2c1的 I2C 设备(不管这个设备是硬件外设实现的,还是软件 GPIO 模拟的),就必须开这个开关。 -
开启后,u8g2 才会生成你
oled.c里用到的u8x8_byte_rtthread_hw_i2c、u8x8_gpio_and_delay_rtthread适配函数,否则会报「函数未定义」的编译错误。
打开软件模拟iic
\
配置board.h,打开软件iic,填入pb8与9的对应引脚

右键main.c,选择属性,文本编码该成utf-8,用于显示中文

oled.c
cs
#include "oled.h"
#include <u8g2.h>
#include <u8g2_port.h>
#include <stdio.h>
// 全局 u8g2 句柄,内部维护,外部无法直接调用
static u8g2_t u8g2;
/**
* @brief 初始化 OLED 屏幕
* @note 配置: SSD1306 128x64, 软件I2C, 设备名 i2c1
*/
void oled_init(void)
{
// 1. 初始化 u8g2 核心
// 关键:使用 u8g2 软件包提供的 RT-Thread 硬件I2C适配函数
// 注意:这里的 "hw_i2c" 指的是 RT-Thread 标准 I2C 设备接口,不是 STM32 硬件外设
u8g2_Setup_ssd1306_i2c_128x64_noname_f(
&u8g2,
U8G2_R0, // 屏幕旋转方向 (0/90/180/270)
u8x8_byte_rtthread_hw_i2c, // RT-Thread I2C 字节传输函数
u8x8_gpio_and_delay_rtthread // RT-Thread 延时函数
);
// 2. 初始化显示硬件
u8g2_InitDisplay(&u8g2);
// 3. 开启显示 (退出省电模式)
u8g2_SetPowerSave(&u8g2, 0);
// 4. 清空显存
u8g2_ClearBuffer(&u8g2);
// 5. 默认设置英文字体
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
}
/**
* @brief 清空屏幕显存
* @note 调用后需要调用 oled_update() 才会生效
*/
void oled_clear(void)
{
u8g2_ClearBuffer(&u8g2);
}
/**
* @brief 刷新屏幕,把显存内容一次性显示出来
*/
void oled_update(void)
{
u8g2_SendBuffer(&u8g2);
}
/**
* @brief 显示英文字符/字符串
* @param x: 像素横坐标 (0~127)
* @param y: 像素纵坐标 (0~63, 注意是像素,不是行号)
* @param str: 要显示的字符串
*/
void oled_show_str(uint8_t x, uint8_t y, const char *str)
{
// 切换到英文字体
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
// 绘制字符串
u8g2_DrawStr(&u8g2, x, y, str);
}
/**
* @brief 显示中文字符串 (UTF-8编码)
* @param x: 像素横坐标
* @param y: 像素纵坐标
* @param str: 要显示的中文字符串 (UTF-8)
* @note 调用此函数的 .c 文件必须是 UTF-8 编码!
*/
void oled_show_chinese(uint8_t x, uint8_t y, const char *str)
{
// 切换到完整版中文字体 (包含所有汉字,如:哈哈)
u8g2_SetFont(&u8g2, u8g2_font_wqy12_t_chinese3);
// 关键:必须用 DrawUTF8 显示中文
u8g2_DrawUTF8(&u8g2, x, y, str);
}
/**
* @brief 显示整数数字
* @param x: 像素横坐标
* @param y: 像素纵坐标
* @param num: 要显示的整数 (支持负数)
*/
void oled_show_num(uint8_t x, uint8_t y, int32_t num)
{
char buf[32] = {0};
// 格式化数字为字符串
rt_snprintf(buf, sizeof(buf), "%d", num);
// 调用英文字符显示函数
oled_show_str(x, y, buf);
}
/**
* @brief 画一个点
* @param x: 横坐标 y: 纵坐标
*/
void oled_draw_pixel(uint8_t x, uint8_t y)
{
u8g2_DrawPixel(&u8g2, x, y);
}
/**
* @brief 画一条直线
* @param x1,y1: 起点坐标
* @param x2,y2: 终点坐标
*/
void oled_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2)
{
u8g2_DrawLine(&u8g2, x1, y1, x2, y2);
}
/**
* @brief 画一个空心矩形框
* @param x: 左上角横坐标
* @param y: 左上角纵坐标
* @param w: 宽度
* @param h: 高度
*/
void oled_draw_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
{
u8g2_DrawFrame(&u8g2, x, y, w, h);
}
/**
* @brief 画一个实心填充矩形
* @param x: 左上角横坐标
* @param y: 左上角纵坐标
* @param w: 宽度
* @param h: 高度
*/
void oled_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h)
{
u8g2_DrawBox(&u8g2, x, y, w, h);
}
/**
* @brief 画空心圆
* @param x,y: 圆心坐标
* @param r: 圆的半径
*/
void oled_draw_circle(uint8_t x, uint8_t y, uint8_t r)
{
u8g2_DrawCircle(&u8g2, x, y, r, U8G2_DRAW_ALL); // U8G2_DRAW_ALL 表示画整个圆
}
/**
* @brief 画空心三角形
* @param x1,y1/x2,y2/x3,y3: 三角形三个顶点的坐标
*/
void oled_draw_triangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3)
{
u8g2_DrawTriangle(&u8g2, x1, y1, x2, y2, x3, y3);
}
代码详细讲解:
cs
u8g2_Setup_ssd1306_i2c_128x64_noname_f(
&u8g2, // 把我们的"遥控器"传进去
U8G2_R0, // 屏幕不旋转
u8x8_byte_rtthread_hw_i2c, // 【关键】告诉u8g2:"你通过RT-Thread的I2C设备接口去发数据"
u8x8_gpio_and_delay_rtthread // 告诉u8g2:"你用RT-Thread的延时函数"
);
oled.h
cs
#ifndef __OLED_H__
#define __OLED_H__
#include <rtthread.h>
#include <stdint.h>
// 滚动方向枚举定义
typedef enum {
OLED_SCROLL_LEFT = 0, // 屏幕内容向左滚动
OLED_SCROLL_RIGHT = 1 // 屏幕内容向右滚动
} oled_scroll_dir_t;
/**
* @brief OLED初始化函数(必须第一个调用)
*/
void oled_init(void);
/**
* @brief 清空OLED显存(仅清空内存,不刷新屏幕)
*/
void oled_clear(void);
/**
* @brief 刷新显存到OLED屏幕(所有绘制后必须调用)
*/
void oled_update(void);
/**
* @brief 显示英文字符串
* @param x: 横坐标(0~127) y: 纵坐标(0~63) str: 英文文本
*/
void oled_show_str(uint8_t x, uint8_t y, const char *str);
/**
* @brief 显示UTF-8编码中文字符串
* @param x: 横坐标 y: 纵坐标 str: 中文文本
*/
void oled_show_chinese(uint8_t x, uint8_t y, const char *str);
/**
* @brief 显示整数数字
* @param x: 横坐标 y: 纵坐标 num: 整数值
*/
void oled_show_num(uint8_t x, uint8_t y, int32_t num);
/**
* @brief 在指定坐标画一个点
* @param x: 横坐标 y: 纵坐标
*/
void oled_draw_pixel(uint8_t x, uint8_t y);
/**
* @brief 画一条直线
* @param x1,y1: 起点坐标 x2,y2: 终点坐标
*/
void oled_draw_line(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2);
/**
* @brief 画空心矩形
* @param x,y: 左上角坐标 w:宽度 h:高度
*/
void oled_draw_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h);
/**
* @brief 画实心矩形
* @param x,y: 左上角坐标 w:宽度 h:高度
*/
void oled_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h);
/**
* @brief 画空心圆
* @param x,y: 圆心坐标 r: 半径
*/
void oled_draw_circle(uint8_t x, uint8_t y, uint8_t r);
/**
* @brief 画空心三角形
* @param x1,y1/x2,y2/x3,y3: 三个顶点坐标
*/
void oled_draw_triangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3);
#endif
cs
u8g2_InitDisplay(&u8g2);
作用:u8g2 通过 I2C 给 SSD1306 发送一串初始化命令(比如设置分辨率、开启电荷泵等),让屏幕从 "休眠" 状态醒过来。
cs
u8g2_SetPowerSave(&u8g2, 0);
作用:退出省电模式,让屏幕亮起来。
cs
u8g2_ClearBuffer(&u8g2);
核心概念:显存(Buffer)
-
u8g2 不会画一个点就立刻刷新屏幕,那样太慢了。
-
它先在内存里开辟一块 "草稿纸"(显存),你所有的绘制操作(写字、画线)都是先画在这张草稿纸上。
-
等你全部画完了,再一次性把草稿纸的内容发到屏幕上。
cs
u8g2_SetFont(&u8g2, u8g2_font_ncenB10_tr);
作用:选一支笔,默认选一支写英文字母的笔。
cs
void oled_clear(void)
{
u8g2_ClearBuffer(&u8g2); // 只是擦干净了内存里的草稿纸
}
注意 :调用这个函数后,屏幕不会立刻变干净,因为你只擦了草稿纸,还没贴到屏幕上。
cs
void oled_update(void)
{
u8g2_SendBuffer(&u8g2); // 【关键】把草稿纸的内容一次性通过I2C发送到屏幕
}
作用:把内存里的 "草稿纸"(显存)通过 I2C 全部发送给 SSD1306,屏幕才会真正显示内容。
新手学习u8g2,只需要知道4个步骤
cs
1. oled_init() → 初始化屏幕,拿到"遥控器"
2. oled_clear() → 擦干净"草稿纸"
3. oled_show_xxx() → 在"草稿纸"上画内容(所有 Draw 函数都是画在内存里)
4. oled_update() → 【关键】把"草稿纸"一次性贴到屏幕上
main.c
cs
#include <rtthread.h>
#include "oled.h"
int main(void)
{
rt_kprintf("=== OLED 测试程序 ===\n");
// 1. 初始化 OLED
oled_init();
// 2. 清屏
oled_clear();
// 3. 测试显示英文
oled_show_str(0, 15, "Hello RT-Thread!");
// 4. 测试显示中文
// 注意:这个 main.c 必须是 UTF-8 编码!
oled_show_chinese(0, 35, "单片机");
// 5. 测试显示数字
oled_show_str(0, 55, "Year: ");
oled_show_num(40, 55, 2026);
// 6. 画个框装饰
oled_draw_rect(0, 0, 128, 64);
// 7. 【关键】刷新屏幕,所有内容才会显示出来
oled_update();
rt_kprintf("1测试显示完成!\n");
rt_thread_mdelay(2000);
oled_clear();
oled_draw_circle(70, 32, 20); // 在屏幕中心画一个半径20的圆
oled_draw_triangle(10, 50, 30, 10, 50, 50); // 画一个三角形
oled_update();
while (1)
{
rt_thread_mdelay(1000);
}
return RT_EOK;
}

