基于STM32F1的IIC设备自动寻址方法(IIC scanner)

基于STM32F1的IIC设备自动寻址方法

  • 1. IIC协议
    • 1.1 概述
    • 1.2 硬件(开漏结构)
      • 1.2.1 拉低
      • 1.2.2 拉高
      • 1.2.3 上拉电阻
      • 1.2.4 总线电阻
    • 1.3 IIC协议规定
      • 1.3.1 开始、停止指令
      • 1.3.2 数据传输
      • 1.3.3 响应ACK与不响应NACK
    • 1.4 IIC数据读写
      • 1.4.1 向从机写入数据
      • 1.4.2 从从机读入数据
  • 2. 基于STM32F1的IIC硬件实现
    • 2.1 GPIO配置
    • 2.2 实现效果

本文章说明了利用STM32F1平台进行IIC设备自动寻址的方法。分为IIC协议介绍与STM32F1代码实现2个部分。

1. IIC协议

1.1 概述

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第1张图片
IIC是一个非常常用且功能强大的总线协议,常常用于主从机设备通信中。IIC总线只需2根通信线,SDA(数据线)与SCL(时钟线),即可在一个总线上同时控制单个或多个从设备,而这也是IIC协议相比于其他通信协议的最大优势。上图说明了这个过程,微控制器(microcontroller)作为IIC的主机,通过一个多路复用器(或开关),同时控制了IO扩展器,传感器,EEPROM,ADC与DAC等。

1.2 硬件(开漏结构)

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第2张图片
开漏(open drain)是一种io端口输出的类型,如上图所示。他能够拉低总线上的电平至GND,或者释放总线让其被Pull up电阻拉高至Vcc。这就意味着协议没有办法实现当一个设备试图通过释放总线来拉高总线,而另一个设备试图拉低总线,这会导致端口被强制拉低(在有pull up电阻)。注意,推挽式输出接口不允许这种设置。

1.2.1 拉低

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第3张图片
如前文所述,开漏输出结构下,总线只能拉低,或者释放总线(而后被上拉电阻拉高至Vcc)。上图说明了这个过程,当需要拉低端口时,使能FET,就可以让端口与GND短接,进而将总线拉低。

1.2.2 拉高

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第4张图片
当主从设备希望传输高电平时,只能通过关闭FET来释放总线。而这将使得总线悬空(floating),上拉电阻将电压拉到电压轨上,这将被解释为高电压。

1.2.3 上拉电阻

在大多数情况下,由于IIC为开漏输出,器件本身只能输出低电平,无法主动输出高电平(因为无法接到电源轨),只能通过外部上拉电阻将信号线拉至高电平。有的微控制器内部集成了上拉电阻,在小电流应用时候(如IIC)可不加,但在需要大功率输出时需要加上拉电阻,通过电阻并联来增加输出电流的能力。
上拉电阻的大小,需要考虑功耗与总线速。如果为了尽量提高速度,那么就牵涉到总线电容C的问题。上拉电阻R与总线电容形成了RC,通信速率较高时将直接影响通讯的有效性。
如果你想尽可能降低功耗,那么就要尽可能增大电阻以最大可能的减小电路各部分的消耗电流从而实现整体降低功耗。但不可能无限大,否则充电时间会非常大。常用的上拉电阻一般为1.5k欧姆~10k欧姆。

1.2.4 总线电阻

IIC协议还定义了串联在SDA、SCL线上电阻Rs。该电阻的作用是,有效抑制总线上的干扰脉冲进入从设备,提高可靠性。这个电阻的选择一般在100~200欧姆左右。当然,这个电阻并不是必须的,在恶劣噪声环境中,可以选用。

1.3 IIC协议规定

1.3.1 开始、停止指令

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第5张图片
IIC协议的被主机发出的START指令初始化,被主机发出的STOP指令终止。当SCL总线处于高电平的时候,SDA由高变低,即被解释为START指令。当SCL总线处于高电平的时候,SDA由低变高,即被解释为STOP指令。

1.3.2 数据传输

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第6张图片
数据在SCL每一次脉冲时候被传输。在SDA线上,1 byte由8 bits组成,可以是设备地址,寄存器地址,或是要写入/读出从机的数据。在START和STOP中,数据从高位起始(MSB),然后逐个传输后续的数据。在SCL为高的时候,SDA的状态必须保持不变,否则就是START或者STOP指令了。

