基于stm32cubemx读写EEPROM实验(AT24C02)

EEPROM (Electrically Erasable Programmable read only memory)是指带电可擦可编程只读存储器。是一种掉电后数据不丢失的存储芯片。

1AT24C02原理图

  • A0,A1,A2:硬件地址引脚

  • WP:写保护引脚,接高电平只读,接地允许读和写

  • SCL和SDA:IIC总线

24C02后面的 02 表示的是可存储 2Kbit 的数据,转换为字节的存储量为2*1024/8 = 256byte;256个字节一共分为32页,每页8个字节。

2设备地址

在AT24C02的参考手册第9页 可以看到下图

AT24C设备地址为如下:

  • 前四位固定 1010

  • A2、A1、A0为由管脚电平决定。此处原理图都接地,默认为000。

  • 最后一位表示读操作还是写操作。读地址为0xA1,写地址为0xA0。

结论:

  • 写24C02的时候,从器件地址为10100000(0xA0)

  • 读24C02的时候,从器件地址为10100001(0xA1)

片内地址寻址:

芯片寻址可对 内部256个字节 中的任一个进行 读/写操作,其寻址范围为00~FF,共256个寻址单位。

3 读写时序

写一个字节

从时序图上可以看出(上面是MCU的信号,下面是存储芯片的信号),写一个字节数据的操作顺序为:

1.MCU 先给芯片发送一个开始信号(START)

2.开始信后之后的第一个字节,发送要写入的设备地址(DEVICE ADDRESS)(注意,因为总线上可能由多个设备,是根据设备地址选择不同的设备的),然后发送写数据命令(0xA0),然后等待应答信号(ACK)

3.发送数据的存储地址。一共有256个字节的存储空间,地址从0x00~0xFF,想存到哪个地址,就发哪个地址

4.发送要存储的数据,发送完成之后MCU会收到应答信号

5.数据发送完成之后,发送结束信号(STOP)停止总线。

读一个字节

从时序图上可以看出(上面是MCU的信号,下面是存储芯片的信号),读一个字节数据的操作顺序为:

1.MCU 先给芯片发送一个开始信号(START)

2.开始信号之后的第一个字节,发送要读取的设备地址(DEVICE ADDRESS)(注意,因为总线上可能由多个设备,是根据设备地址选择不同的设备的),然后发送写数据命令(0xA0),并发送要读取的数据地址(WORD ADDRESS),然后等待应答信号(ACK)

3.再次发送开始信号(START)

4.开始信号之后的第一个字节,发送要读取的设备地址(DEVICE ADDRESS),发送读取数据命令(0xA1)

5.此时,24C02会自动给MCU发送数据。

6.MCU发送结束信号(STOP)停止总线。

写一页数据

时序图和写单个字节差不多,只是每个字节写完之后存储器都会给MCU发送应答信号,之后继续发送下一个字节,写完之后,MCU发送停止信号即可。

256个字节一共分为32页,每页8个字节。AT24C02页写入只支持8个byte,所以需要分32次写入。如果按照上述时序连续写入8个字节后,会重复的继续往该页写数据。(当然也可以 往256个地址中分别写入一个字节。。。)

连续读数据

时序图和都单个字节差不多,存储器给MCU发送完每个字节,MCU要发送应答信号给存储器,直到MCU发送停止信号。且读数据没有8个字节的限制。

4.代码部分

虽然时序看起来很复杂,但是不用担心,很多都已经有实现了。

在生成的工程中,打开stm32f4xx_hal.h,可以看到已经生成了轮询,中断和DMA三种控制方式的代码。

我们只看轮询的,其他的也都差不多,只是应用场景不一样。

// 作为主机 发送数据
// 参数:iic接口、设备地址、发送的数据、数据长度、超时时间
HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 作为主机 接收数据
// 参数:iic接口、设备地址、存储读取到的数据、数据长度、超时时间
HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 作为从机 发送数据
// 参数:iic接口、设备地址、发送的数据、数据长度、超时时间
HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 作为从机 接收数据
// 参数:iic接口、设备地址、存储读取到的数据、数据长度、超时时间
HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);


HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
/* 第1个参数为I2C操作句柄          hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c1   
   第2个参数为从机设备地址         DevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0
   第3个参数为从机寄存器地址       MemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1
   第4个参数为从机寄存器地址长度  写入数据的字节类型 8位还是16位  I2C_MEMADD_SIZE_8BIT   I2C_MEMADD_SIZE_16BIT
   第5个参数为发送的数据的起始地址  
   第6个参数为传输数据的大小
   第7个参数为操作超时时间 */



// 直接发送两个字节数据,并接受数据(就用于我们现在的情况,发送命令 + 接收数据)
// 参数:iic接口、设备地址、发送的数据1、发送的数据2、存储读取到的数据、数据长度、超时时间
HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 查询设备是否就绪
HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);
//例子 while (HAL_I2C_IsDeviceReady(&I2C_Handle, EEPROM_ADDRESS,EEPROM_MAX_TRIALS, I2Cx_TIMEOUT_MAX) == HAL_TIMEOUT);

我们可以直接看HAL_I2C_Mem_WriteHAL_I2C_Mem_Read,刚好可以满足我们这里需要发送 指令 + 地址 的情况,其在发送或者读取数据的过程中,地址还可以自己增加,很方便。

i2c.h中声明如下代码

/* USER CODE BEGIN Prototypes */

#define        AT24C02_ADDR_WRITE          0xA0    // 写命令
#define        AT24C02_ADDR_READ           0xA1    // 读命令
uint8_t At24c02_Write_Byte(uint16_t addr, uint8_t* dat);
uint8_t At24c02_Read_Byte(uint16_t addr, uint8_t* read_buf);
uint8_t At24c02_Write_Amount_Byte(uint16_t addr, uint8_t* dat, uint16_t size);
uint8_t At24c02_Read_Amount_Byte(uint16_t addr, uint8_t* recv_buf, uint16_t size);

/* USER CODE END Prototypes */

i2c.c中添加如下代码

/* USER CODE BEGIN 1 */

#include <string.h>

/**
 * @brief        AT24C02任意地址写一个字节数据
 * @param        addr —— 写数据的地址(0-255)
 * @param        dat  —— 存放写入数据的地址
 * @retval       成功 —— HAL_OK
*/
uint8_t At24c02_Write_Byte(uint16_t addr, uint8_t* dat)
{
    HAL_StatusTypeDef result;
    result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, 1, 0xFFFF);
    HAL_Delay(5);    // 写一个字节,延迟一段时间,不能连续写
    return result;
}

/**
 * @brief        AT24C02任意地址读一个字节数据
 * @param        addr —— 读数据的地址(0-255)
 * @param        read_buf —— 存放读取数据的地址
 * @retval       成功 —— HAL_OK
*/
uint8_t At24c02_Read_Byte(uint16_t addr, uint8_t* read_buf)
{
    return HAL_I2C_Mem_Read(&hi2c1, AT24C02_ADDR_READ, addr, I2C_MEMADD_SIZE_8BIT, read_buf, 1, 0xFFFF);
}

