[APM32F4] APM32F427的I2C驱动(支持AT24CXX全系列驱动)

[复制链接]
31|0
口天土立口 发表于 2025-11-17 09:25 | 显示全部楼层 |阅读模式
本帖最后由 口天土立口 于 2025-11-17 09:28 编辑

#申请原创# #技术资源#
@21小跑堂

1. 外设介绍

本驱动测试通过的AT24CXX型号如下:
AT24C01AT24C02AT24C04AT24C08AT24C16AT24C32AT24C64AT24C128AT24C256AT24C512

3913691a7681e7652.png
APM32F427拥有3I2C外设,且最大支持到400Kbps的通讯速率。

I2C总线的几个信号:
起始信号->SCL稳定在高电平期间,SDA产生一个下降沿;
停止信号->SCL稳定在高电平期间,SDA产生一个上升沿;
10502691a779383169.png
应答信号->发送方在1个字节发送完成后,释放SDA线(输出变为输入),接收方在第9个时钟脉冲期间,拉低SDA线,表示接收到数据,否则为非应答信号;
98807691a779ee08a1.png

I2C总线的SCLSDA线需配置为开漏模式,同时外部需接上拉电阻,以实现输出高电平;
同时,SCLSDA线的开漏模式配置,得以实现SCL的同步和SDA的仲裁。


SCL线的同步:
数据只在SCL的高电平期间有效,因此需要一个确定的时钟进行逐位仲裁;多主机同时发起通讯时,多个主机均会产生自己的时钟脉冲,因SCL开漏产生的线与关系,SCL的低电平时长由最长SCL低电平的主机决定,而先将SCL切换为高电平的主机并不能改变SCL的状态;当所有主机都将SCL拉为高电平后,SCL线状态才切为高电平,同时最开始将SCL线切为低电平的主机首先将SCL拉为低电平状态;如此往复,确定了SCL线的低电平和高电平时长切换;

SDA线的仲裁:
主机只能在总线空闲的时侯启动传输多个主机可能在起始条件的最小持续时间内产生一个起始条件结果在总线上产生一个规定的起始条件SDA线的仲裁,发生在SCL的高电平期间。因SDA开漏产生线与的关系,先将SDA拉为高电平的主机,输掉仲裁,切为从机模式,丢失仲裁的主机可以产生时钟脉冲直到丢失仲裁的该字节末尾。仲裁可以持续多位,首先比较地址位,然后数据位,响应位。I2C总线的地址和数据信息由赢得仲裁的主机决定,在仲裁过程中不会丢失信息。

用时钟同步机制作为握手
在字节级的快速传输中器件可以快速接收数据字节但需要更多时间保存接收到的字节或准备另一个要发送的字节然后从机以一种握手过程在接收和响应一个字节后使 SCL 线保持低电平迫使主机进入等待状态直到从机准备好下一个要传输的字节在位级的快速传输中器件可以通过延长每个时钟的低电平周期减慢总线时钟从而任何主机的速度都可以适配这个器件的内部操作速率

AT24CXX介绍:
AT24CXXEEPROM,使用I2C进行通讯,最高频率可达1MHz。各型号AT24CXX的信息对比如下:
7513691a77ca17a2b.png

AT24CXX的设备地址,高4bits固定为0xA,低3位为A0/A1/A2,硬件拉高为1,拉低为0,但AT24C04AT24C08AT24C16三个型号的情况不同,这三个型号的页写内部自增位数为4bits,但页数量对应的数据位数分别为5/6/7,总共需要的存储地址位数分别为9/10/11,但实际I2C通讯时,只能传8bits的存储地址,因此,AT24C04占用A0补充缺少的最高1位存储地址,AT24C08占用A0/A1补充缺少的最高2位存储地址,AT24C16占用A0/A1/A2补充缺少的最高3位存储地址,所以导致同一条I2C总线挂载的AT24C04数量最多为2^2个(即4个),AT24C08数量最多为2^1个(即2个),AT24C16数量最多为2^0个(即1个),其他型号的AT24CXX不占用A0/A1/A2作为存储地址,所以最多都能挂载2^3个(即8个)。
20657691a77e2374e5.png

