I2C总线协议
Posted 写了程序换酒钱
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了I2C总线协议相关的知识,希望对你有一定的参考价值。
毕业设计中使用到了AT24C04器件,其是Ateml公司出品的,是一种低功耗CMOS串行EEPROM,其使用两线串行的总线和控制器进行通讯。其内部保存的数据在掉电的情况下可以有40年以上的有效期。其采用8 脚的DIP 封装,易于使用。简单来说,AT24C02是一款能在断电的情况下依然能够长时间存储数据的芯片。
可以使用I2C协议总线与AT24C04进行交互。
I2C
I2C总线协议概论
I2C总线是一种由数据线SDA和时钟SCL构成的按串行方式传输的数据总线。所有需要通信的设备都挂载在总线上,但就像计算机网络一样,只有知道正确的IP地址,数据的传输才能正常工作,所以被控电路都拥有唯一的地址。在数据的传输过程中,I2C总线上挂载的每一模块即可以作为主控器,也可以作为接收器,如果它发起通信则是主控器,否则为接收器。主控器发出的信号分为两部分,一部分为地址码,其用来选址,即告诉总线主控器需要和那个设备通信,和操作是读还是写,另一部分为控制量,其为具体的数据,由具体设备和应用程序决定。这样,控制电路虽然挂载在同一条总线上,但是他们之间身份明确,操作互不影响。
基本工作过程
写通讯过程:
1. 如果检测到当前的总线处于空闲状态,如果一个器件想作为一个主控器与其他设备通信,那么它就需要发送一个启动信号来掌握总线;
2. 在设备掌握总线后,成为主控器,那么它就需要发送一个地址字节,高7位地址为需要通信设备的地址,低一位为写控制位;
3. 总线中的一个设备如果检测到主控器发送的地址与自己的地址一致,其就是被控器,那么他就需要回复主控器应答信号(ACK),表示可以进行下一步操作;
4. 当主控收到被控器回复的ACK后就可以开始发送数据字节;
5. 当被控器成功接收了数据后需要向主控器发送一个ACK信号,来向表示主控器前一个字节接收完成,可以继续传送下一个字节数据,如果没有ACK命令,则说明数据接收不成功;
6.一旦主控器完成全部数据发送后,立刻发送停止信号,表示整个通讯过程结束并且释放总线,这样其他的设备可以再使用这条总线。
读通讯过程:
1. 如果检测到当前的总线处于空闲状态,如果一个器件想作为一个主控器与其他设备通信,那么它就需要发送一个启动信号来掌握总线;
2. 在设备掌握总线后,成为主控器,那么它就需要发送一个地址字节,高7位地址为需要通信设备的地址,低一位为读控制位;
3. 总线中的一个设备如果检测到主控器发送的地址与自己的地址一致,其就是被控器,那么他就需要回复主控器应答信号(ACK),并开始向主控器发送数据;
4. 当主控收到被控器回复的ACK后就可以开始读取数据字节,一旦读取到一个字节就需要向从设备发送一个ACK;
5. 当被控器成功接收了主控器发送一个ACK信号,则继续发送字节给主设备;
6.一旦主控器完成全部数据接收,不发送ACK,直接发送停止信号并且释放总线。
时序模拟
使用CC2530中I/O端口模拟I2C总线时,最重要是对I2C总线时序的模拟,就是在作为SDA数据线和SCL时钟线的引脚上,根据I2C总线协议的规定,设置引脚的电平高低以及保持时长。下面的内容介绍了I2C协议中的几个时序和状态。
1、总线空闲状态
当I2C总线的SDA数据线和SCL时钟线都被拉高时,此时表示总线处于空闲状态。在空闲状态下,总线上的各个器件都处于截止状态,即释放总线,电平的上拉是由SDA和SCL各自的上拉电阻完成的。
2、启动信号
总线处于空闲状态时,SDA和SCL都为高电平,这时数据线SDA上的电平由高变低,即发生负跳变,定义这个时序信号为I2C总线的启动的信号,用它来标示一个数据传输过程的的开始。这里需要注意的是启动信号是电平跳变的时序信号,而不是电平信号,其必须要有一个动态的过程,不然不能标示开始。主控器在向总线发送启动信号,I2C总线必须处于空闲状态,不然这个信号不会作为一个启动信号处理。启动信号时序如下图所示。
3、停止信号
总线处于工作状态时,时钟线SCL处于高电平,释放数据线SDA,即SDA由低电平变为高电平,即发生正跳变,总线的两根信号线都为高电平,标示总线恢复到空闲状态,这个动态的过程就称为I2C总线的停止信号,它表示了一次数据传输过程的完成。停止信号时序如下图所示。
4、数据位传输
I2C总线上数据按位串行传输,每位数据的传输都受SCL时钟线电平的控制。在数据传输时,SCL时钟线必须为高电平,此时SDA上的电平必须保持稳定,不能改变,那么这一位数据才是有效的。数据发送是一连串的0和1,如果要对SDA的电平做改变,那么必须使SCL处于低电平状态,此时才能改变SDA上的电平。在发送完一个字节数据后,需要释放数据线。数据传输是边沿触发的。数据传输时序如下图所示。
5、应答信号
主控器成功发送一个字节数据后,就需要在第九个SCL释放数据线,即拉高数据线,等待被控器发送一个应答信号。如果数据线被拉低,说明被控器反馈了一个有效应答位ACK,说明被控器已经成功接收数据;如果数据线没有变化,还是高电平,那么为非应答位(NACK),说明接收器接收数据失败。如果是读操作,也就是接收方是主控器,在完成最后一个字节接收后,主控器需要向被控器发送一个NACK信号,来通知被控器结束发送和释放SDA,然后主控器发送结束信号,完成此次操作过程。ACK和NACK如下图所示。
由于CC2530的I/O端口具有方向性,所以在模拟I2C总线时序时,需要时时刻刻地根据是输出还是输入设置端口的方向寄存器,根据需要设置0还是1。延时的准确性对时序模拟的正确性起到很大的决定作用,在模拟的过程中使用了NOP指令来作为延时的最小时间单位。
Coding
i2c.h
#ifndef I2C_H
#define I2C_H
#include "ioCC2530.h"
#include "util.h"
#define SCL P1_0
#define SDA P2_0
void SCL_0();
void SCL_1();
void SDA_0();
void SDA_1();
void start();
void stop();
uint8 getAck();
void putAck(uint8 ack);
void init_i2c();
void i2c_writeByte(uint8 value);
uint8 i2c_readByte();
uint8 i2c_sendBytes(uint8 addr, uint8 data_addr, uint8 *value, uint8 len);
uint8 i2c_getBytes(uint8 addr, uint8 data_addr, uint8 *buffer, uint8 len);
#endif
i2c.c
#include "i2c.h"
void SCL_0()
P1DIR |= 0x01;
SCL = 0;
void SCL_1()
P1DIR |= 0x01;
SCL = 1;
void SDA_0()
P2DIR |= 0x01;
SDA = 0;
void SDA_1()
P2DIR |= 0x01;
SDA = 1;
uint8 readSDA()
//P2DIR &= 0xfe;
return SDA;
void start()
SDA_1();
delay_us(5);
SCL_1();
delay_us(5);
SDA_0();
delay_us(5);
SCL_0();
void stop()
SDA_0();
delay_us(5);
SCL_1();
delay_us(5);
SDA_1();
delay_us(5);
SCL_0();
uint8 getAck()
uint8 ack;
int i = 0;
P2DIR &= 0xfe;
SDA_1();
SCL_1();
delay_us(12);
while((SDA == 1) && (i < 200))
i++;
ack = readSDA();
SCL_0();
delay_us(12);
return ack;
void putAck(uint8 ack)
if (ack)
SDA_1();
else
SDA_0();
SCL_1();
delay_us(12);
SCL_0();
delay_us(12);
void init_i2c()
SCL_1();
delay_us(5);
SDA_1();
delay_us(12);
void i2c_writeByte(uint8 value)
uint8 i;
for (i = 0; i < 8; ++i)
/* code */
if (value & 0x80)
/* code */
SDA_1();
else
SDA_0();
value <<= 1;
delay_us(5);
SCL_1();
delay_us(5);
SCL_0();
uint8 i2c_readByte()
uint8 i;
uint8 temp;
SDA_1();
for (i = 0; i < 8; ++i)
/* code */
temp <<= 1;
SCL_1();
temp |= readSDA();
SCL_0();
delay_us(5);
return temp;
uint8 i2c_sendBytes(uint8 addr, uint8 data_addr,uint8 *value, uint8 len)
addr &= 0xfe;
start();
i2c_writeByte(addr);
if (getAck())
/* code */
stop();
return 1;
i2c_writeByte(data_addr);
if (getAck())
stop();
return 1;
for (int i = 0; i < len; ++i)
/* code */
i2c_writeByte(*value++);
if (getAck())
/* code */
stop();
return 1;
stop();
return 0;
uint8 i2c_getBytes(uint8 addr, uint8 data_addr, uint8 *buffer, uint8 len)
addr &= 0xfe;
start();
i2c_writeByte(addr);
if (getAck())
stop();
return 1;
i2c_writeByte(data_addr);
if (getAck())
stop();
return 1;
start();
addr |= 0x01;
i2c_writeByte(addr);
if (getAck())
stop();
return 1;
for (int i = 0; i < len; ++i)
/* code */
*buffer++ = i2c_readByte();
putAck(0);
putAck(1);
stop();
return 0;
以上是关于I2C总线协议的主要内容,如果未能解决你的问题,请参考以下文章