1.前言
CSDN关于多路模拟I2C总线并行访问的资料很少,而且讲的我都没看明白,所以花时间研究了同事之前的代码,在硕哥的帮助下,算是搞懂了这方面的实现方案。写这个是希望当你遇到同样问题的时候,能够更快的解决。因为每个人一开始,都是菜鸟,所以相互帮助。
2.设计方案
假设有这样一个场景:你有6个传感器,它们的IIC地址相同,使用单片机实现6个传感器数据读取,要求IIC传输速率尽可能的快。
首先,硬件IIC数量无法满足,很少有单片机有六路硬件IIC总线。那一路挂载多个传感器呢?也不行,因为6个传感器的IIC地址相同。所以需要使用模拟IIC。这时,你会想到直接开6路模拟IIC,然后依次读取,这样实现起来很简单,但是传输速率会很慢。所以,为了最大程度提高传输速率,多个IIC总线并行传输是一个很好的方法,注意,这里的并行是指同一GPIO口的情况,比如A口从P0到P12,开6路I2C,实现6个SCL和SDA同时拉高拉低,这是真正意义上的并行,因为每个GPIO端口都有一组寄存器,通过直接操作这些寄存器,可以同时控制整个端口的所有IO口。
当然,在实际情况中,建议PA口开三路IIC,PB口开三路IIC。至于为什么不能在一个GPIO口同时开六路IIC,我也不清楚真正的原理,我所能理解并且解释的是单个GPIO口的负载能力无法实现,希望有知道不同原因的朋友能够分享。
3.代码实现
3.1 IIC.h
这是PA口实现三路并行模拟IIC总线,我先给出代码,再详细解释。这是IIC.h代码。
#ifndef __IIC_H__
#define __IIC_H__
#include "main.h"
#include "Delay.h"
#define SDA_GPIO_Port GPIOA
#define SCL_GPIO_Port GPIOA
//IIC总线1 SDA:PA11 SCL:PA12
#define SDA_Pin_1 GPIO_PIN_1
#define SCL_Pin_2 GPIO_PIN_2
//IIC总线2 SDA:PA3 SCL:PA4
#define SDA_Pin_3 GPIO_PIN_3
#define SCL_Pin_4 GPIO_PIN_4
//IIC总线3 SDA:PA5 SCL:PA6
#define SDA_Pin_5 GPIO_PIN_5
#define SCL_Pin_6 GPIO_PIN_6
#define SCL_H {HAL_GPIO_WritePin(GPIOA, SCL_Pin_2|SCL_Pin_4|SCL_Pin_6, GPIO_PIN_SET); }
#define SCL_L {HAL_GPIO_WritePin(GPIOA, SCL_Pin_2|SCL_Pin_4|SCL_Pin_6, GPIO_PIN_RESET); }
#define SDA_H {HAL_GPIO_WritePin(GPIOA, SDA_Pin_1|SDA_Pin_3|SDA_Pin_5, GPIO_PIN_SET); }
#define SDA_L {HAL_GPIO_WritePin(GPIOA, SDA_Pin_1|SDA_Pin_3|SDA_Pin_5, GPIO_PIN_RESET); }
void IIC_Start_x6(void);
void IIC_Stop_x6(void);
void IIC_Send_x6(uint8_t data);
void IIC_Data_left_OneBit(uint8_t * Data);
void IIC_Data_INC(uint8_t Recdata_temp,uint8_t *Data);
void IIC_Receive_x6(uint8_t *Data);
uint8_t IIC_Rec_ACK_x6(void);
void IIC_Send_ACK_x6(uint8_t ACK);
uint8_t GPIO_Read_Pin(void);
#endif
这里的重点是宏定义SCL_H,SCL_L,SDA_H,SDA_L的时候,操作引脚的组合,使用按位或操作符 |
来组合三个不同的引脚。
3.2 IIC_Receive
下面是IIC.c的部分,对于开始、停止、发送、发送应答、接收应答的函数和一般IIC函数一样,不需要修改,而其中IIC_Receive需要进行修改,下面重点讲这部分,这也是多路并行模拟I2C总线的核心。建议在这段代码学习之前,要非常熟悉一般情况下IIC_Receive的运行原理。否则下面这个可能有点绕人。
uint8_t GPIO_Read_Pin(void)
{
if(HAL_GPIO_ReadPin(GPIOA,SDA_Pin_1) == 0)
{
IIC_Read_Pin &= 0x06;
}
if(HAL_GPIO_ReadPin(GPIOA,SDA_Pin_3) == 0)
{
IIC_Read_Pin &= 0x05;
}
if(HAL_GPIO_ReadPin(GPIOA,SDA_Pin_5) == 0)
{
IIC_Read_Pin &= 0x03;
}
return IIC_Read_Pin;
}
void IIC_Data_left_OneBit(uint8_t * Data)
{
Data[0] <<= 1;
Data[1] <<= 1;
Data[2] <<= 1;
}
void IIC_Data_INC(uint8_t Recdata_temp,uint8_t *Data)
{
uint8_t i,temp;
for(i = 0,temp = 0x01;i < 3;i++)
{
if((Recdata_temp >> i) & temp)
{
Data[i]++;
}
}
}
void IIC_Receive_x6(uint8_t *Data)
{
uint8_t i,Recdata_temp;
SDA_H;
for(i = 0;i < 8;i++)
{
delay_us(1);
SCL_H;
delay_us(4);
IIC_Read_Pin = 0x07;
Recdata_temp = GPIO_Read_Pin();
IIC_Data_left_OneBit(Data);
IIC_Data_INC(Recdata_temp,Data);
SCL_L;
}
}
第一步。将SDA_H,主机放手,然后SCL_H,这样SCL高电平期间,主机就能读取数据。接着是IIC_Read_Pin = 0x07;这句是将 IIC_Read_Pin
变量设置为 0x07
,写成二进制是0000 0111,用于准备读取并存储三个SDA引脚的数据,可以看到GPIO_Read_Pin(void)中使用了 IIC_Read_Pin,
一共三个if语句。
if(HAL_GPIO_ReadPin(GPIOA,SDA_Pin_1) == 0)
{
IIC_Read_Pin &= 0x06;
}
检测到第一个SDA引脚是0,将 0000 0111和0000 0110 相与读取0。
if(HAL_GPIO_ReadPin(GPIOA,SDA_Pin_3) == 0)
{
IIC_Read_Pin &= 0x05;
}
同样第二个,将 0000 0111和0000 0101 相与读取0。
if(HAL_GPIO_ReadPin(GPIOA,SDA_Pin_5) == 0)
{
IIC_Read_Pin &= 0x03;
}
第三个,将 0000 0111和0000 0011 相与读取0。
这样就完成了三个IIC总线,每个SDA一个位的读取,并且将他们三个数据存储在一个变量Recdata_temp 里。
第二步。进行数据位移,准备接收下一个位数据。
void IIC_Data_left_OneBit(uint8_t * Data)
{
Data[0] <<= 1;
Data[1] <<= 1;
Data[2] <<= 1;
}
注意,Data
是一个数组,其中包含三个字节的数据,就是分别放置了三路IIC的数据,在C语言中,一个字节是8个位。
这个函数通过将每个字节的位向左移动一位来实现“左移一位”的操作。这种操作会将每个字节中最左边的位(最高位)舍弃,并在最右边(最低位)添加一个0。这是处理二进制数据时的常见操作,为新接收的数据位腾出空间。
这里是函数的逻辑:
Data[0] <<= 1;
将Data
数组的第一个元素(第一个字节)的所有位向左移动一位。Data[1] <<= 1;
将Data
数组的第二个元素(第二个字节)的所有位向左移动一位。Data[2] <<= 1;
将Data
数组的第三个元素(第三个字节)的所有位向左移动一位。
每次左移操作都会将相应字节的最高位向左移动并舍弃,同时在最低位添加一个0。这样,每个字节都为接收新的数据位做好了准备。
void IIC_Data_INC(uint8_t Recdata_temp,uint8_t *Data)
{
uint8_t i,temp;
for(i = 0,temp = 0x01;i < 3;i++)
{
if((Recdata_temp >> i) & temp)
{
Data[i]++;
}
}
}
将得到的Recdata_temp的数据依次填入数组Data里,最后循环8次,完成一个字节的读取,共三组IIC。
4.结束
一般情况下,较少用到多路并行模拟IIC,但是我觉得这种想法非常好。不需要使用多线程的操作系统,直接基于硬件对于GPIO口的操作,实现IIC的并行传输,极大地提高了传输速率,尤其是当你使用传感器数量较多时,硬件IIC资源有限,而且STM32的硬件IIC尽量不要使用,问题很多,如果做产品,那就千万不要用。
能力有限,如有错误,请谅解并指出,鄙人感激不尽。