1.3.3 响应ACK与不响应NACK

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第7张图片
每一次byte传输完成之后都会紧跟一个从接收器处发出的ACK位,用于向主机说明从机已经成功接收到指令。本文主要利用这个响应去自动搜索IIC设备的地址。
应当注意,在接收器处发出的ACK位之前,主机必须释放SDA总线。ACK被定义为在SCL为低时,SDA被拉低,其在SCL为高电平时候保持不变。注意,当SDA在SCL为高电平时候为高电平,则被解释为NACK。

1.4 IIC数据读写

IIC的数据读写离不开从机的接收与响应,也离不开寄存器。所谓寄存器是从机中带有信息的存储器。信息可以是设置信息,也可以是采样信息。主机必须向从机寄存器内写入信息,以配置从机设备执行一个特定的任务。
应当注意,不是所有的从机设备都有寄存器,一些简单的从机设备只有一个寄存器,这就需要在写入从机地址后立马操作该寄存器。

1.4.1 向从机写入数据

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第8张图片
为了向IIC总线中写入数据,主机必须发出开始指令,而后紧跟着从机地址,最后位根据写/读置0或1。在从机发出ACK位后,主机发出想写入的寄存器地址,从机将会再次发出ACK位,让主机知道从机准备好了。在此之后,主机将开始传输寄存器数据,直到主机发送完需要传输的所有数据(一般是一个byte),并在停止指令后终止传输。

1.4.2 从从机读入数据

基于STM32F1的IIC设备自动寻址方法(IIC scanner)_第9张图片
从从机读取数据与写入数据的过程十分相似,但有一些额外的步骤。为了从从机中读取数据,主机必须告诉从机它想读哪一个寄存器。首先,主机必须发出开始指令,而后紧跟着从机地址,最后位置0。在从机发出ACK位后,主机发出想读出的寄存器地址,从机将再次发出ACK位。而后,主机将发出开始指令,而后紧跟着从机地址,最后位置1,待从机发出ACK后,释放SDA总线,即可接收到从机制定寄存器内的数据。在每一个byte后面,主机将向从机发出ACK,让从机知道它已经成功接收到了指令。当主机接收到了足够的数据后,将发出NACK指令,告诉从机暂停通讯,并释放总线。主机随后发出停止指令来中止此次传输。

2. 基于STM32F1的IIC硬件实现

STM32F1自带的IIC外设存在着各种问题,故使用GPIO硬件模拟的方式来进行IIC通信。下面对其进行详细说明。

2.1 GPIO配置

在这里我们选用了PA5做SDA线,PA6做SCL线。首先,编写IO口初始化函数IIC_INIT(void),将SDA、SCL初始化为上拉模式。

void IIC_INIT(void)		//IIC初始化
{
	GPIO_InitTypeDef GPIO_INIT;
	RCC_APB2PeriphClockCmd(IIC_RCC,ENABLE);
	
	GPIO_INIT.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_INIT.GPIO_Pin=IIC_SDA | IIC_SCL;
	GPIO_INIT.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(IIC_PORT,&GPIO_INIT);
	
	IIC_SDA_H;
	IIC_SCL_H;
}

首先定义IIC延时函数IIC_Delay,来满足IIC协议的通讯速率要求(100kHz~400kHz为多)

void IIC_Delay(void)
{unsigned char x;
	for(x=1;x>0;x--)
	{
		__NOP();__NOP();__NOP();__NOP();__NOP();
	}
}

而后,设置主机SDA线输出函数IIC_SDA_OUT(void)

void IIC_SDA_OUT(void)	//SDA配置为输出
{
	GPIO_InitTypeDef GPIO_INIT;
	GPIO_INIT.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_INIT.GPIO_Pin=IIC_SDA ;
	GPIO_INIT.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(IIC_PORT,&GPIO_INIT);
}

同时设置主机SDA线释放函数IIC_SDA_IN(void)

void IIC_SDA_IN(void)	//SDA配置为输入
{
	GPIO_InitTypeDef GPIO_INIT;
	GPIO_INIT.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_INIT.GPIO_Pin=IIC_SDA ;
	GPIO_INIT.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(IIC_PORT,&GPIO_INIT);
}

