stm32f103——IIC总线通信与EEPROM

IIC总线通信——集成电路总线


1》物理层


    1,IIC是一种简单的双向二线制串行通信的通信方式,SCL(时钟线),SDA(数据线)(可双向传输数据),注意:IIC是半双工同步通信协议。
    2,IIC是飞利浦公司开发总线协议。IIC是高位先行的通信方式(先发送高位)
    3,IIC可以实现多主机多从机通信,但是IIC没有片选信号。每一个设备都有唯一的ID,主机通过SDA数据线发送设备的地址来查找从机
    4,在IIC总线上必须接两个上拉电阻,用于将总线拉高
    5,IIC的SCL线的作用:用于控制SDA线上的数据的有效性----SCL为高电平时,SDA线上的数据有效。SCL为低电平时,SDA线上的数据无效
    6,当有多个设备同时发起IIC通信时,总线仲裁决定谁掌握总线的控制权,低电平优先原则
    7,IIC的通信速率,标准100kbit/s   快速  400kbit/s     高速3.4Mbit/s
    8,IIC的特点:具有应答机制-----相较于其他方式,更加稳定
         补充:接收方应答,发送放等待应答
    9,IIC通信规定,一个通信过程,必须以起始条件开始,以停止条件结束
   10,能够发起IIC通信的都是主机,时钟线在32中由主机控制


2》协议层


    起始条件:SCL为高电平期间,SDA由高电平跳变到低电平,产生一个有效的下降沿
    停止条件:SCL为高电平期间,SDA由低电平跳变到高电平,产生一个有效的上升沿
    应答信号:SCL为高电平期间,SDA输出一个低电平
    非应答信号:SCL为高电平期间,SDA输出一个高电平
    等待应答信号:拉高SCL线,等待SDA线上的高低电平
    发送数据信号:当SCL线为高电平时,SDA线上的数据是稳定的,当SCL为低电平时,允许SDA线上的数据发生改变
    接收数据信号:拉高SCL线,读取SDA线上的信号,拉低SCL线,使从机准备下一个数据

IIC与SPI的比较:

1,IIC的线数比SPI的少,布线方便,更适合多设备间的通信。

2,IIC的通信速率没有SPI的快。主要是因为SPI有片选信号,而 IIC没有。每当 IIC需要找到对应的从机时都需要SDA数据线发送从机的地址才能够找到从机设备。所以,SPI更适合高速通信,而IIC则是用于低速设备通信。

 为什么IIC的SDA线与SCL线上面需要上拉电阻?

        我们可以看到,在上图中数据线与时钟线都上拉了一个电阻。为什么要怎么做呢? 因为,IIC内部的SCL与SDA都被设计成开漏的,此时IIC如果想要输出高电平,那就必须外接一个上拉电阻,否则无法输出高电平。

那么为什么要设计成开漏呢? 在IIC内部直接上拉电阻不好吗?

       设计成开漏的原因是,如果IIC内部直接已经设计好上拉电阻了,那么当多个IIC器件在一起通信时,由于此时IIC是并联的,所以多个IIC器件在器件与器件之间内部的上拉电阻也就变成并联的了,而电阻的阻值是越并联越小。那么此时上拉电阻阻值变小,电源到IIC的电流就会变大,从而导致IIC器件功耗变大,甚至烧毁。所以,我们就将其设计为内部开漏,直接在外部接上拉电阻,这样的话就不会造成电阻由于并联导致电流变大了。外部的上拉电阻一般取值为1K-10K之间(一般是4.7k)上拉电阻阻值不能取太大也不能取太小。电阻阻值取太大,由于RC会导致通信的上升沿和下降沿变缓很多,影响通信速率。电阻阻值取太小,电阻阻值太小,导致电流太大,增加了IIC的功耗。

        而且上拉电阻不宜过小,还有一个原因,一般IO 端口的驱动能力在2mA~4mA量级。如果上拉电阻阻值过小,VDD灌入端口的电流将较大,这样会导致MOS管不完全导通,有饱和状态变成放大状态,这样端口输出的低电平值增大(I2C协议规定,端口输出低电平的最高允许值为0.4V),这样的话,当下拉的时候就无法到低电平了;如果灌入端口的电流过大,还可能损坏端口。故通常上拉电阻应选取不低于1KΩ的电阻(当VDD=3V时,灌入电流不超过3mA)。

        所以,如果有一个引脚是默认输出高电平,那么说明该引脚为上拉输出,即:该引脚内部很可能默认接了上拉电阻,如果此时再在该引脚外部接一个上拉电阻,那么外面的上拉电阻就会与内部的上拉电阻并联,由于电阻越并越小,那么电流就会变大,此时该引脚接的外设内部的mos管就会不饱和,导致无法下拉到低电平。此时,我们只需要撤除外部的上拉电阻即可。

        所以,具体还是要我们去实际通信速率来测试波形。IIC的通信速率,标准100kbit/s   快速  400kbit/s     高速3.4Mbit/s。