注意:
1. AT24CXX按页写时,内部自增位数到达最大值,会回环为0,即又回到当前页的起始地址处继续写入数据。所以每次开始写数据时,都需要计算当前开始写数据的地址,在当前页剩余可写入的数据量。
2. 每次写入完成当前页的数据,均需等待AT24CXX完成存储数据,否则过快的连续写入,将导致AT24CXX无法正确保存接收到的数据。具体需间隔多长时间才能发起新的写入时序,需查看对应厂商的芯片手册。

2. 硬件
APM32F427ZG TINY


3. 驱动介绍
AT24CXXI2C驱动,按照芯片手册实现读写时序即可。其中AT24C01~AT24C16Word Address片内存储地址,只需发送1个字节,另外,AT24C04~AT24C16Device Address包含片内的存储地址。
77436691a78032dd8c.png
36450691a780780234.png

硬件I2C的初始化位于bsp_i2c_hardware.c文件内,注意需把硬件配置为开漏模式。
  1. /*
  2. * @brief       引脚初始化
  3. *
  4. * @param       None
  5. *
  6. * @retval      None
  7. *
  8. */
  9. static void bsp_i2c_gpio_init(void)
  10. {
  11.     GPIO_Config_T gpioConfig;
  12.    
  13.     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);
  14.    
  15.     /*
  16.      * SCL -> PB6
  17.      * SDA -> PB7
  18.      */
  19.     GPIO_ConfigStructInit(&gpioConfig);
  20.     gpioConfig.pin     = GPIO_PIN_6;
  21.     gpioConfig.mode    = GPIO_MODE_AF;
  22.     gpioConfig.otype   = GPIO_OTYPE_OD;
  23.     gpioConfig.speed   = GPIO_SPEED_50MHz;
  24.     gpioConfig.pupd    = GPIO_PUPD_NOPULL;
  25.     GPIO_Config(GPIOB, &gpioConfig);
  26.     gpioConfig.pin     = GPIO_PIN_7;
  27.     GPIO_Config(GPIOB, &gpioConfig);
  28.    
  29.     GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_6, GPIO_AF_I2C1);
  30.     GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_7, GPIO_AF_I2C1);   
  31. }

  32. /*
  33. * @brief       I2C初始化
  34. *
  35. * @param       dev_id: 自身设备ID
  36. *               speed: 速度
  37. *
  38. * @retval      None
  39. *
  40. */
  41. void bsp_i2c_init(uint8_t dev_id, uint32_t speed)
  42. {
  43.     I2C_Config_T i2cConfig;
  44.    
  45.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_I2C1);
  46.     bsp_i2c_gpio_init();   
  47.    
  48.     I2C_Reset(I2C_INS);
  49.     I2C_ConfigStructInit(&i2cConfig);
  50.     i2cConfig.clockSpeed = speed;
  51.     i2cConfig.mode = I2C_MODE_I2C;
  52.     i2cConfig.dutyCycle = I2C_DUTYCYCLE_2;
  53.     i2cConfig.ownAddress1 = dev_id;
  54.     i2cConfig.ack = I2C_ACK_ENABLE;
  55.     i2cConfig.ackAddress = I2C_ACK_ADDRESS_7BIT;
  56.     I2C_Config(I2C_INS, &i2cConfig);
  57.     I2C_Enable(I2C_INS);
  58. }

软件I2C只需把引脚按照IO输出配置即可,同时引脚模式需配置为开漏模式,而时钟频率的控制,更改bsp_i2c_timing_delay函数内的延时数量即可。后续各个I2C信号(起始信号、停止信号、应答信号)按照I2C的要求产生即可。
  1. /*
  2. * @brief       延时
  3. *
  4. * @param       None
  5. *
  6. * @retval      None
  7. *
  8. */
  9. static void bsp_i2c_timing_delay(void)
  10. {
  11.     /* 调整此处的数值,以控制通讯频率 */
  12.     volatile uint32_t d = 200;   
  13.     while (--d);
  14. }

  15. /*
  16. * @brief       引脚初始化
  17. *
  18. * @param       None
  19. *
  20. * @retval      None
  21. *
  22. */                  
  23. void bsp_i2c_timing_io_init(void)
  24. {
  25.     GPIO_Config_T gpioConfig;
  26.    
  27.     GPIO_CLK_ENABLE;   
  28.     GPIO_ConfigStructInit(&gpioConfig);   
  29.     gpioConfig.pin     = SCL_PIN;
  30.     gpioConfig.mode    = GPIO_MODE_OUT;
  31.     gpioConfig.otype   = GPIO_OTYPE_OD;
  32.     gpioConfig.speed   = GPIO_SPEED_50MHz;
  33.     gpioConfig.pupd    = GPIO_PUPD_NOPULL;
  34.     GPIO_Config(SCL_PORT, &gpioConfig);
  35.    
  36.     gpioConfig.pin     = SDA_PIN;
  37.     GPIO_Config(SDA_PORT, &gpioConfig);
  38.    
  39.     bsp_i2c_timing_stop();
  40. }

