STM32F10X的模拟IIC通讯

IO模拟IIC通讯

1.STM32使用软件IIC原因

  • 由于STM32的硬件IIC会出现一定概率卡死等问题,为了系统更加稳定,少出BUG 。STM32的绝大多数开发者选择使用软件IIC 。软件IIC能让初学者更加容易理解IIC的工作原理,并且相对硬件IIC,软件IIC对不同平台的移植性更好。

2.IIC设备调试注意事项

  • 在调试IIC通讯设备时,尽量不要用杜邦线连接IIC设备,杜邦线接头不稳定,很多时候会出现设备卡死不应答。假如必须用杜邦线连接,请用较短等长杜邦线连接,必要时焊死杜邦线。如果出现设备有时不应答情况,且多次检查IIC通讯程序没问题,那就很大可能是接线不稳造成。经常移动设备,请考虑这点。

3.模拟IIC的基本函数

  • 起始信号:SCL在高电平时,SDA从高电平拉低。
  • 结束信号:SCL在高电平时,SDA从低电平拉高。

STM32F10X的模拟IIC通讯
首先对GPIO的初始化,模拟IIC就用推挽模式就好

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
GPIO_Initstruct.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_2;
GPIO_Initstruct.GPIO_Mode = GPIO_Mode_Out_PP;	//推挽模式
GPIO_Initstruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC,&GPIO_Initstruct);
GPIO_ResetBits(GPIOC,GPIO_Pin_0);
GPIO_ResetBits(GPIOC,GPIO_Pin_2);

后面函数要用到的宏定义在这给出,用宏定义可简化代码,增强可读性与可移植性。

#define SCL_H					GPIO_SetBits(GPIOC,GPIO_Pin_0)
#define	SCL_L					GPIO_ResetBits(GPIOC,GPIO_Pin_0)

#define SDA_H					GPIO_SetBits(GPIOC,GPIO_Pin_2)
#define SDA_L					GPIO_ResetBits(GPIOC,GPIO_Pin_2)

#define MPU6050_SDA_Check_H()	{while(((GPIOC->ODR) & (1<<2))!=(1<<2));}//检测ODR寄存器查看IO输出状态
#define MPU6050_SDA_Check_L()	{while(((GPIOC->ODR) & (0<<2))!=(0<<2));}	

#define MPU6050_SCL_Check_H() 	{while(((GPIOC->ODR) & (1<<0))!=(1<<0));}
#define MPU6050_SCL_Check_L()	{while(((GPIOC->ODR) & (0<<0))!=(0<<0));}

#define SDA_IN() {GPIO_InitTypeDef GPIO_Initstruct;\
									GPIO_Initstruct.GPIO_Pin = GPIO_Pin_2;\
									GPIO_Initstruct.GPIO_Mode = GPIO_Mode_IPU;\
									GPIO_Init(GPIOC,&GPIO_Initstruct);}

#define SDA_OUT() {GPIO_InitTypeDef GPIO_Initstruct;\
									 GPIO_Initstruct.GPIO_Pin = GPIO_Pin_2;\
									 GPIO_Initstruct.GPIO_Mode = GPIO_Mode_Out_PP;\
									 GPIO_Initstruct.GPIO_Speed = GPIO_Speed_50MHz;\
									 GPIO_Init(GPIOC,&GPIO_Initstruct);}
#define IIC_PORT			GPIOC
#define IIC_SDA_Pin		GPIO_Pin_2

用宏定义封装后代码是不是更加通俗易懂,而且放在函数里时不冗杂。每个信号线都有相应的检测函数,保证在确定的电平后再做其他事。

起始信号函数

void IIC_START()
{
	SDA_H;						//SDA先拉高准备后面的下降沿
	SDA_Check_H();				//检查SDA是否拉高
	Systick_Delay_us(1);		//延时1us
			//先拉高SDA再拉高SCL避免信号影响!!!!!
	SCL_H;						//SCL拉高
	SCL_Check_H();				//检查SCL是否拉高
	Systick_Delay_us(1);
			
	SDA_L;
	SDA_Check_L();
	Systick_Delay_us(1);

	SCL_L;						//SCL拉低
	SCL_Check_L();				//SCL拉低检查
	Systick_Delay_us(1);
	
}

结束信号函数

void IIC_STOP()
{
	SDA_L;	//先拉低SDA为上升沿做准备,SCL此时要为低,防止误信号
	SDA_Check_L();
	Systick_Delay_us(1);
	
	SCL_H;
	SCL_Check_H();
	Systick_Delay_us(1);
	
	SDA_H;
	SDA_Check_H();
	Systick_Delay_us(1);
	
	SCL_L;	//获得想要信号后SCL先下降,避免产生误信号
	SCL_Check_L();
	Systick_Delay_us(1);
	
	SDA_L;
	SDA_Check_L();
	Systick_Delay_us(1);
	
}

以上为IIC的起始信号与结束信号注意每个信号的SDA与SCL位置,防止产生误信号。导致通讯失败!!!在通讯时只有在SCL为高电平时才为有效电平,注意每个函数后面都要把SCL拉低,以防误信号。

写一个Byte函数

