当前位置:   article > 正文

【裸机开发】I2C 通信接口(三)—— I2C 底层驱动接口实现_i2c驱动程序开发流程

i2c驱动程序开发流程

目录

一、I2C 初始化

二、产生开始 / 停止信号

1、开始信号

2、重复开始信号

3、停止信号

三、向总线上发送数据(阻塞模式)

四、从总线上读取数据(阻塞模式)

五、整合:数据读写统一调用接口


一、I2C 初始化

初始化步骤如下:

  • 时钟源配置。PERCLK_CLK_ROOT 的时钟源是 66MHz
  • 关闭 I2C。在配置 I2C 相关寄存器时,我们需要先将 I2C 关闭
  • 分频。分频的目的是控制 I2C 的传输速率(如果要控制 I2C 的速率为 100K,可以640 分频)
  • 开启 I2C 

  1. /* i2c 初始化 */
  2. void i2c_init()
  3. {
  4. // 时钟源的初始化放到了时钟初始化(CCM_init)
  5. // 关闭I2C
  6. I2C1_I2CR &= ~(1 << 7);
  7. // 设置分频值(分频值为 640)
  8. I2C1_IFDR = 0x15;
  9. // 打开I2C
  10. I2C1_I2CR |= (1 << 7);
  11. }

 

二、产生开始 / 停止信号

1、开始信号

当总线空闲时,总线上的设备可以发送开始信号来占用总线。如果存在多个设备同时占用总线,此时就会发生冲裁。

  • 判断总线是否空闲
  • 设为主机模式,设为发送模式
  • 发送要通信的设备地址和通信方向
  1. /* 产生开始信号 */
  2. i2c_status_t i2c_master_start(unsigned char address, i2c_direction direction)
  3. {
  4. /* 总线是否被占用 */
  5. if (is_bus_busy())
  6. {
  7. return Status_I2C_Busy;
  8. }
  9. /*
  10. * 发送开始信号
  11. * bit 5: 1 主机
  12. * bit 4: 1 发送
  13. */
  14. I2C1_I2CR |= ((1 << 4) | (1 << 5));
  15. /* 发送通信地址和通信方向 */
  16. I2C1_I2DR = (((unsigned int)address << 1) | (direction == I2C_Read ? 1 : 0));
  17. return Status_I2C_Success;
  18. }

2、重复开始信号

当某个设备在通信时,可能会需要临时改变通信方向,此时就可以发送一个重复开始信号来改变通信方向。发送重复开始信号要求总线空闲,且当前设备是主机,只要有一者不满足,就会发送失败

  • 判断主机是否空闲,且当前设备是否为主机模式
  • 发送一个重复开始信号
  • 发送通信地址和通信方向
  1. /* 重复产生开始信号(可能是修改通信方向、通信设备的地址) */
  2. i2c_status_t i2c_master_repeated_start(unsigned char address, i2c_direction direction)
  3. {
  4. /*
  5. * 总线是否被占用
  6. * - 如果是其他设备占用总线,直接返回
  7. * - 如果是当前设备占用总线,发送一个重复的开始信号
  8. */
  9. if (is_bus_busy() && is_master() == 0)
  10. {
  11. return Status_I2C_Busy;
  12. }
  13. /* 产生一个重复开始信号 */
  14. I2C1_I2CR |= ((1 << 2) | (1 << 4));
  15. /* 发送通信地址和通信方向 */
  16. I2C1_I2DR = (((unsigned int)address << 1U) | (direction & 0x01));
  17. return Status_I2C_Success;
  18. }

 

3、停止信号

当主机打算继续通信时,可以发送一个停止信号来结束通信;如果从机不想继续通信,可以选择不回应主机表示想结束通信。

  • 产生一个停止信号
  • 等待总线被释放
  1. /* 产生停止信号 */
  2. i2c_status_t i2c_master_stop()
  3. {
  4. unsigned short timeout = UINT16_MAX;
  5. /*
  6. * 产生一个停止信号
  7. * bit 3: 0 产生一个ACK
  8. * bit 4: 0 接收(要产生一个ACK,当前设备必须为接收模式)
  9. * bit 5: 0 从机
  10. */
  11. I2C1_I2CR &= ~((1 << 3) | (1 << 4) | (1 << 5));
  12. /* 等待总线被释放 */
  13. while (is_bus_busy() && (timeout--));
  14. // 等待超时
  15. if (timeout == 0)
  16. {
  17. return Status_I2C_Timeout;
  18. }
  19. return Status_I2C_Success;
  20. }

三、向总线上发送数据(阻塞模式)

