串行通信的速度较并行低,但是非常节省端口资源,所以是底层经常接触到的通信方式。
一、串口通信
二、I2C通信
以MPU6050惯性传感器为例,编写模拟I2C的主机程序
2.1编写分段函数
2.1.1发起开始命令(Start condition)
I2C总线平常处于空闲状态,SDA和SCL均为高电平。发起开始命令的做法是,SDA从高到低跳变,I2C总线从空闲->忙
void IIC_WriteStartCondition(void)
{
IIC1_SDA_HIGH;
IIC1_SCK_HIGH; //如图开始的时候两个数据都处于高电平
IIC_Delay(2); //延时一段时间后
IIC1_SDA_LOW; //SDA先与SCL拉低,即为起始条件
IIC_Delay(2); //延时一段时间后
IIC1_SCK_LOW; //SCL拉低,并开始第一针数据(ADDRESS)的收发,也是起始条件结束
IIC_Delay(1); //延时半个周期为下一个函数作准备
}
2.1.2数据传输函数
数据传输中,SDA电平改变只能发生在SCL为低的期间,根据时序图,可以知道在发送起始条件后需要发送从机地址和写位,表示对总线上相应的从机进行写操作:
void I2C_WriteByteDataToSlave(uint8_t data)
{
IIC1_SCK_LOW; //再次拉低数据线,可以忽略
for(i=0;i<8;i++) //发送一个字节(8位数据)
{
(data & 0x80) ? IIC1_SDA_HIGH : IIC1_SDA_LOW;//判断数据最高位是否为1,若是拉高数据线,否则拉低数据线,表示传输字节1/0
data <<= 1; 把数据左移1位,把刚刚发送完的数据剔除
IIC1_SCK_HIGH;//拉高时钟线表示1个位传输结束
IIC_Delay(1); //延时半个周期
IIC1_SCK_LOW; //把SCL拉到低电平准备发送下一位数据
IIC_Delay(1);
}
}
2.1.3 等待应答
I2C发送第一帧数据完毕后,需要等待从机返回应答以确保它收到了信息。等待应答的时候主机释放SDA线,从机接管SDA并保证其低电平直到下一个SCL高电平结束,编程上这里使第9个SCL信号拉高后,一直读取SDA信号直到出现低电平才跳出,最后主机拉低SCL,拉高SDA
Uint8_t IIC_WaitSlaveAsck(void)
{
uint8_t tim = 0;
IIC1_SCK_HIGH; //拉高SCL线
while(IIC1_SDA_DATA) //当数据线为高电平
{
tim++;
Delay(1);
if(tim > 50) //设计一个倒计时,超过50ms则认为从机无应答,I2c出错,返回1
{
tim=0;
I2CERR++;
return 1;
}
}
IIC_Delay(1);
IIC1_SCK_LOW;
IIC1_SDA_HIGH;
IIC_Delay(1);
return 0; //返回0
}
2.1.4发起停止(Stop condition)
停止信号后释放I2C总线,总线返回空闲状态,操作是当SCL在高电平时,SDA发生从第到高的跳变
void IIC_StopCondition(void)
{
IIC1_SDA_LOW;//先让SDA处于低电平
IIC1_SCK_HIGH;//拉高SCL线
IIC_Delay(2);//延时
IIC1_SDA_HIGH;//SDA发生从低到高的跳变
IIC_Delay(2);//延时一段时间等待通信结束
}
2.1.5数据接收
当我们进行MPU6050的读取操作时,需要接收来自MPU6050的数据,具体操作为如2.1.1发起Start 然后发送地址及读取位,之后产生SCL时钟信号,并释放SDA线由MPU6050接管,在SCL高电平接收数据
uint8_t IIC_ReadByteDataFromSlave(void)
{
uint8_t i,data=0;
for(i=0;i<8;i++)
{
IIC1_SCK_HIGH; //拉高时钟线
IIC_Delay(1); //延时一个周期
data <<= 1; //把数据左移一位
data |= IIC1_SDA_DATA; //把新来的数据加在位
IIC1_SCK_LOW; //拉低时钟线
IIC_Delay(1); //延时
}
return data; //返回接收到的数据
}
2.1.6主机应答
在接收到MPU6050的数据时,需要作出应答(连读模式时)来告诉MPU6050继续发送下一帧数据,或者不作出应答直接发出Stop condition表示通信结束
void IIC_MastAsckToSlave(void)
{
IIC1_SCK_LOW;
IIC1_SDA_LOW;
// IIC_Delay(1);
IIC1_SCK_HIGH;
IIC_Delay(1);
IIC1_SCK_LOW;
IIC1_SDA_HIGH;
IIC_Delay(1);
}
void IIC_MastNoteAsckToSlave(void)
{
IIC1_SCK_LOW;
IIC1_SDA_HIGH;
// IIC_Delay(1);
IIC1_SCK_HIGH;
IIC_Delay(1);
IIC1_SCK_LOW;
IIC_Delay(1);
}
2.2编写整段读取
2.2.1 单字节写:
本段函数中分为8个部分,分别是:产生起始信号、写入从机地址+写位、等待应答、写入寄存器地址、等待应答、写入数据、等待应答、产生停止信号。根据已写成的函数进行组合
void MPU6050_SingleByteWrite_Soft( I2C_TypeDef* I2Cx, uint8_t RegisterAddress, uint8_t Data )
{
IIC_WriteStartCondition(); //1
IIC_WriteByteDataToSlave(MPU6050_I2C_ADDRESS);//2
IIC_WaitSlaveAsck(); //3
IIC_WriteByteDataToSlave(RegisterAddress); //4
IIC_WaitSlaveAsck(); //5
IIC_WriteByteDataToSlave(Data); //6
IIC_WaitSlaveAsck(); //7
IIC_StopCondition(); //8
}
2.2.2爆发写(连续写)
关键在于发送寄存器地址以后可以一直只发送数据进行写入,所以利用循环体。
void MPU6050_BurstWrite_Soft( I2C_TypeDef* I2Cx, uint8_t RegisterAddress, uint8_t* DataPointer, uint8_t DataLength )
{
u8 i;
IIC_WriteStartCondition();
IIC_WriteByteDataToSlave(MPU6050_I2C_ADDRESS);
IIC_WaitSlaveAsck();
IIC_WriteByteDataToSlave(RegisterAddress);
IIC_WaitSlaveAsck();
for(i=0;i<DataLength;i++)
{
IIC_WriteByteDataToSlave(*(DataPointer+i));
IIC_WaitSlaveAsck();
}
IIC_StopCondition();
}
2.2.3单字节写
本函数分为11段:分别是 起始条件、从机地址加写、等待应答、寄存器地址、等待从机应答、再次发送起始条件、从机地址加读、等待应答、读取数据、不应答,停止条件
uint8_t MPU6050_SingleByteRead_Soft( I2C_TypeDef* I2Cx, uint8_t RegisterAddress )
{
uint8_t Data;
IIC_WriteStartCondition();
IIC_WriteByteDataToSlave(MPU6050_I2C_ADDRESS);
IIC_WaitSlaveAsck();
IIC_WriteByteDataToSlave(RegisterAddress);
IIC_WaitSlaveAsck();
IIC_WriteStartCondition();
IIC_WriteByteDataToSlave(MPU6050_I2C_ADDRESS+1);
IIC_WaitSlaveAsck();
Data = IIC_ReadByteDataFromSlave();
MastNoteAsckToSlave();
IIC_StopCondition();
return Data;
}
2.2.4爆发式读(连读)
同理爆发写
void MPU6050_BurstRead_Soft( I2C_TypeDef* I2Cx, uint8_t RegisterAddress, uint8_t* DataPointer, uint8_t DataLength )
{
uint8_t i;
IIC_WriteStartCondition();
IIC_WriteByteDataToSlave(MPU6050_I2C_ADDRESS);
IIC_WaitSlaveAsck();
IIC_WriteByteDataToSlave(RegisterAddress);
IIC_WaitSlaveAsck();
IIC_WriteStartCondition();
IIC_WriteByteDataToSlave(MPU6050_I2C_ADDRESS+1);
IIC_WaitSlaveAsck();
for(i=0;i<DataLength-1;i++)
{
* (DataPointer+i)=IIC_ReadByteDataFromSlave();
MastAsckToSlave();
}
* (DataPointer+i)=IIC_ReadByteDataFromSlave();
MastNoteAsckToSlave();
IIC_StopCondition();
}