STM32 多路模拟I2C总线:同型号多设备并行访问

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尽量不要使用,问题很多,如果做产品,那就千万不要用。

能力有限,如有错误,请谅解并指出,鄙人感激不尽。

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值