动图演示常用通信协议原理

这些显示电子系统中信号波形的动图,有助于帮助我们理解传输的机理。

一、SPI传输

1、 SPI协议概述:

SPI(serial peripheral interface)是一种同步串行通信协议,由一个主设备和一个或多个从设备组成,主设备启动与从设备的同步通信,从而完成数据的交换。SPI是一种高速全双工同步通信总线,标准的SPI仅仅使用4个引脚,主要应用在 EEPROM, Flash, 实时时钟(RTC), 数模转换器(ADC), 数字信号处理器(DSP) 以及数字信号解码器之间。有迹象表明,SPI总线首次推出是在1979年,Motorola公司将SPI总线集成在他们第一支改自68000微处理器的微控制器芯片上。由于在芯片中只占用四根管脚 (Pin) 用来控制以及数据传输, 节约了芯片的 pin 数目, 同时为 PCB 在布局上节省了空间。 正是出于这种简单易用的特性, 现在越来越多的芯片上都集成了 SPI技术。
SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps

2、SPI物理层

在这里插入图片描述
SPI 通讯使用 3 条总线及片选线,3 条总线分别为 SCK、MOSI、MISO,片选线为CS(SS),它们的作用介绍如下:
(1) ( Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、CS,以下用 NSS 表示。当有多个 SPI 从设备与 SPI 主机相连时,设备的其它信号线 SCK、MOSI 及 MISO 同时并联到相同的 SPI 总线上,即无论有多少个从设备,都共同只使用这 3 条总线;而每个从设备都有独立的这一条 NSS 信号线,本信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。I2C 协议中通过设备地址来寻址、选中总线上的某个设备并与其进行通讯;而 SPI 协议中没有设备地址,它使用 NSS 信号线来寻址,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI 通讯。所以 SPI 通讯以 NSS 线置低电平为开始信号,以 NSS 线被拉高作为结束信号。

(2) SCK (Serial Clock):时钟信号线,用于通讯数据同步。它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,如 STM32 的 SPI 时钟频率最大为 fpclk/2,两个设备之间通讯时,通讯速率受限于低速设备。

(3) MOSI (Master Output,Slave Input):主设备输出/从设备输入引脚。主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据,即这条线上数据的方向为主机到从机。

(4) MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。主机从这条信号线读入数据,从机的数据由这条信号线输出到主机,即在这条线上数据的方向为从机到主机。

2、协议层

与 I2C 的类似,SPI 协议定义了通讯的起始和停止信号、数据有效性、时钟同步等环节。
请添加图片描述
在这里插入图片描述这是一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。

2.1 SPI通讯的起始和停止信号

NSS 信号线由高变低,是 SPI 通讯的起始信号。NSS 是每个从机各自独占的信号线,当从机在自己的 NSS 线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。在图中的标号处,NSS 信号由低变高,是 SPI 通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

总结:
NSS 信号由高变低时SPI通信开始,NSS 信号由低变高时SPI通信结束。

2.2 数据有效性

SPI 使用 MOSI 及 MISO 信号线来传输数据,使用 SCK 信号线进行数据同步。MOSI 及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。数据传输时,MSB先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定,一般都会采用图 SPI 通讯时序 中的 MSB 先行模式。

观察图中的标号处,MOSI 及 MISO 的数据在 SCK 的上升沿期间变化输出,在 SCK 的下降沿时被采样。即在 SCK 的下降沿时刻,MOSI 及 MISO 的数据有效,高电平时表示数据“1”,为低电平时表示数据“0”。在其它时刻,数据无效,MOSI 及 MISO 为下一次表示数据做准备。

SPI 每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。

总结:
SCK的每个时钟周期传输一位数据,而且数据输入和输出是同时进行的。
MOSI和MISO的数据在SCK上升沿期间变化输出(高 ——> 低 ,低——>高),在SCK下降沿时采样数据。

2.3 CPOL/CPHA 及通讯模式

上面讲述的图 SPI 通讯时序 中的时序只是 SPI 中的其中一种通讯模式,SPI 一共有四种通讯模式,它们的主要区别是总线空闲时 SCK 的时钟状态以及数据采样时刻。为方便说明,在此引入“时钟极性 CPOL”和“时钟相位 CPHA”的概念。

时钟极性 CPOL 是指 SPI 通讯设备处于空闲状态时,SCK 信号线的电平信号 (即 SPI 通讯开始前、NSS 线为高电平时 SCK 的状态)。当CPOL=0 时,SCK 在空闲状态时为低电平,CPOL=1 时,则相反。时钟相位 CPHA 是指数据的采样的时刻,当 CPHA=0 时,MOSI 或 MISO 数据线上的信号将会在SCK 时钟线的“奇数边沿”被采样。当 CPHA=1 时,数据线在 SCK 的“偶数边沿”采样。见图CPHA = 0 时的 SPI 通讯模式 及图 CPHA = 1 时的 SPI 通讯模式。

在这里插入图片描述
我们来分析这个 CPHA=0 的时序图。首先,根据 SCK 在空闲状态时的电平,分为两种情况。SCK信号线在空闲状态为低电平时,CPOL=0;空闲状态为高电平时,CPOL=1。

无论 CPOL=0 还是 =1,因为我们配置的时钟相位 CPHA=0,在图中可以看到,采样时刻都是在SCK 的奇数边沿。注意当 CPOL=0 的时候,时钟的奇数边沿是上升沿,而 CPOL=1 的时候,时钟的奇数边沿是下降沿。所以 SPI 的采样时刻不是由上升/下降沿决定的。MOSI 和 MISO 数据线的有效信号在 SCK 的奇数边沿保持不变,数据信号将在 SCK 奇数边沿时被采样,在非采样时刻,MOSI 和 MISO 的有效信号才发生切换。

类似地,当 CPHA=1 时,不受 CPOL 的影响,数据信号在 SCK 的偶数边沿被采样,见图 CPHA=1
时的 SPI 通讯模式 。

在这里插入图片描述
由 CPOL 及 CPHA 的不同状态,SPI 分成了四种模式,见表 SPI 的四种模式 ,主机与从机需要工作在相同的模式下才可以正常通讯,实际中采用较多的是“模式 0”与“模式 3”。
在这里插入图片描述
总结:
CPOL(时钟极性)等于0时,SCK在空闲状态时为低电平,CPOL = 1时,SCK在空闲状态时为高电平。
时钟相位CPHA是指数据的采样时刻,当CPHA = 0时,MOSI或MISO数据线上的信号将会在SCK时钟线的 “奇数边沿”被采样。当CPHA = 1时,数据线在SCK的“偶数边沿”采样。

3、其他的一些命名规则:

(1)MISO也可以是SIMO,DOUT,DO,SDO或SO(在主机端);

(2)MOSI也可以是SOMI,DIN,DI,SDI或SI(在主机端);

(3)NSS也可以是CE,CS或SSEL;

(4)SCLK也可以是SCK;

请添加图片描述

SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

4、SPI 一对一和一对多

SPI一对一

请添加图片描述

SPI一对多

请添加图片描述
整体的传输大概可以分为以下几个过程:
(1)主机先将NSS信号拉低,这样保证开始接收数据;

(2)当接收端检测到时钟的边沿信号时,它将立即读取数据线上的信号,这样就得到了一位数据(1bit);

由于时钟是随数据一起发送的,因此指定数据的传输速度并不重要,尽管设备将具有可以运行的最高速度(稍后我们将讨论选择合适的时钟边沿和速度)。

(3)主机发送到从机时:主机产生相应的时钟信号,然后数据一位一位地将从MOSI信号线上进行发送到从机;

(4)主机接收从机数据:如果从机需要将数据发送回主机,则主机将继续生成预定数量的时钟信号,并且从机会将数据通过MISO信号线发送;

请添加图片描述
请添加图片描述

5、优缺点:

SPI通讯的优势
使SPI作为串行通信接口脱颖而出的原因很多;

全双工串行通信;
高速数据传输速率。
简单的软件配置;
极其灵活的数据传输,不限于8位,它可以是任意大小的字;
非常简单的硬件结构。从站不需要唯一地址(与I2C不同)。从机使用主机时钟,不需要精密时钟振荡器/晶振(与UART不同)。不需要收发器(与CAN不同)。
SPI的缺点
没有硬件从机应答信号(主机可能在不知情的情况下无处发送);
通常仅支持一个主设备;
需要更多的引脚(与I2C不同);
没有定义硬件级别的错误检查协议;
与RS-232和CAN总线相比,只能支持非常短的距离;

6、代码实现:

(1)野火STM32霸道

官方例程

/**
 ******************************************************************************
 * @file    main.c
 * @author  fire
 * @version V1.0
 * @date    2013-xx-xx
 * @brief   华邦 8M串行flash测试,并将测试信息通过串口1在电脑的超级终端中打印出来
 ******************************************************************************
 * @attention-
 *
 * 实验平台:野火 F103-霸道 STM32 开发板
 * 论坛    :http://www.firebbs.cn
 * 淘宝    :https://fire-stm32.taobao.com
 *
 ******************************************************************************
 */
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
#include "./flash/bsp_spi_flash.h"


typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;

/* 获取缓冲区的长度 */
#define TxBufferSize1   (countof(TxBuffer1) - 1)
#define RxBufferSize1   (countof(TxBuffer1) - 1)
#define countof(a)      (sizeof(a) / sizeof(*(a)))
#define  BufferSize (countof(Tx_Buffer)-1)

#define  FLASH_WriteAddress     0x00000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress



/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = "感谢您选用野火stm32开发板\r\n";
uint8_t Rx_Buffer[BufferSize];

__IO uint32_t DeviceID = 0;
__IO uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;

// 函数原型声明
void Delay(__IO uint32_t nCount);
TestStatus Buffercmp(uint8_t *pBuffer1, uint8_t *pBuffer2, uint16_t BufferLength);

/*
 * 函数名:main
 * 描述  :主函数
 * 输入  :无
 * 输出  :无
 */
int main(void)
{
    LED_GPIO_Config();			// LED 配置
    LED_BLUE;					// 显示蓝灯

    /* 配置串口为:115200 8-N-1 */
    USART_Config();
    printf("\n\n 这是一个8Mbyte串行flash(W25Q64)实验 \n\n");

    /* 8M串行flash W25Q64初始化 */
    SPI_FLASH_Init();

    /* 获取 Flash Device ID */
    DeviceID = SPI_FLASH_ReadDeviceID();
    Delay( 200 );

    /* 获取 SPI Flash ID */
    FlashID = SPI_FLASH_ReadID();
    printf("\r\n FlashID is 0x%X,\
	Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);

    /* 检验 SPI Flash ID */
    if (FlashID == sFLASH_ID)
    {
        printf("\r\n 检测到串行flash W25Q64 !\r\n");

        /* 擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除 */
        // 这里擦除4K,即一个扇区,擦除的最小单位是扇区
        SPI_FLASH_SectorErase(FLASH_SectorToErase);

        /* 将发送缓冲区的数据写到flash中 */
        // 这里写一页,一页的大小为256个字节
        SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);
        printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);

        /* 将刚刚写入的数据读出来放到接收缓冲区中 */
        SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);
        printf("\r\n 读出的数据为:%s \r\n", Rx_Buffer);

        /* 检查写入的数据与读出的数据是否相等 */
        TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);

        if( PASSED == TransferStatus1 )
        {
            LED_GREEN;		// 读取成功显示绿灯+
            printf("\r\n 8M串行flash(W25Q64)测试成功!\n\r");
        }
        else
        {
            LED_RED;
            printf("\r\n 8M串行flash(W25Q64)测试失败!\n\r");
        }
    }// if (FlashID == sFLASH_ID)
    else// if (FlashID == sFLASH_ID)
    {
        LED_RED;
        printf("\r\n 获取不到 W25Q64 ID!\n\r");
    }

    while(1);
}

