[APM32E0] APM32E030的IIC驱动(模拟)

[复制链接]
296|8
口天土立口 发表于 2025-9-6 14:28 | 显示全部楼层 |阅读模式
7777268bbd2e58bcdb.png

7088168bbd309c9d7d.png
APM32E030支持I2C通信,但本次我们使用GPIO模拟实现I2C与EEPROM进行通讯;

本次代码基于开发板:APM32E030R Micro-EVB
IO:SCL->PB6,SDA->PB7


模拟I2C驱动代码如下:
  1. /* IO操作 */
  2. #define IIC_SCL(x)        do{ x ? \
  3.                               GPIO_SetBit(GPIOB, GPIO_PIN_6) : \
  4.                               GPIO_ClearBit(GPIOB, GPIO_PIN_6); \
  5.                           }while(0)       /* SCL */

  6. #define IIC_SDA(x)        do{ x ? \
  7.                               GPIO_SetBit(GPIOB, GPIO_PIN_7) : \
  8.                               GPIO_ClearBit(GPIOB, GPIO_PIN_7); \
  9.                           }while(0)       /* SDA */

  10. #define IIC_READ_SDA     GPIO_ReadInputBit(GPIOB, GPIO_PIN_7) /* 读取SDA */
  1. /**
  2. * @brief       初始化IIC
  3. * @param       无
  4. * @retval      无
  5. */
  6. void iic_init(void)
  7. {
  8.     GPIO_Config_T gpioConfig;
  9.    
  10.     RCM_EnableAHBPeriphClock(RCM_AHB_PERIPH_GPIOB);
  11.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);
  12.    
  13.     /*
  14.      * SCL -> PB6
  15.      * SDA -> PB7
  16.      */
  17.     GPIO_ConfigStructInit(&gpioConfig);   
  18.     gpioConfig.pin     = GPIO_PIN_6;
  19.     gpioConfig.mode    = GPIO_MODE_OUT;
  20.     gpioConfig.outtype = GPIO_OUT_TYPE_PP;
  21.     gpioConfig.speed   = GPIO_SPEED_50MHz;
  22.     gpioConfig.pupd    = GPIO_PUPD_NO;
  23.     GPIO_Config(GPIOB, &gpioConfig);
  24.    
  25.     gpioConfig.outtype = GPIO_OUT_TYPE_OD;
  26.     gpioConfig.pin     = GPIO_PIN_7;
  27.     GPIO_Config(GPIOB, &gpioConfig);
  28.       
  29.     iic_stop();     /* 停止总线上所有设备 */
  30. }
  1. static void delay_us(uint32_t delay)
  2. {
  3.     uint32_t us = 10 * delay;
  4.    
  5.     while (--us);
  6. }
  1. /**
  2. * @brief       IIC延时函数,用于控制IIC读写速度
  3. * @param       无
  4. * @retval      无
  5. */
  6. static void iic_delay(void)
  7. {
  8.     delay_us(2);    /* 2us的延时, 读写速度在250Khz以内 */
  9. }
  1. /**
  2. * @brief       产生IIC起始信号
  3. * @param       无
  4. * @retval      无
  5. */
  6. void iic_start(void)
  7. {
  8.     IIC_SDA(1);
  9.     IIC_SCL(1);
  10.     iic_delay();
  11.     IIC_SDA(0);     /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
  12.     iic_delay();
  13.     IIC_SCL(0);     /* 钳住I2C总线,准备发送或接收数据 */
  14.     iic_delay();
  15. }
  1. /**
  2. * @brief       产生IIC停止信号
  3. * @param       无
  4. * @retval      无
  5. */
  6. void iic_stop(void)
  7. {
  8.     IIC_SDA(0);     /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
  9.     iic_delay();
  10.     IIC_SCL(1);
  11.     iic_delay();
  12.     IIC_SDA(1);     /* 发送I2C总线结束信号 */
  13.     iic_delay();
  14. }
  1. /**
  2. * @brief       等待应答信号到来
  3. * @param       无
  4. * @retval      1,接收应答失败
  5. *              0,接收应答成功
  6. */
  7. uint8_t iic_wait_ack(void)
  8. {
  9.     uint8_t waittime = 0;
  10.     uint8_t rack = 0;

  11.     IIC_SDA(1);     /* 主机释放SDA线(此时外部器件可以拉低SDA线) */
  12.     iic_delay();
  13.     IIC_SCL(1);     /* SCL=1, 此时从机可以返回ACK */
  14.     iic_delay();

  15.     while (IIC_READ_SDA)    /* 等待应答 */
  16.     {
  17.         waittime++;

  18.         if (waittime > 250)
  19.         {
  20.             iic_stop();
  21.             rack = 1;
  22.             break;
  23.         }
  24.     }

  25.     IIC_SCL(0);     /* SCL=0, 结束ACK检查 */
  26.     iic_delay();
  27.     return rack;
  28. }
  1. /**
  2. * @brief       产生ACK应答
  3. * @param       无
  4. * @retval      无
  5. */
  6. void iic_ack(void)
  7. {
  8.     IIC_SDA(0);     /* SCL 0 -> 1  时 SDA = 0,表示应答 */
  9.     iic_delay();
  10.     IIC_SCL(1);     /* 产生一个时钟 */
  11.     iic_delay();
  12.     IIC_SCL(0);
  13.     iic_delay();
  14.     IIC_SDA(1);     /* 主机释放SDA线 */
  15.     iic_delay();
  16. }
  1. /**
  2. * @brief       不产生ACK应答
  3. * @param       无
  4. * @retval      无
  5. */
  6. void iic_nack(void)
  7. {
  8.     IIC_SDA(1);     /* SCL 0 -> 1  时 SDA = 1,表示不应答 */
  9.     iic_delay();
  10.     IIC_SCL(1);     /* 产生一个时钟 */
  11.     iic_delay();
  12.     IIC_SCL(0);
  13.     iic_delay();
  14. }
  1. /**
  2. * @brief       IIC发送一个字节
  3. * @param       data: 要发送的数据
  4. * @retval      无
  5. */
  6. void iic_send_byte(uint8_t data)
  7. {
  8.     uint8_t t;
  9.    
  10.     for (t = 0; t < 8; t++)
  11.     {
  12.         IIC_SDA((data & 0x80) >> 7);    /* 高位先发送 */
  13.         iic_delay();
  14.         IIC_SCL(1);
  15.         iic_delay();
  16.         IIC_SCL(0);
  17.         data <<= 1;     /* 左移1位,用于下一次发送 */
  18.     }
  19.     IIC_SDA(1);         /* 发送完成, 主机释放SDA线 */
  20. }
  1. /**
  2. * @brief       IIC读取一个字节
  3. * @param       ack:  ack=1时,发送ack; ack=0时,发送nack
  4. * @retval      接收到的数据
  5. */
  6. uint8_t iic_read_byte(uint8_t ack)
  7. {
  8.     uint8_t i, receive = 0;

  9.     for (i = 0; i < 8; i++ )    /* 接收1个字节数据 */
  10.     {
  11.         receive <<= 1;  /* 高位先输出,所以先收到的数据位要左移 */
  12.         IIC_SCL(1);
  13.         iic_delay();

  14.         if (IIC_READ_SDA)
  15.         {
  16.             receive++;
  17.         }
  18.         
  19.         IIC_SCL(0);
  20.         iic_delay();
  21.     }

  22.     if (!ack)
  23.     {
  24.         iic_nack();     /* 发送nACK */
  25.     }
  26.     else
  27.     {
  28.         iic_ack();      /* 发送ACK */
  29.     }

  30.     return receive;
  31. }