AT24CXX的读时序,无需特意处理页的问题,AT24CXX会持续在主机的SCL信号到来时,返回下一个地址的数据,直到主机发起停止信号。注意:读时序,需先发起一个写地址时序,然后通过停止信号结束,再重复发起读时序,此时可直接从AT24CXX内读出数据。
  1. /*
  2. * @brief       读数据通用接口
  3. *
  4. * @param       dev_addr: 设备地址
  5. *              m_addr: 存储地址
  6. *              buf: 数据缓存
  7. *              size: 数据大小
  8. *              is_16bit_m: 16位存储
  9. *
  10. * @retval      读取数据量
  11. *
  12. */
  13. static uint32_t bsp_at24cxx_hardware_read_common(uint8_t dev_addr,
  14.                     uint8_t m_addr, uint8_t *buf, uint32_t size, uint8_t is_16bit_m)
  15. {
  16.     uint32_t read_bytes = 0;   
  17.     uint32_t iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  18.    
  19.     /* 等待IIC空闲 */
  20.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  21.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BUSBSY) == SET) {
  22.         if ((iic_timeout--) == 0) {
  23.             I2C_EnableGenerateStop(I2C_INS);
  24.             return read_bytes;
  25.         }
  26.     }
  27.    
  28.     /* 起始信号 */
  29.     I2C_EnableGenerateStart(I2C_INS);
  30.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  31.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_START) != SET) {
  32.         if ((iic_timeout--) == 0) {
  33.             I2C_EnableGenerateStop(I2C_INS);
  34.             return read_bytes;
  35.         }
  36.     }
  37.    
  38.     /* 设备地址 */
  39.     I2C_Tx7BitAddress(I2C_INS, dev_addr, I2C_DIRECTION_TX);
  40.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  41.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_ADDR) == RESET) {
  42.         if ((iic_timeout--) == 0) {
  43.             I2C_EnableGenerateStop(I2C_INS);
  44.             return read_bytes;
  45.         }
  46.     }
  47.     /* 确认设备是发送器模式(写) */
  48.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  49.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TR) == RESET) {
  50.         if ((iic_timeout--) == 0) {
  51.             I2C_EnableGenerateStop(I2C_INS);
  52.             return read_bytes;
  53.         }
  54.     }
  55.    
  56.     /* 发送存储地址 */
  57.     if (is_16bit_m == 0) {
  58.         I2C_TxData(I2C_INS, m_addr);
  59.         iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  60.         while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
  61.             if ((iic_timeout--) == 0) {
  62.                 I2C_EnableGenerateStop(I2C_INS);
  63.                 return read_bytes;
  64.             }
  65.         }
  66.     } else {
  67.         /* 高8bits */
  68.         I2C_TxData(I2C_INS, ((m_addr >> 8) & 0xFF));
  69.         iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  70.         while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TXBE) == RESET) {
  71.             if ((iic_timeout--) == 0) {
  72.                 I2C_EnableGenerateStop(I2C_INS);
  73.                 return read_bytes;
  74.             }
  75.         }
  76.    
  77.         /* 低8bits */
  78.         I2C_TxData(I2C_INS, (m_addr & 0xFF));
  79.         iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  80.         while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
  81.             if ((iic_timeout--) == 0) {
  82.                 I2C_EnableGenerateStop(I2C_INS);
  83.                 return read_bytes;
  84.             }
  85.         }
  86.     }
  87.    
  88.     /* 等待结束 */
  89.     I2C_EnableGenerateStop(I2C_INS);
  90.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  91.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_STOP) != RESET) {
  92.         if ((iic_timeout--) == 0) {
  93.             I2C_EnableGenerateStop(I2C_INS);
  94.             return read_bytes;
  95.         }
  96.     }
  97.    
  98.     /* 重复起始信号 */
  99.     I2C_EnableGenerateStart(I2C_INS);
  100.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  101.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_START) == RESET) {
  102.         if ((iic_timeout--) == 0) {
  103.             I2C_EnableGenerateStop(I2C_INS);
  104.             return read_bytes;
  105.         }
  106.     }
  107.    
  108.     /* 设备地址 */
  109.     I2C_Tx7BitAddress(I2C_INS, dev_addr, I2C_DIRECTION_RX);
  110.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  111.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_ADDR) == RESET) {
  112.         if ((iic_timeout--) == 0) {
  113.             I2C_EnableGenerateStop(I2C_INS);
  114.             return read_bytes;
  115.         }
  116.     }
  117.     /* 确认设备是接收器模式(读) */
  118.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  119.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TR) != RESET) {
  120.         if ((iic_timeout--) == 0) {
  121.             I2C_EnableGenerateStop(I2C_INS);
  122.             return read_bytes;
  123.         }
  124.     }
  125.    
  126.     /* 读数据 */
  127.     while (read_bytes < size) {
  128.         iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  129.         while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_RXBNE) == RESET) {
  130.             if ((iic_timeout--) == 0) {
  131.                 I2C_EnableGenerateStop(I2C_INS);
  132.                 return read_bytes;
  133.             }
  134.         }
  135.         buf[read_bytes++] = I2C_RxData(I2C_INS);
  136.         if ((read_bytes + 1) == size) {
  137.             I2C_DisableAcknowledge(I2C_INS);
  138.         }
  139.     }
  140.    
  141.     /* 等待结束 */
  142.     I2C_EnableGenerateStop(I2C_INS);
  143.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  144.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_STOP) != RESET) {
  145.         if ((iic_timeout--) == 0) {
  146.             I2C_EnableGenerateStop(I2C_INS);
  147.             return read_bytes;
  148.         }
  149.     }
  150.    
  151.     return read_bytes;
  152. }

