你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32开发项目:软件模拟I2C功能

[复制链接]
STMCU小助手 发布时间:2022-4-14 10:31
背景介绍
I2C协议介绍


I2C总线是由Philips公司开发的一种简单、双向二线制同步串行总线。它只需要两根线即可在连接于总线上的器件之间传送信息。

I2C 总线在物理连接上非常简单,分别由SDA(串行数据线)和SCL(串行时钟线)及上拉电阻组成。通信原理是通过对SCL和SDA线高低电平时序的控制,来 产生I2C总线协议所需要的信号进行数据的传递。在总线空闲状态时,这两根线一般被上面所接的上拉电阻拉高,保持着高电平。

1GL8V@3%GJKAFT8_62CG[@K.png

I2C总线特征
I2C总线上的每一个设备都可以作为主设备或者从设备,而且每一个设备都会对应一个唯一的地址(可以从I2C器件的数据手册得知),主从设备之间就通过这 个地址来确定与哪个器件进行通信,在通常的应用中,我们把CPU带I2C总线接口的模块作为主设备,把挂接在总线上的其他设备都作为从设备。

I2C总线上可挂接的设备数量受总线的最大电容400pF 限制,如果所挂接的是相同型号的器件,则还受器件地址位的限制。

I2C总线数据传输速率在标准模式下可达100kbit/s,快速模式下可达400kbit/s,高速模式下可达3.4Mbit/s。一般通过I2C总线接口可编程时钟来实现传输速率的调整,同时也跟所接的上拉电阻的阻值有关。

I2C总线上的主设备与从设备之间以字节(8位)为单位进行双向的数据传输。

I2C总线协议
I2C协议规定,总线上数据的传输必须以一个起始信号作为开始条件,以一个结束信号作为传输的停止条件。起始和结束信号总是由主设备产生。总线在空闲状态 时,SCL和SDA都保持着高电平,当SCL为高电平而SDA由高到低的跳变,表示产生一个起始条件;当SCL为高而SDA由低到高的跳变,表示产生一个 停止条件。在起始条件产生后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C器件无法访问总线;而在停止条件产生后,本次数据传输的主从设备 将释放总线,总线再次处于空闲状态。如图所示:
20200825211251684.png

在了解起始条件和停止条件后,我们再来看看在这个过程中数据的传输是如何进行的。前面我们已经提到过,数据传输以字节为单位。主设备在SCL线上产生每个 时钟脉冲的过程中将在SDA线上传输一个数据位,当一个字节按数据位从高位到低位的顺序传输完后,紧接着从设备将拉低SDA线,回传给主设备一个应答位, 此时才认为一个字节真正的被传输完成。当然,并不是所有的字节传输都必须有一个应答位,比如:当从设备不能再接收主设备发送的数据时,从设备将回传一个否 定应答位。数据传输的过程如图所示:

T%AP$JMF`0O9KOQSKJU$QRY.png

在前面我们还提到过,I2C总线上的每一个设备都对应一个唯一的地址,主从设备之间的数据传输是建立在地址的基础上,也就是说,主设备在传输有效数据之前 要先指定从设备的地址,地址指定的过程和上面数据传输的过程一样,只不过大多数从设备的地址是7位的,然后协议规定再给地址添加一个最低位用来表示接下来 数据传输的方向,0表示主设备向从设备写数据,1表示主设备向从设备读数据。如图所示:

TQW%K}E~3G%2UL(DY_X7YZ9.png


I2C总线操作
对I2C总线的操作实际就是主从设备之间的读写操作。大致可分为以下三种操作情况:
第一,主设备往从设备中写数据。数据传输格式如下:

20200825211445609.png

第二,主设备从从设备中读数据。数据传输格式如下:

20200825211526531.png

第三,主设备往从设备中写数据,然后重启起始条件,紧接着从从设备中读取数据;或者是主设备从从设备中读数据,然后重启起始条件,紧接着主设备往从设备中写数据。数据传输格式如下:

20200825211549622.png

第三种操作在单个主设备系统中,重复的开启起始条件机制要比用STOP终止传输后又再次开启总线更有效率。

本套库的特点
本套库的设计基于三个工程现实问题:
1.硬件I2C数量有限而且会影响代码的可移植性能;
2.I2C通常不会有很高的数据速度和使用频率;
3.不同的从机器件一般不会挂载在同一个总线上;

第一个问题决定了必要性。设计一套软件模拟I2C库函数可以使得I2C的使用不受数量的限制且可移植行更强。第二个问题决定了可行性。使用I2C总线的低速低频特性决定了使用软件直接操作单片机的GPIO模拟I2C协议不至于占用过多的单片机资源或者无法满足性能的要求。第三个问题决定了通用性。软件模拟的I2C库函数必须有一套机制可以通过分时复用的方式驱动多条总线。笔者编写的库函数较好的符合了上述三个特征,以下给出源码以供大家参考。

源码实现
注意,笔者的源码是基于STM32F407平台运行的,延时函数也是从它处调用。在移植本代码的时候应当注意与平台硬件相关部分(例如void I2C_Virtual_SetSDA_Out())以及延时函数(delay_us())的实现。

头文件
  1. #ifndef __I2C_H__
  2. #define __I2C_H__

  3. #include "stm32f4xx.h"
  4. #include "stm32f4xx_conf.h"

  5. #ifdef I2C_VIRTUAL

  6. typedef struct
  7. {
  8.         char SclPort;
  9.         uint8_t SclPin;
  10.         char SdaPort;
  11.         uint8_t SdaPin;
  12. } I2CPort_Struct;

  13. typedef struct
  14. {
  15.         //I2C从机设备的端口
  16.         I2CPort_Struct port;
  17.         /*
  18.          * I2C设备的写地址 = I2C设备地址 << 1 + 0
  19.          * I2C设备的读地址 = (I2C设备地址 << 1) + 1
  20.          */
  21.         //I2C从机设备地址(器件地址占7位,D7~D1)
  22.         uint8_t address;
  23.         //I2C从机设备读写状态(0:写,1:读)
  24. //        uint8_t rwStatus;
  25.         //I2C从机设备的状态(0:错误或不存在,1:正常)
  26.         uint8_t devStatus;
  27. } I2CDevice_Struct;

  28. extern uint8_t I2C_Virtual_ack;

  29. void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin);
  30. void I2C_Virtual_SwitchBus(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin);
  31. void I2C_Virtual_SetSDA_Out(void);
  32. void I2C_Virtual_SetSDA_In(void);
  33. void I2C_Virtual_Init(void);
  34. void I2C_Virtual_Start(void);
  35. void I2C_Virtual_Stop(void);
  36. uint8_t I2C_Virtual_SendByte(uint8_t c);
  37. uint8_t I2C_Virtual_RcvByte(void);
  38. void I2C_Virtual_Ack(void);
  39. void I2C_Virtual_NoAck(void);
  40. void I2C_Virtual_WaitAck(uint16_t time);

  41. #endif
  42. #endif
复制代码

源文件
STM32F407平台
  1. #include "i2c_virtual.h"

  2. #ifdef I2C_VIRTUAL

  3. static uint16_t I2C_Virtual_SDA_Pin;
  4. static GPIO_TypeDef *I2C_Virtual_SDA_Port;
  5. static unsigned long *I2C_Virtual_SDA_OUT_Addr;
  6. static unsigned long *I2C_Virtual_SDA_IN_Addr;
  7. static unsigned long *I2C_Virtual_SCL_OUT_Addr;

  8. uint8_t I2C_Virtual_ack;        //应答标志位

  9. /**
  10. * @brief 配置模拟I2C的引脚端口。
  11. *                 主要实现了MCU的GPIO端口配置,SDA需要配置为开漏输出,SCL需要配置为推挽输出。
  12. *                 此函数实现与硬件平台相关。
  13. * @param SDA_Port: 'A'~'G'
  14. * @param SDA_Pin: 0~15
  15. * @param SCL_Port: 'A'~'G'
  16. * @param SCL_Pin: 0~15
  17. */
  18. void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)
  19. {
  20.         uint16_t I2C_Virtual_SDA_Pin;
  21.         uint16_t I2C_Virtual_SCL_Pin;
  22.         GPIO_TypeDef *I2C_Virtual_SDA_Port;
  23.         GPIO_TypeDef *I2C_Virtual_SCL_Port;
  24.         GPIO_InitTypeDef GPIO_InitStructure;

  25.         switch (SDA_Port)
  26.         {
  27.         case 'A':
  28.                 I2C_Virtual_SDA_Port = GPIOA;
  29.                 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
  30.                 break;
  31.         case 'B':
  32.                 I2C_Virtual_SDA_Port = GPIOB;
  33.                 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
  34.                 break;
  35.         case 'C':
  36.                 I2C_Virtual_SDA_Port = GPIOC;
  37.                 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
  38.                 break;
  39.         case 'D':
  40.                 I2C_Virtual_SDA_Port = GPIOD;
  41.                 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
  42.                 break;
  43.         case 'E':
  44.                 I2C_Virtual_SDA_Port = GPIOE;
  45.                 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
  46.                 break;
  47.         case 'F':
  48.                 I2C_Virtual_SDA_Port = GPIOF;
  49.                 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
  50.                 break;
  51.         case 'G':
  52.                 I2C_Virtual_SDA_Port = GPIOG;
  53.                 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);
  54.                 break;
  55.         default:
  56.                 break;
  57.         }

  58.         I2C_Virtual_SDA_Pin = (uint16_t) (0x0001 << SDA_Pin);

  59.         switch (SCL_Port)
  60.         {
  61.         case 'A':
  62.                         I2C_Virtual_SCL_Port = GPIOA;
  63.                         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);
  64.                         break;
  65.                 case 'B':
  66.                         I2C_Virtual_SCL_Port = GPIOB;
  67.                         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE);
  68.                         break;
  69.                 case 'C':
  70.                         I2C_Virtual_SCL_Port = GPIOC;
  71.                         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
  72.                         break;
  73.                 case 'D':
  74.                         I2C_Virtual_SCL_Port = GPIOD;
  75.                         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD,ENABLE);
  76.                         break;
  77.                 case 'E':
  78.                         I2C_Virtual_SCL_Port = GPIOE;
  79.                         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE,ENABLE);
  80.                         break;
  81.                 case 'F':
  82.                         I2C_Virtual_SCL_Port = GPIOF;
  83.                         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
  84.                         break;
  85.                 case 'G':
  86.                         I2C_Virtual_SCL_Port = GPIOG;
  87.                         RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOG,ENABLE);
  88.                         break;
  89.                 default:
  90.                         break;
  91.         }

  92.         I2C_Virtual_SCL_Pin = (uint16_t) (0x0001 << SCL_Pin);

  93.         GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
  94.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  95.         GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
  96.         GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
  97.         GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);

  98.         GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SCL_Pin;
  99.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  100.         GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  101.         GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  102.         GPIO_Init(I2C_Virtual_SCL_Port, &GPIO_InitStructure);
  103. }

  104. /**
  105. * @brief 使本套库函数可以支持多个独立的软件模拟I2C总线。
  106. *                 通过切换I2C总线引脚,以分别控制不同的芯片。
  107. *                 一般在配置模拟I2C的引脚端口之后,需要调用此函数设置总线。
  108. * @param SDA_Port: 'A'~'G'
  109. * @param SDA_Pin: 0~15
  110. * @param SCL_Port: 'A'~'G'
  111. * @param SCL_Pin: 0~15
  112. */
  113. void I2C_Virtual_SwitchBus(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)
  114. {

  115.         switch (SDA_Port)
  116.         {
  117.         case 'A':
  118.                 I2C_Virtual_SDA_Port = GPIOA;
  119.                 I2C_Virtual_SDA_OUT_Addr = GPIOA_OUT_ADDR(SDA_Pin);
  120.                 I2C_Virtual_SDA_IN_Addr = GPIOA_IN_ADDR(SDA_Pin);
  121.                 break;
  122.         case 'B':
  123.                 I2C_Virtual_SDA_Port = GPIOB;
  124.                 I2C_Virtual_SDA_OUT_Addr = GPIOB_OUT_ADDR(SDA_Pin);
  125.                 I2C_Virtual_SDA_IN_Addr = GPIOB_IN_ADDR(SDA_Pin);
  126.                 break;
  127.         case 'C':
  128.                 I2C_Virtual_SDA_Port = GPIOC;
  129.                 I2C_Virtual_SDA_OUT_Addr = GPIOC_OUT_ADDR(SDA_Pin);
  130.                 I2C_Virtual_SDA_IN_Addr = GPIOC_IN_ADDR(SDA_Pin);
  131.                 break;
  132.         case 'D':
  133.                 I2C_Virtual_SDA_Port = GPIOD;
  134.                 I2C_Virtual_SDA_OUT_Addr = GPIOD_OUT_ADDR(SDA_Pin);
  135.                 I2C_Virtual_SDA_IN_Addr = GPIOD_IN_ADDR(SDA_Pin);
  136.                 break;
  137.         case 'E':
  138.                 I2C_Virtual_SDA_Port = GPIOE;
  139.                 I2C_Virtual_SDA_OUT_Addr = GPIOE_OUT_ADDR(SDA_Pin);
  140.                 I2C_Virtual_SDA_IN_Addr = GPIOE_IN_ADDR(SDA_Pin);
  141.                 break;
  142.         case 'F':
  143.                 I2C_Virtual_SDA_Port = GPIOF;
  144.                 I2C_Virtual_SDA_OUT_Addr = GPIOF_OUT_ADDR(SDA_Pin);
  145.                 I2C_Virtual_SDA_IN_Addr = GPIOF_IN_ADDR(SDA_Pin);
  146.                 break;
  147.         case 'G':
  148.                 I2C_Virtual_SDA_Port = GPIOG;
  149.                 I2C_Virtual_SDA_OUT_Addr = GPIOG_OUT_ADDR(SDA_Pin);
  150.                 I2C_Virtual_SDA_IN_Addr = GPIOG_IN_ADDR(SDA_Pin);
  151.                 break;
  152.         default:
  153.                 break;
  154.         }

  155.         I2C_Virtual_SDA_Pin = (uint16_t) (0x0001 << SDA_Pin);

  156.         switch (SCL_Port)
  157.         {
  158.         case 'A':
  159.                 I2C_Virtual_SCL_OUT_Addr = GPIOA_OUT_ADDR(SCL_Pin);
  160.                 break;
  161.         case 'B':
  162.                 I2C_Virtual_SCL_OUT_Addr = GPIOB_OUT_ADDR(SCL_Pin);
  163.                 break;
  164.         case 'C':
  165.                 I2C_Virtual_SCL_OUT_Addr = GPIOC_OUT_ADDR(SCL_Pin);
  166.                 break;
  167.         case 'D':
  168.                 I2C_Virtual_SCL_OUT_Addr = GPIOD_OUT_ADDR(SCL_Pin);
  169.                 break;
  170.         case 'E':
  171.                 I2C_Virtual_SCL_OUT_Addr = GPIOE_OUT_ADDR(SCL_Pin);
  172.                 break;
  173.         case 'F':
  174.                 I2C_Virtual_SCL_OUT_Addr = GPIOF_OUT_ADDR(SCL_Pin);
  175.                 break;
  176.         case 'G':
  177.                 I2C_Virtual_SCL_OUT_Addr = GPIOG_OUT_ADDR(SCL_Pin);
  178.                 break;
  179.         default:
  180.                 break;
  181.         }
  182. }



  183. /**
  184. * @brief Switch data bus input/output state.
  185. */
  186. void I2C_Virtual_SetSDA_Out(void)
  187. {
  188.         GPIO_InitTypeDef GPIO_InitStructure;

  189.         GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
  190.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  191.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  192.         GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
  193.         GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

  194.         GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);
  195. }



  196. /**
  197. * @brief Switch data bus input/output state.
  198. */
  199. void I2C_Virtual_SetSDA_In(void)
  200. {
  201.         GPIO_InitTypeDef GPIO_InitStructure;

  202.         GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
  203.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  204.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  205.         GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
  206.         GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;

  207.         GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);
  208. }


  209. /**
  210. * @brief Initialization of the virtual I2C.
  211. */
  212. void I2C_Virtual_Init(void)
  213. {
  214.         *I2C_Virtual_SCL_OUT_Addr = 1;
  215.         
  216.         I2C_Virtual_SetSDA_Out();
  217.         *I2C_Virtual_SDA_OUT_Addr = 1;
  218. }



  219. /**
  220. * @brief Start I2C.
  221. */
  222. void I2C_Virtual_Start()
  223. {
  224.         *I2C_Virtual_SDA_OUT_Addr = 1;                        //发送起始条件的数据信号
  225.         delay_us(1);

  226.         *I2C_Virtual_SCL_OUT_Addr = 1;
  227.         delay_us(10);                                                                                //起始条件建立时间大于4.7us,延时

  228.         *I2C_Virtual_SDA_OUT_Addr = 0;                //发送起始信号
  229.         delay_us(10);                                                                                //起始条件锁定时间大于4μ

  230.         *I2C_Virtual_SCL_OUT_Addr = 0;                        //钳住I2C总线,准备发送或接收数据
  231.         delay_us(2);
  232. }



  233. /**
  234. * @brief Stop I2C.
  235. */
  236. void I2C_Virtual_Stop()
  237. {
  238.         *I2C_Virtual_SCL_OUT_Addr = 0;
  239.         delay_us(2);

  240.         *I2C_Virtual_SDA_OUT_Addr = 0;        //发送结束条件的数据信号
  241.         delay_us(1);

  242.         *I2C_Virtual_SCL_OUT_Addr = 1;                //发送结束条件的时钟信号
  243.         delay_us(10);                                                                        //结束条件建立时间大于4μ

  244.         *I2C_Virtual_SDA_OUT_Addr = 1;                //发送I2C总线结束信号
  245.         delay_us(10);
  246. }



  247. /**
  248. * @brief 主机字节数据发送函数
  249. *                 将数据c发送出去,可以是地址,也可以是数据,发完后等待应答,
  250. *                 并对此状态位进行操作.(不应答或非应答都使ack=0 假)
  251. *                 发送数据正常,ack=1; ack=0表示被控器无应答或损坏。
  252. * @retval 应答位
  253. *                 @arg 0:有应答
  254. *                 @arg 1:无应答
  255. */
  256. uint8_t I2C_Virtual_SendByte(uint8_t c)
  257. {
  258.         for (uint8_t BitCnt = 0; BitCnt < 8; BitCnt++)        //要传送的数据长度为8位
  259.         {
  260.                 if ((c << BitCnt) & 0x80)        //判断发送位
  261.                         *I2C_Virtual_SDA_OUT_Addr = 1;
  262.                 else
  263.                         *I2C_Virtual_SDA_OUT_Addr = 0;

  264.                 delay_us(1);

  265.                 *I2C_Virtual_SCL_OUT_Addr = 1;        //置时钟线为高,通知被控器开始接收数据位
  266.                 delay_us(10);        //保证时钟高电平周期大于4μ

  267.                 *I2C_Virtual_SCL_OUT_Addr = 0;
  268.         }

  269.         I2C_Virtual_WaitAck(1000);
  270.         
  271.         return I2C_Virtual_ack;
  272. }



  273. /**
  274. * @brief 主机字节数据接收函数
  275. *                 用来接收从器件传来的数据,并判断总线错误(不发应答信号),
  276. *                 发完后请用应答函数。
  277. */
  278. uint8_t I2C_Virtual_RcvByte()
  279. {
  280.         uint8_t retc;
  281.         uint8_t BitCnt;

  282.         retc = 0;

  283.         *I2C_Virtual_SDA_OUT_Addr = 1;        //置数据线为输入方式

  284.         for (BitCnt = 0; BitCnt < 8; BitCnt++)
  285.         {
  286.                 delay_us(1);
  287.                 *I2C_Virtual_SCL_OUT_Addr = 0;        //置时钟线为低,准备接收数据位
  288.                 delay_us(10);        //时钟低电平周期大于4.7us
  289.                 *I2C_Virtual_SCL_OUT_Addr = 1;        //置时钟线为高使数据线上数据有效
  290.                 //delay1us();
  291.                 retc = retc << 1;
  292.                 I2C_Virtual_SetSDA_In();

  293.                 if (*I2C_Virtual_SDA_IN_Addr == 1)
  294.                         retc = retc + 1;        //读数据位,接收的数据位放入retc中

  295.                 I2C_Virtual_SetSDA_Out();
  296.                 delay_us(1);
  297.         }

  298.         *I2C_Virtual_SCL_OUT_Addr = 0;
  299.         delay_us(2);

  300.         return (retc);
  301. }



  302. /**
  303. * @brief 主机应答函数
  304. */
  305. void I2C_Virtual_Ack(void)
  306. {
  307.         *I2C_Virtual_SDA_OUT_Addr = 0;
  308.         delay_us(2);

  309.         *I2C_Virtual_SCL_OUT_Addr = 1;
  310.         delay_us(10);        //时钟低电平周期大于4μ

  311.         *I2C_Virtual_SCL_OUT_Addr = 0;          //清时钟线,钳住I2C总线以便继续接收
  312.         delay_us(2);
  313. }



  314. /**
  315. * @brief 主机不应答函数
  316. */
  317. void I2C_Virtual_NoAck(void)
  318. {
  319.         *I2C_Virtual_SDA_OUT_Addr = 1;
  320.         delay_us(1);

  321.         *I2C_Virtual_SCL_OUT_Addr = 1;
  322.         delay_us(10);        //时钟低电平周期大于4μ

  323.         *I2C_Virtual_SCL_OUT_Addr = 0;            //清时钟线,钳住I2C总线以便继续接收
  324.         delay_us(2);
  325. }


  326. /**
  327. * @brief 主机等待应答函数
  328. * @param time: wait for timeout
  329. */
  330. void I2C_Virtual_WaitAck(uint16_t time)
  331. {
  332.         uint16_t timeout = 0;

  333.         *I2C_Virtual_SDA_OUT_Addr = 1;        //8位发送完后释放数据线,准备接收应答位
  334.         I2C_Virtual_SetSDA_In();

  335.         *I2C_Virtual_SCL_OUT_Addr = 1;

  336.         delay_us(1);

  337.         while(*I2C_Virtual_SDA_IN_Addr != 0)
  338.         {
  339.                 if(timeout++>time)
  340.                 {
  341.                         I2C_Virtual_ack = 0;
  342.                         
  343.                         I2C_Virtual_SetSDA_Out();
  344.                         I2C_Virtual_Stop();

  345.                         return;
  346.                 }
  347.         }

  348.         I2C_Virtual_ack = 1;

  349.         I2C_Virtual_SetSDA_Out();
  350.         *I2C_Virtual_SCL_OUT_Addr = 0;

  351.         delay_us(2);
  352. }

  353. #endif
复制代码

STM32F103平台
平台的不同影响了void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin), void I2C_Virtual_SetSDA_Out(void),void I2C_Virtual_SetSDA_In(void)这三个与底层硬件相关的函数。

  1. /**
  2. * @brief 配置模拟I2C的引脚端口。
  3. *                 主要实现了MCU的GPIO端口配置,SDA需要配置为开漏输出,SCL需要配置为推挽输出。
  4. *                 此函数实现与硬件平台相关。
  5. * @param SDA_Port: 'A'~'G'
  6. * @param SDA_Pin: 0~15
  7. * @param SCL_Port: 'A'~'G'
  8. * @param SCL_Pin: 0~15
  9. */
  10. void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)
  11. {
  12.         uint16_t I2C_Virtual_SDA_Pin;
  13.         uint16_t I2C_Virtual_SCL_Pin;
  14.         GPIO_TypeDef *I2C_Virtual_SDA_Port;
  15.         GPIO_TypeDef *I2C_Virtual_SCL_Port;
  16.         GPIO_InitTypeDef GPIO_InitStructure;

  17.         switch (SDA_Port)
  18.         {
  19.         case 'A':
  20.                 I2C_Virtual_SDA_Port = GPIOA;
  21.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  22.                 break;
  23.         case 'B':
  24.                 I2C_Virtual_SDA_Port = GPIOB;
  25.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  26.                 break;
  27.         case 'C':
  28.                 I2C_Virtual_SDA_Port = GPIOC;
  29.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
  30.                 break;
  31.         case 'D':
  32.                 I2C_Virtual_SDA_Port = GPIOD;
  33.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
  34.                 break;
  35.         case 'E':
  36.                 I2C_Virtual_SDA_Port = GPIOE;
  37.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
  38.                 break;
  39.         case 'F':
  40.                 I2C_Virtual_SDA_Port = GPIOF;
  41.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
  42.                 break;
  43.         case 'G':
  44.                 I2C_Virtual_SDA_Port = GPIOG;
  45.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
  46.                 break;
  47.         default:
  48.                 break;
  49.         }

  50.         I2C_Virtual_SDA_Pin = (uint16_t) (0x0001 << SDA_Pin);

  51.         switch (SCL_Port)
  52.         {
  53.         case 'A':
  54.                 I2C_Virtual_SCL_Port = GPIOA;
  55.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
  56.                 break;
  57.         case 'B':
  58.                 I2C_Virtual_SCL_Port = GPIOB;
  59.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  60.                 break;
  61.         case 'C':
  62.                 I2C_Virtual_SCL_Port = GPIOC;
  63.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
  64.                 break;
  65.         case 'D':
  66.                 I2C_Virtual_SCL_Port = GPIOD;
  67.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD,ENABLE);
  68.                 break;
  69.         case 'E':
  70.                 I2C_Virtual_SCL_Port = GPIOE;
  71.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
  72.                 break;
  73.         case 'F':
  74.                 I2C_Virtual_SCL_Port = GPIOF;
  75.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOF,ENABLE);
  76.                 break;
  77.         case 'G':
  78.                 I2C_Virtual_SCL_Port = GPIOG;
  79.                 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG,ENABLE);
  80.                 break;
  81.         default:
  82.                 break;
  83.         }

  84.         I2C_Virtual_SCL_Pin = (uint16_t) (0x0001 << SCL_Pin);

  85.         GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
  86.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  87.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  88.         GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);

  89.         GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SCL_Pin;
  90.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  91.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  92.         GPIO_Init(I2C_Virtual_SCL_Port, &GPIO_InitStructure);
  93. }



  94. /**
  95. * @brief Switch data bus input/output state.
  96. */
  97. void I2C_Virtual_SetSDA_Out(void)
  98. {
  99.         GPIO_InitTypeDef GPIO_InitStructure;

  100.         GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
  101.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  102.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;

  103.         GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);
  104. }



  105. /**
  106. * @brief Switch data bus input/output state.
  107. */
  108. void I2C_Virtual_SetSDA_In(void)
  109. {
  110.         GPIO_InitTypeDef GPIO_InitStructure;

  111.         GPIO_InitStructure.GPIO_Pin = I2C_Virtual_SDA_Pin;
  112.         GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  113.         GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;

  114.         GPIO_Init(I2C_Virtual_SDA_Port, &GPIO_InitStructure);
  115. }
复制代码

应用指南
软件模拟I2C属于底层驱动类型的库,一般情况下不会直接使用,而是为各种芯片的库函数提供底层接口。本章节从框架结构上对软件模拟I2C库驱动多个不同总线上的芯片做一个简要说明。

首先应完成GPIO的初始化配置,SDA功能的GPIO配置为开漏输出(总线要上拉)、SCL功能的GPIO配置为推挽或者开漏输出。此处以STM32F103单片机为例:
  1. /**
  2. * ADS1115 - SDA: PB0, SCL: PB1
  3. */
  4. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  5. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  6. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  7. GPIO_Init(GPIOE, &GPIO_InitStructure);

  8. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  9. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  10. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  11. GPIO_Init(GPIOE, &GPIO_InitStructure);

  12. /**
  13. * ADS1115 - SDA: PB2, SCL: PB3
  14. */
  15. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  16. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  17. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  18. GPIO_Init(GPIOE, &GPIO_InitStructure);

  19. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  20. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  21. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
  22. GPIO_Init(GPIOE, &GPIO_InitStructure);
复制代码

本库函数中内置了void I2C_Virtual_ConfigPort(char SDA_Port, uint8_t SDA_Pin, char SCL_Port, uint8_t SCL_Pin)函数,可以直接完成GPIO端口的初始化工作。应当注意的是,此函数的实现与硬件平台密切相关,因此在移植的时候需要注意。

宏定义挂载设备的总线引脚,字符A, B, …,G表示端口号,数字0~15表示引脚号。
  1. #define ADS1115_SCL_PORT        'A'
  2. #define ADS1115_SCL_PIN                6

  3. #define ADS1115_SDA_PORT        'A'
  4. #define ADS1115_SDA_PIN                5
复制代码

设置Device1的I2C总线。
  1. I2C_Virtual_SwitchBus(ADS1115_SDA_PORT, ADS1115_SDA_PIN, ADS1115_SCL_PORT, ADS1115_SCL_PIN);
复制代码
执行Device1的相关操作。
在Device1的库函数的I2C通讯部分,应该调用本库的各种I2C通讯接口,例如void I2C_Virtual_Start(),void I2C_Virtual_Stop(),uint8_t I2C_Virtual_SendByte(uint8_t c)。
  1. ADS1115_Config();
  2. ADS1115_ReadData();
复制代码

切换Device2的I2C总线
  1. I2C_Virtual_SwitchBus(sts31Dev.SdaPort, sts31Dev.SdaPin, sts31Dev.SclPort, sts31Dev.SclPin);
复制代码

执行Device2的相关操作
  1. STS31_Config();
  2. STS31_ReadData();
复制代码




收藏 评论0 发布时间:2022-4-14 10:31

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版