上拉电阻大小对i2c总线的影响_weixin_33963594的博客-CSDN博客

上拉电阻与下拉电阻详解_ch3rry的博客-CSDN博客

IIC总线最多可以挂多少个设备?


        由IIC地址决定,8位地址,减去1位广播地址,是7位地址,2^7=128,但是地址0x00不用,那就是127个地址, 所以理论上可以挂127个从器件。

        但是IIC协议没有规定总线上device最大数目,但是规定了总线电容不能超过400pF。管脚都是有输入电容的,PCB上也会有寄生电容,所以会有一个限制。实际设计中经验值大概是不超过8个器件

规定电容大小的原因:
        IIC的OD(漏极开路)要求外部有电阻上拉,电阻和总线电容产生了一个RC延时效应,电容越大信号的边沿就越缓,有可能带来信号质量风险。

        传输速度越快,信号的窗口就越小,上升沿下降沿时间要求更短更陡峭,所以RC乘积必须更小。

        原文链接:https://blog.csdn.net/qq_38769551/article/details/105108062

IIC仲裁机制:
    低电平优先原则:谁(SDA)先发出低电平,谁掌握总线,谁(SDA)的低电平长谁掌握总线。注意:当有多个主机存在的时候,才会出现仲裁机制,一般我们都是一个主机。

IIC时序图

IIC的实现:
    1》使用片上外设自动生成IIC(即:由硬件外设自动生成IIC时序,不占用CPU的资源)
    2》使用GPIO来模拟IIC时序(用GPIO模拟,占用CPU的资源)

    

    起始条件:SCL为高电平期间,SDA由高电平跳变到低电平,产生一个有效的下降沿
    停止条件:SCL为高电平期间,SDA由低电平跳变到高电平,产生一个有效的上升沿

注意:在SCL为低电平的时候,才允许SDA发生改变。这是因为,当SCL为低电平的时候,SDA的一切电平状态都会被视为无效电平,此时改变SDA不会对其他的任何设备产生影响。然后我们等SDA的电平稳定了以后,再拉高SCL,那么此时SDA的这个稳定后的电平状态就会被视为有效的电平。

代码:

// 起始条件:SCL为高电平期间,SDA由高电平跳变到低电平,产生一个有效的下降沿
void IIC_START(void)        //用I/O口模拟IIC的通信
{   
    SDA_HIGH;        //拉高SDA
    SCL_HIGH;        //拉高SCL
    SDA_LOW;        //拉低SCL
}

根据上图,起始条件:SCL为高电平期间,SDA由高电平跳变到低电平。一开始我们并不知道SDA和SCL是高电平还是低电平,所以我们先拉高SDA和SCL,然后再将SDA拉低就行了。

乍一看,这没什么问题。但是,第一步先执行了SDA_HIGH,那么如果要是一开始,SCL是高电平而SDA是低电平呢?此时SDA由低拉高,SCL又是高电平。此时产生的就是一个有效的停止信号,而不是一个起始信号了。而且,我们还可以发现,这个函数里面没有对GPIO进行模式配置,此时我们也不知道GPIO是输入模式还是输出模式。