只有当上一次的数据全部发送完,才能开始下一次通信。其本质是,每次传输 8 bit(需要 8 个时钟周期),当第 9 个时钟周期的边沿触发时,我们认为数据传输完毕。

  • 等待 DR 寄存器准备就绪
  • 清除中断、仲裁标志位
  • 状态设为发送状态
  • while 循环逐字节发送
    • 每发送一个字节,都要将数据保存到 DR 寄存器
    • 等待传输完成(传输完成时会触发中断)
    • 清除中断标志位
    • 判断仲裁是否失败(可能存在多主机占用总线的情况)
    • 是否收到 NACK(从机是否想继续通信)
    • 如果遇到其他问题,直接跳出while循环
  1. /*
  2. * @description: 阻塞式发送
  3. * @param - sendbuf: 保存要发送的数据(允许多字节)
  4. * @param - len: 发送数据的长度
  5. * @param - stopflag: 下一次是否还要继续发送
  6. */
  7. i2c_status_t i2c_master_write_blocking(unsigned char* sendbuf, unsigned int len, unsigned int stopflag)
  8. {
  9. i2c_status_t i2c_stat = Status_I2C_Success;
  10. /* 等待DR寄存器准备就绪 */
  11. while (((I2C1_I2SR >> 7) & 0x01) == 0);
  12. /* 清除中断标志位 */
  13. clear_intpending_flag();
  14. /* 状态设为发送 */
  15. I2C1_I2CR |= (1 << 4);
  16. /* 逐字节发送 */
  17. while (len--)
  18. {
  19. // 将要发送的数据保存到寄存器
  20. I2C1_I2DR = *(sendbuf++);
  21. // 等待传输完成(传输完成时会触发中断)
  22. while (((I2C1_I2SR >> 1) & 0x01) == 0);
  23. // 清除中断标志位
  24. clear_intpending_flag();
  25. // 仲裁是否失败
  26. if(((I2C1_I2SR >> 4) & 0x01))
  27. {
  28. i2c_stat = Status_I2C_Lost_Arbitration;
  29. }
  30. // 是否收到 NACK
  31. if (((I2C1_I2SR >> 0) & 0x01))
  32. {
  33. i2c_stat = Status_I2C_NAK;
  34. }
  35. if (i2c_stat != Status_I2C_Success)
  36. {
  37. break;
  38. }
  39. }
  40. // 从机不想收了 或者 主机不想发了
  41. if ((i2c_stat == Status_I2C_NAK) || stopflag)
  42. {
  43. clear_intpending_flag();
  44. i2c_stat = i2c_master_stop();
  45. }
  46. return i2c_stat;
  47. }
  48. /* 清除中断标志位 */
  49. void clear_intpending_flag()
  50. {
  51. I2C1_I2SR &= ~(1 << 1);
  52. }

四、从总线上读取数据(阻塞模式)

在DR寄存器准备就绪以后,要将当前状态设为接收,逐字节接收(需要考虑接收缓冲区的大小),一旦接收缓冲区大小不足以接收后续的数据,需要提前发送停止信号告诉对方,不要再继续发了。

接收缓冲区大小 = 0,表示本次接收继续,下一次接收无法进行,直接发送停止信号

接收缓冲区大小 = 1,表示下一次接收继续,但是后续接收无法继续,发送 NACK

  • 等待 DR 寄存器准备就绪
  • 清除中断标志位
  • 状态设为接收
  • 循环读取
    • 等待数据传输完毕(对方将一个字节的数据全部发送到总线上时会触发中断)
    • 清除标志位
    • 如果接收缓冲区大小为 0,直接发送停止信号
    • 如果接收缓冲区大小为 1,发送 NACK
    • 读取 DR 寄存器数据
  1. /*
  2. * @description : 阻塞式读取
  3. * @param - recvbuf : 读取到数据
  4. * @param - len : 要读取的数据大小(单位:字节)
  5. */
  6. i2c_status_t i2c_master_read_blocking(unsigned char* recvbuf, unsigned int len)
  7. {
  8. i2c_status_t result = Status_I2C_Success;
  9. volatile unsigned char dummy = 0;
  10. /* 使用这个变量的目的是避免编译报错 */
  11. dummy++;
  12. /* 等待DR寄存器准备就绪 */
  13. while (((I2C1_I2SR >> 1) & 0x01) == 0);
  14. /* 清除中断标志位 */
  15. clear_intpending_flag();
  16. /* 状态设为接收 */
  17. I2C1_I2CR &= ~((1 << 3) | (1 << 4));
  18. /* 后续只想再接收一个字节的数据 */
  19. if (len == 1)
  20. {
  21. I2C1_I2CR |= (1 << 3);
  22. }
  23. /* 假读 */
  24. dummy = I2C1_I2DR;
  25. while (len--)
  26. {
  27. /* 有一个字节的数据被传输到总线上,中断触发 */
  28. while (((I2C1_I2SR >> 1) & 0x01) == 0);
  29. /* 清除中断标志位 */
  30. clear_intpending_flag();
  31. /* 下一次没有足够的空间去读取 */
  32. if (len == 0)
  33. {
  34. result = i2c_master_stop();
  35. }
  36. // 后续只能再接收一个字节的数据,告诉对方不要再发数据了
  37. if (len == 1)
  38. {
  39. I2C1_I2CR |= (1 << 3);
  40. }
  41. /* 从寄存器中读取 */
  42. *(recvbuf++) = I2C1_I2DR & 0xFF;
  43. }
  44. return result;
  45. }
  46. /* 清除中断标志位 */
  47. void clear_intpending_flag()
  48. {
  49. I2C1_I2SR &= ~(1 << 1);
  50. }