接下来,根据1.3.1的开始指令定义,设置START函数IIC_Start(void)

void IIC_Start(void)	//开始信号
{
	IIC_SDA_OUT();
	IIC_SDA_H;
	IIC_SCL_H;
	IIC_Delay();
	IIC_SDA_L;
	IIC_Delay();
	IIC_SDA_L;
}

STOP函数IIC_Stop(void)

void IIC_Stop(void)	//结束信号
{
	IIC_SDA_OUT();
	IIC_SDA_L;
	IIC_SCL_H;
	IIC_Delay();
	IIC_SDA_H;
	IIC_Delay();
	IIC_SCL_L;
	
	IIC_SCL_H;
	IIC_SDA_H;
}

读取从机发出的ACK函数IIC_ACK_Read(void)

bool IIC_ACK_Read(void)	//读取应答信号
{
	bool ack;

	u8 ucErrTime=0;	
		IIC_SDA_IN();
		IIC_SDA_H;
		IIC_SDA_IN();      //SDA设置为输入
		delay_us(4);
		IIC_SCL_H;
		delay_us(2);
		IIC_SCL_L; 
	while(IIC_SDA_READ())
	{
		if(ucErrTime>250)
		{
			IIC_Stop();
			return false;
		}
		ucErrTime++;
	}
	IIC_Stop();
	
	return true;  
}

主机发出ACK指令函数IIC_ACK_Send(bool ack)

void IIC_ACK_Send(bool ack)	//发送应答信号
{
	IIC_SDA_OUT();
	IIC_SCL_L;
	if(ack == true) 
		IIC_SDA_L;
	else IIC_SDA_H;
	IIC_SCL_H;
	IIC_Delay();
	IIC_SCL_L;
}

结合上面的函数与1.4.1,可以得到主机发送1 byte数据的函数IIC_Send_Byte(unsigned char byte)。注意,这里我把接收ACK指令的函数单独拿出来了。

void IIC_Send_Byte(unsigned char byte)	//IIC发送一位数据
{
	unsigned char i;
	IIC_SDA_OUT();
	IIC_SCL_L;
	for(i=0;i<8;i++)
	{
		if(byte & 0x80)	IIC_SDA_H;
		else IIC_SDA_L;
		IIC_SCL_H;
		IIC_Delay();
		IIC_SCL_L;
		IIC_Delay();
		byte<<=1;
	}
	//IIC_ACK_Read();
}

同样结合1.4.2,可以得到主机读取1 byte数据的函数IIC_Read_Byte(void)。

unsigned char  IIC_Read_Byte(void)	//IIC读取一位数据
{
	unsigned char i,byte=0;
	IIC_SDA_IN();
	for(i=0;i<8;i++)
	{
		IIC_SCL_H;
		byte<<=1;
		if(IIC_SDA_READ() == SET) byte |= 0x01;
		else byte &= 0xFE;
		IIC_SCL_L;
		IIC_Delay();
	}
	IIC_SDA_OUT();
	return byte;
}

由于IIC设备的地址是从0x00~0xFF的,故主函数只需要按如下代码编写即可

int main(void)
{	
	u8 i=0;
	int count=0;
	delay_init();
	uart_init(230400);	 //串口初始化为9600
	IIC_INIT();
	for(i=0x00;i<0xFF;i++)
	 {
		IIC_Start();
		IIC_Send_Byte(i);
		if(IIC_ACK_Read()==true) 
		{
			printf("%x\r\n",i);
			count=1;
		}
		IIC_Stop();
		delay_ms(1);
	 }
	 if(count==0) printf("No IIC device found!\r\n");
	 count=0;
	delay_ms(1000);
}

按如上操作即可实现自动寻址。下为INA219芯片的自动寻址效果。

2.2 实现效果

在这里插入图片描述
可以看到,识别出了INA219设备的0x81地址,代码已经公开CSDN代码。
PS:承接相关PCB layout与STM32F1编程工作,具体私信,不接急活儿。
参考文献:
[1] TI industry. Understanding the I2C bus, available at: https://www.ti.com.cn/cn/lit/an/slva704/slva704.pdf?ts=1593519859664&ref_url=https%253A%252F%252Fwww.ti.com.cn%252Fproduct%252Fcn%252FPCA9535

你可能感兴趣的:(STM32F103,KEIL)