[RISC-V MCU 应用开发] 国产risc-v微控制器读写AT24C32

[复制链接]
 楼主| 点赞 发表于 2025-7-29 20:40 | 显示全部楼层 |阅读模式
本帖最后由 点赞 于 2025-7-30 21:56 编辑

EEPROM(Electrically Erasable Programmable Read-Only Memory)即电可擦除可编程只读存储器,是一种可以通过电信号多次擦除和改写数据的非易失性存储器。它在断电后仍能保持存储的数据,广泛应用于需要灵活存储和修改少量关键信息的场景。
一、EEPROM 的核心特点
  • 非易失性
    断电后数据不会丢失,这是其与 RAM(随机存取存储器)的核心区别,适合存储需要长期保存的信息(如设备配置参数、校准数据等)。
  • 电可擦除与改写
    无需紫外线照射(不同于早期的 EPROM),可通过电信号直接擦除和写入数据,操作便捷,支持单字节或单页级别的擦写(部分型号)。
  • 擦写寿命有限
    通常有10 万至 100 万次的擦写寿命,多次擦写后可能出现数据错误,因此设计时需避免频繁对同一地址写入(可通过 “磨损均衡” 算法优化)。
  • 读写速度适中
    读取速度较快(与 ROM 接近),但写入速度较慢(需先擦除再写入,通常毫秒级),不适合高频数据更新场景。
  • 容量较小
    常见容量为几十字节到几兆字节(MB),远小于 Flash 存储器,适合存储少量关键数据(如设备序列号、用户设置等)。
二、EEPROM 的典型应用
  • 嵌入式系统:存储设备的启动配置、硬件参数(如传感器校准值)。
  • 消费电子:智能手表的用户设置、打印机的墨盒计数。
  • 汽车电子:存储车辆 VIN 码、故障码、里程数据等。
  • 工业控制:PLC(可编程逻辑控制器)的运行参数、设备状态记录。
三、AT24C32介绍

    EEPROM 存储器芯片型号非常多,我们今天使用的AT24C32就是其中比较常见的串行EEPROM 存储器芯片。它具有如下特点:
  • 存储容量:AT24C32 的存储容量为 32Kb,即 4096 字节,适合存储少量的配置信息、校准参数、设备标识等数据。
  • 工作电压:支持 2.7V 至 5.5V 的工作电压范围,部分版本也支持 1.8V 至 5.5V,能适应不同的供电环境。
  • 低功耗:在 5.5V 电压下,静态电流仅为 2μA,对于电池供电或能量采集系统尤为重要。
  • 串行接口:采用两线式串行接口(I2C)进行数据通信,只需要 SCL(串行时钟输入引脚)和 SDA(串行数据输入 / 输出引脚)两个接口,占用引脚少,适合资源受限的嵌入式系统或小型设备。
  • 数据保护:具有写保护引脚 WP,当 WP 引脚为高电平时,AT24C32 将无法写入数据,起到对数据的保护作用。
  • 页写模式:支持 32 字节的页写模式,部分页写操作也是允许的,提高了数据写入效率。
  • 高可靠性:耐久性达到 100 万次写入周期,数据保留时间长达 100 年,具有较高的稳定性和可靠性。
  • 多种封装形式:提供 8 引脚 JEDEC PDIP、8 引脚 JEDEC SOIC、8 引脚 EIAJ SOIC 和 8 引脚 TSSOP 等封装形式,满足不同应用场景的需求。



 楼主| 点赞 发表于 2025-7-29 20:43 | 显示全部楼层
本帖最后由 点赞 于 2025-7-30 21:26 编辑

电路原理图
74473688a1aa9d0ef0.png

今天我使用的是一个同时集成了AT24C32和DS3231的模块。它们都使用I2C通讯,在这个模块上都挂在同一个I2C总线上,使用不同的地址来访问。其中AT24C32相关的原理图如下:

12423688a1bd4dcdb6.png

原理图上器件型号写的是AT24C02,实际模块上焊接的是AT24C32,它们pin2pin兼容,只是容量不同。
AT24C32具有三个地址配置引脚:A0/A1/A2。
在原理图上,这三个地址引脚默认是上拉状态,即器件的地址是7。当使用其他地址或多个AT24CXX都挂在同一个I2C总线上时,可以通过短接R2~R4给器件分配不同的地址。这个地址在后面的控制代码中会用到。
18044688a1d3a4cdc5.png

该模块上的I2C总线已经有上拉电阻,在控制板中该总线可以不再接上拉电阻。



 楼主| 点赞 发表于 2025-7-29 20:45 | 显示全部楼层
