STM32F103通过IIC总线读取EEPROM

380 阅读4分钟

  小知识,大挑战!本文正在参与「程序员必备小知识」创作活动

  本文已参与 「掘力星计划」 ,赢取创作大礼包,挑战创作激励金。

  IIC总线是常用的串行总线,它只需要简单的两根线就可以实现数据的高速传输,同时还可以实现多机通信功能。

  在单片机中用的比较多的就是通过IIC总线操作EEPROM芯片。比较常用的EEPROM芯片就是24Cxx系列的芯片,主要用来存储系统运行过程中的关键数据。

image.png

要操作这个芯片的话,必须按照一定的时序去读写。这个时序通常被称为通信协议。24Cxx系列芯片通信协议如下。

I2 C 总线协议

I 2 C 总线协议定义如下:

  • 只有在总线空闲时才允许启动数据传送
  • 在数据传送过程中 当时钟线为高电平时 数据线必须保持稳定状态 不允许有跳变 时钟线为高电平时 数据线的任何电平变化将被看作总线的起始或停止信号。

image.png

起始信号

时钟线保持高电平期间 数据线电平从高到低的跳变作为 I 2 C 总线的起始信号

停止信号

时钟线保持高电平期间 数据线电平从低到高的跳变作为 I 2 C 总线的停止信号

image.png

image.png

image.png

image.png

这里就使用最简单的几个通信时序起始、停止、读、写和应答时序。

为了IIC协议的通用性,将这几个协议封装为一个c文件,这样以后每个使用IIC协议的器件都能调用这个文件。

//IIC产生起始信号
void IIC_Start(void)
{
    //START:when CLK is high,DATA change form high to low
    SDA_OUT();
    IIC_SDA = 1;
    IIC_SCL = 1;
    delay_us(4);
    IIC_SDA = 0;
    delay_us(4);
    IIC_SCL = 0;
}
//产生停止信号
void IIC_Stop(void)
{
    //STOP:when CLK is high DATA change form low to high
    SDA_OUT();
    IIC_SCL = 0;
    IIC_SDA = 0;
    delay_us(4);
    IIC_SCL = 1;
    IIC_SDA = 1;
    delay_us(4);
}
//等待应答信号
//返回值: 1 应答失败
//        0 应答成功
u8 IIC_Wait_Ack(void)
{
    u8 errTime = 0;
    SDA_IN();
    IIC_SDA = 1;
    delay_us(1);
    IIC_SCL = 1;
    delay_us(1);
    while(READ_SDA)
    {
        errTime++;
        if(errTime > 250)
        {
            IIC_Stop();
            return 1;
        }
    }
    IIC_SCL = 0;
    return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
    IIC_SCL = 0;
    SDA_OUT();
    IIC_SDA = 0;
    delay_us(2);
    IIC_SCL = 1;
    delay_us(2);
    IIC_SCL = 0;
}
//不产生应答
void IIC_NAck(void)
{
    IIC_SCL = 0;
    SDA_OUT();
    IIC_SDA = 1;
    delay_us(2);
    IIC_SCL = 1;
    delay_us(2);
    IIC_SCL = 0;
}
//IIC发送一个字节
void IIC_Send_Byte(u8 txd)
{
    u8 t;
    SDA_OUT();
    IIC_SCL = 0;
    for(t = 0; t < 8; t++)
    {
        if((txd & 0x80) >> 7)
            IIC_SDA = 1;
        else
            IIC_SDA = 0;
        txd <<= 1;
        delay_us(2);
        IIC_SCL = 1;
        delay_us(2);
        IIC_SCL = 0;
        delay_us(2);
    }
}
//读1个字节,ack=1时,发送ACK,ack=0,发送NACK
u8 IIC_Read_Byte(unsigned char ack)
{
    unsigned char i, receive = 0;
    SDA_IN();
    for(i = 0; i < 8; i++)
    {
        IIC_SCL = 0;
        delay_us(2);
        IIC_SCL = 1;
        receive <<= 1;
        if(READ_SDA)
            receive++;
        delay_us(1);
    }
    if(!ack)
        IIC_NAck();
    else
        IIC_Ack();
    return receive;
}

这里将最常用的几个信号封装为函数,当操作24Cxx芯片的时候,直接调用这几个函数。

#include "24Cxx.h"
#include "delay.h"

