RT thread—iic—at24c04读写操作

工程文件:

通过网盘分享的文件:new.zip

链接: https://pan.baidu.com/s/1Uw_h1k_UWVCCZXFsJDLU8Q?pwd=xcy5 提取码: xcy5

at24c04介绍:

存储容量:4 Kbits(即 512 字节)。内部结构为 32 页,每页 16 字节。地址0x000-0x1FF

通信接口:标准 I2C(时钟线 SCL 和数据线 SDA),支持最高 400 kHz 的快速模式。

想要访问512字节的地址,需要9个二进制,但是单片机最大只能8位。设计者,把512字节的存储空间分成两个数据块,每个块256字节。从i2c的设备地址借走一个位,用来当做数据块的选择开关。

AT24C04 的 8 位 I2C 设备寻址格式: 1 0 1 0 A2 A1 P0 R/W

  • 1 0 1 0:EEPROM 的固定前缀。

  • A2, A1:由芯片外部的硬件引脚高低电平决定(注意:AT24C04 的 A0 引脚在内部是悬空的,不起作用)。

  • P0(Page 0) :这就是借来的"第 9 位地址"!也叫块选择位

  • R/W:读写方向位。

第一块(Block 0):存储地址 0x00 ~ 0xFF (前 256 字节)

  • 你需要把设备地址里的 P0 设为 0

  • 此时 I2C 设备地址是:1010 000 0 -> 0xA0

  • 内部字地址正常发送 0x00 ~ 0xFF

第二块(Block 1):存储地址 0x100 ~ 0x1FF (后 256 字节)

  • 你需要把设备地址里的 P0 设为 1

  • 此时 I2C 设备地址变成了:1010 001 0 -> 0xA2

  • 内部字地址依然发送 0x00 ~ 0xFF,但芯片知道你要操作的是后半区。

at24c04由于设计与压缩成本的问题,会产生卷回操作。

存储芯片并不是一个连续的、毫无边界的巨大黑板。在微观晶体管层面,它被划分成了一排一排的独立单元,每一排被称为一个页。比如一页是 8 字节或 16 字节。 之所以这样设计,是因为把数据永久"烧录"进存储单元需要极高的瞬间电压,而芯片内部的高压电荷泵,一次只能对齐并作用于同一页的物理线路

为了提高烧录(5ms)的速度,加入了一个临时缓存区(托盘),这个托盘大小等于一个物理页大小(at24c02是8字节,04是16字节)。

数据流向:数据------托盘------存储区。当托盘存满16个字节,还继续给托盘发数据,这时候托盘不会进行换页操作,直接把托盘的最低位数据进行覆盖。

比如:当你的单片机执行 rt_i2c_master_send ()时,它在 I2C 总线上发送: [START] -> [设备地址] -> [内部存储地址] -> [数据1] -> [数据2] ...[STOP]。这时候数据都会被存入托盘中,容易造成数据覆盖。

最标准且高效的修复方法是在软件驱动层引入**"自动分页写入算法**",通过智能切分数据包来迁就硬件的机制。在每次发起通信前,程序会先计算目标地址距离当前物理页的结尾还剩多少空余位置;如果待写入的数据总量大于这些空位,程序只会发送刚好能填满当前页的数据,随后立即停止 I2C 通信并强制延时(通常 5 毫秒)等待芯片安全烧录,待烧录彻底完成后,再将地址光标对齐到下一物理页的绝对开头继续发送剩余数据,利用这种"化整为零、满页即停"的策略即可彻底避开卷回陷阱。

首先在board.h,把注释取消,然后配置软件iic的引脚,pb1跟pb2,采用软件iic。

在rt thread settings中设置

设置之后,会出现这个宏定义

使用i2c设备驱动程序: RT-Thread I2C 总线设备驱动框架的全局总开关,是下面所有功能的前提。自动在rtconfig.h中定义RT_USING_I2C宏,启用 RT-Thread 官方的 I2C 驱动框架;编译器会编译 I2C 核心驱动代码,提供你代码里用到的rt_i2c_master_sendrt_i2c_master_recvrt_i2c_bus_device_register等核心 API;只有开启这个总开关,下面的硬件 I2C 调试、GPIO 模拟 I2C 的开关才会生效。