所以,我们需要先将SCL拉低,让所有的操作信号都变为无效信号,那么后面的操作就无法影响到其他的任何设备了。然后我们再配置GPIO的模式,然后再将SDA拉高(此时由于SCL是低电平,所有这里拉高SDA后,它不会被认为是一个有效的停止信号)。然后我们再将SCL拉高,由于SCL是高电平,那么此后的操作都会被认为是有效信号。然后我们再拉低SDA,此时SCL是高电平,SDA由高变为低,此时就会被认为是有效的起始信号了。修改如下:

void IIC_START(void)        //用I/O口模拟IIC的通信
{
    SCL_LOW;               //拉低SCL,让后面的操作无效
    IIC_SDA_OUT();        /*配置GPIO为开漏输出模式。注意: IIC_SDA_OUT(); 一定要写在 SCL_LOW的后面,否则如果一开始SCL是高电平,SDA是低电平,那么此时设置GPIO为输出模式,而此时恰好GPIO输出寄存器内是一个1,那么此时GPIO就会输出一个高电平,那么此时SDA就会变为高电平了,由于此时SCL是高电平,这里就会产生一个有效的停止信号,这是我们不希望的。*/
    
    SDA_HIGH;        //拉高SDA
    SCL_HIGH;        //拉高SCL,让后面的操作有效
    Systick_NusDelay(5);        //拉高SDA后,需要等待SDA上面的电平稳定后,再拉低SDA来产生一个有效的起始信号
    
    SDA_LOW;        //拉低SDA,此时产生有效的起始信号
    Systick_NusDelay(5);        //拉低SDA后,还需要等待SDA电平稳定

    SCL_LOW;        //拉低SCL,此时将IIC总线的传输数据的权利牢牢掌握在自己的手中,让其他设备无法再来抢夺IIC总线。
}

疑问:

等待SDA上面的电平稳定后,再拉低SDA来产生一个有效的起始信号,需要等待多久长时间,才能再拉低SDA?

 芯片手册上面有标识,我们拉高SDA后需要等待4.7uS以上,才能再拉低SDA。然后,拉低SDA后,还需要再等待4uS,才能再进行其他的操作。   这里实际上就是在设置IIC中SDA与SCL的最大频率了。

 // 停止条件:SCL为高电平期间,SDA由低电平跳变到高电平,产生一个有效的上升沿
void IIC_STOP(void)
{
    SCL_LOW;
    IIC_SDA_OUT();

    SDA_LOW;
    SCL_HIGH;
    Systick_NusDelay(5);

    SDA_HIGH;
    Systick_NusDelay(5);
}

疑问:

为什么停止信号函数的最后没有加上,SCL_LOW来掌控IIC的主控权?

因为停止信号本身表达的就是放弃IIC的主控权,所以不需要去掌控IIC的主控权。

  应答信号:SCL为高电平期间,SDA输出一个低电平(接收方接收到数据的时候,反馈给发送方一个应答信号,表示接收到数据)

  注意:主机即可以作为发送方也可以做为接收方,从机亦是如此。

  非应答信号:SCL为高电平期间,SDA输出一个高电平。有时。主机会向从机发送一个非应答信号。

// 应答信号:SCL为高电平期间,SDA输出一个低电平。给发送方发送一个应答信号。
void IIC_SendACK(void)
{
    SCL_LOW;         //拉低SCL,让后面的操作无效
    IIC_SDA_OUT();

    SDA_LOW;        //拉低SDA,产生应答信号
    SCL_HIGH;         //拉高SCL,上面的SDA的电平变为有效电平
    Systick_NusDelay(5);

    SCL_LOW;        //拉低SCL,此时将IIC总线的传输数据的权利牢牢掌握在自己的手中,让其他设备无法再来抢夺IIC总线。
}

// 非应答信号:SCL为高电平期间,SDA输出一个高电平
void IIC_SendNoACK(void)
{
    SCL_LOW;        //拉低SCL,让后面的操作无效
    IIC_SDA_OUT();

    SDA_HIGH;
    SCL_HIGH;        //拉高SCL,上面的SDA的电平变为有效电平
    Systick_NusDelay(5);

    SCL_LOW;            //拉低SCL,此时将IIC总线的传输数据的权利牢牢掌握在自己的手中,让其他设备无法再来抢夺IIC总线。
}

