STM32基础知识:IIC总线操作EEPROM存储模块AT24C02

描述

STM32基础:IIC总线操作EEPROM存储模块AT24C02

参考文档:AT24C02数据手册

STM32基础:IIC概述与软件模拟IIC一文中,详细介绍了使用STM32的GPIO口模拟IIC总线的方法,如果读者对IIC总线还不了解,请先阅读此文。

本文是IIC总线的实际应用,将带领读者一步一步阅读AT24C02数据手册,看时序图了解如何使用IIC接口EEPROM存储模块AT24C02,并编写代码使用STM32驱动这个模块。

1 AT24C02概述

1.1 基本描述

存储模块
《AT24C02数据手册》P1

从上文可以知道,AT24C02提供2048位串行电可擦除和可编程只读存储器(EEPROM),组织256字节。该器件针对许多汽车应用进行了优化,在这些应用中,低功耗和低电压操作是必不可少的。

AT24C02具有节省空间的8引脚JEDEC SOIC和8引脚TSSOP封装,可通过 双线串行接口(即IIC) 访问。此外,整个系列还提供2.7V (2.7V至5.5V)版本。

1.2 重要特点

AT24C02有如下特点,重点用红色标出:

存储模块
《AT24C02数据手册》P1

  • 存储器内部按组织256字节 × 8位 (2K)组织
  • 双线串行接口(IIC)
  • 兼容400kHz通信速率
  • 具有硬件数据保护的写保护引脚
  • 8字节/页写模式
  • 允许部分页写入
  • 高可靠性:100万次写周期,数据保留:100年

1.3 引脚定义

存储模块
《AT24C02数据手册》P1

存储模块
《AT24C02数据手册》P2

存储模块
《AT24C02数据手册》P3

DA、SCL为IIC总线使用引脚不再赘述。从上文可以知道,A2,A1和A0引脚用于AT24C02的设备地址输入。WP为写保护引脚,提供硬件数据保护。写保护引脚在连接到地(GND)时允许正常的读写操作。当写保护引脚接在VCC上时,写保护功能开启,操作如下表所示。

存储模块
《AT24C02数据手册》P3

在开发板的原理图上可以看到,设备地址输入A2、A1、A0都为0,WP已近接在GND上关闭了写保护,我们可以正常读写。

存储模块

1.4 存储空间

存储模块
《AT24C02数据手册》P4

AT24C02,2K,串行EEPROM内部组织为32页,每页8字节,2K需要一个8位的字地址进行随机字寻址。

1.5 设备地址

存储模块
《AT24C02数据手册》P8

2K EEPROM设备都需要一个 8位设备地址字包含一个启动条件 ,以使芯片能够进行读或写操作。

设备地址字前4位最高有效位为1010。这对所有串行EEPROM设备都是通用的。接下来的3位是1K/2K EEPROM的A2、A1和A0设备地址位。设备地址的第8位是读写操作选择位。如果该位高,则进行读操作;如果该位低,则进行写操作。

综上,如果对AT24C02进行读操作,则设备地址为10100001B=A1H;如果对AT24C02进行写操作,则设备地址为10100000B=A0H.

存储模块
《AT24C02数据手册》P9

2 AT24C01编程

2.1 写操作:字编程和页编程

存储模块
《AT24C02数据手册》P8

写数据有字编程和页编程两种方式:

  • 字编程:一次写入一个字节。
  • 页编程:1K/2K EEPROM能够一次写入一个8字节的页。

如果打算写入数据,则需要知道数据的存储地址。因为AT24C02的存储空间为2K(2^11),故寻址空间为02^11-1,即000H7FFH。每页8字节,故第1页地址000H,第2页地址008H,第3页地址010H,……,第256页地址7F8H。

下图描述了字编程写数据的过程,根据图中的分析和前文的讲述,可以写出字编程函数:

存储模块
《AT24C02数据手册》P10

/**
 * @brief AT24C02初始化
 * 
 */
void AT24C02_Init(void)
{
  IIC_Init();  // 初始化IIC总线
}

/**
 * @brief AT24C02字节写入
 * 
 * @param Address: 字节地址
 * @param Data: 待写入的数据
 */
void AT24C02_ByteWrite(uint8_t Address, uint8_t Data)
{
  IIC_StartSignal();        // 发送开始信号

  // 发送设备地址,写操作
  IIC_SendBytes(0xA0);     
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the device address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the device address: OKn");
  }

  // 发送字地址
  IIC_SendBytes(Address);   
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the word address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the word address: OKn");
  }

  // 发送待写入的数据
  IIC_SendBytes(Data);     
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the data: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the data: OKn");
  }

  IIC_StopSignal();         // 发送停止信号
}