AT24CXX的写时序,写入存储地址后,可立刻传入数据。
  1. /*
  2. * @brief       写数据通用接口
  3. *
  4. * @param       dev_addr: 设备地址
  5. *              m_addr: 存储地址
  6. *              buf: 数据缓存
  7. *              size: 数据大小
  8. *              is_16bit_m: 16位存储
  9. *
  10. * @retval      写入数据量
  11. *
  12. */
  13. static uint32_t bsp_at24cxx_hardware_write_common(uint8_t dev_addr,
  14.                     uint16_t m_addr, uint8_t *buf, uint32_t size, uint8_t is_16bit_m)
  15. {
  16.     uint32_t write_bytes = 0;
  17.     uint32_t iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  18.    
  19.     /* 等待IIC空闲 */
  20.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  21.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BUSBSY) == SET) {
  22.         if ((iic_timeout--) == 0) {
  23.             I2C_EnableGenerateStop(I2C_INS);
  24.             return write_bytes;
  25.         }
  26.     }
  27.    
  28.     /* 起始信号 */
  29.     I2C_EnableGenerateStart(I2C_INS);
  30.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  31.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_START) != SET) {
  32.         if ((iic_timeout--) == 0) {
  33.             I2C_EnableGenerateStop(I2C_INS);
  34.             return write_bytes;
  35.         }
  36.     }
  37.    
  38.     /* 设备地址 */
  39.     I2C_Tx7BitAddress(I2C_INS, dev_addr, I2C_DIRECTION_TX);
  40.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  41.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_ADDR) == RESET) {
  42.         if ((iic_timeout--) == 0) {
  43.             I2C_EnableGenerateStop(I2C_INS);
  44.             return write_bytes;
  45.         }
  46.     }
  47.     /* 确认设备是发送器模式(写) */
  48.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  49.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TR) == RESET) {
  50.         if ((iic_timeout--) == 0) {
  51.             I2C_EnableGenerateStop(I2C_INS);
  52.             return write_bytes;
  53.         }
  54.     }
  55.    
  56.     /* 发送存储地址 */
  57.     if (is_16bit_m == 0) {
  58.         I2C_TxData(I2C_INS, m_addr);
  59.         iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  60.         while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
  61.             if ((iic_timeout--) == 0) {
  62.                 I2C_EnableGenerateStop(I2C_INS);
  63.                 return write_bytes;
  64.             }
  65.         }
  66.     } else {
  67.         /* 高8bits */
  68.         I2C_TxData(I2C_INS, ((m_addr >> 8) & 0xFF));
  69.         iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  70.         while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TXBE) == RESET) {
  71.             if ((iic_timeout--) == 0) {
  72.                 I2C_EnableGenerateStop(I2C_INS);
  73.                 return write_bytes;
  74.             }
  75.         }
  76.    
  77.         /* 低8bits */
  78.         I2C_TxData(I2C_INS, (m_addr & 0xFF));
  79.         iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  80.         while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
  81.             if ((iic_timeout--) == 0) {
  82.                 I2C_EnableGenerateStop(I2C_INS);
  83.                 return write_bytes;
  84.             }
  85.         }
  86.     }
  87.    
  88.     /* 写数据 */
  89.     while (write_bytes < size) {
  90.         I2C_TxData(I2C_INS, buf[write_bytes]);
  91.         iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  92.         while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_TXBE) == RESET) {
  93.             if ((iic_timeout--) == 0) {
  94.                 I2C_EnableGenerateStop(I2C_INS);
  95.                 return write_bytes;
  96.             }
  97.         }
  98.         write_bytes++;
  99.     }
  100.    
  101.     /* 等待发送完成 */
  102.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  103.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_BTC) == RESET) {
  104.         if ((iic_timeout--) == 0) {
  105.             I2C_EnableGenerateStop(I2C_INS);
  106.             return write_bytes;
  107.         }
  108.     }
  109.         
  110.     /* 等待结束 */
  111.     I2C_EnableGenerateStop(I2C_INS);
  112.     iic_timeout = AT24CXX_HARDWARE_TIMEOUT;
  113.     while (I2C_ReadStatusFlag(I2C_INS, I2C_FLAG_MS) != RESET) {
  114.         if ((iic_timeout--) == 0) {
  115.             I2C_EnableGenerateStop(I2C_INS);
  116.             return write_bytes;
  117.         }
  118.     }
  119.    
  120.     return write_bytes;
  121. }