// 等待应答信号:拉高SCL线,等待SDA线上的高低电平。等待接收方反馈一个应答信号过来。
uint8_t IIC_WaitACK(void)
{
    uint8_t temp=0;
    SCL_LOW;               //拉低SCL,让后面的操作无效
    IIC_SDA_IN();         //配置GPIO为输入模式。

    SCL_HIGH;             //拉高SCL,让后面读取到的SDA的数据有效
    Systick_NusDelay(4);
    while(SDA_READ){        /*等待读取SDA的数据,如果读到的是非应答信号1,那么这里就进入循环内部,如果循环读取250次,还没有读到应答信号0,那么就返回NOACK(非应答)。如果读到应答信号0,那么就不再进入while循环内,然后拉低SCL,并返回ACK(应答信号)*/
        temp++;
        if(temp>250){        //超时等待,读取SDA数据线250次
            return NOACK;
        }
    }
    SCL_LOW;            
    return ACK;        //返回应答信号
}

// 发送数据信号:当SCL线为高电平时,SDA线上的数据是稳定的,当SCL为低电平时,允许SDA线上的数据发生改变
void IIC_SendByteData(uint8_t data)        //将要发送的数据作为参数填入
{
    uint8_t i;
    SCL_LOW;
    IIC_SDA_OUT();

    for(i=0;i<8;i++){          //循环8次
        if(data&0x80){        //IIC通讯是高位先行,所以先发送的是最高位。所以用data & 1000 0000来判断data的最高位是什么
            SDA_HIGH;        //如果data最高位是1,那么SDA就为高电平
        }else{
            SDA_LOW;        //如果data最高位是0,那么SDA就为低电平
        }
        SCL_HIGH;        //拉高SCL,发送上面的SDA电平
        Systick_NusDelay(5);
        SCL_LOW;        //拉低SCL,此时将IIC总线的传输数据的权利牢牢掌握在自己的手中,让其他设备无法再来抢夺IIC总线。
        Systick_NusDelay(5);
        data=data<<1;        //将data左移一位,来判断data的次高位
    }
    SCL_LOW;
}

// 接收数据信号:拉高SCL线,读取SDA线上的信号,拉低SCL线,使从机准备下一个数据
uint8_t IIC_RxByteData(uint8_t ackflag)        /*ackflag可以填应答信号,也可以填非应答信号。如果这里填应答信号,那么表示接收完数据后,返回一个应答信号回去。如果这里填非应答信号,那么表示接收完数据后,返回一个非应答信号回去*/
{
    uint8_t i;
    uint8_t Rxdata=0;        // Rxdata是一个8bit的数值。
    SCL_LOW;
    IIC_SDA_IN();
    
    for(i=0;i<8;i++){        //循环8次
        SCL_HIGH;        //拉高SCL,让后面接收到的SDA都是有效数据
        Systick_NusDelay(5);
        Rxdata=Rxdata<<1;        /*注意:  Rxdata=Rxdata<<1; 不能放在下面,只能放在这里,因为for循环是循环8次,放在下面,最后一次左移,会将读到的最高位给移除掉*/
        if(SDA_READ){        /*读取SDA数据,如果读到的是1,那么使用Rxdata|0x01将Rxdata的最低位置1。如果读到的是0,那么Rxdata本身就是0。所以不需要清0。*/
            Rxdata=Rxdata|0x01;
        }
        SCL_LOW;
        Systick_NusDelay(5);
    }

    if(ACK==ackflag){        //如果ackflag为应答信号,那么表示接收完数据后,发送一个应答信号回去。如果ackflag为非应答信号,那么表示接收完数据后,发送一个非应答信号回去
        IIC_SendACK();
    }else{
        IIC_SendNoACK();
    }

    SCL_LOW;
    return Rxdata;
}

IIC完整代码

头文件代码:

#ifndef __DRV_IIC_H__
#define __DRV_IIC_H__

#include "stm32f10x.h"
#include "drv_systick.h"

#define SCL_HIGH   GPIO_SetBits(GPIOB,GPIO_Pin_6)
#define SCL_LOW    GPIO_ResetBits(GPIOB,GPIO_Pin_6)

