STM32的I2C通信原理以及软件I2C模拟

今天总算把I2C大致理清楚了。

先大致讲一下I2C:类比串口通信,I2C也只有一条数据线,也就是SDA,串口的通信协议是默认高电平,遇到拉低电平的情况,等半个位的时间,检测是否低电平,若是,通信开始;
I2C的也有点类似,但是它需要另一跟线配合,也就是SCL时钟线;SDA和SCL这两条线从物理结构上就可以知道,默认是高电平的,所以I2C的启动信号是这样的:当SCL位高电平时,SDA
若是被拉低一段时间,那么I2C通信开始。

I2C的协议有这样的规则:先说大规则,数据也就是SDA必须在SCL为高电平的时候保持不变,在SCL为低电平的时候随意变动。

在这个前提下,I2C的协议开始了:首先是4种时序:
1.启动时序,当SCL为高电平的时候,SDA由高变低,并保持一段时间,视为启动信号(夸张地说你想保持多久都可以)

2.数据时序,这个没啥特殊时序,简单概括就是SCL低电平的时候数据可以变动,SCL高电平的时候数据保持不变,从机采样时间是在SCL信号有低变高的上升沿。
(所以说,我的时钟可以是变动的,可以每一个周期都不一样,虽然这样只会给自己增加开发难度,但是这确实是协议的规则范围之内的事情,发送数据讲究的是什么,
当然是接收方能收到数据,既然接收方也就是从机的判断方式这么简单:SCL上升沿进行数据采样,那么时钟周期真的就身段很柔软了,想怎么变就怎么变,这也是同步时钟的优势吧)。

3.应答时序,这个ACK(应答时序)必须是紧挨着数据的,数据一发完,主机一释放完SDA(这个释放的时机有坑,我已经踩了,后续会说明),从机会在SCL为低电平的时候拉低SDA(
若是应答的话),若是从机不应答,那么它不会碰SDA线,也就是说SDA线主机从机都不碰,会是高,当第九个时钟周期高电平期间,SDA=1,则是NACK。主机收到信号后需要发出STOP

4.结束时序,也就是STOP,当SCL为高电平时,SDA由低变高,并持续一段时间,跟启动时序基本上相反。

这些时序除了应答,都是主机掌控的。

然后I2C的连续写入是这样的(向设备发送控制码和数据)
START--ADDR--ACK--DATA(芯片内部某个寄存器地址)--ACK--DATA--ACK--DATA-------NACK--STOP
image

I2C的先读后写是这样的(读取设备的数据)
START--ADDR写--ACK--DATA(芯片内部你所需数据的地址)--ACK--START--ADDR读--ACK--从机发送DATA--主机ACK--从机发送DATA--主机ACK------主机NACK--STOP

STM32软件模拟I2C主机的硬件结构是这样的:主机的I2C引脚是开漏输出,只能拉低,需要外加上拉电阻拉高电平,这样的结构可以防止烧坏单片机引脚。具体来讲就是如果单片机选择
推挽输出,那么当想要控制其中一条线的电平为高或者低时,必然有一个MOS管道通,要么导通3.3V,要么导通GND,要是导通了3.3V,这个时候外部从机某一个将电平拉低,
那么直接就是3.3->0V的路径,大电流烧坏单片机内部接着3.3V的Mos管(下面那个应该是烧不着的,毕竟外部有上拉电阻)。

然后说说从机的I2C的大致硬件结构

1.SDA和SCL
2.检测启动信号/停止信号的寄存器
3.移位寄存器
4.ack相关逻辑

所以整个I2C通信流程就是,主机发送开始信号,从机一直在监听这个,从机收到起始信号,打开移位寄存器,接收完地址信息,对比是否是自己的,如果不是就关闭移位寄存器(节能),
如果是,就处理相关指令并发送应答信号,应答信号只会持续一个时钟周期,直到下一次的SCL下降沿,然后移位寄存器持续接收(有的从机处理相关指令需要其自身的cpu参与,
有的则只需要硬件电路,比如OLED就只有一堆硬件电路,没有CPU)。

再简单聊聊我用软件模拟的事:做I2C模拟必须要有逻辑分析仪,当然有示波器更好。然后就是第一步,确定延时,I2C的时钟没有像串口那么严格,很多设备支持400khz以下,也就是说
只要满足这个条件,时钟信号的快慢就随便你自己了,毕竟I2C协议一共也就只有那么4条。确定延时的时候首先排除系统自带是systick时钟,因为它会产生中断,虽然可以精确计时,但是
对于us级的计时,用它的话系统得卡成什么样子,还不如最简单的while循环。说到while循环的时候,我又担心被中断打断循环,结果发现,一般中断好像还不好打断while循环,因为它实在
是太快了,总之AI跟我说加个中断总开关,就能安心延时不怕打扰了。准备工作就绪,那么接下来就是先写这4个时序,启动,停止,应答,一个字节数据,还算比较顺利,然后就是上电看波形,
也可以说是看时序逻辑。我先前说的踩了一个坑就是当我把数据和应答连在一起使用的时候,由于考虑不当,在最后一个数据发送完之后,SCL还是高电平的期间,我直接将SDA释放去检测应答
信号了,结果就导致通讯失败,因为SCL高电平的时候SDA必须保持不变。解决这个之后我就尝试给OLED发了一个地址,收到了回应!成功的第一步,接下来就是改写OLED的驱动函数了,总体比较顺利,
还是花了大半天写代码,前期理解通讯协议,硬件结构还花费了一天左右,真是不容易啊。好在终于学得比较清楚了,I2C拿下!额,还有硬件I2C,明天在看看吧。

image

Note:搞懂下面的,写起模拟I2C有底气啊。
从机回应NACK之后会自动关闭移位寄存器。
主机发送数据是从数据位7开始到0结束。
从机是在SCL上升沿读取SDA信号。
从机读取信号不看时钟周期,只关注SCL上升沿。
停止信号不需要跟着时钟周期走,只看两个电平的对应关系。
I2C通讯是只有在需要的时候才开那么以下(知道这个我好像突然就悟了该怎么编写软件I2C了)
开始信号不是一个脉冲信号(SCL)
开始信号必须SCL和SDA先保持一段时间高电平(4US以上),才能进行拉低处理(留给从机准备的时间)
主机写入从机的时候顺序地址+寄存器地址+数据
为什么从机要写寄存器地址,因为从机的寄存器有很多,只知道从机地址不够,得有门牌号
当主机连续读取从机的数据是,从机的指针会自动+1,因为数据的存放是排排放的。
先写后读的逻辑比较烦,
从机应答信号不需要CPU参与,硬件功能
移位寄存器8个位

这是工程文档
https://gitee.com/liang-deping/restart-stm32

posted @ 2026-04-09 01:27  青春猪头青年  阅读(37)  评论(0)    收藏  举报