STM32HAL 快速入门(二十四):I2C 编程(一)—— 从 OLED 显示初识 I2C 协议

前言

大家好,这里是 Hello_Embed 。前面我们学习的 UART 是 "异步传输"------ 两台设备通过发送引脚(TX)、接收引脚(RX)和共地端互连,能同时发送和接收(全双工)。但嵌入式系统中还有很多其他传输协议,今天要讲的 I2C 就和 UART 有很大不同:它是 "半双工" 的,因为只有一条数据线(SDA),发送时不能接收,接收时不能发送;同时它只用三条线(时钟线 SCL、数据线 SDA、地线 GND),更重要的是 ------I2C 总线支持多设备连接,总线上可以同时接多个传感器、显示屏等,这是它的一大优势。

为了直观理解 I2C 协议的工作方式,我们以 OLED 显示屏为例(OLED 常用 I2C 通信),通过 "在 OLED 上显示字符" 的案例,一步步揭开 I2C 传输的面纱。

一、先看效果:OLED 显示字符串的代码与现象

之前我们用过 OLED 显示字符串,代码很简单,先初始化、清屏,再调用打印函数:

c 复制代码
int main(void)
{
    OLED_Init();// 初始化OLED显示屏
    OLED_Clear();// 清屏(清空屏幕显示内容)
    // 在x=0、y=3的位置打印字符串
    OLED_PrintString(0, 3, "Hello, embed!");
    // 在x=0、y=5的位置打印另一个字符串
    OLED_PrintString(0, 5, "Hello, world!");
    
    while (1)
    {
        /* 主循环无需额外操作,显示内容已固定 */
    }
}

烧录后屏幕显示正常,两行字符串清晰可见:

二、硬件框图:OLED 与 I2C 的连接逻辑

要理解 "为什么这段代码能让屏幕显示字符",先看 OLED 的硬件结构:

右侧是 OLED 模块,核心由两部分组成:

  • 屏幕面板:负责发光显示;
  • 主控芯片 SSD1306 :相当于 OLED 的 "大脑",屏幕通过引脚与它连接,我们的程序其实是和 SSD1306 通信,而不是直接控制屏幕。
    工作逻辑很简单:
  1. 我们的程序通过 I2C 协议向 SSD1306 发送数据(比如 "Hello, embed!" 的点阵);
  2. 数据被存入 SSD1306 内部的 "显存"(一块专门存储显示数据的内存);
  3. SSD1306 会不断把显存中的数据刷新到屏幕上,于是我们就看到了字符。
三、程序流程:从 "打印字符串" 到 "I2C 传输" 的四层逻辑

上面的代码看似简单,背后其实经历了四层 "分工协作",就像公司完成一个项目:

1. 应用程序:"老板"------ 明确需求

对应 main.c 中的代码,是整个流程的起点,明确 "要显示什么内容、在哪里显示":

c 复制代码
OLED_PrintString(0, 3, "Hello, embed!");
OLED_PrintString(0, 5, "Hello, world!");

这里的 0 是 x 坐标(横向位置),35 是 y 坐标(纵向位置),字符串就是要显示的内容 ------ 这一步只提需求,不关心 "如何显示"。

2. 库函数:"高管"------ 拆解任务

应用程序提了需求,接下来由 OLED_PrintString 函数(库函数)把任务拆解:字符串是由多个字符组成的,所以需要逐个字符处理。

跳转到 OLED_PrintString 的定义看看:

c 复制代码
int OLED_PrintString(uint8_t x, uint8_t y, const char *str)
{   
    int i = 0;
    // 循环取出字符串中的每个字符(直到遇到结束符'\0')
    while (str[i])
    {
        // 调用OLED_PutChar显示单个字符
        OLED_PutChar(x, y, str[i]);
        x++;  // 每个字符占1个x位置,所以x坐标+1
        
        // 如果x超过屏幕最大横向位置(15),就换行(y+2),x重置为0
        if(x > 15)
        {
            x  = 0;
            y += 2;
        }
                
        i++;
    }
    return i;  // 返回打印的字符总数
}

这个函数的作用很明确:把字符串拆成单个字符(比如把 "Hello" 拆成 'H'、'e'、'l'、'l'、'o'),然后调用 OLED_PutChar 函数逐个显示 ------ 相当于 "高管" 把大任务拆成小任务,分给具体的执行者。