#define SDA_HIGH   GPIO_SetBits(GPIOB,GPIO_Pin_7)
#define SDA_LOW    GPIO_ResetBits(GPIOB,GPIO_Pin_7)
#define SDA_READ    GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)


#define ACK  0
#define NOACK 1

void IIC_Config(void);
void IIC_SDA_OUT(void);
void IIC_SDA_IN(void);
void IIC_START(void);
void IIC_STOP(void);
void IIC_SendACK(void);
void IIC_SendNoACK(void);
uint8_t IIC_WaitACK(void);
void IIC_SendByteData(uint8_t data);
uint8_t IIC_RxByteData(uint8_t ackflag);


#endif //__DRV_IIC_H__
 

IIC驱动代码

#include "drv_iic.h"
void IIC_Config(void)        //初始化SCL
{
     GPIO_InitTypeDef GPIO_InitStruct;
    // 1,打开时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);

    // 2,初始化GPIO
    GPIO_InitStruct.GPIO_Pin    =GPIO_Pin_6;     
    GPIO_InitStruct.GPIO_Mode   =GPIO_Mode_Out_OD;
    GPIO_InitStruct.GPIO_Speed  =GPIO_Speed_10MHz;        //这里实际上就是在设置SCL的上升沿和下降沿的时间
    GPIO_Init(GPIOB,&GPIO_InitStruct);    
}

void IIC_SDA_OUT(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin    =GPIO_Pin_7;//SDA
    GPIO_InitStruct.GPIO_Mode   =GPIO_Mode_Out_OD;
    GPIO_InitStruct.GPIO_Speed  =GPIO_Speed_10MHz;        //这里实际上就是在设置SDA的上升沿和下降沿时间
    GPIO_Init(GPIOB,&GPIO_InitStruct);
}

void IIC_SDA_IN(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.GPIO_Pin    =GPIO_Pin_7;        //SDA
    GPIO_InitStruct.GPIO_Mode   =GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB,&GPIO_InitStruct);
}

// 起始条件:SCL为高电平期间,SDA由高电平跳变到低电平,产生一个有效的下降沿
void IIC_START(void)
{
    SCL_LOW;
    IIC_SDA_OUT();
    
    SDA_HIGH;
    SCL_HIGH;
    Systick_NusDelay(5);
    
    SDA_LOW;
    Systick_NusDelay(5);

    SCL_LOW;
}


// 停止条件:SCL为高电平期间,SDA由低电平跳变到高电平,产生一个有效的上升沿
void IIC_STOP(void)
{
    SCL_LOW;
    IIC_SDA_OUT();

    SDA_LOW;
    SCL_HIGH;
    Systick_NusDelay(5);

    SDA_HIGH;
    Systick_NusDelay(5);
}


// 应答信号:SCL为高电平期间,SDA输出一个低电平
void IIC_SendACK(void)
{
    SCL_LOW;
    IIC_SDA_OUT();

    SDA_LOW;
    SCL_HIGH;
    Systick_NusDelay(5);

    SCL_LOW;
}


// 非应答信号:SCL为高电平期间,SDA输出一个高电平
void IIC_SendNoACK(void)
{
    SCL_LOW;
    IIC_SDA_OUT();

    SDA_HIGH;
    SCL_HIGH;
    Systick_NusDelay(5);

    SCL_LOW;    
}

// 等待应答信号:拉高SCL线,等待SDA线上的高低电平
uint8_t IIC_WaitACK(void)
{
    uint8_t temp=0;
    SCL_LOW;
    IIC_SDA_IN();

    SCL_HIGH;
    Systick_NusDelay(4);
    while(SDA_READ){
        temp++;
        if(temp>250){
            return NOACK;
        }
    }
    SCL_LOW;    
    return ACK;
}

// 发送数据信号:当SCL线为高电平时,SDA线上的数据是稳定的,当SCL为低电平时,允许SDA线上的数据发生改变
void IIC_SendByteData(uint8_t data)
{
    uint8_t i;
    SCL_LOW;
    IIC_SDA_OUT();

    for(i=0;i<8;i++){
        if(data&0x80){
            SDA_HIGH;
        }else{
            SDA_LOW;
        }
        SCL_HIGH;
        Systick_NusDelay(5);
        SCL_LOW;
        Systick_NusDelay(5);
        data=data<<1;
    }
    SCL_LOW;
}