下图描述了页编程写数据的过程,根据图中的分析和前文的讲述,可以写出页编程函数:

存储模块
《AT24C02数据手册》P10

/**
 * @brief AT24C02页编程(8字节/页)
 * 
 * @param Address: 页地址
 * @param buf: 待写入的数据
 * @param DataLen: 待写入的数据长度 
 */
void AT24C02_PageWrite(uint32_t Address, uint8_t *buf, uint8_t DataLen)
{
  IIC_StartSignal();        // 发送开始信号

  // 发送设备地址,写操作
  IIC_SendBytes(0xA0);
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the device address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the device address: OKn");
  }

  // 发送页地址
  IIC_SendBytes(Address);
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the page address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the page address: OKn");
  }

  // 循环发送数据
  while (DataLen--)
  {
    IIC_SendBytes(*buf++);  // 发送数据
    if (IIC_WaitACK() == 1) // AT24C02没应答
    {
      printf("[AT24C02] Answered the data: Errorn");
      IIC_StopSignal();     // 发送停止信号
    }
    else
    {
      printf("[AT24C02] Answered the data: OKn");
    }
  }

  IIC_StopSignal();         // 发送停止信号
}

2.2 读操作:当前地址读

存储模块
《AT24C02数据手册》P19

  1. 当前地址读:内部地址计数器保存着上次访问时最后一个地址加1的值 。(使用”当前地址读“读出的数据,其地址为上次访问的最后一个地址加1。)
  2. 只要芯片有电,该地址就一直保存,当读到最后页的最后字节,地址会回转到0:
  3. 当写到某页尾的最后一个字节,地址会回转到该页的首字节。
  4. 接收器件地址(读/写选择位为"1")、EEPROM应答ACK后,当前地址的数据就随时钟送出。主器件无需应答"0",但需发送停止条件。

存储模块
《AT24C02数据手册》P10

根据此图和前文的讲述,编写当前地址读函数:

/**
 * @brief AT24C02当前地址读
 * 
 * @return uint8_t 当前地址的数据
 */
uint8_t AT24C02_CurrentAddressRead(void)
{
  uint8_t Data;

  IIC_StartSignal(); // 发送开始信号

  // 发送设备地址,读操作
  IIC_SendBytes(0xA1);     
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the device address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the device address: OKn");
  }
  
  // 读取1字节数据
  Data = IIC_ReadBytes();

  // 发送应答信号
  IIC_MasterACK(1); // 不应答

  IIC_StopSignal(); // 发送停止信号
  return Data;      // 返回接收到的数据
}

下面我们测试前面写的四个函数(AT24C02_InitAT24C02_ByteWriteAT24C02_PageWriteAT24C02_CurrentAddressRead),测试过程为:

  • 使用AT24C02_Init初始化AT24C02
  • 使用AT24C02_PageWrite向0x00写入“0123456”
  • 使用AT24C02_ByteWrite向0x07写入“7”
  • 使用AT24C02_CurrentAddressRead进行“当前地址读”
    (0x00-0x07已经被写入“01234567”,即第一页写满了,此时地址会回到该页首字节,使用“当前地址读”读出的应该是该页第一个字节“0”)

如果输出的提示信息全部正常且返回值为“0”则说明这四个函数编写正确。测试效果如下图,测试成功。

uint8_t data;

AT24C02_Init()

AT24C02_PageWrite(0x00, "0123456", 6);
HAL_Delay(100);

AT24C02_ByteWrite(0x07, '7');  
HAL_Delay(100);

data = AT24C02_CurrentAddressRead();
printf("AT24C02_CurrentAddressRead: %c", data);

存储模块

2.3 读操作:随机读

存储模块
《AT24C02数据手册》P9

随机读需先写一个目标字地址 ,一旦EEPROM接收器设备地址(读/写选择位为"0")和字地址并应答了ACK,主机就产生一个重复的起始条件。然后,主器件发送设备地址(读/写选择位为"1"),EEPROM应答ACK,并随时钟送出数据。主器件无需应答"0",但需发送停止条件。

存储模块
《AT24C02数据手册》P11

根据此图和前文的讲述,编写随机读函数:

/**
 * @brief AT24C02随机读1字节数据
 * 
 * @param Address: 字地址 
 * @return uint8_t 读取到的数据
 */