**Use I2C debug message:**硬件 I2C 外设的调试日志开关,硬件 I2C 运行时,会在串口自动打印详细运行信息:I2C 总线初始化结果、设备地址是否 ACK 应答、发送 / 接收的字节数、通信超时、NACK 错误等。

使用GPIO模拟i2c: 启用软件模拟 I2C功能,自动定义RT_USING_I2C_BITOPS宏,启用软件模拟 I2C 的核心驱动代码;你可以在board.h里配置任意 GPIO 作为模拟 I2C 的 SCL/SDA,注册成比如"i2c2"的总线设备,和硬件 I2C1 互不影响。

Use simulate I2C debug message: 模拟 I2C 的专属调试日志开关,模拟 I2C 运行时,会打印时序模拟细节、ACK/NACK 检测结果、引脚操作状态、发送接收日志。

at24c04.c

cs 复制代码
#include "at24c04.h"
#define DBG_TAG "I2C_soft"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include <rtthread.h> // 需要引入 rtthread.h 以使用 rt_malloc 和 rt_free

#define     I2C1_NAME   "i2c1"
#define     BASE_SLAVE_ADDR  (0xA0 >> 1) // 基础设备地址 0x50

// data_add 为16位数据地址(0x000~0x1FF),data为纯数据指针,data_byte为纯数据长度
int write_i2c1reg(rt_uint16_t data_add, rt_uint8_t *data, rt_uint8_t data_byte)
{
    rt_uint8_t i2c1_flag = 0;
    struct rt_i2c_bus_device *i2c1_bus;
    rt_uint8_t slave_addr;
    rt_uint8_t *send_buf;

    i2c1_bus = (struct rt_i2c_bus_device *)rt_device_find(I2C1_NAME);
    if(i2c1_bus == RT_NULL){
        LOG_D("failed to i2c1 bus find");
        return -1;
    }

    // 1. 动态计算设备地址:提取 16位地址 的第 9 位(bit 8),拼接到设备地址中
    slave_addr = BASE_SLAVE_ADDR | ((data_add >> 8) & 0x01);

    // 2. 动态分配临时缓冲区:长度 = 1字节内部地址 + 实际数据长度
    send_buf = (rt_uint8_t *)rt_malloc(data_byte + 1);
    if(send_buf == RT_NULL){
        LOG_D("failed to malloc send buffer");
        return -1;
    }

    // 3. 组装发送帧:第 0 个字节装入低 8 位内部地址,后面跟上有效数据
    send_buf[0] = (rt_uint8_t)(data_add & 0xFF);
    for(int i = 0; i < data_byte; i++){
        send_buf[i + 1] = data[i];
    }

    // 4. 发送数据
    i2c1_flag = (rt_uint8_t)rt_i2c_master_send(i2c1_bus, slave_addr, RT_NULL, send_buf, data_byte + 1);

    // 5. 释放内存
    rt_free(send_buf);

    if(i2c1_flag != (data_byte + 1)){
        LOG_D("failed to i2c1 send");
        return -1;
    }
    return 0;
}
#define AT24C04_PAGE_SIZE 16  // AT24C04 的页大小是 16 字节

/**
 * @brief  AT24C04 自动分页写入函数 (防卷回高级封装)
 * @param  addr: 16位目标物理起始地址 (0x000 ~ 0x1FF)
 * @param  data: 要写入的数据缓冲区指针
 * @param  len:  要写入的总字节数 (支持任意长度,如 20、50、100 字节)
 * @retval 0:成功, -1:失败
 */