void IIC_Write_Byte(uint8_t Byte)
{
	uint8_t num;
	for(num = 0;num < 8;num++)
	{
		if(Byte&0x80)//最高位是否为1
		{
			SDA_H;
			SDA_Check_H();
			Systick_Delay_us(1);
			SCL_H;
			SCL_Check_H();
			Systick_Delay_us(1);
			SCL_L;
			SCL_Check_L();
			Systick_Delay_us(1);
		}
		else
		{
			SDA_L;
			SDA_Check_L();
			Systick_Delay_us(1);
			SCL_H;
			SCL_Check_H();
			Systick_Delay_us(1);
			SCL_L;
			SCL_Check_L();
			Systick_Delay_us(1);
		}
		Byte <<= 1;	//数据是高位先行所以左移,注意左移符号后面要有“=”不然通讯失败,编译器还不报错
	}
}

读一个Byte函数

uint8_t IIC_Read_Byte()
{
	unsigned char num=0,temp=0,change=0;
	SDA_IN();					//SDA为上拉输入模式
	for(num = 0;num < 8;num++)
	{
		change=0;				//每次清0变量防止数据错误
		SCL_H;
		SCL_Check_H();
		if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2) == 1)
			change = 1;			//不要习惯用++
		temp =(temp<<1)+change;	//更新数据
		SCL_L;					//结束关闭SCL防止误数据
		SCL_Check_L();
		Systick_Delay_us(1);
	}
	return temp;
}

等待从设备响应函数

uint8_t Wait_ASK()
{
	uint16_t time = 1000;	//为了防止程序卡死
	SDA_IN();				//SDA为上拉输入
	SCL_H;
	SCL_Check_H();
	while(GPIO_ReadInputDataBit(IIC_PORT,IIC_SDA_Pin) 	!= 0)//当低电平时设备响应,响应跳过下面条件
	{
		time --;
		if(time == 0)
		{
			SDA_OUT();		//设备未响应,设置SDA为输出,return退出,防止卡死。调试时建议死循环
			return 0;
		}
	}
	SCL_L;
	SCL_Check_L();
	SDA_OUT();				//SDA为输出模式
	return 1;
}

主动发送响应信号让从机继续发送数据
主动发送响应函数(从机继续发数据)

void IIC_Send_ASC()
{
	IIC_SDA_OUT();	
	IIC_SDA_L;		//SDA拉低表示应答
	IIC_SDA_Check_L();
	IIC_SCL_H;
	IIC_SCL_Check_H();
	IIC_SCL_L;
	IIC_SCL_Check_L();
}

主动发送非应答信号终止从机的数据传输
非应答信号函数

void Send_NASC()
{
	SDA_OUT();
	SDA_H;		//SDA拉高表示非应答,结束数据传输
	SDA_Check_H();
	SCL_H;
	SCL_Check_H();
	SCL_L;
	SCL_Check_L();
	SDA_L;
	SDA_Check_L();
}

3.写寄存器与读寄存器

前面做好了几个基本函数,后面就可以根据前面的基本函数来组成IIC的数据基本格式,来写寄存器与读寄存器。

写寄存器函数

void IIC_Write_Reg(uint8_t addr,uint8_t Byte)
{
	IIC_START();				//起始信号
	IIC_Write_Byte(DEV_ADDR);	//发送设备地址
	Wait_ASK();					//等待从机响应
	IIC_Write_Byte(addr);		//写入寄存器地址
	Wait_ASK();					//等待从机响应
	IIC_Write_Byte(Byte);		//写入要写数据
	Wait_ASK();					//等待从机响应
	IIC_STOP();					//发送停止信号
}

读寄存器函数

uint8_t IIC_Read_Reg(uint8_t addr)
{
	uint8_t temp;
	IIC_START();				//起始信号
	IIC_Write_Byte(DEV_ADDR);	//发送设备地址
	Wait_ASK();					//等待响应
	IIC_Write_Byte(addr);		//写入寄存器地址
	Wait_ASK();					//等待响应
	IIC_START();				//再次起始信号
	IIC_Write_Byte(DEV_ADDR+1);	//发送设备地址,最后一位为1表示读。
	Wait_ASK();					//等待响应
	temp = IIC_Read_Byte();		//读数据
	Send_NASC();				//发送非应答信号
	IIC_STOP();					//发送停止信号
	return temp;				//返回数据
}

这里的设备地址默认为8位,最后一位默认为0也就是写,列如,厂商给你7位设备地址为0X68则在这的DEV_ADDR为左移1位,也就是0XD0.所以要定义DEV_ADDR为0XD0;
读寄存器和写寄存器的函数写好后建议各位测试一下,列如:

uint8_t temp;
IIC_Write_Reg(0XE0,0XB6);		//向0xE0写0XB6
temp = IIC_Read_Reg(0XE0);		//读取0XE0寄存器值

通过仿真看看读出值是否正确,不正确就检查IIC函数,不要纠结硬件问题。设备地址也是很容易出错的,多检查几次,设备地址若是7位要左移一位才是DEV_ADDR的真实地址。

结语

  • 学习通讯协议要用IO口模拟后印象才能深刻,简单用硬件通讯协议对整个通讯过程的理解不深,而且移植性较差。移植中要注意IIC的设备地址,SDA与SCL线的顺序,不然产生误信号会导致不能通讯,并且难以查找错误。
上一篇:PCA9685通过IIC协议控制多个舵机方法


下一篇:串行总线:SPI、IIC、UART