void AT24Cxx_Init(void)
{
    IIC_Init();
}
//在AT24CXX指定地址读出一个数据
//ReadAddr:开始读数的地址
//返回值  :读到的数据
u8 AT24Cxx_ReadOneByte(u16 ReadAddr)
{
    u8 temp = 0;
    IIC_Start();
    if(EE_TYPE > AT24C16)
    {
        IIC_Send_Byte(0xA0);			//发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(ReadAddr >> 8);	//发送高地址
        IIC_Wait_Ack();
    }
    else
        IIC_Send_Byte(0xA0 + ((ReadAddr / 256) << 1));	//发送器件地址0XA0,写数据

    IIC_Wait_Ack();
    IIC_Send_Byte(ReadAddr % 256);		//发送低地址
    IIC_Wait_Ack();
    IIC_Start();
    IIC_Send_Byte(0xA1);				//进入接收模式
    IIC_Wait_Ack();
    temp = IIC_Read_Byte(0);
    IIC_Stop();							//产生一个停止条件
    return temp;
}
//在AT24CXX指定地址写入一个数据
//WriteAddr  :写入数据的目的地址
//DataToWrite:要写入的数据
void AT24Cxx_WriteOneByte(u16 WriteAddr, u8 DataToWrite)
{
    IIC_Start();
    if(EE_TYPE > AT24C16)
    {
        IIC_Send_Byte(0xA0);			//发送写命令
        IIC_Wait_Ack();
        IIC_Send_Byte(WriteAddr >> 8);	//发送高地址
    }
    else
        IIC_Send_Byte(0xA0 + ((WriteAddr / 256) << 1));	//发送器件地址0XA0,写数据
    IIC_Wait_Ack();
    IIC_Send_Byte(WriteAddr % 256);
    IIC_Wait_Ack();
    IIC_Send_Byte(DataToWrite);
    IIC_Wait_Ack();
    IIC_Stop();
    delay_ms(10);
}
//在AT24CXX里面的指定地址开始写入长度为Len的数据
//该函数用于写入16bit或者32bit的数据.
//WriteAddr  :开始写入的地址
//DataToWrite:数据数组首地址
//Len        :要写入数据的长度2,4
void AT24Cxx_WriteLenByte(u16 WriteAddr, u32 DataToWrite, u8 Len)
{
    u8 t;
    for(t = 0; t < Len; t++)
    {
        AT24Cxx_WriteOneByte(WriteAddr + t, (DataToWrite >> (8 * t)) & 0xff);
    }
}
//在AT24CXX里面的指定地址开始读出长度为Len的数据
//该函数用于读出16bit或者32bit的数据.
//ReadAddr   :开始读出的地址
//返回值     :数据
//Len        :要读出数据的长度2,4
u32 AT24Cxx_ReadLenByte(u16 ReadAddr, u8 Len)
{
    u8 t;
    u32 temp = 0;
    for(t = 0; t < Len; t++)
    {
        temp <<= 8;
        temp += AT24Cxx_ReadOneByte(ReadAddr + Len - t - 1);
    }
    return temp;
}
//检查AT24CXX是否正常
//这里用了24XX的最后一个地址(255)来存储标志字.
//如果用其他24C系列,这个地址要修改
//返回1:检测失败
//返回0:检测成功
u8 AT24Cxx_Check(void)
{
    u8 temp;
    temp = AT24Cxx_ReadOneByte(255);
    if(temp == 0x55)
        return 0;
    else
    {
        AT24Cxx_WriteOneByte(1023, 0x55);
        temp = AT24Cxx_ReadOneByte(255);
        if(temp == 0x55)
            return 0;
    }
    return 1;
}

//在AT24CXX里面的指定地址开始读出指定个数的数据
//ReadAddr :开始读出的地址 对24c02为0~255
//pBuffer  :数据数组首地址
//NumToRead:要读出数据的个数
void AT24Cxx_Read(u16 ReadAddr, u8 *pBuffer, u16 NumToRead)
{
    while(NumToRead)
    {
        *pBuffer++ = AT24Cxx_ReadOneByte(ReadAddr++);
        NumToRead--;
    }
}
//在AT24CXX里面的指定地址开始写入指定个数的数据
//WriteAddr :开始写入的地址 对24c02为0~255
//pBuffer   :数据数组首地址
//NumToWrite:要写入数据的个数
void AT24Cxx_Write(u16 WriteAddr, u8 *pBuffer, u16 NumToWrite)
{
    while(NumToWrite--)
    {
        AT24Cxx_WriteOneByte(WriteAddr, *pBuffer);
        WriteAddr++;
        pBuffer++;
    }
}

这里就是对具体的芯片操作函数,在主函数中通过这几个函数就可读写EEPROM存储芯片的内容了。

int main(void)
{

    u8 key;
    u16 i = 0, j = 0;
    u8 datatemp[SIZE];

    delay_init();
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    uart_init(115200);
    LED_Init();
    KEY_Init();
   
    AT24Cxx_Init();

    printf("IIC test!!!\r\n");
    while(AT24Cxx_Check())
    {
        printf("The chip is not detected, please check whether the hardware connection is normal!!!\r\n");
        delay_ms(300);
        LED1 = !LED1;
    }

    while(1)
    {

        key = KEY_Sacn(0);
        if(key == WKUP_PRES)
        {
            printf("\r\nStart Write 24C02....\r\n");
            AT24Cxx_Write(0, (u8 *)TEXT_Buffer, SIZE);
            printf("24C02 Write Finished!\r\n");
        }
        else if(key == KEY1_PRES)
        {
            printf("\r\nStart Read 24C02....\r\n");
            AT24Cxx_Read(0, datatemp, SIZE);
            printf("The Data Readed Is:  ");
            for(j = 0; j < SIZE; j++)
            {
                printf("%c", datatemp[j]);
            }
        }

        i++;
        if(i == 20)
        {
            i = 0;
            LED0 = !LED0;
        }
        delay_ms(5);
    }
}

这里通过按键来测试存储芯片,一个按键按下后向芯片内写入数据,另一个按键按下后从芯片中读取刚才写入的内容。