/**
 * @brief        AT24C02任意地址连续写多个字节数据
 * @param        addr —— 写数据的地址(0-255)
 * @param        dat  —— 存放写入数据的地址
 * @retval       成功 —— HAL_OK
*/
uint8_t At24c02_Write_Amount_Byte(uint16_t addr, uint8_t* dat, uint16_t size)
{
    uint8_t i = 0;
    uint16_t cnt = 0;        // 写入字节计数
    HAL_StatusTypeDef result;    // 返回是否写入成功

    /* 对于起始地址,有两种情况,分别判断 */
    if(0 == addr % 8)
    {
        /* 起始地址刚好是页开始地址 */
        /* 对于写入的字节数,有两种情况,分别判断 */
        if(size <= 8)
        {
            // 写入的字节数不大于一页,直接写入
            result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, size, 0xFFFF);
            HAL_Delay(20);    // 写完八个字节(最多八个字节),延迟久一点
            return result;
        }
        else
        {
            // 写入的字节数大于一页,先将整页循环写入
            for(i = 0; i < size/8; i++)
            {
                HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], 8, 0xFFFF);
                // 一次写入了八个字节,延迟久一点
                HAL_Delay(20);    // 写完八个字节,延迟久一点
                addr += 8;
                cnt += 8;
            }
            // 将剩余的字节写入
            result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], size - cnt, 0xFFFF);
            HAL_Delay(20);    // 写完八个字节(最多八个字节),延迟久一点
            return result;
        }
    }
    else
    {
        /* 起始地址偏离页开始地址 */
        /* 对于写入的字节数,有两种情况,分别判断 */
        if(size <= (8 - addr%8))
        {
            /* 在该页可以写完 */
            result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, size, 0xFFFF);
            HAL_Delay(20);    // 写完八个字节(最多八个字节),延迟久一点
            return result;
        }
        else
        {
            /* 该页写不完 */
            // 先将该页写完
            cnt += 8 - addr%8;
            HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, cnt, 0xFFFF);
            HAL_Delay(20);    // 写完八个字节(最多八个字节),延迟久一点
            addr += cnt;

            // 循环写整页数据
            for(i = 0;i < (size - cnt)/8; i++)
            {
                HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], 8, 0xFFFF);
                HAL_Delay(20);    // 写完八个字节,延迟久一点
                addr += 8;
                cnt += 8;
            }
            // 将剩下的字节写入
            result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], size - cnt, 0xFFFF);
            HAL_Delay(20);    // 写完八个字节(最多八个字节),延迟久一点
            return result;
        }            
    }
}

/**
 * @brief        AT24C02任意地址连续读多个字节数据
 * @param        addr —— 读数据的地址(0-255)
 * @param        dat  —— 存放读出数据的地址
 * @retval       成功 —— HAL_OK
*/
uint8_t At24c02_Read_Amount_Byte(uint16_t addr, uint8_t* recv_buf, uint16_t size)
{
    return HAL_I2C_Mem_Read(&hi2c1, AT24C02_ADDR_READ, addr, I2C_MEMADD_SIZE_8BIT, recv_buf, size, 0xFFFF);
}

/* USER CODE END 1 */

main.c中添加如下代码