AT24CXX的读数据操作,需特别注意AT24C04~AT24C16三个型号的设备地址和存储地址计算,然后直接调用读时序接口即可,同时无需等待AT24CXX的额外操作,能一次发起读时序操作读取出全部的数据。
  1. /*
  2. * @brief       读数据
  3. *
  4. * @param       op: 操作接口
  5. *              m_addr: 存储地址
  6. *              buf: 数据缓存
  7. *              size: 数据量
  8. *
  9. * @retval      读取数据量
  10. *
  11. */
  12. uint32_t at24cxx_read_data(struct at24cxx_op_f *op, uint16_t m_addr, uint8_t *buf, uint32_t size)
  13. {
  14.     uint32_t read_bytes = size;
  15.     uint8_t dev_addr = 0;                   /* 设备地址 */
  16.     uint16_t mem_addr = m_addr;             /* 存储地址 */
  17.     uint16_t mem = 0;
  18.    
  19.     /* 入参检查 */
  20.     if ((op == ((void *)0)) || (op->at24cxx >= AT24CXX_NUM)
  21.         || (mem_addr >= at24cxx_info[op->at24cxx].capacity) || (size == 0)
  22.         || ((((uint32_t)mem_addr) + size) > at24cxx_info[op->at24cxx].capacity)) {
  23.         return read_bytes;
  24.     }
  25.         
  26.     /* 计算设备地址和存储地址 */
  27.     if ((op->at24cxx >= AT24C04) && (op->at24cxx <= AT24C16)) {
  28.         dev_addr = op->dev_addr + (((mem_addr / at24cxx_info[op->at24cxx].page_size) >> 4) << 1);
  29.         mem = mem_addr & 0xFF;
  30.     } else {
  31.         dev_addr = op->dev_addr;
  32.         mem = mem_addr;
  33.     }
  34.    
  35.     if (op->at24cxx <= AT24C16) {
  36.         if (op->i2c_read(dev_addr, mem, buf, read_bytes) != read_bytes) {
  37.             read_bytes = 0;
  38.         }
  39.     } else {
  40.         if (op->i2c_read_16bit_m_addr(dev_addr, mem, buf, read_bytes) != read_bytes) {
  41.             read_bytes = 0;
  42.         }
  43.     }
  44.    
  45.     return read_bytes;
  46. }