3. SSD1306 驱动:"开发"------ 执行具体操作

OLED_PutChar 函数是真正处理 "如何显示单个字符" 的,它需要:

  • 确定字符在屏幕上的位置(通过 x、y 计算);
  • 取出字符对应的点阵数据(字符由点阵组成,比如 'A' 是 8x16 的点阵);
  • 把点阵数据发送给 SSD1306 芯片。
    OLED_PutChar 的代码:
c 复制代码
void OLED_PutChar(uint8_t x, uint8_t y, char c)
{
    uint8_t page = y;  // y坐标对应SSD1306的"页"(纵向位置单位)
    uint8_t col  = x*8;  // x坐标对应"列"(横向位置单位,每个字符占8列)
    
    // 检查坐标是否超出屏幕范围(超出则不显示)
    if (y > 7 || x > 15)
        return;
    
    // 第一步:设置显存地址(告诉SSD1306:接下来的数据要存在哪个位置)
    OLED_SetPosition(page, col);
    // 第二步:发送字符上半部分的8个字节点阵
    OLED_WriteNBytes((uint8_t*)&ascii_font[c][0], 8);
    
    // 同理,设置下一页地址,发送字符下半部分的8个字节点阵
    OLED_SetPosition(page + 1, col);
    OLED_WriteNBytes((uint8_t*)&ascii_font[c][8], 8);
}

关键在这两句:

  • OLED_SetPosition:设置 SSD1306 显存的地址(就像告诉快递员 "东西要放在哪个货架");
  • OLED_WriteNBytes:发送字符的点阵数据(ascii_font 是存储所有 ASCII 字符点阵的数组,比如 ascii_font['A'] 就是 'A' 的点阵)。
    这一步完全遵循 SSD1306 芯片手册的要求:必须先设置地址,再发送数据,且每个字符的 16 个点阵字节要分两次发送(每次 8 字节)。
4. I2C 控制器驱动(HAL 库):"跑腿"------ 物理层传输

OLED_WriteNBytes 函数最终会调用 STM32 的 I2C 控制器,通过硬件引脚(SCL、SDA)把数据真正发送出去。这一步由 HAL 库的 I2C 函数(比如 HAL_I2C_Master_Transmit)完成,相当于 "跑腿的",负责把数据从 STM32 传到 SSD1306。

结尾

今天我们从 "OLED 显示字符" 这个案例入手,从上到下梳理了 I2C 协议的应用流程:应用程序提需求→库函数拆任务→SSD1306 驱动做具体配置→I2C 控制器完成物理传输。

下一篇我们会反过来,从下往上深入:先了解 I2C 协议的底层时序(SCL 和 SDA 线上的高低电平变化规律),再学习 STM32 的 I2C 控制器如何配置,最终理解 "点阵数据是如何通过两条线(SCL、SDA)从 STM32 传到 SSD1306 的"。
Hello_Embed 继续带你从现象到本质,吃透 I2C 协议,敬请期待~

相关推荐
凯尔萨厮2 小时前
Java学习笔记四(继承)
java·笔记·学习
ホロHoro2 小时前
学习笔记:Javascript(5)——事件监听(用户交互)
javascript·笔记·学习
爱喝水的鱼丶3 小时前
SAP-MM:SAP核心组织单元:工厂(Plant)全面学习指南及配置图解
学习·sap·abap·配置·工厂·mm模块
励志不掉头发的内向程序员3 小时前
STL库——AVL树
开发语言·c++·学习
朱自清的诗.4 小时前
stm32中 中断和事件的区别
stm32·单片机
晨非辰5 小时前
#C语言——刷题攻略:牛客编程入门训练(十一):攻克 循环控制(三),轻松拿捏!
c语言·开发语言·经验分享·学习·visual studio
xiaoxiaoxiaolll6 小时前
期刊速递 | 《Light Sci. Appl.》超宽带光热电机理研究,推动碳纳米管传感器在制药质控中的实际应用
人工智能·学习
嫣语岁月6 小时前
【BMS电池管理】基于BQ76920与STM32的BMS设计开发
c语言·vscode·stm32·单片机·嵌入式硬件
励志码农7 小时前
JavaWeb 30 天入门:第二十三天 —— 监听器(Listener)
java·开发语言·spring boot·学习·servlet