/*
 * 函数名:Buffercmp
 * 描述  :比较两个缓冲区中的数据是否相等
 * 输入  :-pBuffer1     src缓冲区指针
 *         -pBuffer2     dst缓冲区指针
 *         -BufferLength 缓冲区长度
 * 输出  :无
 * 返回  :-PASSED pBuffer1 等于   pBuffer2
 *         -FAILED pBuffer1 不同于 pBuffer2
 */
TestStatus Buffercmp(uint8_t *pBuffer1, uint8_t *pBuffer2, uint16_t BufferLength)
{
    while(BufferLength--)
    {
        if(*pBuffer1 != *pBuffer2)
        {
            return FAILED;
        }

        pBuffer1++;
        pBuffer2++;
    }
    return PASSED;
}

void Delay(__IO uint32_t nCount)
{
    for(; nCount != 0; nCount--);
}
/*********************************************END OF FILE**********************/

(2)SPI例程

#include "stm32f10x.h"

/* RCC时钟配置 */
void RCC_config(void)
{
    ErrorStatus HSEStartUpStatus;

    /* RCC寄存器设置为默认配置 */
    RCC_DeInit();
    /* 打开外部高速时钟 */
    RCC_HSEConfig(RCC_HSE_ON);
    /* 等待外部高速时钟稳定 */
    HSEStartUpStatus = RCC_WaitForHSEStartUp();
    if(HSEStartUpStatus == SUCCESS)
    {
        /* 设置HCLK = SYSCLK */
        RCC_HCLKConfig(RCC_SYSCLK_Div1);
        /* 设置PCLK2 = HCLK */
        RCC_PCLK2Config(RCC_HCLK_Div1);
        /* 设置PCLK1 = HCLK / 2 */
        RCC_PCLK1Config(RCC_HCLK_Div2);
        //  /* 设置FLASH代码延时 */
        //  FLASH_SetLatency(FLASH_Latency_2);
        //  /* 使能预取址缓存 */
        //  FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable);
        /* 设置PLL时钟源为HSE倍频9 72MHz */
        RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
        /* 使能PLL */
        RCC_PLLCmd(ENABLE);
        /* 等待PLL稳定 */
        while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
        /* 设置PLL为系统时钟源 */
        RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
        /* 等待系统时钟源切换到PLL */
        while(RCC_GetSYSCLKSource() != 0x08);
    }
}