// 接收数据信号:拉高SCL线,读取SDA线上的信号,拉低SCL线,使从机准备下一个数据
uint8_t IIC_RxByteData(uint8_t ackflag)
{
    uint8_t i;
    uint8_t Rxdata=0;
    SCL_LOW;
    IIC_SDA_IN();
    
    for(i=0;i<8;i++){
        SCL_HIGH;
        Systick_NusDelay(5);
        Rxdata=Rxdata<<1;
        if(SDA_READ){
            Rxdata=Rxdata|0x01;
        }
        SCL_LOW;
        Systick_NusDelay(5);
    }

    if(ACK==ackflag){
        IIC_SendACK();
    }else{
        IIC_SendNoACK();
    }

    SCL_LOW;
    return Rxdata;
}

IIC驱动EEPROM

EEPROM 电可擦除可改写存储器  
特点:具有掉电保护的功能

我们经常将设置好的参数、密码保存在EEPROM中,当单片机再次上电时,我们的再将我们之前设置的参数、密码从EEPROM中取出,那么这样的话,我们就不需要再次去花费时间去再设置一遍参数和密码了。

那么,我们怎么将数据存储到EEPROM中去呢?我们可以使用IIC总线。

 图中的AT24C02就是EEPROM器件,单片机运行程序,通过IIC总线将数据存储到EEPROM中去。

我们来看看EEPROM的规格书:

 TWO-wire Serial EEPROM:两线串行的EEPROM。其中,两线:即是IIC的两线,串行:串行通信。

 

 描述:

AT24C01A/02/04/08A/16A提供1024/2048/4096/8192/16384位串口电可擦除和可编程只读存储器(EEPROM)组织为128/256/512/1024/2048个8位一字节。该设备经过优化,适用于许多工业和商业应用,其中低功率和低电压操作是必不可少的。AT24C01A/02/04/08A/16A可提供节省空间的8引脚PDIP, 8引脚JEDEC SOIC, 8引脚MAP, 5引脚SOT23(AT24C01A/AT24C02/AT24C04), 8引脚TSSOP和8球dBGA2封装,通过两线串行接口访问。此外,整个系列还有2.7V (2.7V至5.5V)和1.8V (1.8V至5.5V)版本。

A0-A2引脚是地址的输入引脚,也是设备ID的输入

SDA:串行的数据线

SCL:串行的时钟线

WP:写保护引脚。一般是低电平有效,当写保护有效的时候,就不能往里面写数据了,就只能读取数据了。

 这个是设备的ID(即:从设备地址),我们可以看到不同容量的存储设备,它们的ID的高4个位都是1010开头,只有A2、A1、A0是设备的ID,最后一位R/W是读/写位。所以,表达地址的就只有7位,即:1010 A2 A1 A0

 我们可以看见A0、A1、A2(NC)都是接的地,所以它们都是0。所以,该从设备地址是101 0000,即0101 0000(0x50)。再加上一个读/写位,如下:

读数据操作:1010 0001,即0xa1

写数据操作:1010 0000,即0xa0

注意:从设备地址依然是0101 0000(0x50),只不过在读写数据的时候,会将从设备地址和读写位一起发送出去。

主芯片向EEPROM写一个字节数据

 

word address:字地址。表示EEPROM中的内存地址。发送给EEPROM的数据,需要存储在EEPROM中的哪个地址上去。

注意:读/写位:读操作为高电平,写操作为低电平。

注意:图中的写数据操作是主芯片向EEPROM写数据,然后主芯片等待EEPROM返回一个应答信号。

写数据代码