AT24CXX的写数据操作,也需特别注意AT24C04~AT24C16三个型号的设备地址和存储地址计算,同时写操作为按页写,需计算当前起始地址所处页可连续写的剩余数据数量,以避免页内地址位自增后回环的问题。AT24CXX写入一页数据后,保存数据需消耗一定的时间,具体时间可能不同厂商有不同的要求,因此,写入数据后,需等待AT24CXX保存完数据再发起下一次页写操作,不能连续写两页及以上。
  1. /*
  2. * @brief       写数据
  3. *
  4. * @param       op: 操作接口
  5. *              m_addr: 存储地址
  6. *              buf: 数据缓存
  7. *              size: 数据量
  8. *
  9. * @retval      写入数据量
  10. *
  11. */
  12. uint32_t at24cxx_write_data(struct at24cxx_op_f *op, uint16_t m_addr, uint8_t *buf, uint32_t size)
  13. {
  14.     uint32_t write_bytes = 0;
  15.     uint8_t dev_addr = 0;                   /* 设备地址 */
  16.     uint16_t mem_addr = m_addr;             /* 存储地址 */
  17.     uint16_t mem = 0;
  18.     uint8_t current_page_write_size = 0;    /* 当前页写大小 */
  19.     uint8_t *data = buf;
  20.    
  21.     /* 入参检查 */
  22.     if ((op == ((void *)0)) || (op->at24cxx >= AT24CXX_NUM)
  23.         || (mem_addr >= at24cxx_info[op->at24cxx].capacity) || (size == 0)
  24.         || ((((uint32_t)mem_addr) + size) > at24cxx_info[op->at24cxx].capacity)) {
  25.         return write_bytes;
  26.     }
  27.         
  28.     do {
  29.         /* 计算设备地址和存储地址 */
  30.         if ((op->at24cxx >= AT24C04) && (op->at24cxx <= AT24C16)) {
  31.             dev_addr = op->dev_addr + (((mem_addr / at24cxx_info[op->at24cxx].page_size) >> 4) << 1);
  32.             mem = mem_addr & 0xFF;
  33.         } else {
  34.             dev_addr = op->dev_addr;
  35.             mem = mem_addr;
  36.         }
  37.         /* 当前页可写数据量 */
  38.         current_page_write_size = at24cxx_info[op->at24cxx].page_size - (mem_addr % at24cxx_info[op->at24cxx].page_size);
  39.         /* 当前页需写数据量 */
  40.         current_page_write_size = (current_page_write_size <= (size - write_bytes)) ? current_page_write_size : (size - write_bytes);

  41.         if (op->at24cxx <= AT24C16) {
  42.             if (op->i2c_write(dev_addr, mem, data, current_page_write_size) != current_page_write_size) {
  43.                 break;
  44.             }
  45.         } else {
  46.             if (op->i2c_write_16bit_m_addr(dev_addr, mem, data, current_page_write_size) != current_page_write_size) {
  47.                 break;
  48.             }
  49.         }
  50.         
  51.         mem_addr += current_page_write_size;
  52.         write_bytes += current_page_write_size;
  53.         data += current_page_write_size;
  54.         op->delay_5ms();    /* 等待存储完成操作,否则过快操作异常 */
  55.     } while (write_bytes < size);
  56.    
  57.     return write_bytes;
  58. }

4. 测试
AT24CXX的测试代码如下,只需要定义struct at24cxx_op_f结构体,并传入对应的时序接口函数,然后调用at24cxx_write_dataat24cxx_read_data函数接口执行读写操作即可。如果只是使用AT24C01~AT24C16几个型号,则不需要填充16bit存储地址的时序函数,同理,只使用AT24C32~AT24C512几个型号,也无需填充默认的8bit存储地址的时序函数,但延时函数必须填充。
  1. /* 最大的EEPROM容量为 64KB */
  2. #define BUF_SIZE    (1024 * 64)

  3. static struct at24cxx_op_f at24cxx_test = {
  4.     AT24C02,
  5.     0xA0,
  6.     bsp_at24cxx_hardware_write,
  7.     bsp_at24cxx_hardware_read,
  8.     bsp_at24cxx_hardware_write_16bit_m_addr,
  9.     bsp_at24cxx_hardware_read_16bit_m_addr,
  10.     bsp_at24cxx_hardware_delay5ms
  11. };

  12. uint8_t w_buf[BUF_SIZE];
  13. uint8_t r_buf[BUF_SIZE];
  14. volatile int8_t result = -1;

  15. // 应用初始化
  16. void app_init(void)
  17. {
  18.     uint32_t at24cxx_capacity = 0;
  19.    
  20.     bsp_i2c_init(0xC0, 400000);
  21.     at24cxx_capacity = at24cxx_get_capacity(at24cxx_test.at24cxx);
  22.    
  23.     /* 填充数据 */
  24.     for (uint32_t i = 0; i < at24cxx_capacity; i++) {
  25.         w_buf[i] = 40 + i;
  26.     }
  27.     /* 写入读取测试 */
  28.     at24cxx_write_data(&at24cxx_test, 0, w_buf, at24cxx_capacity);
  29.     at24cxx_read_data(&at24cxx_test, 0, r_buf, at24cxx_capacity);
  30.     /* 测试结果 */
  31.     result = memcmp(w_buf, r_buf, sizeof(w_buf));
  32. }

