如何更加深入理解I2C总线、协议及应用


开始和停止条件
scl时钟电平为高:
sda数据线由高 -> 低 为总线开始条件;
sda数据线由低 -> 高 为总线结束条件;
(注意:开始之后将scl变为低电平,防止误操作sda使其通信停止,见源代码)
时序图:
源代码程序:
/************************************************
函数名称 : i2c_start
功 能 : i2c开始
参 数 : 无
返 回 值 : 无
作 者 : strongerhuang
*************************************************/
voidi2c_start(void)
{
i2c_scl_high; //scl高
i2c_delay();
i2c_sda_high; //sda高 -> 低
i2c_delay();
i2c_sda_low; //sda低
i2c_delay();
i2c_scl_low; //scl低(待写地址/数据)
i2c_delay();
}
/************************************************
函数名称 : i2c_stop
功 能 : i2c停止
参 数 : 无
返 回 值 : 无
作 者 : strongerhuang
*************************************************/
void i2c_stop(void)
{
i2c_sda_low; //sda低 -> 高
i2c_delay();
i2c_scl_high; //scl高
i2c_delay();
i2c_sda_high; //sda高
i2c_delay();
}

数据位传输
scl时钟电平为低, 可以改换sda数据线的电平,在scl上升沿的过程将sda数据发送出去。
(切记:请先将scl变为低电平,再改变sda电平状态。 主要用于i2c读写byte函数,这两个函数网上很多人写的不规范,引用需注意,在下面我会举例说明)
时序图:
发送一位“高”数据流程:
scl_low时钟低 -> sda_high数据 -> scl_high时钟高

应答位信息
i2c是以字节(8位)的方式进行传输,总线上每传输完1字节之后会有一个应答信号,主器件(主机)需要产生对应的一个额外时钟。
应答位产生及接收:
1.在(主机)写数据的时候是从机应答(给主机),主机检测;
2.在(主机)读数据的时候是主机应答(给从机),从机检测;
(我们借助i2c读写函数一起理解)
1.主机写,从机应答,主机读取应答
时序图:
源代码:
/************************************************
函数名称 : i2c_getack
功 能 : i2c主机读取应答(或非应答)位
参 数 : 无
返 回 值 : i2c_ack ----- 应答
i2c_noack --- 非应答
作 者 : strongerhuang
*************************************************/
uint8_t i2c_getack(void)
{
uint8_t ack;
i2c_scl_low; //scl低 -> 高
i2c_delay();
i2c_sda_high; //释放sda(开漏模式有效)
i2c_delay();
i2c_scl_high; //scl高(读取应答位)
i2c_delay();
if(i2c_sda_read)
ack = i2c_noack;//非应答
else
ack = i2c_ack; //应答
i2c_scl_low; //scl低
i2c_delay();
returnack;
}
2.主机读,主机产生应答
时序图:
源代码:
/************************************************
函数名称 : i2c_putack
功 能 : i2c主机产生应答(或非应答)位
参 数 : i2c_ack ----- 应答
i2c_noack --- 非应答
返 回 值 : 无
作 者 : strongerhuang
*************************************************/
voidi2c_putack(uint8_t ack)
{
i2c_scl_low; //scl低
i2c_delay();
if(i2c_ack == ack)
i2c_sda_low; //应答
else
i2c_sda_high; //非应答
i2c_delay();
i2c_scl_high; //scl高 -> 低
i2c_delay();
i2c_scl_low; //scl低
i2c_delay();
}

