STM32IO口模拟IIC时序

发布时间 2023-07-07 09:42:00作者: xioahuhu

正点原子IIC讲解:https://www.bilibili.com/video/BV1o8411n7o9/?spm_id_from=333.337.search-card.all.click&vd_source=e35b16eeaf19ae2b23ff9587a735ee20

一、IIC总线

1. 物理层

(1)支持多设备,一个IIC通讯总线中可以连接多个IIC设备,支持多个通讯主机及多个通讯从机;
(2)两条线:双向串行数据线SDA,串行时钟线SCL,数据线用于传输数据,时钟线用于数据收发同步;
(3)每个连接到总线的设备都有一个独立的地址,主机通过这个地址对不同从机进行访问;
(4)总线通过上拉电阻接到电源,当IIC设备空闲时会输出高阻态。
(5)三种传输模式:标准模式(100kbit/s)、快速模式(400kbit/s)、高速模式(3.4Mbit/s)。
(6)连接相同总线的IC数量受总线最大电容400pf的限制。

2. 协议层

(1)起始信号:主机的IIC接口产生的传输,连接到IIC总线上的从机都会接收到这个信号。
(2)从机地址:起始信号产生后,所有从机开始等待主机广播从机地址信号,当广播地址与某个设备地址相同时,该设备就被选中,没有选择的设备会忽略之后的数据信号。IIC协议规定从机地址可以是7位或10位。
(3)传输方向位:地址位之后是传输方向选择位,该位为0时,表示后面数据传输方向是主机写数据到从机;该位为1时,主机读取从机数据。
(4)应答信号:
第一种,从机接收到匹配的地址,从机返回应答信号(ACK),表示找到对应的从设备;
第二种,当从机被寻址后,主机写数据到从机时,从机每收到一个字节的数据都需要给主机一个应答信号(ACK),主机只有接收到应答信号后才能继续发送数据。
(5)停止信号:发送停止信号结束通信。

S:起始信号
SLAVE ADDRESS:从机地址
R/W:传输方向,1读 0写
A:ACK表示应答信号
A非:NACK非应答信号,表示本次通信结束
P:停止信号

阴影部分为主机给从机发的,空白部分为从机给主机发的。

1、IIC主机写数据到从机

2、IIC主机由从机读取数据

3、IIC复合通信

4、IIC通信时序图

(1)起始信号、停止信号

起始信号:SDA、SCL均为高电平,首先处理器让SCL时钟保持高电平,然后让SDA数据信号由高变低就表示一个开始信号。同时IIC总线上的设备检测到这个开始信号就知道处理器要发送数据了,此时总线处于占用状态。

停止信号:处理器让SCL时钟保持高电平,然后让SDA数据信号由低变高就表示一个停止信号。同时IIC总线上的设备检测到这个停止信号就知道处理器已经结束了数据传输,此时总线处于空闲状态。

(2)数据格式/应答

字节从高位到低位传输。发送器每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。
应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字
节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

(3)通信

开始标志(S)发出后,主设备会传送一个7 位的从机地址,并且后面跟着一个第8位,称为Read/Write 位。R/W 位表示主设备是在接收从设备的数据还是在向从设备写数据。然后,主设备释放SDA线,等待从设备的应答信号(ACK)。每个字节的传输都要跟随有一个应答位,应答产生时(把SDA拉低)。从设备将SDA 线拉低并且在SCL 为高电平时保持低。数据传输总是以停止信号标志结束,然后释放通信线路。然而,主设备也可以产生重复的开始信号去操作另一台从设备,而不发出结束标志。综上可知,所有的SDA 信号变化都要在SCL 时钟为低电平时进行,除了开始和结束标志。

二、软件模拟IIC通信

iic.h


#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"

//IO方向设置
#define SDA_IN()  {GPIOA->CRL&=0X0FFFFFFF;GPIOA->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOA->CRL&=0X0FFFFFFF;GPIOA->CRL|=(u32)3<<28;}

#define IIC_SCL    PAout(4) //SCL
#define IIC_SDA    PAout(5) //输出SDA	 
#define READ_SDA   PAin(5)  //输入SDA 