int main(void)
{
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_USART1_UART_Init();

    // 单个字节 读写测试
    uint8_t simple_write_dat = 0xa5;    // 一个字节
    uint8_t simple_recv_buf = 0;

    if(HAL_OK == At24c02_Write_Byte(10, &simple_write_dat)){
        printf("Simple data write success \r\n");
    } else {
        printf("Simple data write fail \r\n");
    }
    
    HAL_Delay(50);      // 写一次和读一次之间需要短暂的延时
    
    if(HAL_OK == At24c02_Read_Byte(10, &simple_recv_buf)){
        printf("Simple data read success, recv_buf = 0x%02X \r\n", simple_recv_buf);
    } else {
        printf("Simple data read fail \r\n");
    }
    printf("--------------------- \r\n");
    // 单个字节读写 测试结束
    
    // 浮点数 读写测试
    union float_union{
        float float_write_dat;        // 浮点数占4个字节
        double double_write_dat;    // 双精度浮点数占8个字节
        uint8_t buf[8];                // 定义 8个字节 的空间
    };
    union float_union send_float_data;    // 用来发送
    union float_union rev_float_data;    // 用来接收
    
    // 先测试第一个 浮点数
    send_float_data.float_write_dat = 3.1415f;
    if(HAL_OK == At24c02_Write_Amount_Byte(20, send_float_data.buf, 4)){
        printf("Float data write success \r\n");
    } else {
        printf("Float data write fail \r\n");
    }
    HAL_Delay(50);
    if(HAL_OK == At24c02_Read_Amount_Byte(20, rev_float_data.buf, 4)){
        // 默认输出六位小数
        printf("Float data read success, recv_buf = %f \r\n", rev_float_data.float_write_dat);
    } else {
        printf("Float data read fail \r\n");
    }
    // 测试第二个 双精度浮点数
    send_float_data.double_write_dat = 3.1415f;
    if(HAL_OK == At24c02_Write_Amount_Byte(20, send_float_data.buf, 8)){
        printf("Double data write success \r\n");
    } else {
        printf("Double data write fail \r\n");
    }
    HAL_Delay(50);
    if(HAL_OK == At24c02_Read_Amount_Byte(20, rev_float_data.buf, 8)){
        // 最多15位小数
        printf("Double data read success, recv_buf = %.15f \r\n", rev_float_data.double_write_dat);
    } else {
        printf("Double data read fail \r\n");
    }
    printf("--------------------- \r\n");
    // 浮点数读写测试 测试结束
  
    // 连续数据读写测试
    uint8_t write_dat[22] = {0};        // 22个字节
    uint8_t recv_buf[22] = {0};
    
    printf("正在往数组中填充数据... \r\n");
    for(int i = 0; i < 22; i++){
        write_dat[i] = i;
        printf("%02X ", write_dat[i]);
    }
    printf("\r\n 数组中数据填充完毕... \r\n");
    
    if(HAL_OK == At24c02_Write_Amount_Byte(0, write_dat, 22)){
        printf("24c02 write success \r\n");
    } else {
        printf("24c02 write fail \r\n");
    }
    
    HAL_Delay(50);      // 写一次和读一次之间需要短暂的延时
    
    if(HAL_OK == At24c02_Read_Amount_Byte(0, recv_buf, 22)){
        printf("read success \r\n");
        for(int i = 0; i < 22; i++) {
            printf("0x%02X ", recv_buf[i]);
        }
    } else {
        printf("read fail\r\n");
    }
    // 连续数据读写 测试结束
  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }

为每次发送或者接收只能按照一个字节的单位进行,因此对于 uint8_t 类型的整数没什么问题。但是对于浮点数等,占用多个字节的,就可以通过共用体(union)的方式进行。

此方法在许多应用场景中都有应用,比如串口发送浮点数,也可以用这样的方式进行。

5.效果验证

编译、烧录

用串口助手观察现象。

  • 4
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
在使用HAL库进行STM32的IIC读写AT24C02时,你可以参考以下步骤: 1. 首先,你需要在你的工程中包含AT24CXX.c文件,并在代码中引用该文件。该文件中定义了一些常量和函数,用于初始化AT24CXX芯片、写入数据和读取数据等操作。 2. 在AT24CXX.c文件中,你可以看到定义了一些常量,如AT24C02的地址为255。这些常量可以根据你所使用的芯片型号进行修改。 3. 在AT24CXX.c文件中,还定义了一些函数,如AT24CXX_Init()用于初始化AT24CXX芯片,AT24CXX_Write()用于写入数据,AT24CXX_Read()用于读取数据,AT24CXX_Check()用于检查AT24CXX芯片是否正常工作。你可以根据需要调用这些函数来实现对AT24C02读写操作。 4. 在配置STM32的引脚时,你需要将IIC的引脚与AT24C02芯片的引脚相连接。具体的引脚配置可以参考AT24CXX.c文件中的注释。 5. 在配置串口时,你可以选择使用串口进行数据查看,以便调试和验证读写操作的结果。 6. 最后,根据你的需求选择适当的时钟频率,生成Keil工程代码。 综上所述,你可以使用HAL库的函数和AT24CXX.c文件中定义的函数来实现对AT24C02芯片的读写操作。 #### 引用[.reference_title] - *1* *3* [STM32 (基于HAL库) 硬件IIC读写任意AT24CXX芯片](https://blog.csdn.net/weixin_56565733/article/details/124401443)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [STM32系列(HAL库)——F103C8T6通过IIC/I2C方式读写AT24C02—(EEPROM 存储模块)](https://blog.csdn.net/lwb450921/article/details/124394615)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值