i2c写一字节
这里说的i2c写,是主机往从机接入1byte的数据;
“写”要求按照上面的“数据为传输”来操作:在scl时钟为低电平时准备好,待scl为高电平时发送出去。
写完一字节(8位)之后,读取从机的应答位:
若为0,表示从机应答,可以继续下一步操作;
若为1,表示从机非应答,不能进行下一步操作。
注意:
i2c写一字节,不是eeprom写一字节(需要区分开来)
写一字节时序(前面8位数据 + 最后1为应答):
源代码:
/************************************************
函数名称 : i2c_writebyte
功 能 : i2c写一字节
参 数 : data --- 数据
返 回 值 : 无
作 者 : strongerhuang
*************************************************/
void i2c_writebyte(uint8_t data)
{
uint8_t cnt;
for(cnt=0; cnt<8; cnt++)
{
i2c_scl_low; //scl低(scl为低电平时变化sda有效)
i2c_delay();
if(data & 0x80)
i2c_sda_high;//sda高
else
i2c_sda_low; //sda低
data <<= 1;
i2c_delay();
i2c_scl_high; //scl高(发送数据)
i2c_delay();
}
i2c_scl_low; //scl低(等待应答信号)
i2c_delay();
i2c_getack(); //读取应答位
}
提示:
网上常见几种关于“i2c写数据函数”的不规范写法, 或许整个i2c驱动能通信成功,但各个函数之间依赖关系很强,不便理解,也不是标准的函数。
1.首先将scl置高:
voidi2c_writebyte(uint8_t data)
{
uint8_t cnt;
for(cnt=0; cnt<8; cnt++)
{
i2c_scl_high;
if(data & 0x80)
i2c_sda_high;
else
i2c_sda_low;
data <<= 1;
i2c_scl_low;
}
i2c_getack();
}
这种程序的写法有一个致命的地方(有可能停止,或重新开始i2c通信):
首先将scl置高:
a.若之前sda是低电平,第一位写入高电平,将停止i2c通信。
b.若之前sda是高电平,第一位写入低电平,将重新开始i2c通信。
2.写完8位数据之后,未将scl置低(也就是scl保持高电平状态)
由于写完8位数据之后,将要读取应答信号,也就是要sda将从输出状态变为输入状态。
这个时候scl为高,如果sda最后一位是低且sda是开漏模式,需要将sda释放,也就是要将sda置位高,那么,这个时候就进行了一个停止操作。
3.时序混乱
void i2c_writebyte(uint8_t data)
{
uint8_t cnt;
i2c_scl_high;
for(cnt=0; cnt<8; cnt++)
{
if(data & 0x80)
i2c_sda_high;
else
i2c_sda_low;
data <<= 1;
i2c_scl_low;
i2c_scl_high;
}
i2c_getack();
}
多种问题的例子,有可能产生以下问题:
a.有可能多写1位数据;
b.有可能停止i2c通信;
c.有可能重新开始i2c通信。

i2c读一字节
i2c的读一字节函数,其实和“写一字节”类似,只是数据传输方向相反,应答的方向也是相反。
读完一字节(8位)之后,由主机产生应答(或非应答)位:
若产生应答,表示可以继续读下一字节操作(从设备地址指向下一字节);
若产生非应答,表示不可以继续读下一字节操作;
网上i2c读数据程序和“写数据”类似,存在很多不标准的版本,参考时请注意。
读一字节时序(主机读取前面8位数据 + 主机产生1为非应答):
源代码:
/************************************************
函数名称 : i2c_readbyte
功 能 : i2c读一字节
参 数 : ack --------- 产生应答(或者非应答)位
返 回 值 : data -------- 读取的一字节数据
作 者 : strongerhuang
*************************************************/
uint8_t i2c_readbyte(uint8_t ack)
{
uint8_t cnt;
uint8_t data;
i2c_scl_low; //scl低
i2c_delay();
i2c_sda_high; //释放sda(开漏模式有效)
for(cnt=0; cnt<8; cnt++)
{
i2c_scl_high; //scl高(读取数据)
i2c_delay();
data <<= 1;
if(i2c_sda_read)
data |= 0x01; //sda为高(数据有效)
i2c_scl_low; //scl低
i2c_delay();
}
i2c_putack(ack); //产生应答(或者非应答)位
return data; //返回数据
}

ikbc全新TableE系列机械键盘评测 值不值得买
DB-Engines数据库流行度排行榜发布了12月份的数据
Marvell、三星等市场主流SSD主控详解
达6000条测试用例!忆联消费级存储实验室向业界标杆看齐
高压断路器的型号代表什么意思
如何更加深入理解I2C总线、协议及应用
详细的ChatGPT Plus会员充值教程
iPhone8再曝光:新增多项重磅功能,价格上涨
IDEA REST Client使用教程
笙泉M0系列MCU应用于电池管理系统(BMS)
若英特尔与紫光集团合体进攻3D NAND市场,恐让市场供需失衡
软件定义汽车,变的到底是什么?
国内主要电动夹爪厂家专利技术分布情况
烧钱难长久,新造车企业遭遇“交付死”
如何解决变频器对PLC模拟量干扰?
5G将为VR/AR的发展带来新的机遇
基于一种手机白光LED背光驱动电路的设计
宜百利带您走近小米蓝牙遥控器
英特尔欲转型AI迎战英伟达,建立英特尔帝国
ZG PDU电源分配解决方案 实现12V/24V电源输出分配管理