//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);		   //发送IIC开始信号
void IIC_Stop(void);	  	   //发送IIC停止信号
void IIC_Send_Byte(u8 TX_DATA);		//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 		   //IIC等待ACK信号
void IIC_Ack(void);		  //IIC发送ACK信号
void IIC_NAck(void);	         //IIC不发送ACK信号

void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);	  


#endif

iic.c

#include "myiic.h"
#include "delay.h"

/*
*@Function  :IIC_Init()
*@brief     :初始化IIC GPIO
*@param     :void
*@retval    : void
*/
void IIC_Init(void)
{					     
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA, ENABLE );	//使能GPIOA时钟
	   
	GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_4 | GPIO_Pin_5;//PA4---SCL PA5---SDA
	GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_OD ;      //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	GPIO_SetBits(GPIOA,GPIO_Pin_4 | GPIO_Pin_5); 	       //PA4,PA5 输出高
}

/*
*@Function  :IIC_Start()
*@brief     :产生IIC起始信号
*@param     :void
*@retval    : void
*/
void IIC_Start(void)
{
	SDA_OUT();  //sda线输出

	IIC_SDA=1; //处于空闲状态  	  
	IIC_SCL=1; //处于空闲状态
	delay_us(4);//延时时间看从机设备
 	IIC_SDA=0;//时钟线为高时数据线由高变低 
	delay_us(4);
	IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
}	


/*
*@Function  :IIC_Stop()
*@brief     :产生IIC停止信号
*@param     :void
*@retval    : void
*/  
void IIC_Stop(void)
{
	SDA_OUT();//sda线输出

	IIC_SCL=0;
	IIC_SDA=0;
 	delay_us(4);
        IIC_SCL=1;
	IIC_SDA=1;//时钟线为高,数据线由低变高
	delay_us(4);							   	
}

/*
*@Function  :IIC_Ack()
*@brief     :产生ACK应答
*@param     :void
*@retval    : void
*/ 
void IIC_Ack(void)
{
	SDA_OUT();//sda线输出
        IIC_SCL=0;
	IIC_SDA=0;//数据线为低电平表示应答
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

/*
*@Function  :IIC_NAck()
*@brief     :不产生ACK应答
*@param     :void
*@retval    : void
*/ 		    
void IIC_NAck(void)
{
	SDA_OUT();//sda线输出
        IIC_SCL=0;
	IIC_SDA=1;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}

/*
*@Function  :IIC_Wait_Ack()
*@brief     :等待应答信号
*@param     :1,接收应答失败    0,接收应答成功
*@retval    : void
*/
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_IN(); //SDA设置为输入  
	IIC_SDA=1;//释放sda线
        delay_us(1);	   
	IIC_SCL=1;//从机返回ack
        delay_us(1);	 
	while(READ_SDA) //scl为高电平时读取sda
	{
		ucErrTime++;
		if(ucErrTime>250) //超时判断
		{
			IIC_Stop();//sda高电平表示从机无应答
			return 1;//接收应答失败
		}
	}
	IIC_SCL=0;//时钟输出0,结束ack检查 	   
	return 0;  
} 

/*
*@Function  :IIC_Send_Byte()
*@brief     :IIC发送一个字节
*@param     :TX_DATA 要发送的数据
*@retval    : void
*/ 					 				     	  
void IIC_Send_Byte(u8 TX_DATA)
{                        
    u8 t;   
	SDA_OUT(); 	    
    IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        //IIC_SDA=(TX_DATA&0x80)>>7;//先发高位,与运算取出最高位,移到最后一位
		if((TX_DATA&0x80)>>7)
			IIC_SDA=1;
		else
			IIC_SDA=0;
		TX_DATA <<=1; //左移一位用于下一位发送,最高位已经发送出去	  
		delay_us(2);  
		IIC_SCL=1;
		delay_us(2); 
		IIC_SCL=0;	
		delay_us(2);
    }	 
}


/*
*@Function  :IIC_Read_Byte()
*@brief     :IIC读1个字节
*@param     :ack=1时,发送ACK,ack=0,发送nACK
*@retval    : 读取的数据
*/ 
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_IN();//SDA设置为输入
    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;
}