参考资料
NXP 官方网站提供的IIC总线规范
I2C-bus specification and user manua
https://www.nxp.com/docs/en/user-guide/UM10204.pdf
别再去下那些狗东西不知道从哪里弄来还要下载积分的烂玩意了!
本篇所有图片均摘自 IIC总线规范 。
IICBus
IIC一共只有两条线,双向串行数据线SDA、串行时钟线SCL。
IIC是半双工的!
-
SDA:Serial Data(数据信号线)
-
SCL: Serial Colck Line(时钟信号线)
IIC应用电路
IIC 总线要求每个设备的SCL/SDA线都是
漏极开路(OD)模式
,因此总线必须带有上拉电阻才能正常工作。
上拉电阻的大小也会影响电平转换的时间,4.7K 就行。(是我在5V通讯下验证的值,应用需注意)
什么是漏极开路模式?
想想 51 单片机的 P0 口,就是很典型的漏极开路输出,因此要作为准双向口时,需要上拉电阻。
对于晶体管和场效应管来说:就是将 OUTPUT 端口接在漏极:
图中电阻即为上拉电阻,因 MOSFET 导通时,只有将 OUTPUT 拉低的能力而没有将 OUTPUT 抬升的能力,所以需要上拉电阻拉高 OUTPUT 的电平。
这种办法应用在驱动电路上是有很大好处的,驱动电流也不用很大就能控制大负载。
IIC 可在元件不同电压下进行通讯
各种电源电压共享同一IIC总线的例子
数据有效性
SDA 线上的数据要在时钟信号为高电平时保持稳定,在时钟信号为低电平时进行切换!
起始和停止
当 SCL(时钟信号线) 为高电平时,SDA 下降沿表示为起始信号,SDA 上升沿表示停止信号。
在这个起始和停止检测时,对硬件IIC很简单,软件IIC作为从机时,需要注意一下。
数据位
手册规定。SDA 上的数据必须为8位每字节。但是每次传输的字节数不受限制,每字节后要跟一个应答位(ACK)。
数据先由最高位(MSB)传输。
从机或主机暂时无法传输或接收完整数据时,可将SCL保持为低,进入等待状态。待时钟恢复时继续传输。
关于应答位:
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
发送完一个字节后,主机释放 SDA 的控制权,以便从机发送应答位。
-
ACK
:SCL高电平时,SDA为低电平,表示数据被接收了。
-
NACK
:(无应答)SCL高电平时,SDA为高电平
从机地址
开始后,发送7位地址,第8位为读/写标志位(0:W、1:R)。
开始后的第一个字节:
这样有几种情况,从机向主机发送信息,主机向从机发送信息,发送接收交替进行。
主机向从机写入的情况
主机读取从机情况
改变数据流方向
十位地址寻址方式
第一字节的前七位为
1111 0XX
,最后两位是十位地址的最高有效位(MSB)。第8位为 R/W 位。
有这种使用方法:
10 位寻址未广泛使用,不做过多介绍。
软件IIC时还应注意电平跳变不是瞬间完成的,需要一定的延时!!!
IIC总线接口的软件实现-89C51
为了方便理解,将软件实现分为几个部分:
sbit I2C_SDA = P1^0;
sbit I2C_SCL = P1^1;
延时函数
nop 代表运行一个时钟周期,此函数常常用在某些协议中的一个短延时!
void I2C_Delay(void)
{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}
IICBus发出起始信号
起始信号是在SCL高电平时SDA实现下降沿。
void I2C_Start(void)
{
I2C_SDA = 1;
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 0;
I2C_Delay();
I2C_SCL = 0;
}
IICBus发出结束信号
结束信号是在SCL高电平时SDA实现上升沿。
void I2C_Stop(void)
{
I2C_SDA = 0;
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 1;
I2C_Delay();
I2C_SCL = 0;
}
IICBus发出应答信号
SCL高电平时,SDA为低电平,表示数据被接收了。
void I2C_ASK(void)
{
I2C_SDA = 0;
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 1;
I2C_SCL = 0;
}
IICBus发出非应答信号
void I2C_NOASK(void)
{
I2C_SDA = 1;
I2C_SCL = 1;
I2C_Delay();
I2C_SDA = 0;
I2C_SCL = 0;
}
IICBus上发送一字节的数据
void I2C_SendByte(unsigned char c)
{
unsigned char i;
for(i=8; i>0; i--)
{
if(c & 0x80)
I2C_SDA = 1;
else
I2C_SDA = 0;
I2C_SCL = 1;
I2C_Delay();
I2C_SCL = 0;
C = C<<1;
}
I2C_SDA = 1; //释放 I2C_SDA 数据线,准备接收应答信号
I2C_SCL = 1;
I2C_Delay();
while(!(0 == I2C_SDA && 1 == I2C_SCL));
// 这里可加入超时执行其他函数的功能
}
把上述代码后四行改成检测主机应答位的情况
void I2C_ChickACK(void)
{
I2C_SDA = 1;
I2C_SCL = 1;
F0 = 0;
if(I2C_SDA == 1)
F0 = 1; //无应答
I2C_SCL = 0;
}
IICBus上接收一字节的数据
unsigned char I2C_ReceiveByte(void)
{
unsigned char i;
unsigned char r;
I2C_SDA = 1;
for(i=8; i>0; i--)
{
r = r<<1;
I2C_SCL = 1;
I2C_Delay();
if(I2C_SDA)
r = r|0x01;
I2C_SCL = 0;
}
return r;
}
实际例子
读 ADS1100 的 A/D 转换值。
#define READ_ADDR 0X90 // 1001 0000 // 读寄存器地址
#define WRITE_ADDR 0X91 // 1001 0001 // 写寄存器地址
#define CFG_WORD 0X8F // 1000 1111 // 配置寄存器预设
unsigned char AD_H; // 存储AD转换高八位
unsigned char AD_L; // 存储AD转换低八位
bit SYS_ERR; // 从机错误标志位
/* 配置寄存器进行设置
* @ setting_data: 要配置的参数
*/
void ADS1100_INIT(unsigned char setting_data)
{
I2C_Start();
I2C_SendByte(WRITE_ADDR);
I2C_CheckACK();
if(FO == 1)
{
SYS_ERR = 1;
return;
}
I2C_SendByte(setting_data);
I2C_CheckACK();
if(FO == 1)
{
SYS_ERR = 1;
return;
}
I2C_Stop();
}
/* 用于读取A/D转换的结果
*/
void READ_ADS1100(void)
{
I2C_Start();
I2C_SendByte(READ_ADDR);
I2C_CheckACK();
{
SYS_ERR = 1;
return;
}
AD_H = I2C_ReceiveByte();
I2C_ASK();
AD_L = I2C_ReceiveByte();
I2C_NOACK();
I2C_Stop();
}
void main()
{
ADS1100_INIT(CFG_WORD);
READ_ADS1100();
}
现在基本上芯片都有硬件I2C接口了,此只是作为一个了解,另此部分代码为自己推理,位编译进行验证,仅供参考用。
🎉 今日学习达成!