当前位置: 代码迷 >> 综合 >> UART IIC SPI协议总结
  详细解决方案

UART IIC SPI协议总结

热度:72   发布时间:2023-12-12 03:29:48.0

总体概述

本文将总结嵌入式当中常用的3种通信协议,重点从协议本身,以及协议的时序图展开概述。在展开概述前,我们还得了解下一些电气特性,例如
在这里插入图片描述
在实际的线路传输数据时,无非就是高低电平,即0和1,上面这个图是从其它地方拷贝过来的,链接我忘记了,对于实际的电气特性来说,有时候是3.3v代表1,有时候是5v代表1,所以为了主机和从机之间的这个电平特性保持一致,一般来说,在外部ic当中都会有电平的转换(有专门的电平转换芯片,例如CH340)。这个对于软件从事人员可以这样简单的了解一下。由于自己对单片机稍微了解一些,所以下面从单片机的角度去总结这三个协议。
如果有朋友对FPGA比较熟悉,也可参考
FPGA协议

UART

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART。

协议概述

UART协议的话,是异步的,异步是什么意思呢?
大家看以下这个图,有RX和TX,这个是一个串口设备,可以
发送数据到电脑上,电脑上使用一些上位机软件,就可以显示收到的数据是什么。这个设备是支持UART协议的,它没有时钟线,所以是异步的,异步可以理解为发送方和接收方的一个时间上的一致性。这个图当中,左边直接接入电脑,右边可以看到有5v和3.3v这两个电压是给这个模块供电的,具体使用那个,需要看外部ic,而rx和tx需要和外部的tx和rx相连,也就是发送连接接收端,接收端连接发送端。这里的外部ic指的是单片机,我们这里不关心数据是怎么发送和接收的,重点是分析数据是怎么通过rx和tx进行传输的。
在这里插入图片描述

时序分析

比如说,单片机要通过tx的这个引脚发送一个字符给pc,那么数据格式是怎么样的呢?
对于UART协议来说,大体上数据格式可以分为两种,一种是带校验的,一种是不带校验的。校验的话是奇偶校验。
(1)不带校验的。
格式:起始位(一个bit,这里S是起始位,为低电平)+数据位(一个字节/8个bit)+停止位(一个bit,高电平)具体传输数据时是低为再前,高位在后。这里有两个地方需要注意一下,就是我没发数据时,一开始是高电平还是低电平?当然是高电平,从下面这个图也可以看的出来,S的左边是不是高电平?为什么一开始是高呢?其实也容易理解,如果一开始是低电平,那么接收方会误以为是收到了一个起始信号,那后面就会认为是数据部分了,此外,当数据传输了到了停止位这里的时候,如果此时没有数据传输了,那就相当于是空闲了,后面出现的是高电平。如果后面接着有数据进行传输,那么又会接着从起始位+数据部分+停止位的格式进行发送。
在这里插入图片描述
对硬件熟悉的朋友可以用示波器或者逻辑分析仪接到tx或者rx这个引脚上,观察具体的波形是怎么样的,这样可以有助于理解时序图。
(2)带校验的
带校验的话,格式为:
起始位(一个bit,为低电平)+数据位(一个字节/8个bit)+校验位+停止位(一个bit,高电平)
至于校验位是高电平还是低电平需要看数据部分和是奇校验还是偶校验。这里不谈这点。
在实际工作当中,不同的设备可能有校验部分,有的也可能没有,我们到时翻阅手册即可。最后,就是波特率了,因为UART是异步的,所以为了接收以及发送的统一,双发要规定一个时间段。UART的波特率比较固定,以115200来说,就是传输一个bit为要花多长时间,从上面那个图来看大约是9us,即1/115200s。

伪代码概述

UART部分的伪代码的话,这里简单的介绍下,以单片机来说,就是设置了一下引脚的模式,然后设置具体的传输模式,可以是DMA传输,也可以是中断传输,最后都是将数据通过引脚发送出去的。具体的内部原理我们在这里就不介绍了。

IIC

协议概述

IIC协议的话,可以参考下:IIC
个人觉得篇文章讲的非常的不错的。

时序分析

对于IIC协议来说,主要有时钟线和数据线,靠着这两根线,接收方和发送方进行数据的交互。因为有时钟线嘛,所以它是同步的。也就是在一个时钟周期内,接收方会查看数据线上面是高电平还是低电平,在下一个周期内,又会接着去看。

通过IIC协议进行交流时,有以下几个重要的信号。
(1)起始信号
(2)停止信号
这两个信号UART协议也是有的,那么IIC是怎么样表示的呢?
具体可以参考上面那篇文章。
(3)应答信号,也就是说对于通过IIC协议连接的两个设备来说无论是谁发数据,谁接收数据,都要有一个应答
(4)空闲信号,即没有数据发送时,时钟线和数据线是高电平还是低电平?