EEPROM操作代码如下:
  1. /* 最大容量 1KB */
  2. #define AT24C08_MAX_CAPACITY        ((uint32_t)0x400)
  3. /* 单页大小 */
  4. #define AT24C08_PAGE_SIZE           ((uint8_t)16)
  5. /* 设备地址 */
  6. #define AT24C08_DEV_ADDR            ((uint8_t)0xA0)
  1. /* 延时 */
  2. static void bsp_delay(void)
  3. {
  4.     volatile uint32_t ms = 0x3FFF;  /* 2.5ms */
  5.     while (--ms);
  6. }
  1. /* 写数据 */
  2. uint16_t bsp_at24c08_simulate_write(uint16_t addr, uint8_t *buf, uint16_t size)
  3. {
  4.     uint16_t write_bytes = 0;
  5.     uint8_t dev_addr = AT24C08_DEV_ADDR;    /* 设备地址 */
  6.     uint8_t mem_addr = addr;                /* 存储地址 */
  7.     uint16_t write_index = 0;
  8.     uint8_t current_page_write_size = 0;    /* 当前页写大小 */
  9.    
  10.     /* 入参检查 */
  11.     if ((addr >= AT24C08_MAX_CAPACITY) || (size == 0) || ((addr + size) > AT24C08_MAX_CAPACITY)) {
  12.         return write_bytes;
  13.     }
  14.    
  15.     do {
  16.         /* 计算地址 */
  17.         dev_addr = AT24C08_DEV_ADDR + (((addr / AT24C08_PAGE_SIZE) >> 4) << 1);
  18.         mem_addr = (addr % AT24C08_PAGE_SIZE) + (((addr / AT24C08_PAGE_SIZE) & 0x0F) << 4);
  19.         current_page_write_size = AT24C08_PAGE_SIZE - (addr % AT24C08_PAGE_SIZE);
  20.         current_page_write_size = (current_page_write_size <= (size - write_bytes)) ? current_page_write_size : (size - write_bytes);
  21.         write_index = 0;
  22.         
  23.         iic_start();
  24.         iic_send_byte(dev_addr);
  25.         iic_wait_ack();
  26.         iic_send_byte(mem_addr);
  27.         iic_wait_ack();
  28.         do {            
  29.             iic_send_byte(buf[write_bytes++]);
  30.             iic_wait_ack();
  31.         } while (++write_index < current_page_write_size);
  32.         iic_stop();
  33.         addr += current_page_write_size;
  34.         bsp_delay();
  35.     } while (write_bytes < size);
  36.    
  37.     return write_bytes;
  38. }
  1. /* 读数据 */
  2. uint16_t bsp_at24c08_simulate_read(uint16_t addr, uint8_t *buf, uint16_t size)
  3. {
  4.     uint16_t read_bytes = 0;
  5.     uint8_t dev_addr = AT24C08_DEV_ADDR;    /* 设备地址 */
  6.     uint8_t mem_addr = addr;                /* 存储地址 */
  7.     uint16_t read_index = 0;
  8.     uint16_t current_page_read_size = 0;    /* 当前页写大小 */
  9.    
  10.     /* 入参检查 */
  11.     if ((addr >= AT24C08_MAX_CAPACITY) || (size == 0) || ((addr + size) > AT24C08_MAX_CAPACITY)) {
  12.         return read_bytes;
  13.     }
  14.    
  15.     do {
  16.         /* 计算地址 */
  17.         dev_addr = AT24C08_DEV_ADDR + (((addr / AT24C08_PAGE_SIZE) >> 4) << 1);
  18.         mem_addr = (addr % AT24C08_PAGE_SIZE) + (((addr / AT24C08_PAGE_SIZE) & 0x0F) << 4);
  19. //        current_page_read_size = AT24C08_PAGE_SIZE - (addr % AT24C08_PAGE_SIZE);
  20. //        current_page_read_size = (current_page_read_size <= (size - read_bytes)) ? current_page_read_size : (size - read_bytes);
  21.         read_index = 0;
  22.         current_page_read_size = size;
  23.         
  24.         iic_start();
  25.         iic_send_byte(dev_addr);
  26.         iic_wait_ack();
  27.         iic_send_byte(mem_addr);
  28.         iic_wait_ack();
  29.         iic_stop();
  30.         iic_start();
  31.         iic_send_byte(dev_addr | 0x01);
  32.         iic_wait_ack();
  33.         do {        
  34.             if ((read_index + 1) < current_page_read_size) {
  35.                 buf[read_bytes++] = iic_read_byte(1);
  36.             } else {
  37.                 buf[read_bytes++] = iic_read_byte(0);
  38.             }
  39.         } while (++read_index < current_page_read_size);
  40.         iic_stop();
  41.         addr += current_page_read_size;
  42.     } while (read_bytes < size);
  43.    
  44.     return read_bytes;
  45. }


