打印
[RISC-V MCU 应用开发]

国产risc-v微控制器读写AT24C32

[复制链接]
366|6
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
点赞|  楼主 | 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 编辑

电路原理图


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



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


该模块上的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 项目,在打开的“新建项目”选项卡,填写项目名称,选择“基于开发板”,其他选项默认,点“完成”按钮。



二、配置工程资源

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





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






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




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




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





使用特权

评论回复
5
点赞|  楼主 | 2025-7-30 21:30 | 只看该作者
本帖最后由 点赞 于 2025-7-30 21:46 编辑

主要代码:
读寄存器:

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

    msgs.addr = AT24CXX_ADDR | dev->AddrInput;
    msgs.flags = RT_I2C_RD;
    msgs.buf = buf;
    msgs.len = len;

    if (rt_i2c_transfer(dev->i2c, &msgs, 1) == 1)
    {
        return RT_EOK;
    }
    else
    {
        return -RT_ERROR;
    }
}


读一个字节:
static uint8_t at24cxx_read_one_byte(at24cxx_device_t dev, uint16_t readAddr)
{
    rt_uint8_t buf[2];
    rt_uint8_t temp;
#if (PKG_AT24CXX_EE_TYPE > AT24C16)
    buf[0] = (uint8_t)(readAddr>>8);
    buf[1] = (uint8_t)readAddr;
    if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, RT_I2C_WR, buf, 2) == 0)
#else
    buf[0] = readAddr;
    if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, RT_I2C_WR, buf, 1) == 0)
#endif
    {
        return RT_ERROR;
    }
    read_regs(dev, 1, &temp);
    return temp;
}


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


    if (rt_i2c_master_send(dev->i2c, AT24CXX_ADDR | dev->AddrInput, RT_I2C_WR, buf, 2) == 2)
#endif
        return RT_EOK;
    else
        return -RT_ERROR;

}


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

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

#if (PKG_AT24CXX_EE_TYPE > AT24C16)
    AddrBuf[0] = readAddr >> 8;
    AddrBuf[1] = readAddr;
    msgs[0].buf = AddrBuf;
    msgs[0].len = 2;
#else
    AddrBuf[0] = readAddr;
    msgs[0].buf = AddrBuf;
    msgs[0].len = 1;
#endif

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

    if(rt_i2c_transfer(dev->i2c, msgs, 2) <= 0)
    {
        return RT_ERROR;
    }

    return RT_EOK;
}


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

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

#if (PKG_AT24CXX_EE_TYPE > AT24C16)
    AddrBuf[0] = (uint8_t)(wirteAddr>>8);
    AddrBuf[1] = (uint8_t)wirteAddr;
    msgs[0].buf = AddrBuf;
    msgs[0].len = 2;
#else
    AddrBuf[0] = wirteAddr;
    msgs[0].buf = AddrBuf;
    msgs[0].len = 1;
#endif

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

    if(rt_i2c_transfer(dev->i2c, msgs, 2) <= 0)
    {
        return RT_ERROR;
    }

    return RT_EOK;
}


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

    temp = at24cxx_read_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1);
    if (temp == 0x55) return RT_EOK;
    else
    {
        at24cxx_write_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1, 0x55);
        rt_thread_mdelay(EE_TWR);                 // wait 5ms befor next operation
        temp = at24cxx_read_one_byte(dev, AT24CXX_MAX_MEM_ADDRESS - 1);
        if (temp == 0x55) return RT_EOK;
    }
    return RT_ERROR;
}


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

    if(ReadAddr + NumToRead > AT24CXX_MAX_MEM_ADDRESS || NumToRead == 0)
    {
        return RT_ERROR;
    }

    result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        while (NumToRead)
        {
            *pBuffer++ = at24cxx_read_one_byte(dev, ReadAddr++);
            NumToRead--;
        }
    }
    else
    {
        LOG_E("The at24cxx could not respond  at this time. Please try again");
    }
    rt_mutex_release(dev->lock);

    return RT_EOK;
}


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

    if(WriteAddr + NumToWrite > AT24CXX_MAX_MEM_ADDRESS || NumToWrite == 0)
    {
        return RT_ERROR;
    }

    result = rt_mutex_take(dev->lock, RT_WAITING_FOREVER);
    if (result == RT_EOK)
    {
        while (1) //NumToWrite--
        {
            if (at24cxx_write_one_byte(dev, WriteAddr, pBuffer[i]) == RT_EOK)
            {
                rt_thread_mdelay(2);
                WriteAddr++;
            }
            if (++i == NumToWrite)
            {
                break;
            }
            rt_thread_mdelay(EE_TWR);
        }
    }
    else
    {
        LOG_E("The at24cxx could not respond  at this time. Please try again");
    }
    rt_mutex_release(dev->lock);

    return RT_EOK;
}


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

    RT_ASSERT(i2c_bus_name);

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

    dev->i2c = rt_i2c_bus_device_find(i2c_bus_name);
    if (dev->i2c == RT_NULL)
    {
        LOG_E("Can't find at24cxx device on '%s' ", i2c_bus_name);
        rt_free(dev);
        return RT_NULL;
    }

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

    if(AddrInput > 7)
    {
        LOG_E("The AddrInput is invalid");
        rt_free(dev);
        return RT_NULL;
    }
    else
    {
#if (PKG_AT24CXX_EE_TYPE == AT24C04)
        if(AddrInput > 3)
        {
            LOG_E("The AddrInput is invalid");
            rt_free(dev);
            return RT_NULL;
        }
#elif (PKG_AT24CXX_EE_TYPE == AT24C08)
        if(AddrInput > 1)
        {
            LOG_E("The AddrInput is invalid");
            rt_free(dev);
            return RT_NULL;
        }
#elif (PKG_AT24CXX_EE_TYPE == AT24C16)
        if(AddrInput != 0)
        {
            LOG_E("The AddrInput is invalid");
            rt_free(dev);
            return RT_NULL;
        }
#endif  //PKG_AT24CXX_EE_TYPE
    }

    dev->AddrInput = AddrInput;
    return dev;
}




使用特权

评论回复
6
点赞|  楼主 | 2025-7-30 21:55 | 只看该作者
验证效果:

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

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

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


然后读取at24c32:
at24cxx read
在串口助手会打印如下信息:
read at24cxx : WELCOM TO 21IC
其中:“WELCOM TO 21IC”是刚才写入at24c32的一个字符串。

使用特权

评论回复
7
时光迷宫| | 2025-8-1 14:27 | 只看该作者
不是哥们,你读写一个AT24C32,至于要上操作系统??给力给力。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

4

主题

19

帖子

0

粉丝