本帖最后由 点赞 于 2025-7-30 21:30 编辑

HPM5361与MPU6050的连接:

使用的HPM5361开发板P1接口有两组I2C接口,这里使用I2C0接口,及PB03和PB02引脚。



这两个引脚,在开发板上已经有了10K的上拉电阻,使用的AT24C32模块上也有上拉电阻,这里可以共存,不需要特殊处理。
 楼主| 点赞 发表于 2025-7-29 20:49 | 显示全部楼层
本帖最后由 点赞 于 2025-7-30 21:38 编辑

工程创建及资源配置:
一、新建工程

打开RT-Thread Studio,在菜单选:文件-->新建-->RT-Thread 项目,在打开的“新建项目”选项卡,填写项目名称,选择“基于开发板”,其他选项默认,点“完成”按钮。

57824688a1ea5d82d2.png

二、配置工程资源

双击工程下的“RT-Thread Setting”,打开RT-Thread Setting选项卡。
在软件包项目下,点“添加软件包”。


61845688a1f1540482.png


在打开的搜索页面搜索“at24”,在出现的项目中点“at24cxx“软件包的”添加“按钮。




66981688a1ed06754e.png

在”软件包“标签页,配置at24cxx软件包:


23326688a201390602.png

在”组件“标签页打开”使用I2C设备驱动程序“


25432688a202be8340.png

在”硬件“标签页,打开”Enable I2C“选项,并打开I2C0。


69879688a203d0024e.png


 楼主| 点赞 发表于 2025-7-30 21:30 | 显示全部楼层
本帖最后由 点赞 于 2025-7-30 21:46 编辑

主要代码:
读寄存器:

  1. static rt_err_t read_regs(at24cxx_device_t dev, rt_uint8_t len, rt_uint8_t *buf)
  2. {
  3.     struct rt_i2c_msg msgs;

  4.     msgs.addr = AT24CXX_ADDR | dev->AddrInput;
  5.     msgs.flags = RT_I2C_RD;
  6.     msgs.buf = buf;
  7.     msgs.len = len;

  8.     if (rt_i2c_transfer(dev->i2c, &msgs, 1) == 1)
  9.     {
  10.         return RT_EOK;
  11.     }
  12.     else
  13.     {
  14.         return -RT_ERROR;
  15.     }
  16. }


读一个字节:
  1. static uint8_t at24cxx_read_one_byte(at24cxx_device_t dev, uint16_t readAddr)
  2. {
  3.     rt_uint8_t buf[2];
  4.     rt_uint8_t temp;
  5. #if (PKG_AT24CXX_EE_TYPE > AT24C16)
  6.     buf[0] = (uint8_t)(readAddr>>8);
  7.     buf[1] = (uint8_t)readAddr;
  8.     if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, RT_I2C_WR, buf, 2) == 0)
  9. #else
  10.     buf[0] = readAddr;
  11.     if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, RT_I2C_WR, buf, 1) == 0)
  12. #endif
  13.     {
  14.         return RT_ERROR;
  15.     }
  16.     read_regs(dev, 1, &temp);
  17.     return temp;
  18. }


写一个字节:
  1. static rt_err_t at24cxx_write_one_byte(at24cxx_device_t dev, uint16_t writeAddr, uint8_t dataToWrite)
  2. {
  3.     rt_uint8_t buf[3];
  4. #if (PKG_AT24CXX_EE_TYPE > AT24C16)
  5.     buf[0] = (uint8_t)(writeAddr>>8);
  6.     buf[1] = (uint8_t)writeAddr;
  7.     buf[2] = dataToWrite;
  8.     if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, RT_I2C_WR, buf, 3) == 3)
  9. #else
  10.     buf[0] = writeAddr; //cmd
  11.     buf[1] = dataToWrite;
  12.     //buf[2] = data[1];


  13.     if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, RT_I2C_WR, buf, 2) == 2)
  14. #endif
  15.         return RT_EOK;
  16.     else
  17.         return -RT_ERROR;

  18. }


