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总线的停止信号,它表示了一次数据传输过程的完成。停止信号时序如下图所示。
I2C总线协议_第1张图片

4、数据位传输
I2C总线上数据按位串行传输,每位数据的传输都受SCL时钟线电平的控制。在数据传输时,SCL时钟线必须为高电平,此时SDA上的电平必须保持稳定,不能改变,那么这一位数据才是有效的。数据发送是一连串的0和1,如果要对SDA的电平做改变,那么必须使SCL处于低电平状态,此时才能改变SDA上的电平。在发送完一个字节数据后,需要释放数据线。数据传输是边沿触发的。数据传输时序如下图所示。
I2C总线协议_第2张图片
5、应答信号
主控器成功发送一个字节数据后,就需要在第九个SCL释放数据线,即拉高数据线,等待被控器发送一个应答信号。如果数据线被拉低,说明被控器反馈了一个有效应答位ACK,说明被控器已经成功接收数据;如果数据线没有变化,还是高电平,那么为非应答位(NACK),说明接收器接收数据失败。如果是读操作,也就是接收方是主控器,在完成最后一个字节接收后,主控器需要向被控器发送一个NACK信号,来通知被控器结束发送和释放SDA,然后主控器发送结束信号,完成此次操作过程。ACK和NACK如下图所示。
I2C总线协议_第3张图片
由于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;




}

你可能感兴趣的:(嵌入式,C/C++)