如下图,因写数据每次只能写1页,且写完1页需等5msAT24C02完成数据存储,所以写完整一片AT24C02耗时相对较长,而后部分的读数据能一次性将所有数据读回,所有耗时很短。
15699691a78eed2b26.png

5. AT24CXX驱动代码移植说明
I2C的模拟时序移植,只需要更改实现如下的代码即可,bsp_at24cxx_simulate_timingbsp_at24cxx的代码均无需修改。
  1. #define SCL_PORT    (GPIOB)
  2. #define SCL_PIN     (GPIO_PIN_6)

  3. #define SDA_PORT    (GPIOB)
  4. #define SDA_PIN     (GPIO_PIN_7)

  5. #define GPIO_CLK_ENABLE     RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB)

  6. /* IO操作 */
  7. #define I2C_SCL(x)        do{ x ? \
  8.                               GPIO_SetBit(SCL_PORT, SCL_PIN) : \
  9.                               GPIO_ResetBit(SCL_PORT, SCL_PIN); \
  10.                           } while(0)       /* SCL */

  11. #define I2C_SDA(x)        do{ x ? \
  12.                               GPIO_SetBit(SDA_PORT, SDA_PIN) : \
  13.                               GPIO_ResetBit(SDA_PORT, SDA_PIN); \
  14.                           } while(0)       /* SDA */

  15. #define I2C_READ_SDA      (GPIO_ReadInputBit(SDA_PORT, SDA_PIN) == BIT_SET) /* 读取SDA */

  16. /*
  17. * @brief       延时
  18. *
  19. * @param       None
  20. *
  21. * @retval      None
  22. *
  23. */
  24. static void bsp_i2c_timing_delay(void)
  25. {
  26.     /* 调整此处的数值,以控制通讯频率 */
  27.     volatile uint32_t d = 200;   
  28.     while (--d);
  29. }

  30. /*
  31. * @brief       引脚初始化
  32. *
  33. * @param       None
  34. *
  35. * @retval      None
  36. *
  37. */                  
  38. void bsp_i2c_timing_io_init(void)
  39. {
  40.     GPIO_Config_T gpioConfig;
  41.    
  42.     GPIO_CLK_ENABLE;   
  43.     GPIO_ConfigStructInit(&gpioConfig);   
  44.     gpioConfig.pin     = SCL_PIN;
  45.     gpioConfig.mode    = GPIO_MODE_OUT;
  46.     gpioConfig.otype   = GPIO_OTYPE_OD;
  47.     gpioConfig.speed   = GPIO_SPEED_50MHz;
  48.     gpioConfig.pupd    = GPIO_PUPD_NOPULL;
  49.     GPIO_Config(SCL_PORT, &gpioConfig);
  50.    
  51.     gpioConfig.pin     = SDA_PIN;
  52.     GPIO_Config(SDA_PORT, &gpioConfig);
  53.    
  54.     bsp_i2c_timing_stop();
  55. }

I2C的硬件时序,因不同的MCU外设操作有差异,需要实现bsp_i2c_hardwarebsp_at24cxx_hardware_timing文件的全部代码,而bsp_at24cxx文件无需修改。

不管是模拟I2C方式还是硬件I2C方式,在应用层调用的方式均一致。

6. 详细驱动代码
I2C硬件驱动代码和模拟驱动代码:
I2C_Hardware.zip (6.72 MB, 下载次数: 0)
I2C_Simulate.zip (6.73 MB, 下载次数: 0)
您需要登录后才可以回帖 登录 | 注册

本版积分规则

29

主题

59

帖子

0

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