I2C总线与E²PROM

目录

概述

特点

类别

用途

I2C时序

I2C寻址模式

24C01(E²PROM器件)

E²PROM写数据流程:

E²PROM读数据流程:

E²PROM的页写入

按时序写的程序

多字节读写程序

按页写程序


概述

特点

I2C总线的主要特点是:两条线可以挂多个参与通信的器件,即多机模式,而且任何一个器件都可以作为主机,当然同一时刻只能有一个主机。

类别

I2C属于同步通信,SCL时钟线负责收发双方的时钟节拍,SDA数据线负责传输数据。

I2C的发送方和接收方都以SCL这个时钟节拍为基准进行数据的发送和接收

半双工通信

用途

I2C多用于板内通信

I2C:通信协议

E²PROM:器件

I2C时序

I2C总线是由始终总线SCL和数据总线SDA两条线构成。连接到总线上的所有器件的SCL都连到一起,所有SDA都连到一起。

I2C总线是开漏引脚并联的结构,因此外部需要添加上拉电阻。添加上拉电阻后,所有线就组成了线“与”的关系,即所有接入的器件均保持高电平,这条线才是高电平;任意一个器件都可以拉低电平,也就是任何一个器件都可以作为主机。

 I2C中有起始信号、数据传输和停止信号。其中数据传输部分可以一次通信过程传输很多个字节,字节数是不受限制的,而每个字节后都跟一位,答应位,通常用ACK表示。

I2C通信,必须两条线都参与工作才能完成

起始信号:

SCL为高电平期间,SDA由高电平向低电平变化产生一个下降沿,表示起始信号

数据传输:

I2C通信是高位在前、低位在后。

I2C没有固定波特率,但是有时序要求。要求当SCL在低电平的时候,SDA允许变化。当SCL是高电平的时候,SDA绝对不可以变化,因为此时接收方要读取当前SDA的电平信号是0还是1,必须保证SDA的稳定。

停止信号:

SCL为高电平期间,SDA由低电平向高电平变化产生一个上升沿,表示结束信号。

I2C寻址模式

I2C通信在字节级的传输中也有固定的时序要求。

I2C在起始信号(Start)后,首先要发送一个从机地址,这个地址一共有7位;紧跟着第8位,数据方向位(R/W),“0”表示接下来要发送数据(写),“1”表示接下来是请求数据(读)。

若是发送的地址存在,从机SDA输出“0”;如果不存在,从机SDA输出“1”,即应答信号ACK

主机向从机发送数据,数据传输方向在整个过程中不变:

S

从机地址

0

A

数据

A

数据

A/A非

P

主机在第一个字节后,立即从从机读取数据:

S

从机地址

1

A

数据

A

数据

A/A非

P

在传输过程中,当需要改变方向时,起始信号和从机地址都被重复产生一次,但两次读/写方向位正好相反:

S

从机地址

0

A

数据

A/A非

S

从机地址

1

A

数据

A非

P

24C01(E²PROM器件)

24C02和24C01原理图完全一样

24C02一共256个字节的存储空间,地址从0x00~0xff

24C02的七位地址中,其中高4位是固定的0b1010,

低3位的地址取决于电路设计,由A0、A1、A2的实际电平决定

E²PROM写数据流程:

第一步:首先是I2C的起始信号

紧接着跟上I2C的器件地址

在读写方向上选择“写”操作

第二步:发送数据的存储地址

第三步:发送要存储的数据,每个字节都会回应一个“应答位0”,来告诉我们写E²PROM数据成功,若是没有应答位,说明写入不成功。

注:每成功写入一个字节,E²PROM存储空间的地址都会自动加1,当加到0xff,再加则会溢出,变为0x00。

E²PROM读数据流程:

第一步:首先是I2C的起始信号

紧接着跟上I2C的器件地址

在读写方向上选择“写”操作

第二步:发送要读取的数据的地址

第三步:再次发送I2C的起始信号

紧接着跟上I2C的器件地址

在读写方向上选择“读”操作

第四步:读取完一个字节,如果想要接着读取,就发送一个“应答位ACK(0)”,若是不想读了,就发送一个“非应答位NAK(1)”

注:无论是读还是写,SCL始终都是由主机控制的

写的时候,应答信号由从机发出,表示从机是否正确的接收了数据

读的时候,应答信号由主机发出,表示是否继续读下去

E²PROM的页写入

由于一个字节一个字节的写效率过低,所以厂商就对E²PROM进行了分页管理

24C01、24C02这两个型号是8个字节一个页

24C04、24C08、24C16是16个字节一个页

分配好页之后,就可以一次性的把一页的数据写入非易失区域,然后再发送一个停止位,再进行空闲检测,发送下一页。

按时序写的程序

#include <reg52.h>
#include <intrins.h>

#define I2CDelay()  {_nop_();_nop_();_nop_();_nop_();}
sbit I2C_SCL = P3^7;
sbit I2C_SDA = P3^6;