五、整合:数据读写统一调用接口

实际上我们真正要调用的接口,只需要这一个即可,上述内容其实都是在为这个接口做铺垫。当数据开始传输的时候,

  • 等待 DR 寄存器准备就绪
  • 清除中断、仲裁标志位
  • 判断是否为读时序,如果是读时序,需要临时修改通信方向(因为要先发送寄存器地址让对方知道自己要读取哪个寄存器)
  • 发送开始信号(发送设备地址和通信方向)
  • 等待信号发送完毕
  • 发送寄存器地址(考虑到寄存器地址可能不止一个字节)
  • 发送 / 读取数据

这里需要分别参考读时序和写时序的通信过程:I2C 通信时序

写时序:​​​​​​​

读时序:

  1. typedef struct
  2. {
  3. unsigned char slaveAddress; // 要通信的设备地址
  4. i2c_direction direction; // 传输方向
  5. unsigned char reg; // 要读写的寄存器地址
  6. unsigned int reg_size; // 寄存器地址大小(单位: 字节, 一般是1个字节,也有可能大于1个字节)
  7. unsigned char stopFlag; // 下一次是否要停止发送
  8. unsigned char* buf; // 缓冲区
  9. unsigned int buf_size; // 缓冲区大小
  10. } i2c_transfer_t;
  11. /* 数据传输(发送 / 接收) */
  12. i2c_status_t i2c_master_transfer(i2c_transfer_t* xfer)
  13. {
  14. i2c_direction direction = xfer->direction;
  15. /* 传输之前清除所有的标志位(仲裁、中断挂起) */
  16. I2C1_I2SR &= ~((1 << 1) | (1 << 4));
  17. /* DR 寄存器是否准备就绪 */
  18. while (!((I2C1_I2SR >> 7) & 0x01));
  19. /* 如果是读时序,需要先临时修改通信方向 */
  20. if ((xfer->reg_size > 0) && (xfer->direction == I2C_Read))
  21. {
  22. direction = I2C_Write;
  23. }
  24. /* 1、发送开始信号 */
  25. i2c_status_t ret = i2c_master_start(xfer->slaveAddress, direction);
  26. if (ret != Status_I2C_Success)
  27. {
  28. return ret;
  29. }
  30. /* 等待传输完成 */
  31. while (!((I2C1_I2SR >> 1) & 0x01));
  32. /* 检查传输是否发生错误 */
  33. if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
  34. {
  35. i2c_master_stop();
  36. return ret;
  37. }
  38. /* 2、发送寄存器地址 */
  39. if (xfer->reg_size > 0)
  40. {
  41. do
  42. {
  43. clear_intpending_flag();
  44. xfer->reg_size --;
  45. /* 写入地址 */
  46. I2C1_I2DR = ((xfer->reg) >> (xfer->reg_size * 8)); // 先传输高字节
  47. /* 等待传输完成 */
  48. while (((I2C1_I2SR >> 1) & 0x01) == 0);
  49. /* 检查传输是否发生错误 */
  50. if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
  51. {
  52. i2c_master_stop();
  53. return ret;
  54. }
  55. } while (xfer->reg_size > 0);
  56. /* 如果是读时序,需要先发送重复开始信号;如果是写时序,直接发送数据 */
  57. if (xfer->direction == I2C_Read)
  58. {
  59. clear_intpending_flag();
  60. /* 发送重复开始信号 */
  61. ret = i2c_master_repeated_start(xfer->slaveAddress, I2C_Read);
  62. if (ret != Status_I2C_Success)
  63. {
  64. i2c_master_stop();
  65. return ret;
  66. }
  67. /* 等待传输完成 */
  68. while (((I2C1_I2SR >> 1) & 0x01) == 0);
  69. /* 检查传输是否发生错误 */
  70. if ((ret = i2c_check_and_clear_error()) != Status_I2C_Success)
  71. {
  72. i2c_master_stop();
  73. return ret;
  74. }
  75. }
  76. }
  77. /* 到此,准备工作完毕 */
  78. /* 发送数据 */
  79. if (xfer->direction == I2C_Write)
  80. {
  81. ret = i2c_master_write_blocking(xfer->buf, xfer->buf_size, xfer->stopFlag);
  82. }
  83. if (xfer->direction == I2C_Read)
  84. {
  85. ret = i2c_master_read_blocking(xfer->buf, xfer->buf_size);
  86. }
  87. return ret;
  88. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/AllinToyou/article/detail/205817?site
推荐阅读
相关标签
  

闽ICP备14008679号