int at24c04_page_write(rt_uint16_t addr, rt_uint8_t *data, rt_uint16_t len)
{
    rt_uint16_t page_offset;
    rt_uint16_t space_left_in_page;
    rt_uint16_t chunk_size;

    while (len > 0)
    {
        // 1. 计算当前地址在物理页内的偏移量 (例如 addr = 0x05,偏移量就是 5)
        page_offset = addr % AT24C04_PAGE_SIZE;

        // 2. 计算当前物理页还剩下多少个空位
        space_left_in_page = AT24C04_PAGE_SIZE - page_offset;

        // 3. 决定这一次要写入的数据块大小 (Chunk)
        // 如果剩下要写的数据量 < 页内剩余空间,就全写进去;否则,只填满当前页。
        if (len < space_left_in_page)
        {
            chunk_size = len;
        }
        else
        {
            chunk_size = space_left_in_page;
        }

        // 4. 调用底层的 I2C 发送函数,只发送这个计算好的 chunk_size
        if (write_i2c1reg(addr, data, (rt_uint8_t)chunk_size) != 0)
        {
            LOG_E("Page write failed at addr: 0x%03X", addr);
            return -1;
        }

        // 5. 【极其关键】等待当前这半页或一页的数据固化到 Flash!
        rt_thread_mdelay(5);

        // 6. 更新指针和剩余长度,准备下一轮循环 (如果还没写完的话)
        addr += chunk_size;   // 目标地址往后推
        data += chunk_size;   // 数据缓冲区指针往后推
        len  -= chunk_size;   // 剩余需要写入的长度减少
    }

    return 0;
}
// data_add 为16位数据地址(0x000~0x1FF),data为接收缓冲区,data_byte为读取长度
int read_i2c1reg(rt_uint16_t data_add, rt_uint8_t *data, rt_uint8_t data_byte)
{
    rt_uint8_t i2c1_flag = 0;
    struct rt_i2c_bus_device *i2c1_bus;
    rt_uint8_t slave_addr;
    rt_uint8_t word_addr;

    i2c1_bus = (struct rt_i2c_bus_device *)rt_device_find(I2C1_NAME);
    if(i2c1_bus == RT_NULL){
        LOG_D("failed to i2c1 bus find");
        return -1;
    }

    // 1. 动态计算设备地址:提取第 9 位
    slave_addr = BASE_SLAVE_ADDR | ((data_add >> 8) & 0x01);

    // 2. 提取低 8 位内部地址
    word_addr = (rt_uint8_t)(data_add & 0xFF);

    // 3. 伪写操作:发送低 8 位内部地址定位光标
    rt_i2c_master_send(i2c1_bus, slave_addr, RT_NULL, &word_addr, 1);

    // 4. 连续读取操作
    i2c1_flag = (rt_uint8_t)rt_i2c_master_recv(i2c1_bus, slave_addr, RT_NULL, data, data_byte);

    if(i2c1_flag != data_byte){
        LOG_D("failed to i2c1 recv");
        return -1;
    }
    return 0;
}

at24c04.h

复制代码
#ifndef __AT24C04_H__
#define __AT24C04_H__

#include <rtthread.h>

/* ==========================================
 * AT24C04 硬件参数宏定义 (供上层应用参考)
 * ========================================== */
#define AT24C04_PAGE_SIZE   16      // AT24C04 的物理页大小为 16 字节
#define AT24C04_TOTAL_SIZE  512     // AT24C04 总容量为 512 字节
#define AT24C04_ADDR_MIN    0x000   // 最小存储地址
#define AT24C04_ADDR_MAX    0x1FF   // 最大存储地址

/* ==========================================
 * 函数声明
 * ========================================== */

/**
 * @brief  向 AT24C04 写入数据 (当前版本暂未包含跨页/卷回保护)
 * @param  data_add:  16位目标物理地址 (范围: 0x000 ~ 0x1FF)
 * @param  data:      要写入的纯数据缓冲区指针
 * @param  data_byte: 准备写入的字节总数
 * @retval 0:成功, -1:失败
 */
int write_i2c1reg(rt_uint16_t data_add, rt_uint8_t *data, rt_uint8_t data_byte);

/**
 * @brief  从 AT24C04 连续读取数据 (硬件支持自动跨块/跨页读取)
 * @param  data_add:  16位目标物理起始地址 (范围: 0x000 ~ 0x1FF)
 * @param  data:      用于存放读回数据的接收缓冲区指针
 * @param  data_byte: 准备读取的字节总数
 * @retval 0:成功, -1:失败
 */
int read_i2c1reg(rt_uint16_t data_add, rt_uint8_t *data, rt_uint8_t data_byte);