伪代码概述

#include "iic.h"
/**************************************************************** @file iic.c* @brief IIC初始化* @return 无* @date 2022/04/11 **************************************************************/
void IIC_Init(void)
{
    /*这里是设置引脚的属性,比如是输出还是输入等。*/
}
/**************************************************************** @file iic.c* @brief IIC_SDA输出模式* @return 无* @date 2022/04/11 **************************************************************/
void IIC_SDAModeOut(void)
{
    //...数据线是输出模式
}
/**************************************************************** @file iic.c* @brief IIC_SDA输入模式* @return 无* @date 2022/04/11 **************************************************************/
void IIC_SDAModeIn(void)
{
    //...数据线是输入模式
}
/**************************************************************** @file iic.c* @brief IIC起始信号函数 ---》当 SCL 为高电平时,SDA 由高变低* @return 无* @date 2022/04/11**************************************************************/
void IIC_Start(void)
{
    IIC_SDAModeOut();IIC_SDA_OUT(1);IIC_SCL_OUT(1);delay_us(5);IIC_SDA_OUT(0);delay_us(5);IIC_SCL_OUT(0);
}
/**************************************************************** @file iic.c* @brief IIC停止信号函数 ---》当 SCL 为高电平时,SDA 由低变高* @return 无* @date 2022/04/11**************************************************************/
void IIC_Stop(void) 
{
    IIC_SDAModeOut();IIC_SDA_OUT(0);IIC_SCL_OUT(0);delay_us(5);IIC_SCL_OUT(1);delay_us(5);IIC_SDA_OUT(1);
}
/**************************************************************** @file iic.c* @brief IIC写一个字节函数* @return 无* @date 2022/04/11**************************************************************/
void IIC_WriteByte(uint8_t data)     //01101011
{
    int i = 0;IIC_SDAModeOut();IIC_SCL_OUT(0);delay_us(5);for(; i < 8; i++){
    if(data & (0x1 << (7-i))){
    IIC_SDA_OUT(1);}else{
    IIC_SDA_OUT(0);}IIC_SCL_OUT(1);delay_us(5);IIC_SCL_OUT(0);delay_us(5);}
}
/**************************************************************** @file iic.c* @brief IIC读一个字节函数* @return data* @date 2022/04/11**************************************************************/
uint8_t IIC_ReadByte(void)   //01101011
{
    uint8_t data;int i = 0;IIC_SDAModeIn();IIC_SCL_OUT(0);delay_us(5);for(; i < 8; i++) {
    IIC_SCL_OUT(1);delay_us(5);if(IIC_SDA_IN()) {
    data |= (0x1 << (7-i));} else {
    data &= ~(0x1 << (7-i));}IIC_SCL_OUT(0);delay_us(5);}return data;
}
/**************************************************************** @file iic.c* @brief IIC应答函数 --->24C02 写给 STM32 数据,STM32 给 24c02 一个回复* @return 无* @date 2022/04/11**************************************************************/
void IIC_Ack(uint8_t ack)
{
    IIC_SDAModeOut();IIC_SCL_OUT(0);delay_us(5);if(ack){
    IIC_SDA_OUT(1);     }else{
    IIC_SDA_OUT(0);}IIC_SCL_OUT(1);delay_us(5);IIC_SCL_OUT(0);
}/** 功 能:IIC等待应答* 参 数:无* 返回值:成功得到的回复 * 1 --->不应答* 0 --->应答*/
uint8_t IIC_WaitAck(void)
{
    uint8_t i = 0;IIC_SDAModeIn();IIC_SCL_OUT(0);delay_us(5);IIC_SCL_OUT(1);delay_us(5);if(IIC_SDA_IN()){
    i = 1;}else{
    i = 0;}    IIC_SCL_OUT(0);    return i;
}

从上面的伪代码不难看出,具体的函数有,起始信号函数停止函数发送数据函数以及接收数据函数等待应答函数等,他们都是依据时序图来写的,对于写函数来说,发送数据时,也是一个bit一个bit进行发送的。而伪代码里面的延时是为了体现出SCL这根线的作用,也就是制造出一个时钟周期,然后在这个周期里面让SDA引脚进行输入或者输出。

SPI

协议概述

SPI协议相对来说,线就多了几根了。

时序分析和伪代码概述

理论:SPI
参考:视频
最后贴一个利用SPI协议在一个OLED屏上显示自己名字的图。
在这里插入图片描述