STM32 I2C AT24C02(E2PROM)

137 阅读6分钟

环境

  • MCU型号:STM32F103ZET6
  • 开发方式:库函数

一、I2C总线介绍

I2C通讯协议(Inter - Integrated Circuit)是由Phiilps公司开发,由于它引脚少,硬件简单实现,可扩展性强,不需要USART、CAN等通讯协议的外部收发设备,被广泛使用在系统内多个集成电路间通讯。

  1. I2C总线结构
  • I2C是由2根双向总线组成
    • SCL 串行时钟线,由主设备产生,控制数据传输的时序。
    • SDA 串行数据线,用于传输数据。

2.I2C总线特点

  • 简单性:仅需要两根线即可实现通讯,结构简单,易于实现
  • 多主设备:I2C总线可以连接多个主设备,但同一时刻只能由一个主设备控制总线
  • 多从设备:I2C总线可以连接多个从设备,每个从设备都有一个唯一的地址
  • 低速:I2C总线传输速率较低,一般用于短距离低速数据传输
  • 开漏输出:I2C总线的设备都采用开漏输出,通过上拉电阻将信号拉高

3.I2C总线通讯过程

image.png

1.起始信号:主设备拉低SDA线,然后拉低SCL线,产生起始信号,表示开始一次通讯。 2.设备地址:主设备发送7位或10位从设备地址,以及读/写位。 3.数据传输: 主设备和从设备之间按照字节为单位进行传输。 4.停止信号:主设备拉高SCL线,然后拉高SDA,产生停止信号,表示一次通讯结束。

  1. I2C总线应用
  • 传感器:温湿度传感器,光传感器,加速度传感器等。
  • 储存器:E2PROM,实时时钟等。
  • 显示器:LCD、OLED等。
  • 音频设备:CODEC等。

5.I2C总线注意事项

  • 上拉电阻:I2C总线需要连接上拉电阻,保证SCL和SDA在空闲状态下保持高电平
  • 时序:I2C通讯对时序要求高,需要严格按照时序操作。
  • 总线负载:I2C总线上的设备数量和总线长度会影响总线的性能。

二、E2PROM介绍

2.1 芯片地址介绍

image.png

image.png

image.png

查手册和原理图得知AT24C02的设备地址前4位是固定的1010,我们设备是2K的,所以是第一行中的,我们的设备地址应该是10100000(写)或10100001(读)。

(题外话:如果设备是不是2K的,是4K、8K、16K,读写设备地址怎么看?),在手册中后面位的P0、P1、P2是由未分配的储存块决定的。什么意思呢?假设我们的型号是4K的,就要用组合方式和地址扩展来说明P1和P0是如何工作的

  • 组合方式: P1和P0的组合可以产生四种不同的状态(00、01、10、11),对应着芯片内部的四个不同的存储块。
  • 地址扩展: 通过将P1和P0的值与固定的1010和A2 A1 A0组合起来,形成一个更长的地址,从而对不同的存储块进行寻址
举例说明

假设AT24C08的总存储容量为1024字节,分为4个存储块,每个存储块256字节。当P1=0,P0=0时,访问的是第一个存储块;当P1=1,P0=1时,访问的是第四个存储块。是为提高更多的寻址能力设计的。

2.2 写入的指令流程

image.png

解释

  1. 发送起始信号
  2. 发送设备地址(写)
  3. 等待AT24C02应答,低电平有效
  4. 发送储存地址,AT24C02内部有(2k/8)=256字节,地址范围0x00到0xFF,发送地址就是告诉AT24C02接下来的数据存在哪个地方
  5. 等待AT24C02应答,低电平有效
  6. 发送8个字节的数据,这些数据就是想储存到AT24C02的数据(这里手册中规定型号2K的只能写8个字节,也就是循环一次)
  7. 等待AT24C02应答,低电平有效
  8. 发送停止信号

2.3 从任意地址读任意字节的指令流程

image.png

image.png

解释(按随机读,这种最常用)

  1. 发送起始信号
  2. 发送设备地址(写权限)
  3. 等待AT24C02应答,低电平有效
  4. 发送要读的储存地址,AT24C02内部有(2k/8)=256字节,地址范围0x00到0xFF,发送地址就是告诉AT24C02接下来的应该返回哪个地址的数据给单片机
  5. 等待AT24C02应答,低电平有效
  6. 重新发送起始信号
  7. 发送设备地址(读权限)
  8. 等待AT24C02应答,低电平有效
  9. 循环读取数据,接收AT24C02返回的数据,读数据没有字节限制,可以读1个,也可以一次性读完
  10. 发送非应答,高电平有效
  11. 发送停止信号

三、AT24C02读写代码

  1. 写入一个字节 image.png

代码

#if !defined(_AT24C02_H_)
#define _AT24C02_H_

#include "i2c.h"

#define AT24C02_Address 0xA0
#define AT24C02_Read_Address 0xA1
void AT24C02_Init(void);
void AT24C02_WriteOneByte(u8 Address,u8 Dat);
u8 AT24C02_ReadOneByte(u8 Address);

#endif // _AT24C02_H_


#include "at24c02.h"

void AT24C02_Init(void)
{
    IIC_Init();
}

void AT24C02_WriteOneByte(u8 Address, u8 Dat)
{
    IIC_Start();
    IIC_Send_Byte(AT24C02_Address);
    IIC_Wait_Ack();
    IIC_Send_Byte(Address); // 这里是EEPROM的储存地址
    IIC_Wait_Ack();
    IIC_Send_Byte(Dat);
    IIC_Wait_Ack();
    IIC_Stop();
}

u8 AT24C02_ReadOneByte(u8 Address)
{
    u8 temp = 0;
    IIC_Start();
    IIC_Send_Byte(AT24C02_Address);
    IIC_Wait_Ack();

    IIC_Send_Byte(Address); // 这里是EEPROM的储存地址
    IIC_Wait_Ack();
    IIC_Start();

    IIC_Send_Byte(AT24C02_Read_Address);
    IIC_Wait_Ack();
    temp = IIC_Read_Byte(0);
    IIC_Stop();
    return temp;
}

u32 AT24C02_ReadLenByte(u8 Address, u8 Length)
{
    u32 temp;
    for (u8 i = 0; i < Length; i++)
    {
        temp = temp << 8;
        temp = AT24C02_ReadOneByte(Address) + temp;
    }
    return temp;
}

void AT24C02_Read(u8 Address, u8 *pBuffer, u16 Length)
{

    while (Length--)
    {
        Address++;
        *pBuffer = AT24C02_ReadOneByte(Address);
    }
}

void AT24C02_Write(u8 Address, u8 *pBuffer, u16 Length)
{
    while (Length--)
    {
        AT24C02_WriteOneByte(Address, *pBuffer);
        Address++;
        pBuffer++;
    }
}