读一页:
  1. static rt_err_t at24cxx_read_page(at24cxx_device_t dev, uint32_t readAddr, uint8_t *pBuffer, uint16_t numToRead)
  2. {
  3.     struct rt_i2c_msg msgs[2];
  4.     uint8_t AddrBuf[2];

  5.     msgs[0].addr = AT24CXX_ADDR | dev->AddrInput;
  6.     msgs[0].flags = RT_I2C_WR;

  7. #if (PKG_AT24CXX_EE_TYPE > AT24C16)
  8.     AddrBuf[0] = readAddr >> 8;
  9.     AddrBuf[1] = readAddr;
  10.     msgs[0].buf = AddrBuf;
  11.     msgs[0].len = 2;
  12. #else
  13.     AddrBuf[0] = readAddr;
  14.     msgs[0].buf = AddrBuf;
  15.     msgs[0].len = 1;
  16. #endif

  17.     msgs[1].addr = AT24CXX_ADDR | dev->AddrInput;
  18.     msgs[1].flags = RT_I2C_RD;
  19.     msgs[1].buf = pBuffer;
  20.     msgs[1].len = numToRead;

  21.     if(rt_i2c_transfer(dev->i2c, msgs, 2) <= 0)
  22.     {
  23.         return RT_ERROR;
  24.     }

  25.     return RT_EOK;
  26. }


写一页:
  1. static rt_err_t at24cxx_write_page(at24cxx_device_t dev, uint32_t wirteAddr, uint8_t *pBuffer, uint16_t numToWrite)
  2. {
  3.     struct rt_i2c_msg msgs[2];
  4.     uint8_t AddrBuf[2];

  5.     msgs[0].addr = AT24CXX_ADDR | dev->AddrInput;
  6.     msgs[0].flags = RT_I2C_WR;

  7. #if (PKG_AT24CXX_EE_TYPE > AT24C16)
  8.     AddrBuf[0] = (uint8_t)(wirteAddr>>8);
  9.     AddrBuf[1] = (uint8_t)wirteAddr;
  10.     msgs[0].buf = AddrBuf;
  11.     msgs[0].len = 2;
  12. #else
  13.     AddrBuf[0] = wirteAddr;
  14.     msgs[0].buf = AddrBuf;
  15.     msgs[0].len = 1;
  16. #endif

  17.     msgs[1].addr = AT24CXX_ADDR | dev->AddrInput;
  18.     msgs[1].flags = RT_I2C_WR | RT_I2C_NO_START;
  19.     msgs[1].buf = pBuffer;
  20.     msgs[1].len = numToWrite;

  21.     if(rt_i2c_transfer(dev->i2c, msgs, 2) <= 0)
  22.     {
  23.         return RT_ERROR;
  24.     }

  25.     return RT_EOK;
  26. }


通过在eeprom的最后一个字节,先写后读检查eeprom:
  1. rt_err_t at24cxx_check(at24cxx_device_t dev)
  2. {
  3.     uint8_t temp;
  4.     RT_ASSERT(dev);

  5.     temp = at24cxx_read_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1);
  6.     if (temp == 0x55) return RT_EOK;
  7.     else
  8.     {
  9.         at24cxx_write_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1, 0x55);
  10.         rt_thread_mdelay(EE_TWR);                 // wait 5ms befor next operation
  11.         temp = at24cxx_read_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1);
  12.         if (temp == 0x55) return RT_EOK;
  13.     }
  14.     return RT_ERROR;
  15. }


读取任意位置任意长度的数据:
  1. rt_err_t at24cxx_read(at24cxx_device_t dev, uint32_t ReadAddr, uint8_t *pBuffer, uint16_t NumToRead)
  2. {
  3.     rt_err_t result;
  4.     RT_ASSERT(dev);

  5.     if(ReadAddr + NumToRead > AT24CXX_MAX_MEM_ADDRESS || NumToRead == 0)
  6.     {
  7.         return RT_ERROR;
  8.     }

  9.     result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
  10.     if (result == RT_EOK)
  11.     {
  12.         while (NumToRead)
  13.         {
  14.             *pBuffer++ = at24cxx_read_one_byte(dev, ReadAddr++);
  15.             NumToRead--;
  16.         }
  17.     }
  18.     else
  19.     {
  20.         LOG_E("The at24cxx could not respond  at this time. Please try again");
  21.     }
  22.     rt_mutex_release(dev->lock);

  23.     return RT_EOK;
  24. }