测试代码如下:
  1. uint8_t w_buf[1024];
  2. uint8_t r_buf[1024];
  3. int8_t result = -1;


  4. // 应用初始化
  5. void app_init(void)
  6. {   
  7.     /* 模拟IIC */
  8.     iic_init();
  9.     for (uint16_t i = 0; i < sizeof(w_buf); i++) {
  10.         w_buf[i] = 30 + i;
  11.     }
  12.     bsp_at24c08_simulate_write(0, w_buf, sizeof(w_buf));
  13.     bsp_at24c08_simulate_read(0, r_buf, sizeof(r_buf));
  14.    
  15.     result = memcmp(w_buf, r_buf, sizeof(w_buf));
  16. }

  17. // 应用任务
  18. void app_task(void)
  19. {
  20. }

详细代码,请查看附件:
AT24C08_Simulate.zip (1.99 MB, 下载次数: 3)

7197968bbd2f1ecfc0.png
天鹅绒之夜 发表于 2025-9-7 23:13 | 显示全部楼层
模拟I2C一点儿也不好用。
永恒的一瞥 发表于 2025-9-8 19:42 | 显示全部楼层
我也觉得模拟I2C没有啥实用价值
 楼主| 口天土立口 发表于 2025-9-9 13:24 | 显示全部楼层
永恒的一瞥 发表于 2025-9-8 19:42
我也觉得模拟I2C没有啥实用价值

资源充足的情况下,模拟I2C不待见,但在资源紧张冲突的时候,模拟I2C还是会派上用场的。一切都是成本的选择。
暖茶轻语 发表于 2025-9-10 21:03 | 显示全部楼层
代码写得很清楚,注释也很到位。我很好奇,你是如何确保模拟I2C的时序准确的?有没有什么特别的技巧或者工具?
 楼主| 口天土立口 发表于 2025-9-11 13:41 | 显示全部楼层
暖茶轻语 发表于 2025-9-10 21:03
代码写得很清楚,注释也很到位。我很好奇,你是如何确保模拟I2C的时序准确的?有没有什么特别的技巧或者工 ...

逻辑分析仪
天体书记 发表于 2025-9-12 20:12 | 显示全部楼层
模块方式实现的代码还是挺整洁的
星空魔法师 发表于 2025-9-23 13:58 | 显示全部楼层
代码写得很清楚,注释也很到位
梦境摆渡人 发表于 2025-9-24 13:54 | 显示全部楼层
代码看起来逻辑清晰,注释也很详细
您需要登录后才可以回帖 登录 | 注册

本版积分规则

19

主题

45

帖子

0

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