/**
 * @brief  AT24C04 自动分页写入函数 (防卷回高级封装)
 * @param  addr: 16位目标物理起始地址 (0x000 ~ 0x1FF)
 * @param  data: 要写入的数据缓冲区指针
 * @param  len:  要写入的总字节数 (支持任意长度,如 20、50、100 字节)
 * @retval 0:成功, -1:失败
 */
int at24c04_page_write(rt_uint16_t addr, rt_uint8_t *data, rt_uint16_t len);
#endif /* __AT24C04_H__ */

代码详解

1:对slave_addr = BASE_SLAVE_ADDR | ((data_add >> 8) & 0x01)详细解析

  • I2C 限制:I2C 协议每次发"内部地址"只能发 8 个 bit。

  • AT24C04 的办法:芯片规定,那多出来的第 9 位(最高位),不要放在内部地址里发,而是塞进 I2C 的设备地址的最末尾发过来。

    • 访问前半段(地址 0~255),最高位是 0,设备地址用 0x50

    • 访问后半段(地址 256~511),最高位是 1,设备地址用 0x51

场景 A:你想访问地址 0x05(在前半段 Block 0)

data_add 的值是 0x0005,二进制是:0000 0000 0000 0101

  • 第一步:右移 8 位 (data_add >> 8) 把二进制整体向右移动 8 个格子。 原数:0000 0000 0000 0101 移位后:0000 0000 0000 0000 目的:把高 8 位推到了低 8 位的位置,我们真正需要的那"第 9 位"现在来到了最低位(bit 0)。

  • 第二步:按位与 & 0x01 0x01 的二进制是 0000 0001。任何数和 1 做与运算保持原样,和 0 做与运算变成 0。 计算:0000 0000 & 0000 0001 = 0目的:屏蔽掉其他所有干扰位,只干干净净地保留最低位。此时结果为 0

  • 第三步:按位或 BASE_SLAVE_ADDR | ...0x50101 0000)和刚才的结果 0 进行或运算。 计算:101 0000 | 0000 0000 = 101 0000(即 0x50)。 结论:算出的设备地址是 0x50,芯片知道你要操作前半区。


场景 B:你想访问地址 0x105(在后半段 Block 1)

data_add 的值是 0x0105(即十进制的 261),二进制是:0000 0001 0000 0101。 (注意看,第 9 位是 1)

  • 第一步:右移 8 位 (data_add >> 8) 原数:0000 0001 0000 0101 移位后:0000 0000 0000 0001 那关键的"第 9 位"被移到了最右边。

  • 第二步:按位与 & 0x01 计算:0000 0001 & 0000 0001 = 1成功提取出了块选标志位 1

  • 第三步:按位或 BASE_SLAVE_ADDR | ...0x50101 0000)和 10000 0001)进行或运算。 计算:101 0000 | 0000 0001 = 101 0001(即 0x51)。 结论:算出的设备地址变成了 0x51,完美通知芯片切换到后半区!

2:send_buf = (rt_uint8_t *)rt_malloc(data_byte + 1);详细解释

rt_malloc:该函数将从内存堆上分配用户指定大小的内存块。

rt_i2c_master_send():该函数的api调用,只接受数据首地址指针,然后发送连续数据流。但是有效数据data与data_add存储地址,他们在单片机的内存存储区域是分离的,这时候我们需要把两者进行结合。

3:rt_i2c_master_send(i2c1_bus, slave_addr, RT_NULL, &word_addr, 1);详细解释

在 AT24C04 芯片内部,有一个专门的硬件寄存器,叫做内部数据地址指针。你可以把它想象成电脑屏幕上闪烁的光标。芯片不管读还是写,都只能在这个指针当前指向的位置进行。

word_addr是8位地址数据。

在at24c04中,只要是一次全新的写通信,收到的第 1 个字节,绝对是目标地址,必须把它装进的内部数据地址指针里。这时直接定位到该地址下。

4:rt_i2c_master_send(struct rt_i2c_bus_device *bus, rt_uint16_t addr, rt_uint16_t flags, const rt_uint8_t *buf, rt_uint32_t count);