在任意地址写任意长度的数据:
  1. rt_err_t at24cxx_write(at24cxx_device_t dev, uint32_t WriteAddr, uint8_t *pBuffer, uint16_t NumToWrite)
  2. {
  3.     uint16_t i = 0;
  4.     rt_err_t result;
  5.     RT_ASSERT(dev);

  6.     if(WriteAddr + NumToWrite > AT24CXX_MAX_MEM_ADDRESS || NumToWrite == 0)
  7.     {
  8.         return RT_ERROR;
  9.     }

  10.     result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
  11.     if (result == RT_EOK)
  12.     {
  13.         while (1) //NumToWrite--
  14.         {
  15.             if (at24cxx_write_one_byte(dev, WriteAddr, pBuffer[i]) == RT_EOK)
  16.             {
  17.                 rt_thread_mdelay(2);
  18.                 WriteAddr++;
  19.             }
  20.             if (++i == NumToWrite)
  21.             {
  22.                 break;
  23.             }
  24.             rt_thread_mdelay(EE_TWR);
  25.         }
  26.     }
  27.     else
  28.     {
  29.         LOG_E("The at24cxx could not respond  at this time. Please try again");
  30.     }
  31.     rt_mutex_release(dev->lock);

  32.     return RT_EOK;
  33. }


硬件初始化:
  1. at24cxx_device_t at24cxx_init(const char *i2c_bus_name, uint8_t AddrInput)
  2. {
  3.     at24cxx_device_t dev;

  4.     RT_ASSERT(i2c_bus_name);

  5.     dev = rt_calloc(1, sizeof(struct at24cxx_device));
  6.     if (dev == RT_NULL)
  7.     {
  8.         LOG_E("Can't allocate memory for at24cxx device on '%s' ", i2c_bus_name);
  9.         return RT_NULL;
  10.     }

  11.     dev->i2c = rt_i2c_bus_device_find(i2c_bus_name);
  12.     if (dev->i2c == RT_NULL)
  13.     {
  14.         LOG_E("Can't find at24cxx device on '%s' ", i2c_bus_name);
  15.         rt_free(dev);
  16.         return RT_NULL;
  17.     }

  18.     dev->lock = rt_mutex_create("mutex_at24cxx", RT_IPC_FLAG_FIFO);
  19.     if (dev->lock == RT_NULL)
  20.     {
  21.         LOG_E("Can't create mutex for at24cxx device on '%s' ", i2c_bus_name);
  22.         rt_free(dev);
  23.         return RT_NULL;
  24.     }

  25.     if(AddrInput > 7)
  26.     {
  27.         LOG_E("The AddrInput is invalid");
  28.         rt_free(dev);
  29.         return RT_NULL;
  30.     }
  31.     else
  32.     {
  33. #if (PKG_AT24CXX_EE_TYPE == AT24C04)
  34.         if(AddrInput > 3)
  35.         {
  36.             LOG_E("The AddrInput is invalid");
  37.             rt_free(dev);
  38.             return RT_NULL;
  39.         }
  40. #elif (PKG_AT24CXX_EE_TYPE == AT24C08)
  41.         if(AddrInput > 1)
  42.         {
  43.             LOG_E("The AddrInput is invalid");
  44.             rt_free(dev);
  45.             return RT_NULL;
  46.         }
  47. #elif (PKG_AT24CXX_EE_TYPE == AT24C16)
  48.         if(AddrInput != 0)
  49.         {
  50.             LOG_E("The AddrInput is invalid");
  51.             rt_free(dev);
  52.             return RT_NULL;
  53.         }
  54. #endif  //PKG_AT24CXX_EE_TYPE
  55.     }

  56.     dev->AddrInput = AddrInput;
  57.     return dev;
  58. }




 楼主| 点赞 发表于 2025-7-30 21:55 | 显示全部楼层
验证效果:

把完整代码编译并烧写到开发板,连接好串口助手,在串口助手输入如下命令进行测试验证。

先探查at24c32:
  1. at24cxx probe i2c0 0xae
其中:i2c0是总线名称。0xae是at24c32的设备地址,二进制为b1010 1110 ,其中的连续三个1及该AT24C32三个地址引脚配置的器件地址。

先写at24c32:
  1. at24cxx write
在串口助手会有反馈信息打印出来:
  1. write ok


然后读取at24c32:
  1. at24cxx read
在串口助手会打印如下信息:
  1. read at24cxx : WELCOM TO 21IC
其中:“WELCOM TO 21IC”是刚才写入at24c32的一个字符串。
时光迷宫 发表于 2025-8-1 14:27 | 显示全部楼层
不是哥们,你读写一个AT24C32,至于要上操作系统??给力给力。
作业天敌在此 发表于 2025-8-8 20:48 | 显示全部楼层
AT24C32的低功耗和串行接口特性使其非常适合电池供电的嵌入式系统
您需要登录后才可以回帖 登录 | 注册

本版积分规则

5

主题

20

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部

5

主题

20

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部