/* 产生总线起始信号 */
void I2CStart()
{
    I2C_SDA = 1; //首先确保SDA、SCL都是高电平
    I2C_SCL = 1;
    I2CDelay();
    I2C_SDA = 0; //先拉低SDA
    I2CDelay();
    I2C_SCL = 0; //再拉低SCL
}
/* 产生总线停止信号 */
void I2CStop()
{
    I2C_SCL = 0; //首先确保SDA、SCL都是低电平
    I2C_SDA = 0;
    I2CDelay();
    I2C_SCL = 1; //先拉高SCL
    I2CDelay();
    I2C_SDA = 1; //再拉高SDA
    I2CDelay();
}
/* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
bit I2CWrite(unsigned char dat)
{
    bit ack;  //用于暂存应答位的值
    unsigned char mask;  //用于探测字节内某一位值的掩码变量

    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        if ((mask&dat) == 0)  //该位的值输出到SDA上
            I2C_SDA = 0;
        else
            I2C_SDA = 1;
        I2CDelay();
        I2C_SCL = 1;          //拉高SCL
        I2CDelay();
        I2C_SCL = 0;          //再拉低SCL,完成一个位周期
    }
    I2C_SDA = 1;   //8位数据发送完后,主机释放SDA,以检测从机应答
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    ack = I2C_SDA; //读取此时的SDA值,即为从机的应答值
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线

    return (~ack); //应答值取反以符合通常的逻辑:
                   //0=不存在或忙或写入失败,1=存在且空闲或写入成功
}
/* I2C总线读操作,并发送非应答信号,返回值-读到的字节 */
unsigned char I2CReadNAK()
{
    unsigned char mask;
    unsigned char dat;

    I2C_SDA = 1;  //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 1;   //8位数据发送完后,拉高SDA,发送非应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成非应答位,并保持住总线

    return dat;
}
/* I2C总线读操作,并发送应答信号,返回值-读到的字节 */
unsigned char I2CReadACK()
{
    unsigned char mask;
    unsigned char dat;

    I2C_SDA = 1;  //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 0;   //8位数据发送完后,拉低SDA,发送应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线

    return dat;
}

按照E²PROM读写流程写的程序

/* 读取EEPROM中的一个字节,addr-字节地址 */
unsigned char E2ReadByte(unsigned char addr)
{
    unsigned char dat;
    
    I2CStart();
    I2CWrite(0x50<<1); //寻址器件,后续为写操作
    I2CWrite(addr);    //写入存储地址
    I2CStart();        //发送重复启动信号
    I2CWrite((0x50<<1)|0x01); //寻址器件,后续为读操作
    dat = I2CReadNAK();       //读取一个字节数据
    I2CStop();
    
    return dat;
}
/* 向EEPROM中写入一个字节,addr-字节地址 */
void E2WriteByte(unsigned char addr, unsigned char dat)
{
    I2CStart();
    I2CWrite(0x50<<1); //寻址器件,后续为写操作
    I2CWrite(addr);    //写入存储地址
    I2CWrite(dat);     //写入一个字节数据
    I2CStop();
}

多字节读写程序

/* E2读取函数,buf-数据接收指针,addr-E2中的起始地址,len-读取长度 */
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
{
    do {                       //用寻址操作查询当前是否可进行读写操作
        I2CStart();
        if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
        {
            break;
        }
        I2CStop();
    } while(1);
    I2CWrite(addr);            //写入起始地址
    I2CStart();                //发送重复启动信号
    I2CWrite((0x50<<1)|0x01);  //寻址器件,后续为读操作
    while (len > 1)            //连续读取len-1个字节
    {
        *buf++ = I2CReadACK(); //最后字节之前为读取操作+应答
        len--;
    }
    *buf = I2CReadNAK();       //最后一个字节为读取操作+非应答
    I2CStop();
}
/* E2写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度 */
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
    while (len--)
    {
        do {                       //用寻址操作查询当前是否可进行读写操作
            I2CStart();
            if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
            {
                break;
            }
            I2CStop();
        } while(1);
        I2CWrite(addr++);  //写入起始地址
        I2CWrite(*buf++);  //写入一个字节数据
        I2CStop();         //结束写操作,以等待写入完成
    }
}

按页写程序

读的程序没有改变,依旧是一个字节一个字节的读取

/* E2写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度 */
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
    while (len > 0)
    {
        //等待上次写入操作完成
        do {                       //用寻址操作查询当前是否可进行读写操作
            I2CStart();
            if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
            {
                break;
            }
            I2CStop();
        } while(1);
        //按页写模式连续写入字节
        I2CWrite(addr);           //写入起始地址
        while (len > 0)
        {
            I2CWrite(*buf++);     //写入一个字节数据
            len--;                //待写入长度计数递减
            addr++;               //E2地址递增
            if ((addr&0x07) == 0) //检查地址是否到达页边界,24C02每页8字节,
            {                     //所以检测低3位是否为零即可
                break;            //到达页边界时,跳出循环,结束本次写操作
            }
        }
        I2CStop();
    }
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值