关于STM32硬件I2C驱动SSD1306 OLED屏幕的实现方法,结合u8g2图形库的移植需求,以下是基于STM32标准库的硬件I2C单文件实现代码及移植说明:

硬件I2C + u8g2单文件实现代码

/*************************************************
 * 硬件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);
    }
}

关键实现说明

  1. 硬件I2C配置

    • 使用STM32标准库配置I2C1接口(PB6/PB7),时钟400kHz,开漏模式58

    • 通过I2C_CheckEvent实现事件驱动的通信状态检测,避免死循环等待38

  2. u8g2移植要点

    • 实现u8x8_byte_i2c_cb回调函数,处理起始、停止和数据传输事件

    • 使用u8g2_Setup_ssd1306_i2c_128x64_noname_f初始化显示驱动,支持硬件I2C模式56

  3. 显示控制逻辑

    • 采用u8g2的分页渲染机制,通过u8g2_FirstPageu8g2_NextPage实现高效刷新6

    • 支持标准图形API如DrawStrDrawLine等,可扩展复杂界面

 

移植注意事项

  1. 依赖文件

    • 需包含u8g2库的核心文件(u8g2.hu8x8.h),建议从官方仓库获取最新版本

  2. 硬件适配

    • 若更换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);
    }
}

关键实现步骤说明:

  1. 字库添加

// 在u8g2库的u8g2_fonts.c文件中添加:
#include "u8g2_font_wqy12_t_chinese3.h"
// 或在工程配置中启用中文包(需下载u8g2官方中文字库)
  1. 编译器设置

    • 在Keil MDK中:Options → C/C++ → Misc Controls 添加 --locale=english --multibyte_chars

    • 在GCC中:添加编译选项 -finput-charset=UTF-8 -fexec-charset=UTF-8

  2. 源码编码要求

    • 必须使用UTF-8编码保存源代码文件

    • 在代码编辑器(如VSCode)右下角确认编码显示为UTF-8

  3. 字体选择建议

    /* 常用中文字体(需在u8g2库中启用) */
    u8g2_font_wqy12_t_chinese3  // 12像素文泉驿点阵宋体(推荐)
    u8g2_font_baby_tf           // 8像素幼圆体
    u8g2_font_unifont_t_chinese // 16像素Unifont

    中文显示优化技巧

  4. 自定义精简字库(使用工具生成仅包含所需汉字)

// 使用工具: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);

 

  1. 混合排版布局

 

// 中英文混合布局示例
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[] = {...};

常见问题解决方法

  1. 显示乱码

 

// 检查清单:
- 确认源文件编码为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屏幕的常规中文显示。

你可能感兴趣的:(stm32,单片机,嵌入式硬件)