/*************************************************
* 硬件I2C驱动SSD1306 OLED + u8g2移植单文件实现
* 开发环境:STM32标准库
* 硬件连接:I2C1 (SCL=PB6, SDA=PB7)
* 依赖:u8g2库核心文件(需自行添加)
*************************************************/
#include "stm32f10x.h"
#include "u8g2.h"
// I2C配置
#define I2C_SPEED 400000 // 400kHz
#define OLED_ADDRESS 0x78 // SSD1306地址(0x3C左移1位)
// u8g2对象声明
u8g2_t u8g2;
// I2C初始化函数
void I2C_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStruct;
I2C_InitTypeDef I2C_InitStruct;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
// 配置GPIO为复用开漏模式
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStruct);
// I2C参数配置
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;
I2C_InitStruct.I2C_OwnAddress1 = 0x00;
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable;
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
I2C_InitStruct.I2C_ClockSpeed = I2C_SPEED;
I2C_Init(I2C1, &I2C_InitStruct);
I2C_Cmd(I2C1, ENABLE);
}
// u8g2硬件I2C通信接口
uint8_t u8g2_i2c_callback(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
switch(msg) {
case U8X8_MSG_BYTE_SEND: { // 发送数据
uint8_t *data = (uint8_t *)arg_ptr;
while(arg_int--) {
I2C_SendData(I2C1, *data++);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}
break;
}
case U8X8_MSG_BYTE_START_TRANSFER: { // 起始信号
I2C_GenerateSTART(I2C1, ENABLE);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
break;
}
case U8X8_MSG_BYTE_END_TRANSFER: { // 停止信号
I2C_GenerateSTOP(I2C1, ENABLE);
break;
}
}
return 0;
}
// u8g2初始化
void u8g2_Init(void) {
u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8g2, U8G2_R0, u8g2_i2c_callback, u8x8_byte_empty);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
u8g2_ClearBuffer(&u8g2);
}
// 主程序
int main(void) {
SystemInit();
I2C_Configuration();
u8g2_Init();
while(1) {
u8g2_FirstPage(&u8g2);
do {
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
u8g2_DrawStr(&u8g2, 10, 30, "Hello u8g2!");
} while(u8g2_NextPage(&u8g2));
Delay_ms(1000);
u8g2_ClearBuffer(&u8g2);
}
}
硬件I2C配置
使用STM32标准库配置I2C1接口(PB6/PB7),时钟400kHz,开漏模式58
通过I2C_CheckEvent
实现事件驱动的通信状态检测,避免死循环等待38
u8g2移植要点
实现u8x8_byte_i2c_cb
回调函数,处理起始、停止和数据传输事件
使用u8g2_Setup_ssd1306_i2c_128x64_noname_f
初始化显示驱动,支持硬件I2C模式56
显示控制逻辑
采用u8g2的分页渲染机制,通过u8g2_FirstPage
和u8g2_NextPage
实现高效刷新6
支持标准图形API如DrawStr
、DrawLine
等,可扩展复杂界面
依赖文件
需包含u8g2库的核心文件(u8g2.h
、u8x8.h
),建议从官方仓库获取最新版本
硬件适配
若更换I2C接口(如I2C2),需修改GPIO初始化代码和OLED_ADDRESS
不同OLED型号需调整u8g2_Setup
函数参数(如SSD1306_128x32)
/*************************************************
* 硬件I2C驱动SSD1306 OLED + u8g2移植单文件实现
* 开发环境:STM32标准库
* 硬件连接:I2C1 (SCL=PB6, SDA=PB7)
* 依赖:u8g2库核心文件(需自行添加)
*************************************************/
#include "stm32f10x.h" // STM32标准库头文件
#include "u8g2.h" // u8g2图形库头文件
/* 硬件配置宏定义 */
#define I2C_SPEED 400000 // I2C时钟频率(400kHz,SSD1306支持的最高速度)
#define OLED_ADDRESS 0x78 // OLED设备地址(原始7位地址0x3C左移1位,最低位为读写位)
/* u8g2全局对象声明 */
u8g2_t u8g2; // 用于存储显示状态和配置信息
/*************************************************
* 函数名:I2C_Configuration
* 功能:配置I2C1硬件接口
* 参数:无
* 说明:初始化PB6(SCL)和PB7(SDA)为I2C复用开漏模式
*************************************************/
void I2C_Configuration(void) {
GPIO_InitTypeDef GPIO_InitStruct; // GPIO配置结构体
I2C_InitTypeDef I2C_InitStruct; // I2C配置结构体
/* 使能相关时钟 */
// 使能GPIOB和AFIO时钟(复用功能需要)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// 使能I2C1时钟(位于APB1总线)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
/* 配置GPIO为复用开漏模式 */
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; // PB6(SCL), PB7(SDA)
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD; // 复用开漏模式(支持总线仲裁)
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // 高速模式
GPIO_Init(GPIOB, &GPIO_InitStruct); // 应用GPIO配置
/* I2C参数配置 */
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C; // 标准I2C模式
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2; // Tlow/Thigh = 2(标准模式)
I2C_InitStruct.I2C_OwnAddress1 = 0x00; // 主机自身地址(从机模式时才需要)
I2C_InitStruct.I2C_Ack = I2C_Ack_Enable; // 启用应答检测
I2C_InitStruct.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; // 7位地址模式
I2C_InitStruct.I2C_ClockSpeed = I2C_SPEED; // 设置通信速率
I2C_Init(I2C1, &I2C_InitStruct); // 应用I2C配置
I2C_Cmd(I2C1, ENABLE); // 使能I2C外设
}
/*************************************************
* 函数名:u8g2_i2c_callback
* 功能:u8g2与硬件I2C的通信接口回调函数
* 参数:
* u8x8 - u8g2底层对象指针
* msg - 消息类型(起始/停止/数据传输)
* arg_int - 附加参数(数据长度等)
* arg_ptr - 数据指针
* 返回值:总是返回0(u8g2标准要求)
* 说明:实现u8g2与硬件I2C的底层通信协议
*************************************************/
uint8_t u8g2_i2c_callback(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) {
switch(msg) {
case U8X8_MSG_BYTE_SEND: // 数据发送请求(arg_int=数据长度,arg_ptr=数据指针)
{
uint8_t *data = (uint8_t *)arg_ptr;
while(arg_int--) {
I2C_SendData(I2C1, *data++); // 发送单字节数据
// 等待字节传输完成事件(避免死循环可加超时判断)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
}
break;
}
case U8X8_MSG_BYTE_START_TRANSFER: // 开始I2C传输(发送START信号)
{
I2C_GenerateSTART(I2C1, ENABLE); // 产生START信号
// 等待START信号完成(EV5事件)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
// 发送设备地址(写模式)
I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);
// 等待地址发送完成(EV6事件)
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
break;
}
case U8X8_MSG_BYTE_END_TRANSFER: // 结束I2C传输(发送STOP信号)
{
I2C_GenerateSTOP(I2C1, ENABLE); // 产生STOP信号
break;
}
}
return 0; // 必须返回0(u8g2协议要求)
}
/*************************************************
* 函数名:u8g2_Init
* 功能:初始化u8g2库和OLED硬件
* 参数:无
* 说明:配置显示驱动参数并清空显存
*************************************************/
void u8g2_Init(void) {
// 初始化u8g2结构体(SSD1306驱动,128x64分辨率,无旋转)
u8g2_Setup_ssd1306_i2c_128x64_noname_f(
&u8g2, // u8g2对象指针
U8G2_R0, // 显示方向(不旋转)
u8g2_i2c_callback, // I2C通信回调函数
u8x8_byte_empty // 空字节处理(未使用)
);
u8g2_InitDisplay(&u8g2); // 发送初始化命令序列
u8g2_SetPowerSave(&u8g2, 0); // 关闭节能模式(唤醒显示)
u8g2_ClearBuffer(&u8g2); // 清空显示缓冲区
}
/*************************************************
* 主函数
*************************************************/
int main(void) {
SystemInit(); // 系统时钟初始化(默认72MHz)
I2C_Configuration(); // 配置硬件I2C
u8g2_Init(); // 初始化u8g2和OLED
/* 主循环 */
while(1) {
// 开始分页渲染(FirstPage会重置绘制位置)
u8g2_FirstPage(&u8g2);
do {
// 设置字体(14点阵字体)
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
// 在坐标(10,30)绘制字符串
u8g2_DrawStr(&u8g2, 10, 30, "Hello u8g2!");
} while(u8g2_NextPage(&u8g2)); // 自动刷新到屏幕并返回是否需要继续绘制
Delay_ms(1000); // 延时1秒
u8g2_ClearBuffer(&u8g2); // 清空缓冲区(准备下次绘制)
}
}
以下是基于STM32标准库和u8g2库实现中文显示的补充代码及详细说明(在原代码基础上添加中文支持):
/*************************************************
* 添加中文显示支持说明:
* 1. 需要添加中文字库(这里以u8g2自带文泉驿点阵宋体为例)
* 2. 使用UTF-8编码保存源文件
* 3. 修改编译器字符集设置为UTF-8
*************************************************/
// 在文件头部添加字体声明(需确保u8g2库包含对应字体)
extern const uint8_t u8g2_font_wqy12_t_chinese3[]; // 文泉驿12点阵中文字体
// 修改主循环中的显示代码
int main(void) {
// ...初始化代码同前...
while(1) {
u8g2_FirstPage(&u8g2);
do {
/* 英文显示 */
u8g2_SetFont(&u8g2, u8g2_font_ncenB14_tr);
u8g2_DrawStr(&u8g2, 10, 20, "Hello u8g2!");
/* 中文显示 */
u8g2_SetFont(&u8g2, u8g2_font_wqy12_t_chinese3); // 设置中文字体
u8g2_DrawUTF8(&u8g2, 10, 40, "腾讯云开发者"); // UTF-8编码字符串
u8g2_DrawUTF8(&u8g2, 10, 60, "Hello 世界!"); // 中英文混合
} while(u8g2_NextPage(&u8g2));
Delay_ms(1000);
u8g2_ClearBuffer(&u8g2);
}
}
字库添加
// 在u8g2库的u8g2_fonts.c文件中添加:
#include "u8g2_font_wqy12_t_chinese3.h"
// 或在工程配置中启用中文包(需下载u8g2官方中文字库)
编译器设置
在Keil MDK中:Options → C/C++ → Misc Controls 添加 --locale=english --multibyte_chars
在GCC中:添加编译选项 -finput-charset=UTF-8 -fexec-charset=UTF-8
源码编码要求
必须使用UTF-8编码保存源代码文件
在代码编辑器(如VSCode)右下角确认编码显示为UTF-8
字体选择建议
/* 常用中文字体(需在u8g2库中启用) */
u8g2_font_wqy12_t_chinese3 // 12像素文泉驿点阵宋体(推荐)
u8g2_font_baby_tf // 8像素幼圆体
u8g2_font_unifont_t_chinese // 16像素Unifont
自定义精简字库(使用工具生成仅包含所需汉字)
// 使用工具:PCtoLCD2002
// 生成步骤:
// 1. 输入需要显示的汉字(如"腾讯云开发")
// 2. 选择取模方式:列行式/逐行式
// 3. 输出格式:u8g2兼容的C数组
// 生成结果示例:
const uint8_t my_font[] U8G2_FONT_SECTION("my_font") = {
0x00,0x12,0x0C,0x00,0xFE,0x0C,... // 字体数据
};
// 使用自定义字体
u8g2_SetFont(&u8g2, my_font);
混合排版布局
// 中英文混合布局示例
u8g2_SetFont(&u8g2, u8g2_font_ncenB08_tr); // 英文字体
u8g2_DrawStr(&u8g2, 0, 10, "Temperature:");
u8g2_SetFont(&u8g2, u8g2_font_wqy12_t_chinese3); // 中文字体
u8g2_DrawUTF8(&u8g2, 0, 25, "当前温度");
闪存优化方案
// 在stm32f10x.h中启用闪存优化
#if defined ( __CC_ARM )
__attribute__((section(".ARM.__at_0x08010000"))) // 指定字体存放地址
#elif defined ( __ICCARM__ )
#pragma location=0x08010000
#endif
const uint8_t my_font[] = {...};
显示乱码
// 检查清单:
- 确认源文件编码为UTF-8
- 确认编译器字符集设置为UTF-8
- 确认字体包含目标汉字(用u8g2_font_get_glyph检测)
显示不全
// 调整显示坐标:
u8g2_DrawUTF8(&u8g2, x, y+font_height, "文本"); // 注意y坐标递增
内存不足
// 优化策略:
- 使用u8g2的局部刷新功能
- 选择小字号字体(如12px代替16px)
- 启用压缩存储模式(u8g2_font_xxx_tf格式)
完整工程需要包含u8g2库文件及中文字体数据,建议参考u8g2官方文档进行深度定制。实际开发中可根据屏幕尺寸选择合适字号,推荐12px字体用于128x64屏幕的常规中文显示。