uint8_t AT24C02_RandomRead(uint8_t Address)
{
  uint8_t Data;

  IIC_StartSignal(); // 发送开始信号

  // 发送设备地址,写操作
  IIC_SendBytes(0xA0);     
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the device address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the device address: OKn");
  }

  // 发送字数据的地址
  IIC_SendBytes(Address);   
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the word address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the word address: OKn");
  }

  IIC_StartSignal();        // 发送开始信号

  // 发送设备地址,读操作
  IIC_SendBytes(0xA1);     
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the device address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the device address: OKn");
  }
  
  // 读取1字节数据
  Data = IIC_ReadBytes();

  // 发送应答信号
  IIC_MasterACK(1); // 不应答

  IIC_StopSignal(); // 发送停止信号

  return Data;      // 返回接收到的数据
}

下面我们测试AT24C02_RandomRead函数,测试过程为:

  • 使用AT24C02_Init初始化AT24C02
  • 使用AT24C02_PageWrite向0x00写入“01234567”
  • 使用AT24C02_RandomRead读出0x07处的数据
uint8_t data;

AT24C02_Init();
AT24C02_PageWrite(0x00, "01234567", 7);
HAL_Delay(100);

data = AT24C02_RandomRead(0x07);
printf("AT24C02_RandomRead: %c", data);

如果输出的提示信息全部正常且返回值为“7”则说明函数编写正确。测试效果如下图,测试成功。

存储模块

如果想要“随机读”函数可以一次读取多个字节,可以修改函数为如下形式:

/**
 * @brief AT24C02随机读n个字节
 * 
 * @param Address: 字地址 
 * @param RecvBuf: 接收缓冲区 
 * @param DataLen: 接收数据长度 
 */
void AT24C02_RandomRead(uint8_t Address, uint8_t *RecvBuf, uint8_t DataLen)
{
  uint8_t Data;

  IIC_StartSignal(); // 发送开始信号

  // 发送设备地址,写操作
  IIC_SendBytes(0xA0);     
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the device address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the device address: OKn");
  }

  // 发送字数据的地址
  IIC_SendBytes(Address);   
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the word address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the word address: OKn");
  }

  IIC_StartSignal();        // 发送开始信号

  // 发送设备地址,读操作
  IIC_SendBytes(0xA1);     
  if (IIC_WaitACK() == 1)   // AT24C02没应答
  {
    printf("[AT24C02] Answered the device address: Errorn");
    IIC_StopSignal();       // 发送停止信号
  }
  else
  {
    printf("[AT24C02] Answered the device address: OKn");
  }
  
  // 读取n字节数据
  DataLen -= 1;
  while (DataLen--) // 读取n-1字节数据,最后1字节数据单独读取
  {
    *RecvBuf++ = IIC_ReadBytes();

    // 发送应答信号
    IIC_MasterACK(0); // 应答
  }
  *RecvBuf++ = IIC_ReadBytes(); // 读取最后1字节数据
  
  // 发送应答信号
  IIC_MasterACK(1); // 不应答

  IIC_StopSignal(); // 发送停止信号

  return Data;      // 返回接收到的数据
}

下面我们测试AT24C02_RandomRead函数,测试过程为:

  • 使用AT24C02_Init初始化AT24C02
  • 使用AT24C02_PageWrite向0x00写入“01234567”
  • 使用AT24C02_RandomRead读出0x00-0x07处的数据
uint8_t RecvBuf[10] = {0};

AT24C02_Init();
AT24C02_PageWrite(0x00, "01234567", 8);
HAL_Delay(100);

AT24C02_RandomRead(0x00, RecvBuf, 8);
printf("AT24C02_RandomRead: %s", RecvBuf);

如果输出的提示信息全部正常且返回值为“01234567”则说明函数编写正确。测试效果如下图,测试成功。

存储模块

2.4 读操作:顺序读

存储模块
《AT24C02数据手册》P9

AT24C02还有一种读操作,就是顺序读取。顺序读取由当前地址读取或随机地址读取启动。主机接收到一个数据字后,它以确认响应。只要 EEPROM 接收到应答,将自动增加字地址并继续随时钟发送后面的数据。当达到内存地址限制时,地址自动回转到0,认可继续顺序读取数据。主机不应答“0”,而发送停止条件,即可结束顺序读操作。

存储模块
《AT24C02数据手册》P11

正所谓“纸上得来终觉浅,绝知此事要躬行”。在对顺序读写略加分析后,顺序读取的代码这里不再给出,读者可以结合本文对前面几种读写操作的讲解和顺序读操作的流程自行完成。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分