STM32 软件模拟IIC

0、系列目录

  1. STM32 软件模拟IIC
  2. STM32 使用DMP库处理MPU6050数据
  3. STM32 MPU6050与匿名上位机通讯(V2.6版)

1、IIC通讯过程

在这里插入图片描述
SCL和SDA在空闲时候均为高电平。
MCU接收到通讯命令后,首先广播从机地址(例:MPU6050地址为0x68,0x69)+读/写操作,从机收到后产生应答,随后开始SCL强制拉低,开始传送一字节数据
①收到应答信号后,继续传送第二个字节……
②收到非应答信号后,停止传输

2、IIC通讯协议

在这里插入图片描述
从图中可以看到
IIC起始信号:SCL保持高电平,SDA从高电平跳变到低电平
IIC结束信号:SCL保持高电平,SDA从低电平跳变到高电平
开始和结束信号的SCL相同,SDA相反。
IIC应答信号:STM32接收到8bit的数据后,向发送数据的外设发送低电平,表示收到数据,CPU向外设发出信号后,等待外设发送一个应答信号,接收到应答信号后判断是否要继续传递数据。未收到应答,表示外设故障。

复合通讯协议

在这里插入图片描述

3、有效数据传输

在这里插入图片描述
IIC的SCL在高电平的时候会读取SDA,所以必须保持SDA电平时间多于SCL高电平时间,数据有效。
在软件模拟IIC通讯时,不需要IIC时必须保持SCL和SDA为高电平,在广播从机地址得到应答后,必须保持SCL为低电平,相当于钳制住SCL,以保证MCU与从机正常通讯。

4、代码示例

代码的编写要严格按照IIC的通讯协议控制IO的电平,否则会出现数据错误。

/**  
  *  功能:软模拟IIC引脚初始化 PB6->SCL  PB7->SDA
  *  入口参数:无
  *  返回值:无
  */
void IIC_Config(void)
{
    
    
	GPIO_InitTypeDef GPIO_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_6 | GPIO_Pin_7);
	
}
//IIC操作的宏定义
//IO方向设置
#define SDA_IN() 			{GPIOB->CRL &= 0x0FFFFFFF;GPIOB->CRL |=(u32)8<<28;}
#define SDA_OUT() 			{GPIOB->CRL &= 0x0FFFFFFF;GPIOB->CRL |=(u32)3<<28;}

//IO操作函数
#define IIC_SCL				PBout(6)
#define IIC_SDA				PBout(7)//输出数据
#define READ_SDA			PBin(7)//读数据

起始信号

/**  
  *  功能:IIC开始信号 SCL保持高电平,SDA从高电平跳变到低电平
  *  入口参数:无
  *  返回值:无
  */
void IIC_Start(void)
{
    
    
	SDA_OUT();
	IIC_SDA=1;
	IIC_SCL=1;
	delay_us(4);
	IIC_SDA=0;
	delay_us(4);
	IIC_SCL=0;//钳制IIC总线,准备发送或接受数据
}

结束信号

/**  
  *  功能:IIC结束信号 SCL保持高电平,SDA从低电平跳变到高电平
  *  入口参数:无
  *  返回值:无
  */	
void IIC_Stop(void)
{
    
    
	SDA_OUT();
	IIC_SDA=0;
	IIC_SCL=0;
	delay_us(4);
	IIC_SCL=1;
	IIC_SDA=1;
	delay_us(4);
}

等待应答信号ACK

/**  
  *  功能:等待应答信号ACK
  *  入口参数:无
  *  返回值:0,接受应答成功;1,接受应答失败
  */
u8 IIC_Wait_Ack(void)
{
    
    
	u8 ucErrTime = 0;
	SDA_IN();
	IIC_SDA=1;delay_us(1);
	IIC_SCL=1;delay_us(1);
	while(READ_SDA)
	{
    
    
		ucErrTime++;
		if(ucErrTime>250)
		{
    
    
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL=0;
	return 0;
}

产生应答信号

/**  
  *  功能:产生ACK应答
  *  入口参数:无
  *  返回值:无
  */
void IIC_Ack(void)
{
    
    
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

产生非应答信号

/**  
  *  功能:产生非ACK应答
  *  入口参数:无
  *  返回值:无
  */
void IIC_NAck(void)
{
    
    
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

发送一个字节数据

/**  
  *  功能:IIC发送一个字节(8 bit)
  *  入口参数:无
  *  返回值:返回从机有无应答。0,无应答;1,有应答
  */
void IIC_Send_Byte(u8 txd)
{
    
    
	u8 t;
	SDA_OUT();
	IIC_SCL=0;//拉低IIC总线时钟开始数据传输
	for(t=0;t<8;t++)
	{
    
    
		IIC_SDA=(txd&0x80)>>7;
		txd<<=1;
		delay_us(2);
		IIC_SCL=1;
		delay_us(2);
		IIC_SCL=0;
		delay_us(2);
	}
}

读取一个字节数据

/**  
  *  功能:IIC读取一个字节(8 bit)
  *  入口参数:ack,应答
  *  返回值:一个字节(8 bit)
  */
u8 IIC_Read_Byte(unsigned char ack)
{
    
    
	unsigned char i,receive=0;
	SDA_IN();
	for(i=0;i<8;i++)
	{
    
    
		IIC_SCL=0;
		delay_us(2);
		IIC_SCL=1;
		receive<<=1;
		if(READ_SDA)
			receive++;
		delay_us(1);
	}
	if(!ack)
		IIC_NAck();//发送NACK
	else
		IIC_Ack();//发送ACK
	return receive;
}

总结

IIC通讯协议不难,只需要按照协议,一步一步控制IO的电平,就可以模拟出IIC通讯,下篇会使用软模拟IIC获取MPU6050的原始数据,牵扯到IIC的连续读和连续写。连续读写,以及与MCU与从机之间的通讯方式非常重要,本节作为基础,理解最好。

猜你喜欢

转载自blog.csdn.net/qq_44011116/article/details/113699574