I2C总线协议

来源:互联网 发布:鹿晗性格知乎分析 编辑:程序博客网 时间:2024/06/11 21:15

毕业设计中使用到了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_0void 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;}
0 0
原创粉丝点击