常用串行通信

串行通信的速度较并行低,但是非常节省端口资源,所以是底层经常接触到的通信方式。

本文分为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

 

    

  

posted @ 2017-06-21 11:11  HongYi_Liang  阅读(2005)  评论(0编辑  收藏  举报