/* GPIO配置 */
void GPIO_config(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;


    /* 时钟配置 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    /* MISO配置为浮空输入 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOA, &GPIO_InitStructure);


    /* SCK和MOSI配置为复用推挽输出 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);


    /* NSS配置为推挽输出 */
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
}


/* SPI配置 */
void SPI_config(void)
{
    SPI_InitTypeDef SPI_InitStructure;

    /* 时钟配置 */
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO | RCC_APB2Periph_SPI1, ENABLE);

    /* 配置SPI模式 */
    SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
    SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
    SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
    SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
    SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
    SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
    SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_InitStructure.SPI_CRCPolynomial = 7;
    SPI_Init(SPI1, &SPI_InitStructure);

    /* 打开SPI1 */
    SPI_Cmd(SPI1, ENABLE);
}


/* 毫秒延时函数 */
void delay_ms(uint16_t time)
{
    uint16_t i = 0;

    while(time--)
    {
        i = 12000;
        while(i--);
    }
}


/* SPI发送一个数据 */
uint8_t SPI_SendByte(uint8_t data)
{
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI1, data);
    while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
    return SPI_I2S_ReceiveData(SPI1);
}


/* 主函数 */
int main(void)
{
    volatile uint8_t i = 0, j;

    /* RCC时钟配置 */
    RCC_config();

    /* GPIO配置 */
    GPIO_config();

    /* SPI配置 */
    SPI_config();

    while(1)
    {
        GPIO_ResetBits(GPIOA, GPIO_Pin_4);
        j = SPI_SendByte(i++);
        GPIO_SetBits(GPIOA, GPIO_Pin_4);

        /* 1秒延时 */
        delay_ms(500);
    }
}

2、I2C 传输

请添加图片描述

3、UART传输

请添加图片描述
请添加图片描述

4、红外控制

请添加图片描述
红外通信相关文章请移步此处:详解红外遥控编解码。请添加图片描述
请添加图片描述

5、串并转换电路

请添加图片描述
请添加图片描述
请添加图片描述

6、其他波形动画

请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值