常用串行通信
串行通信的速度较并行低,但是非常节省端口资源,所以是底层经常接触到的通信方式。
本文分为4部分
1、RS232串口通信
2、I2C通信
3、SPI通信
4、CAN通信
一、RS232串口通信
二、I2C通信
在I2C通信中,每个器件都有自己的固定地址,分为7位寻址和10位寻址模式,其中7位寻址较为常用,即每个设备的地址为7位。
通信速率:
标准模式:100kbit/s
快速模式:400kbit/s
高速模式:3.4Mbit/s
在通信中,首先发送起始条件 然后发送目标器件的7位地址+R/W位,等待应答,发送/接收数据,等待应答/作出应答,发送结束条件。
另外,存在广播呼叫方式:发送个地址0x00,对所有从设备进行通信(不是所有器件都适用)
以MPU6050惯性传感器为例,编写模拟I2C的主机程序
在芯片手册中找到描述如下
2.1编写分段函数
II1_SDA_HGH-拉高数据线
II1_SDA_LOW-拉低数据线
II1_SCL_HIGH-拉高时钟线
II1_SCL_LOW-拉低数据线
II1_SDA_DATA-读取SDA线上的电平
IIC_Delay(x):定义一个x*4us的延时函数
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爆发写(连续写)
关键在于发送寄存器地址以后可以一直只发送数据进行写入,利用for循环体。
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();
}
三、SPI通信
3.1 SPI重要参数
SPI为4线的串行通信协议
分别为
1、SCK:发生时钟信号,由主机发出信号
2、MISO:主进从出,主机的接收信号端,由从机发出信号
3、MOSI:主出从进,主机的发送信号端,由主机发出信号
4、CS:片选,主机控制(拉低表示传输开始)
也有部分设备省略CS端,称为3线SPI
首先SPI的两个参数会导致程序编写的不同
1 时钟极性(CPOL)
2 时钟相位(CPHA)
当CPOL=0的时候,串行同步时钟的空闲状态为低电平
当CPOL=1的时候,串行同步时钟的空闲状态为高电平
如下图所示:红框内表示空闲时间电平
当CPHA=0,串行同步时钟的第一个跳变沿(上升下降沿)数据被采样
当CPHA=1,串行同步时钟的第二个跳变沿(上升下降沿)数据被采样
如下图所示:红框内表示1还是2跳变沿采样
3.2模拟SPI串行通信
设定CPOL=0;CPHA=0;数据从MSB开始发送,如下图所示
3.2.1 主机部分
注释:以下程序出现到的函数
Uint8_t:定义为unsigned char 类型
CS_HIGH():表示拉高CS信号
CS_LOW():表示拉低CS信号
SCK_HIGH():表示拉高时钟线
SCK_LOW():表示拉低时钟线
MOSI_HIGH():表示拉高MOSI信号
MOSI_LOW():表示拉低MOSI信号
READ_MISO():表示读取MISO信号
SPI_DELAY(x):表示延时x*1/2个时钟周期
3.2.1.1 编写读一字节的函数
uint8_t SPI_ReadWriteByte(uint8_t Data)
{
uint8_t i;
uint8_t ReturnData;
CS_LOW();//拉低片选信号准备开始数据传输
SPI_DELAY(2);//等待从机反应过来
for(i=0;i<8;i++)
{
If(Data&0x80==0x80)
MOSI_HIGH();//如果最高位为高电平则
else
MOSI_LOW();//如果最高位为低电平则拉低MOSI线
Data=Data<<1; //把数据左移一位准备发送下一位数据
SPI_DELAY(1):
SCK_HIGH(): //在上升沿数据生效
ReturnData<<1;
ReturnData|=READ_MISO()://把新接收到的数据作为次高位
SPI_DELAY(1):
SCL_LOW(); //拉低时钟信号
}
SPI_DELAY(2);//延时一段时间等待从机完全接收完成
CS_HIGH();//拉高片选信号表示传输结束
return ReturnData;//返回接收到的数据
}
3.2.1 从机部分
MISO_HIGH():表示拉高MISO信号
MISO_LOW():表示拉低MISO信号
READ_MOSI():表示读取MOSI信号
READ_SCK():表示读取SCK信号
3.2.2.1 编写读一字节的函数
uint8_t SPI_ReadWriteByte(uint8_t Data)
{
uint8_t i;
uint8_t ReturnData;
for(i=0;i<8;i++)
{
If(data&0x80==0x80)
MISO_HIGH():
else
MISO_LOW():
Data<<1;
While(!READ_SCK);//等待上升沿
ReturnData<<1;
ReturnData|=READ_MISO()://把新接收到的数据作为次高位
While(READ_SCK);//等待下一次低电平的到来
}
Return ReturnData;
}
四、CAN通信
4.1CAN总线简介
在这里介绍四种通信中最特别的通信方式,常常用于汽车电子中,其特色只需要链接2线CAN_LOW和CAN_HIGH(不用接地线),以及是一种多主的通信方式,具有抗干扰能力强、通信速度高(常用125K、500K)、通信距离远、自动诊断剔除错误设备等等的优点。是多机组网通信的优秀形式。
它也存在缺点:每帧携带的数据较少(最多8个字节)、需要外挂设备
4.2CAN总线电平
CAN通信使用的两根线(常以双绞线形式出现)CAN_LOW和CAN_H
(此处补图)
显性电平:逻辑0:CAN_LOW输出1.5V CAN_HIGH输出3.5V,两线电压差2V
隐性电平:逻辑1:CAN_HIGH输出2.5V CAN_HIGH输出2.5V ,两线电压差0V
电气连接:
上拉模式:同I2C的连接方式,CAN_L 和CAN_H接上拉电阻到电源
回环模式:在CAN_L和CAN_H之间连接电阻
(此处补图)
4.4CAN总线数据帧
CAN总线协议具有固定报文形式,以下对其进行详细介绍
首先是4种基本帧:
一、标准数据帧
(此处补图)
组成:1位SOF+11位仲裁段(标准ID+RTR)+6位控制段(IDE+r0+DLC)+ 0~64位数据段(MSB在前)+15位CRC校验段+ACK段(ACK槽+ACK界定符)+EOF
中裁段:
ID段:ID号越小,表示具有越高的优先级,当CAN总线同时有两个主机发送信息时,优先级高的信息具有有限传送资格
RTR:远程发送请求位:标准帧中为显性
控制段:
IDE:识别符扩展位:在标准帧中为控制段,并为显性。
R0:保留
DLC:
二、扩展数据帧
(此处补图)
组成:SOF+29位仲裁段(标准ID+RRR+IDE+扩展ID+RTR)+6位控制段(r0+r1+DLC)+ 0~64位数据段(MSB在前)+15位CRC校验段+ACK段(ACK槽+ACK界定符)+EOF
三、标准远程帧(标准遥控帧)
(此处补图)
组成同标准数据帧,但是没有数据段
组成:1位SOF+11位仲裁段(标准ID+RTR)+6位控制段(IDE+r0+DLC)+15位CRC校验段+ACK段(ACK槽+ACK界定符)+EOF
四、扩展远程帧(扩展遥控帧)
(此处补图)
组成同扩展数据帧,但是没有数据段
组成:SOF+29位仲裁段(标准ID+RRR+IDE+扩展ID+RTR)+6位控制段(r0+r1+DLC))+15位CRC校验段+ACK段(ACK槽+ACK界定符)+EOF