void AT24Cxx_WriteByteData(uint8_t ADDR,uint8_t DATA)        //ADDR表示EEPROM中存储数据的地址,DATA表示要写的数据
{
    IIC_START();        //发送起始信号

    IIC_SendByteData(0xa0);        //发送从设备ID,并设置为写操作
    if(ACK!=IIC_WaitACK()){         //等待从设备返回应答信号
        IIC_STOP();                        //如果没有返回应答信号,则停止传输数据
        return;
    }

    IIC_SendByteData(ADDR);        //发送字地址
    if(ACK!=IIC_WaitACK()){           //等待从设备返回应答信号  
        IIC_STOP();                          //如果没有返回应答信号,则停止传输数据
        return;
    }

    IIC_SendByteData(DATA);             //开始发送数据
    if(ACK!=IIC_WaitACK()){                //等待从设备返回应答信号 
        IIC_STOP();                                //如果没有返回应答信号,则停止传输数据
        return;
    }

    IIC_STOP();        //停止信号
}

主芯片从EEPROM中读取一个字节数据

 读数据操作与写数据操作不同。首先,主芯片需要进行写操作,向EEPROM写入需要读的是EEPROM中哪个地址上面的数据,然后主芯片再进行读操作,读取EEPROM中该地址上面的数据。读完数据后,主芯片再向EEPROM发送一个非应答信号,表示读完数据了,此时EEPROM不用再发送数据给主芯片了。

读数据代码

uint8_t AT24Cxx_ReadByteData(uint8_t ADDR)        // ADDR表示EEPROM中存储数据的地址
{
    uint8_t Rxdata=0;
    IIC_START();                         //起始信号

    IIC_SendByteData(0xa0);       //先进行写操作
    if(ACK!=IIC_WaitACK()){        //等待从设备返回应答信号
        IIC_STOP();
        return 0;
    }

    IIC_SendByteData(ADDR);        //需要读取的是EEPROM中哪个地址上面的数据
    if(ACK!=IIC_WaitACK()){
        IIC_STOP();
        return 0;
    }

    IIC_START();                                //起始信号
    
    IIC_SendByteData(0xa1);            //进行读操作
    if(ACK!=IIC_WaitACK()){
        IIC_STOP();
        return 0;
    }

    Rxdata=IIC_RxByteData(NOACK);        //开始读数据。并且在读完数据后,主芯片向EEPROM发送一个非应答信号,表示读完数据了

    IIC_STOP();                        //停止信号
    return Rxdata;                     //返回读到的数据
}

完整代码

#include "drv_at24cxx.h"
void AT24Cxx_WriteByteData(uint8_t ADDR,uint8_t DATA)
{
    IIC_START();

    IIC_SendByteData(0xa0);
    if(ACK!=IIC_WaitACK()){
        IIC_STOP();
        return;
    }

    IIC_SendByteData(ADDR);
    if(ACK!=IIC_WaitACK()){
        IIC_STOP();
        return;
    }

    IIC_SendByteData(DATA);    
    if(ACK!=IIC_WaitACK()){
        IIC_STOP();
        return;
    }

    IIC_STOP();
}


uint8_t AT24Cxx_ReadByteData(uint8_t ADDR)
{
    uint8_t Rxdata=0;
    IIC_START();

    IIC_SendByteData(0xa0);
    if(ACK!=IIC_WaitACK()){
        IIC_STOP();
        return 0;
    }

    IIC_SendByteData(ADDR);
    if(ACK!=IIC_WaitACK()){
        IIC_STOP();
        return 0;
    }

    IIC_START();
    
    IIC_SendByteData(0xa1);
    if(ACK!=IIC_WaitACK()){
        IIC_STOP();
        return 0;
    }

    Rxdata=IIC_RxByteData(NOACK);

    IIC_STOP();
    return Rxdata;
}


#include "drv_systick.h"
#include "stdio.h"
#include "drv_exti.h"
#include "drv_basetim.h"
#include "drv_iic.h"
#include "drv_at24cxx.h"

int main(void)
{
    RCC_DeInit();
    RCC_ConfigTo72M();
    Systick_Config(72);
    IIC_Config();

    AT24Cxx_WriteByteData(0x55,0x80);        //向EEPROM中的0x55地址,写入数据0x80
    Systick_NmsDelay(2000);
    printf("AT24Cxx(0x55)=%x\n",AT24Cxx_ReadByteData(0x55));        //读取EEPROM中0x55地址中存储的数据,并将其用串口打印出来
    while(1){}
}


 


 

  • 7
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值