|-------|-----------------------------------------------------------|
| bus | I2C 总线设备句柄 |
| addr | I2C 从设备地址 |
| flags | 标志位,可为上文提到的除 RT_I2C_WR RT_I2C_RD之外的其他标志位,可以进行 "|" 操作 |
| buf | 待发送数据缓冲区 |
| count | 待发送数据大小(单位:字节) |

|-----------|--------|
| 返回 | ------ |
| 消息数组的元素个数 | 成功 |
| 错误码 | 失败 |

5:rt_size_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus, rt_uint16_t addr, rt_uint16_t flags, rt_uint8_t *buf, rt_uint32_t count);

|-----------|-----------------------------------------------------------|
| bus | I2C 总线设备句柄 |
| addr | I2C 从设备地址 |
| flags | 标志位,可为上文提到的除 RT_I2C_WR RT_I2C_RD之外的其他标志位,可以进行 "|" 操作 |
| buf | 数据缓冲区 |
| count | 缓冲区大小(单位:字节,要大于等于最大接收到的数据长度) |
| 返回 | ------ |
| 消息数组的元素个数 | 成功 |
| 错误码 | 失败 |

6:at24c04_page_write 自动分页算法

假设你要写入 20 个字节,不同的起始地址会导致不同的切分策略:

场景 A:从页的首地址开始写(例如 addr = 0x0100

  1. 第一轮循环

    • 偏移量是 0x0100 % 16 = 0

    • 页内剩余空间是 16 - 0 = 16

    • 你要写 20 个字节,大于 16,所以本次只能写 16 字节

    • 底层发送 16 字节,然后延时 5ms。

    • 更新状态:地址变成 0x0110,剩余要写 4 字节。

  2. 第二轮循环

    • 偏移量是 0x0110 % 16 = 0

    • 页内剩余空间是 16。

    • 你还剩 4 个字节,小于 16。所以本次将剩下的 4 字节一次性写入。

    • 延时 5ms,结束。

    • 结果:20 字节被切分成了 [16] + [4] 两次发送,完美避开卷回。

场景 B:从页的中间开始写(例如 addr = 0x0105

  1. 第一轮循环

    • 偏移量是 0x0105 % 16 = 5

    • 页内剩余空间是 16 - 5 = 11

    • 要写 20 字节,大于 11。所以先把当前页填满,写 11 字节 (一直写到 0x010F 边界)。

    • 延时 5ms。

    • 更新状态:地址变成 0x0110(精准落入下一页开头),剩余 9 字节。

  2. 第二轮循环

    • 偏移量是 0,剩余空间是 16。

    • 要写 9 字节,全部写入。

    • 延时 5ms,结束。

    • 结果:20 字节被切分成了 [11] + [9] 两次发送,完美避开卷回。

main.c

cs 复制代码
#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

// 引入我们刚才写好的 AT24C04 驱动头文件
#include "at24c04.h"

int main(void)
{
    // 1. 准备要写入的测试数据 (5个字节)
    rt_uint8_t write_buf[20] = {
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
            0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14
                                };
    // 2. 准备接收缓冲区 (注意:长度必须大于等于要读取的长度,这里设为 5)
    rt_uint8_t read_buf[20] = {0};

    // 3. 设定测试的物理地址
    // 我们故意选 0x105 (十进制的 261),测试底层驱动是否能自动切换到设备地址 0x51
    rt_uint16_t test_addr = 0x0105;

    LOG_D("=== AT24C04 Read/Write Test Start ===");

    // --------------------------------------------------------
    // 第一步:执行写入操作
    // --------------------------------------------------------
    LOG_D("Writing 5 bytes to address 0x%03X...", test_addr);


    //write_i2c1reg(test_addr, write_buf, 20)
    at24c04_page_write(test_addr, write_buf, 20);


    // 【极度重要】每次写完必须延时!留给 EEPROM 内部 5ms 的 Flash 烧写时间。
    // 这里给 50ms 比较宽裕,保证绝对稳定。
    rt_thread_mdelay(50);

    // --------------------------------------------------------
    // 第二步:执行读取操作
    // --------------------------------------------------------
    LOG_D("Reading 5 bytes from address 0x%03X...", test_addr);
    if (read_i2c1reg(test_addr, read_buf, 20) == 0)
    {
        LOG_D("Read command successful. Data is:");
        // 将读回来的数据打印到串口终端
        for(int i = 0; i < 20; i++)
        {
            rt_kprintf("  -> read_buf[%d]: 0x%02X\n", i, read_buf[i]);
        }
    }
    else
    {
        LOG_E("Read command failed!");
    }

    // --------------------------------------------------------
    // 第三步:数据自动校验
    // --------------------------------------------------------
    int is_match = 1;
    for(int i = 0; i < 20; i++)
    {
        if(write_buf[i] != read_buf[i])
        {
            is_match = 0;
            break; // 只要有一个字节对不上,就判为失败
        }
    }

    if(is_match)
    {
        LOG_D("=== Test PASS! Data matched perfectly. ===");
    }
    else
    {
        LOG_E("=== Test FAIL! Data mismatch detected. ===");
    }

    // --------------------------------------------------------
    // 第四步:线程挂起 (RTOS 规范)
    // --------------------------------------------------------
    // main 本质上也是一个线程,不能让它执行完退出。
    // 用死循环配合延时,交出 CPU 控制权给其他任务。
    while (1)
    {
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

采用了自动分页算法

没采用自动分页,前4个数据会被覆盖。

如果我现在要使用硬件iic如何操作:

关闭软件iic

进入cubemx里面配置iic

配置成功之后会:自动取消注释

配置成功之后:把void HAL_I2C_MspInit(I2C_HandleTypeDef* i2cHandle)完整的函数全部复制到board.c里面。

这个函数的作用是:开启 I2C1 外设时钟、配置 PB8/PB9 的复用功能为 I2C1、开启上拉电阻,正好补全 RT-Thread 硬件 I2C 驱动需要的底层引脚初始化。

改成: GPIO_InitStruct.Pull = GPIO_PULLUP;

打开board.h进行引脚修改:

复制代码
#define BSP_USING_HARD_I2C1
#ifdef BSP_USING_HARD_I2C1
#define BSP_I2C1_SCL_PIN    GET_PIN(B, 8)
#define BSP_I2C1_SDA_PIN    GET_PIN(B, 9)

#define I2C1_BUS_CONFIG  \
{                        \
    .name = "i2c1",      \
    .Instance = I2C1,    \
    .evirq_type = I2C1_EV_IRQn, \
    .erirq_type = I2C1_ER_IRQn, \
    .timeout = 100,      \
}
#endif  // 这个#endif 严格匹配上面的#ifdef,绝对不多不少!

直接改成跟我一样的

我使用的是4.1.1版本,在drivers里面没有硬件支持包,我们直接添加:

https://gitee.com/rtthread/rt-thread/tree/v5.2.2/bsp/stm32/libraries/HAL_Drivers/drivers

把文件下载,然后拷贝这两个文件去我们的drivers里面

打开drv_hard_i2c.c,在最顶部添加这 2 行头文件:

复制代码
#include <rtthread.h>
#include <rtdevice.h>  // 解决 rt_ssize_t / RT_EFAULT 报错

搜索return -RT_EFAULT;,全部改成

复制代码
return -RT_ERROR;

在drv_hard_i2c.c搜索:

复制代码
static rt_ssize_t stm32_i2c_master_xfer(struct rt_i2c_bus_device *bus,

改成:

复制代码
static rt_size_t stm32_i2c_master_xfer(struct rt_i2c_bus_device *bus,

main函数可以改一下:

cs 复制代码
#include <rtthread.h>

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

// 引入我们刚才写好的 AT24C04 驱动头文件
#include "at24c04.h"

#include <rtdevice.h> // 必须包含这个头文件

int test_print_devices(void)
{
    rt_kprintf("=== Registered Devices ===\n");

    // 遍历系统设备链表并打印
    struct rt_device *dev;
    dev = rt_device_find("uart1"); // 先找个你肯定有的设备测试
    if (dev != RT_NULL)
    {
        rt_kprintf("Found: uart1\n");
    }

    dev = rt_device_find("i2c1"); // 重点找 i2c1
    if (dev != RT_NULL)
    {
        rt_kprintf("Found: i2c1  <-- 成功!硬件 I2C 已注册!\n");
    }
    else
    {
        rt_kprintf("Error: i2c1 NOT FOUND!  <-- 硬件 I2C 驱动没跑起来!\n");
    }

    rt_kprintf("=== End of Device List ===\n");
    return 0;
}
INIT_APP_EXPORT(test_print_devices);

int main(void)
{
    // 1. 准备要写入的测试数据 (5个字节)
    rt_uint8_t write_buf[20] = {
            0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
            0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14
                                };
    // 2. 准备接收缓冲区 (注意:长度必须大于等于要读取的长度,这里设为 5)
    rt_uint8_t read_buf[20] = {0};

    // 3. 设定测试的物理地址
    // 我们故意选 0x105 (十进制的 261),测试底层驱动是否能自动切换到设备地址 0x51
    rt_uint16_t test_addr = 0x0105;

    LOG_D("=== AT24C04 Read/Write Test Start ===");

    // --------------------------------------------------------
    // 第一步:执行写入操作
    // --------------------------------------------------------
    LOG_D("Writing 5 bytes to address 0x%03X...", test_addr);


    //write_i2c1reg(test_addr, write_buf, 20);
    LOG_D("Writing 20 bytes to address 0x%03X...", test_addr);
    if (at24c04_page_write(test_addr, write_buf, 20) != 0)
    {
        LOG_E("Write failed!");
        while(1);
    }
    LOG_D("Write success!");

    // 【极度重要】每次写完必须延时!留给 EEPROM 内部 5ms 的 Flash 烧写时间。
    // 这里给 50ms 比较宽裕,保证绝对稳定。
    rt_thread_mdelay(50);

    // --------------------------------------------------------
    // 第二步:执行读取操作
    // --------------------------------------------------------
    LOG_D("Reading 5 bytes from address 0x%03X...", test_addr);
    if (read_i2c1reg(test_addr, read_buf, 20) == 0)
    {
        LOG_D("Read command successful. Data is:");
        // 将读回来的数据打印到串口终端
        for(int i = 0; i < 20; i++)
        {
            rt_kprintf("  -> read_buf[%d]: 0x%02X\n", i, read_buf[i]);
        }
    }
    else
    {
        LOG_E("Read command failed!");
    }

    // --------------------------------------------------------
    // 第三步:数据自动校验
    // --------------------------------------------------------
    int is_match = 1;
    for(int i = 0; i < 20; i++)
    {
        if(write_buf[i] != read_buf[i])
        {
            is_match = 0;
            break; // 只要有一个字节对不上,就判为失败
        }
    }

    if(is_match)
    {
        LOG_D("=== Test PASS! Data matched perfectly. ===");
    }
    else
    {
        LOG_E("=== Test FAIL! Data mismatch detected. ===");
    }

    // --------------------------------------------------------
    // 第四步:线程挂起 (RTOS 规范)
    // --------------------------------------------------------
    // main 本质上也是一个线程,不能让它执行完退出。
    // 用死循环配合延时,交出 CPU 控制权给其他任务。
    while (1)
    {
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

然后工程清除一下,进行编译

相关推荐
数智化管理手记1 小时前
精益生产中的TPM管理是什么?一文破解设备零故障的密码
服务器·网络·数据库·低代码·制造·源代码管理·精益工程
翊谦1 小时前
Java Agent开发 Milvus 向量数据库安装
java·数据库·milvus
難釋懷2 小时前
OpenResty实现Redis查询
数据库·redis·openresty
别抢我的锅包肉3 小时前
【MySQL】第四节 - 多表查询、多表关系全解析
数据库·mysql·datagrip
Database_Cool_3 小时前
OpenClaw-Observability:基于 DuckDB 构建 OpenClaw 的全链路可观测体系
数据库·阿里云·ai
刘~浪地球3 小时前
Redis 从入门到精通(五):哈希操作详解
数据库·redis·哈希算法
zzh0814 小时前
MySQL高可用集群笔记
数据库·笔记·mysql
Shely20174 小时前
MySQL数据表管理
数据库·mysql
爬山算法4 小时前
MongoDB(80)如何在MongoDB中使用多文档事务?
数据库·python·mongodb
APguantou4 小时前
NCRE-三级数据库技术-第2章-需求分析
数据库·需求分析