[APM32F0] 实现APM32F003F6P6和EEPROM芯片(AT24C02C)的模拟I2C通信

[复制链接]
 楼主| dffzh 发表于 2025-7-21 17:12 | 显示全部楼层 |阅读模式
#申请原创#
@21小跑堂


AT24C02C 是一款由 MicrochipTechnology 生产的串行EEPROM(电可擦可编程只读存储器)芯片,是属于 I²C 接口的非易失性存储器件,可以用来存储需要掉电保存的数据,包括设备配置参数、校准数据和日志记录等。因为手里正好有一块带极海MCU APM32F003F6P6和AT24C02C的小板子,就抽空尝试用模拟I2C的方法实现了对AT24C02C芯片的数据写入和读取操作,下面就给大家分享一下基于模拟I2C的AT24C02C驱动代码及实测仿真结果。
硬件原理图部分如下所示:
2828687e02cbc7ff4.png
对应的引脚是PB4接I2C的CLK时钟信号,PB5接I2C的DAT数据信号,首先需要对两个引脚进行初始化配置,这里需要注意,一般情况下,I2C的两个信号脚尽量配置为开漏输出模式,至于为什么要这么做,有兴趣的可以看下作者发的一篇文章(链接:https://bbs.21ic.com/icview-3464630-1-1.html)。以下便是引脚初始化配置代码:
  1. static void i2c_gpio_init(void)
  2. {
  3.     GPIO_Config_T gpioConfig;
  4.     gpioConfig.mode = GPIO_MODE_OUT_OD; //open drain output
  5.     gpioConfig.speed = GPIO_SPEED_10MHz;
  6.     gpioConfig.intEn = GPIO_EINT_DISABLE;
  7.     gpioConfig.pin = I2C_CLK_PIN | I2C_DATA_PIN ;
  8.     GPIO_Config(I2C_GPIO, &gpioConfig);
  9.         I2C_CLK_SET; //init high
  10.         I2C_DATA_SET; //init high
  11. }
其中为了增加可读性和移植性,用宏定义实现相关配置,代码如下:
  1. #define I2C_GPIO       GPIOB
  2. #define I2C_CLK_PIN    GPIO_PIN_4
  3. #define I2C_DATA_PIN   GPIO_PIN_5

  4. #define I2C_CLK_SET      GPIO_SetBit(I2C_GPIO, I2C_CLK_PIN)
  5. #define I2C_CLK_RESET    GPIO_ClearBit(I2C_GPIO, I2C_CLK_PIN)

  6. #define I2C_DATA_SET     GPIO_SetBit(I2C_GPIO, I2C_DATA_PIN)
  7. #define I2C_DATA_RESET   GPIO_ClearBit(I2C_GPIO, I2C_DATA_PIN)

  8. #define I2C_DATA_MODE_IN   GPIOB->MODE &= 0XFFFFFFDF;
  9. #define I2C_DATA_MODE_OUT  GPIOB->MODE |= (1<<5);
  10. #define READ_I2C_DATA()    GPIO_ReadInputBit(I2C_GPIO,I2C_DATA_PIN)
接着就是通过模拟I2C方法实现AT24C02C的驱动代码,所谓的模拟I2C,也就是通过写入GPIO口输出高低电平信号及读取GPIO高低电平信号的方法来模拟标准I2C的通信时序,类似的还有模拟UART和模拟SPI等。
以下即为AT24C02C的驱动代码,每个函数接口都加了注释说明:

  1. #include "at24c02c.h"

  2. void Delay_us(uint32_t delay){
  3.         uint32_t i;
  4.         for (i = 0; i < (delay*100); i++);
  5. }

  6. /**************************************************************
  7. 函数名称:I2c_Start()
  8. 功能:I2C起始信号
  9.           IN:NONE
  10.           OUT: NONE
  11. ***************************************************************/
  12. void I2c_Start(){
  13.         I2C_DATA_MODE_OUT;
  14.         I2C_CLK_SET;
  15.           I2C_DATA_SET;
  16.         Delay_us(2);
  17.         I2C_DATA_RESET;
  18.         Delay_us(2);
  19.         I2C_CLK_RESET;
  20. }
  21. /**************************************************************
  22. 函数名称:I2c_Stop()
  23.     功能:I2C停止信号
  24.       IN:NONE
  25.      OUT: NONE
  26. ***************************************************************/
  27. void I2c_Stop(){
  28.           I2C_DATA_MODE_OUT;
  29.         Delay_us(2);
  30.         I2C_DATA_RESET;
  31.         Delay_us(3);
  32.         I2C_CLK_SET;
  33.         Delay_us(2);       
  34.         I2C_DATA_SET;
  35.         Delay_us(5);
  36. }
  37. /**************************************************************
  38. 函数名称:I2c_Free()
  39.     功能:I2C总线空闲信号
  40.       IN:NONE
  41.      OUT: NONE
  42. ***************************************************************/
  43. void I2c_Free(){
  44.         I2C_DATA_MODE_OUT;
  45.         I2C_DATA_SET;
  46.         Delay_us(5);
  47.         I2C_CLK_SET;
  48.         Delay_us(5);
  49. }
  50. /**************************************************************
  51. 函数名称:I2c_sendAck()
  52.     功能:I2C发送应答信号
  53.     IN:NONE
  54.      OUT: NONE
  55. ***************************************************************/
  56. void I2c_sendAck(){
  57.         I2C_DATA_MODE_OUT;
  58.         Delay_us(2);
  59.         I2C_DATA_RESET;
  60.         Delay_us(3);
  61.         I2C_CLK_SET;
  62.         Delay_us(5);
  63.         I2C_CLK_RESET;
  64. }
  65. /**************************************************************
  66. 函数名称:I2c_Nack()
  67.     功能:I2C发送无应答信号
  68.       IN:NONE
  69.      OUT: NONE
  70. ***************************************************************/
  71. void I2c_Nack(){
  72.         I2C_DATA_MODE_OUT;
  73.         Delay_us(2);
  74.         I2C_DATA_SET;
  75.         Delay_us(3);
  76.         I2C_CLK_SET;
  77.         Delay_us(5);
  78.         I2C_CLK_RESET;
  79. }
  80. /**************************************************************
  81. 函数名称:i2c_checkack()
  82.     功能:I2C验证应答信号
  83.       IN:NONE
  84.      OUT: NONE
  85. ***************************************************************/
  86. uint8_t i2c_checkack(){
  87.         uint8_t temp;
  88.         Delay_us(2);       
  89.         I2C_DATA_MODE_IN;
  90.         Delay_us(3);
  91.         I2C_CLK_SET;
  92.         Delay_us(2);
  93.         if(READ_I2C_DATA()){
  94.                 temp = 1;
  95.         }else{
  96.                 temp = 0;
  97.         }       
  98.         Delay_us(1);
  99.         I2C_CLK_RESET;
  100.         return temp;       
  101. }
  102. /**************************************************************
  103. 函数名称:I2c_Send_Byte()
  104.     功能:I2C发送1个字节·数据
  105.       IN:data :需要发送的数据
  106.      OUT: NONE
  107. ***************************************************************/
  108. void I2c_Send_Byte(uint8_t data){
  109.         uint8_t temp;
  110.           I2C_DATA_MODE_OUT;       
  111.         for(temp=0;temp<8;temp++){
  112.                 Delay_us(1);
  113.                 if((data & 0x80) == 0x80){
  114.                         I2C_DATA_SET;
  115.                 }else{
  116.                         I2C_DATA_RESET;
  117.                 }
  118.                 data <<= 1;
  119.                 Delay_us(2);
  120.                 I2C_CLK_SET;
  121.                 Delay_us(5);
  122.                 I2C_CLK_RESET;
  123.         }
  124. }
  125. /**************************************************************
  126. 函数名称:I2c_read_Byte()
  127.     功能:I2C接收数据
  128.       IN:NONE
  129.      OUT: 接收到的数据
  130. ***************************************************************/
  131. uint8_t I2c_read_Byte(){
  132.         uint8_t temp,data;
  133.         I2C_DATA_MODE_IN;
  134.         for(temp=0;temp<8;temp++){
  135.                 data <<= 1;
  136.                 Delay_us(5);
  137.                 I2C_CLK_SET;
  138.                 Delay_us(2);
  139.                 if(READ_I2C_DATA()) {       
  140.                         data += 1;
  141.                 }
  142.                 //
  143.                 Delay_us(3);
  144.                 I2C_CLK_RESET;
  145.         }
  146.         return(data);
  147. }
  148. /**************************************************************
  149. 函数名称:write_24c02()
  150.     功能:I2C写入数据到24c02
  151.       IN:ADDR 写入地址  len 写入长度 data 写入数据BUF
  152.      OUT: NONE
  153. ***************************************************************/
  154. void write_24c02(uint8_t addr,uint8_t len,u32 data){
  155.         uint8_t temp,data_len;
  156.         u32 temp1;
  157.         temp1 = data;
  158.         I2c_Start();
  159.         I2c_Send_Byte(0xa0);
  160.         temp = i2c_checkack();
  161.         I2c_Send_Byte(addr);
  162.         temp = i2c_checkack();
  163.         for(data_len=0;data_len<len;data_len++){
  164.                 I2c_Send_Byte((u8)temp1);
  165.           temp = i2c_checkack();
  166.                 temp1 >>= 8;
  167.         }
  168.         I2c_Stop();
  169. }
  170. /**************************************************************
  171. 函数名称:read_24c02()
  172.     功能:从24c02中读出数据
  173.       IN:ADDR 读出地址  len 读出长度 data 读出数据BUF
  174.      OUT: NONE
  175. ***************************************************************/
  176. u32 read_24c02(uint8_t addr,uint8_t len){
  177.           uint8_t temp,data_len;
  178.         u8 trmp2[4];
  179.         u32 temp1=0;
  180.           I2c_Start();
  181.         I2c_Send_Byte(0xa0);
  182.         temp = i2c_checkack();
  183.         I2c_Send_Byte(addr);
  184.         temp = i2c_checkack();
  185.         Delay_us(5);
  186.         I2c_Start();
  187.         I2c_Send_Byte(0xa1);
  188.         temp = i2c_checkack();
  189.         for(data_len=0;data_len<len;data_len++){
  190.                 trmp2[data_len] = I2c_read_Byte();
  191.                 if((len-data_len) != 1){
  192.                         I2c_sendAck();
  193.                 }else{
  194.                         I2c_Nack();
  195.                 }
  196.         }
  197.         I2c_Stop();
  198.         temp1 = trmp2[3];
  199.         temp1 <<= 8;
  200.         temp1 += trmp2[2];       
  201.         temp1 <<= 8;
  202.         temp1 += trmp2[1];
  203.         temp1 <<= 8;
  204.         temp1 += trmp2[0];               
  205.         return (temp1);
  206. }
  207. /**************************************************************
  208. 函数名称:write_24c02_byte()
  209.     功能:24c02写入数据
  210.       IN:ADDR 写入地址   data 写入数据BUF
  211.      OUT: NONE
  212. ***************************************************************/
  213. void write_24c02_byte(uint8_t addr,uint8_t data){
  214.         uint8_t temp;
  215.         I2c_Start();
  216.         I2c_Send_Byte(0xa0);
  217.         temp = i2c_checkack();
  218.         I2c_Send_Byte(addr);
  219.         temp = i2c_checkack();
  220.         I2c_Send_Byte(data);
  221.         temp = i2c_checkack();
  222.         I2c_Stop();
  223. }
  224. /**************************************************************
  225. 函数名称:read_24c02_byte()
  226.     功能:从24c02中读出数据
  227.       IN:ADDR 读出地址  
  228.      OUT: 读出的数据
  229. ***************************************************************/
  230. uint8_t read_24c02_byte(uint8_t addr){
  231.   uint8_t temp,data;
  232.   I2c_Start();
  233.         I2c_Send_Byte(0xa0);
  234.         temp = i2c_checkack();
  235.         I2c_Send_Byte(addr);
  236.         temp = i2c_checkack();
  237.         Delay_us(5);
  238.         I2c_Start();
  239.         I2c_Send_Byte(0xa1);
  240.         temp = i2c_checkack();
  241.         data = I2c_read_Byte();
  242.         I2c_Nack();
  243.         I2c_Stop();
  244.         return data;
  245. }

其中Delay_us为非精准延时函数,对通信实时性要求不高的场景,可以将延时基准加长。write_24c02函数和read_24c02函数为写入指定长度数据和读取指定长度数据的接口;write_24c02_byte函数和read_24c02_byte函数为写入一个字节数据到指定地址和从指定地址读取一个字节数据的接口,实际使用时根据需求可自行选择。
接下来通过使用write_24c02_byte函数和read_24c02_byte函数编写测试代码实测一下写入和读取功能,测试代码为循环写入10个数据,写入的起始地址为1;写入完成后,再循环读取出来并保存到一个全局数组里,通过查看全局数组的值即可知道写入和读取是否成功。测试代码如下;
  1. #define  EEPROM_DATA_LEN  (10)
  2.     uint8_t i2c_data[EEPROM_DATA_LEN];
  3. for(uint8_t i=0; i< EEPROM_DATA_LEN; i++)
  4.         {
  5.             write_24c02_byte(i+1,i+0x55);
  6.         }
  7.        
  8.         for(uint8_t i=0; i< EEPROM_DATA_LEN; i++)
  9.         {
  10.             i2c_data[i]= read_24c02_byte(i+1);
  11.         }
将GPIO初始化接口和测试代码一起,放到main初始化位置即可:
64034687e04134914b.png
使用jlink仿真器+Keil IDE进入Debug运行测试,并使用Watch窗口监控i2c_data全局数组的值,实测结果如下图所示:
97651687e0424d5dc0.png
从以上结果可以看出,已成功完成对EEPROM芯片的数据写入和读取操作,但是在通信稳定性方面,当然也需要通过编写测试程序来验证,比如可以写入一定量的数据到不同的地址,然后将读取出来的数据与写入的数据逐一进行比较,如果出现不相等的情况,串口打印相关日志信息以记录,以此来判断通信稳定性,有兴趣的坛友如有条件,可以实测一下,顺带帮作者验证一下驱动代码是否存在Bug~~。




真的问题不大 发表于 2025-7-23 13:41 | 显示全部楼层
先收藏了,对新手特友好 不过有看到微秒延时是用一个 for 循环实现的。将来把代码移植到不同主频的MCU上时,延时精确性会不会发生很大变化?
 楼主| dffzh 发表于 2025-7-23 14:33 | 显示全部楼层
真的问题不大 发表于 2025-7-23 13:41
先收藏了,对新手特友好 不过有看到微秒延时是用一个 for 循环实现的。将来把代码移植到不同主频的M ...
是的,非精确延时确实会受主频影响,MCU平台移植时可能需要修改延时函数里面的100的值,只是不占用硬件定时器资源,也可以考虑用硬件定时器实现精确延时。
ShadowDance 发表于 2025-7-23 14:43 | 显示全部楼层
F003系列,这成本敏感型的MCU啊
您需要登录后才可以回帖 登录 | 注册

本版积分规则

109

主题

1163

帖子

22

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

109

主题

1163

帖子

22

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