
🎬 渡水无言 :个人主页渡水无言
❄专栏传送门 : 《linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》
❄专栏传送门 : 《freertos专栏》 《STM32 HAL库专栏》
⭐️流水不争先,争的是滔滔不绝
📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生
| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生
在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连
目录
[一、I2C 总线核心基础知识](#一、I2C 总线核心基础知识)
[二、I2C 总线通信时序过程](#二、I2C 总线通信时序过程)
[2.3、 寻址阶段](#2.3、 寻址阶段)
[三、CubeMX 配置 I2C+OLED 屏通信](#三、CubeMX 配置 I2C+OLED 屏通信)
[3.2、CubeMX 完整配置步骤](#3.2、CubeMX 完整配置步骤)
[3.2.2、选择SYS 外设的核心配置及设置引脚模式](#3.2.2、选择SYS 外设的核心配置及设置引脚模式)
[3.3.1、查找OLED屏的I2C 地址(查找手册)](#3.3.1、查找OLED屏的I2C 地址(查找手册))
[3.3.2、OLED 屏控制指令(核心)](#3.3.2、OLED 屏控制指令(核心))
[3.3.3、 I2C 核心 API(CubeMX 生成)](#3.3.3、 I2C 核心 API(CubeMX 生成))
[写数据 API:HAL_I2C_Master_Transmit(向从机写数据)](#写数据 API:HAL_I2C_Master_Transmit(向从机写数据))
[读数据 API:HAL_I2C_Master_Receive(从从机读数据)](#读数据 API:HAL_I2C_Master_Receive(从从机读数据))
前言
本此博客基于 STM32F103 系列单片机,从 I2C 总线核心原理(开漏输出、线与逻辑、通信时序)讲起,使用 CubeMX 配置 I2C、实现 OLED 屏数据收发,并联动控制板载 LED。
一、I2C 总线核心基础知识
串口只能实现 "一对一" 通信,一个串口可以让单片机跟一个外部设备相连,所以通过串口USART1、USART2、USART3,我们的单片机可以跟三个外部设备相连,如下图所示:

1.1、总线组成与硬件规则
但如果想要连接更多的设备,就要使用总线了,比如本期博客我们要讲的I2C总线。
而 I2C 总线是多设备互联的核心方案 ------ 仅需 SCL(时钟线)、SDA(数据线)两根线,就能让一个主机挂载多个从机,是传感器、显示屏等外设的主流通信方式。如下图所示:

I2C总线由SCL、SDA组成。
SCL负责传输高低变化的方波信号,每个时钟周期传输一位,所以时钟的频率越高,传输的速率越快。
SDA负责传输数据,高电压表示1,低电压表示0;
主机和从机各有SCL、SDA引脚,两种引脚各自与I2C总线的SCL、SDA引脚相连;
SCL、SDA各连接一个阻值4.7k的上拉电阻;
主/从机的SCL、SDA引脚都使用开漏输出 。开漏输出如下图所示:

疑问:为什么I2C所有的引脚都要配置开漏输出?为什么要配置两个上拉电阻呢?
答案:这其实是为了实现逻辑线与。

当主机、从机均配置高阻抗时,总线由于配置了上拉电阻,所以显示高电压;
只要有一个"机"配置的低电压,那么Vdd的电流就会直接通往此"机",从而总线显示低电压
二、I2C 总线通信时序过程
2.1、总体流程
I2C 通信由主机全程主导,完整时序分为 4 个核心阶段,流程图直观拆解全过程:

2.2、起始位
过程如下图所示:

- 触发条件:主机控制SCL 保持高电平 ,SDA 产生下降沿(高→低);
- 作用:告诉所有从机 "通信开始,准备接收地址"。
2.3、 寻址阶段

主机动作:通过 SCL 控制时钟节奏,SDA 依次发送7 位从机地址 (图中比如是0x78)+ 1 位读写方向位(0 = 写,1 = 读);
从机动作:所有从机对比自身地址,匹配成功的从机在第 9 个时钟周期向 SDA 发送1 位应答位(ACK)(低电平 = 应答成功);
核心:每台 I2C 设备都有唯一地址,主机通过寻址确定 "和谁通信"。
注意:寻址阶段 NAK 的原因
地址填错,要寻址的从机不存在
要寻址从机正忙,来得及回复 ACK
从机故障
2.4、数据传输阶段

写数据:主机在 SCL 时钟同步下,通过 SDA 逐位发送有效数据,从机每接收 1 字节后回复 1 位应答位;
读数据:读写方向位为 "读" 时,从机在 SCL 时钟同步下发送数据,主机接收后回复应答位;
关键:无论读写,SCL 始终由主机控制,仅 SDA 的收发方互换。
2.5、停止位

触发条件:主机控制SCL 保持高电平,SDA 产生上升沿(低→高);
作用:告诉所有从机 "本次通信结束"。
2.6、全过程总结

2.7、波特率
波特率越大,数据传输越快,但是越不稳定,f103单片机只支持前两钟模式:


注意:通常使用2:1的模式。
三、CubeMX 配置 I2C+OLED 屏通信
3.1、OLED简介
OLED显示屏:性能优异的新型显示屏,具有功耗低、相应速度快、宽视角、轻薄柔韧等特点
0.96寸OLED模块:小巧玲珑、占用接口少、简单易用,是电子设计中非常常见的显示屏模块。
供电:3~5.5V,通信协议:I2C/SPI,分辨率:128*64
3.1.1、实物及原理图
实物图如下图所示:

原理图如下:

VCC - 电源 +
GND - 接地
SCL - I2C 接口的串行时钟线
SDA - I2C 接口的串行数据线
3.2、CubeMX 完整配置步骤
CubeMX新建项目------选型号------选调试接口------配置PC13引脚(板载LED,跟前几次操作相同)------设置I2C接口
1.打开STM32CubeMX,新建工程,选择芯片
2.设置芯片的调试接口(如果忘记这个步骤,芯片的调试接口将被锁死,程序烧录不进去)
3.2.1、创建新工程及选择芯片型号
首先点击File --->点击New project

选中STM32F103C8T6,再点击start project
3.2.2、选择SYS 外设的核心配置及设置引脚模式
在SYS Mode and Configuration栏中,按如下配置:
调试模式(Debug):选定了Serial Wire(SW 模式)。
这是 STM32 最常用的调试接口模式,仅需占用SWDIO和SWCLK两个引脚。
时基源(Timebase Source):选定了SysTick。
这表示将系统滴答定时器作为 HAL 库的时基来源,用于实现HAL_Delay()等延时函数,是 STM32 工程的标准配置。
并在右侧芯片图中点击PC13设置为GPIO_Output"
PA9引脚设置为GPIO_input"

3.2.3、pc13引脚参数设置
点击GPIO选中对应的引脚,即可设置参数,设置为开漏模式,初值为高电平,这样led默认为熄灭。

3.2.4、设置I2C接口
在connectivity里可以看到,单片机有两个I2C接口I2C1和I2C2,任选一个即可

I2C有三种模式:

I2C为标准I2C接口(一般使用这个)
下边两行为系统管理总线(不常用)
选完第一种,可以看到底下出现了各种参数
当STM32当作主机时,设置Master Features(主机参数,更常用),如下图所示:

速度等级其实也就是波特率等级,决定着下面比特率参数的范围。

我们的OLED显示器支持快速模式,所以点击快速模式,并出现新的参数:

占空比只要没特殊要求一般就选择2:1。
可以看到软件已经自动帮我们分配了I2C的引脚:

继续点击GPIO的I2C,可以看到CubeMX已经自动将GPIO模式设置成立复用输出开漏模式。保持默认就好。

3.2.5项目管理与生成
给项目取名、设置位置、选择开发的工具链,再点击GENERATE CODE生成

3.3、代码编写
接线图如下:

注意:市面多数 OLED 屏均自带上拉电阻,接线仅需 SCL/SDA + 正负极(所以可不接电阻)
3.3.1、查找OLED屏的I2C 地址(查找手册)

可知地址为:0x78
3.3.2、OLED 屏控制指令(核心)
通过 I2C 向 OLED 屏发送特定指令,可实现屏幕初始化、显示控制等功能,手册核心指令示例:
// 示例:OLED屏初始化指令数组(需根据手册调整)
uint8_t oled_cmd[] = {
0x00, // 指令起始位
0xAE, // 关闭显示
0xD5, // 设置显示时钟分频比/振荡器频率
0x80, // 分频比设置
0xA8, // 设置多路复用率
0x3F, // 多路复用率值
0xa5 //全白
// ... 其他初始化指令
0xAF // 开启显示
};
3.3.3、 I2C 核心 API(CubeMX 生成)
CubeMX 为 I2C1 生成句柄hi2c1,核心通信接口:
写数据 API:HAL_I2C_Master_Transmit(向从机写数据)
HAL_StatusTypeDef HAL_I2C_Master_Transmit(
I2C_HandleTypeDef *hi2c, // I2C句柄指针,实战填&hi2c1(CubeMX生成)
uint16_t DevAddress, // 从机地址,实战填OLED屏地址0x78
uint8_t *pData, // 要发送的数据缓冲区(指令/数据数组)
uint16_t Size, // 发送数据长度,以字节为单位
uint32_t Timeout // 超时时间(ms),HAL_MAX_DELAY表示无限等待
);
| 参数 / 返回值 | 实战说明 | 图片标注对应项 |
|---|---|---|
*hi2c |
固定传入&hi2c1,指定使用 I2C1 接口通信 |
黄色标注:&hi2c1 |
DevAddress |
OLED 屏写地址固定为0x78,不可修改 |
黄色标注:0x78 |
*pData |
传入指令数组(如oled_init_cmd)或数据数组 |
图片标注:要发送的数据 |
Size |
传入数组长度(如sizeof(oled_init_cmd)) |
图片标注:要发送数据的数量 |
Timeout |
调试阶段可用HAL_MAX_DELAY(无限等待),量产建议设 100~500ms |
黄色标注:HAL_MAX_DELAY |
| 返回值 | HAL_OK:发送成功;HAL_ERROR:发送出错;HAL_BUSY:I2C 接口忙;HAL_TIMEOUT:发送超时 |
图片标注:成功 / 失败返回结果 |
读数据 API:HAL_I2C_Master_Receive(从从机读数据)
HAL_StatusTypeDef HAL_I2C_Master_Receive(
I2C_HandleTypeDef *hi2c, // I2C句柄指针,实战填&hi2c1
uint16_t DevAddress, // 从机读地址
uint8_t *pData, // 接收数据缓冲区(存储读取的状态字/数据)
uint16_t Size, // 接收数据长度,以字节为单位
uint32_t Timeout // 超时时间(ms),建议设100ms以上
);
| 参数 / 返回值 | 实战说明 |
|---|---|
*hi2c |
同写数据 API,固定传入&hi2c1 |
DevAddress |
OLED 屏读地址为ox78 |
*pData |
传入单字节数组(如uint8_t status[1]),存储读取的状态字 |
Size |
读取状态字时填 1,读取多字节数据时填对应长度 |
Timeout |
建议设 100ms,避免因通信异常导致程序阻塞 |
| 返回值 | 与HAL_I2C_Master_Transmit一致,通过返回值判断读取是否成功 |
打开keli可以发现CubeMX已经为I2C1生成了一个句柄:
3.3.4、keil最终程序
现在使用第一个接口将命令发送出去,在最开始,我们需要生成一个数组,存放命令:

此外,我们还想将板载指示灯当作OLED屏指示灯来使用,当屏幕开启时,点亮这颗LED,查询手册可知屏幕有一个状态字可表示屏幕的开启或关闭。如下图所示:



总结
使用 CubeMX 配置 I2C、实现 OLED 屏数据收发,并联动控制板载 LED。


