常用串行通信

Posted HongYi_Liang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常用串行通信相关的知识,希望对你有一定的参考价值。

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

一、串口通信

 

二、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();

 

 

三、SPI通信

以上是关于常用串行通信的主要内容,如果未能解决你的问题,请参考以下文章

什么叫串行通信和并行通信?异步通信和同步通信有何区别?

什么是串行数据总线

ALB学习笔记基于事件触发方式的串行通信接口数据接收案例

用VC6.0实现上位机串口通信

最大串行通信数

工业机器人-串口通信技术与MODBUS协议