0、系列目录
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与从机之间的通讯方式非常重要,本节作为基础,理解最好。