0.序
我用的一个PCA9536老是出问题,怀疑是IIC应答或者停止位出问题了,所以特地来仔细看看IIC的原理和操作。
(已经排除软件问题,是硬件电路问题)
1. IIC简介
IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行总线,用于连接 微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。 在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 400kbps 以上。目前大部分 MCU 都带有 IIC 总线接口,STM32F1 也不例外。
但是这里我们不使用 STM32F1 的硬件 IIC 来读写 24C02,而是通过软件模拟。ST 为了规避飞利浦 IIC 专利问题,将 STM32 的硬件 IIC 设计的比较复杂,而且稳定性不怎么好,所以这里我们不推荐使用。有兴趣的读者可以研究一下 STM32F1 的硬件 IIC。
用软件模拟 IIC,最大的好处就是方便移植,同一个代码兼容所有 MCU,任何一个单片机只要有 IO 口,就可以很快的移植过去,而且不需要特定的 IO 口。而硬件 IIC,则换一款 MCU, 基本上就得重新搞一次,移植是比较麻烦的,这也是推荐使用软件模拟 IIC 的另外一个原因。
2. 三种信号
器件不同对应的IIC时序不同,因此信号可能稍有差异。本文使用的是24C02,时序如下图所示。
开始信号:SCL 为高电平时,SDA 由高电平向低电平跳变,开始传送数据。
结束信号:SCL 为高电平时,SDA 由低电平向高电平跳变,结束传送数据。
应答信号:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲, 表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
在原子的参考手册里有这样一句话,"这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要",具体情况我不清楚,但是我用的一个PCA9536老是出问题,怀疑是IIC应答或者停止位出问题了,所以特地来仔细看看IIC的原理和操作。
3. 硬件端口
我用的是正点原子精英板子,型号是F103ZET6,板子24C02硬件接口如下
我以为是任意IO口模拟IIC
没想到使用CubeMX中选了IIC之后,自动就出来PB6,PB7,看来他用的还是IIC特定的IO口
4. 硬件资料——时序
如图,直接看24C02的datasheet,下图是时序图
下图是起止信号和应答信号
5. 硬件资料——地址
对于IIC,在代码层面,我们主要关注的就是他的设备地址,还有读写指令,如下图示设备地址,这里我用的是24C02,也就是2k的这个。
可以看出,前4位是1010,也就是十六进制的A。
再看看A2,A1,A0三位,我查了资料说是用来做多个IIC设备挂在一个IIC总线上,用来区分地址的。我们这里只用1个24C02把这三个都置0就可以了。
最后一位是读写操作,读的话置1,写的话置0。
如下图是某位同学使用的多IIC设备,不过他貌似遇到了点问题
6. 硬件资料——读写操作
写步骤:
起始信号——发送设备地址——收到应答——发送写地址——收到应答——写一个字节——收到应答——停止信号
读步骤:
起始信号——发送设备地址——收到应答——收到一个字节——单片机主机应答——停止信号
7. 软件设计——开始信号
如图,开始时候,SDA和SCL都从高电平变成低电平
看看下降沿和上升沿的时间
看看上升下降延时,这里有最大时间,没有最小时间,因此我们不需要对电平跳变进行延时,只要保证SDA先于SCL跳变就行
代码表示就是这样的,借用原子哥的一段代码,从高电平到低电平,实际上就是让GPIO输出高低电平,因此前面还有一个SDA_OUT(),这个是原子写的寄存器方法配置IO口,学过51的同学会很熟悉(感兴趣的同学可以去看看我的51相关文章)。
原子在高低电平转换的地方加了一个4us的延时,我这里去掉试试。
-
//产生IIC起始信号
-
void IIC_Start(void)
-
{
-
SDA_OUT();
//sda线输出
-
IIC_SDA=
1;
-
IIC_SCL=
1;
-
delay_us(
4);
-
IIC_SDA=
0;
//START:when CLK is high,DATA change form high to low
-
//delay_us(4);
-
IIC_SCL=
0;
//钳住I2C总线,准备发送或接收数据
-
}
这里看到cubeMX自带的代码,最后面尝试分析一下这些代码的作用(当前先用软件模拟分析IIC的原理,最后再使用CubeMX自带代码试试)
8. 软件设计——停止信号
同理可得停止信号(因为另一个项目刚好用上24c02,这里详细分析一下,因此后面代码会改成51单片机的代码,因为用的是软件模拟,因此本代码与平台无关)
-
void EEP_I2c_Stop(void)
-
{
-
SCL=
0;
-
SDA=
0;
-
DelayUs(
2);
-
SCL=
1;
-
SDA=
1;
-
DelayUs(
2);
-
}
9. 软件设计——应答信号
可以看到,在每个写命令后面都会跟上一个应答信号
这里把前面的三种信号里面说的应答信号再复制一遍:接收数据的 IC 在接收到 8bit 数据后,向发送数据的 IC 发出特定的低电平脉冲, 表示已收到数据。CPU 向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU 接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若未收到应答信号,由判断为受控单元出现故障。
我们在这里输出一下,前面加一个0x99是为了区分是哪一帧的数据
这里对0xff写一个0x33
在这里输出SDA的值
我们通过串口输出SDA看看,发现只有三个0,说明在数据完成后,会直接把SDA置为0,因为前面SDA=1做过一次置1操作
实际上我发现在数据写完之后,24C02会立刻把SDA置0,并且是持续的置0脉冲。这个我们在后面写数据里面分析。
10. 软件设计——写数据
本节先做指定地址单字节写操作,页写暂时不做分析
写一个字节时序如下
写一个字节对应代码如下
这个操作是从高位起,一个位一个位的向设备写入数据 0x80对应1000 0000。
向指定地址写一个字节,使用下面函数,主要步骤如下:起始信号——发送设备地址——收到应答——发送写地址——收到应答——写一个字节——收到应答——停止信号
这里说说上一节说过的SDA置零问题
在SCL跳变之前打印SDA,代码和打印结果如下
在SCL跳变之后打印SDA,代码和打印结果如下,可以看到在最后SDA被置零,结合前面的应答信号来看,在数据写完之后,存储芯片发会送连续的低电平脉冲,因为在应答信号中我们先把SDA置为1,然后还是会得到0的结果
10. 软件设计——读数据
读数据3种方式
第一种是读数据流,发送设备地址之后就会读第一个数据
第二种是随机读取,我们需要用到的也是这种方式
起始信号——发送设备地址——收到应答——发送需要读取的数据地址——收到应答——发送设备地址——收到应答——收到一个字节——单片机主机应答——停止信号
第三种是连续读取,在第一种方式的基础上读取指定个数的字节,本节暂时不讨论次方法
读取一个字节如下
从特定地址读一个字节如下
看下读取结果
左边是往0xf0地址写数据0x33,右边是从0xf0地址读数据,发现读出来的是0xff,读取结果不正确
看了原子的代码发现,我和它的唯一区别是地址不同,于是加上了
发现结果还是一样
难道是地址不对??
结果还是不对???
11. 软件设计——发现错误
仔细看我的存储芯片型号发现wtmd用的是24c64,不是24c02,本文以上内容代码部分大家不要看了,后面重新写,啊啊啊啊啊!!!
12. 重新开始写代码
转载:https://blog.csdn.net/qq_35697978/article/details/116375880