SPI同步串行通信协议

SPI概述

SPI的定义

  • SPI简介

    SPI全称serial peripheral interface 串行外设接口,该接口是一种高速、全双工、同步方式的通信总线

    外设是指如传感器芯片、储存器芯片、图像信号处理芯片的功能芯片,这些外设的通信数据量较大,需要高速的通信协议,UART这样异步的通信协议不能满足要求,因此要用到SPI

  • SPI的引脚

    SPI在芯片引脚上只占用4根线,有节约引脚与PCB布线资源的优点

    SPI使用的四个引脚如下,这些引脚与GPIO引脚功能复用,具体引脚要查阅数据手册:

    1. MISO(PC7):主设备输入/从设备输出
    2. MOSI(PC6):主设备输出/从设备输入
    3. NSS(PE5):片选引脚,其上电时设备有效
    4. SCK(PC5):同步引脚

    使用前对引脚的初始化需要注意:如果SPI设定为高速模式,那么引脚的输出也该配置为高速输出

  • SPI的一主多从特性

    与单对单的UART相比,SPI通信系统不仅支持一主一从的单对单通信,还能支持一主多从结构:主设备由具备控制功能的单元担任(即MCU),从设备是各种功能外设单元(具备SPI接口的功能芯片),一主多从就是一台主设备同时连接多个从设备

  • 要确定谁是主设备,谁是从设备,首先通过对SPI_CR1_MSTR位配置(该位置1的为主设备,0为从设备)

    配置完成后还要进行硬件配置或者软件配置,用以决定设备模式,即其应该扮演什么角色,在后文介绍具体配置方法

  • 而对一主多从的SPI通信系统,要确定哪个从设备在与主设备通信,则可以对需要通信的从设备配置为从模式,不需要通信的从设备则配为主模式,使其被禁用,具体方法同见后文

SPI的环形数据通信

  • SPI的同步串行通信过程依赖设备中的位移寄存器,具体通信过程如下:

    数据先从主设备的MOSI中传出,经由从设备的MOSI进入从设备的位移寄存器中,被从设备接收后作出响应,再把相应的数据从MISO引脚输出到主设备

    这样的通信过程,让数据在主设备和从设备之间形成了一个环

  • 作为同步通信,设备间通信还要依赖同步时钟,即SCK的“节拍”,在移位寄存器中的数据按节拍被一截一截地逐位“砍断”,再通过引脚逐一发送出去

    需要注意的是,这样逐一发送的机制使得在进行发送之前必须确定发送的缓冲器是否为空,当发送缓冲器空时才能开始发送,同理当接收缓冲区非空时才应该开始读取

STM8的SPI资源

STM8的SPI特点

  • STM8单片机的同步串行外设接口能与其他具备SPI接口的设备以半/全双工、同步、串行方式通信,通信频率最高为fmaster/2
  • 该接口可以配置为主模式,并为从设备提供通信时钟SCK,最大SPI速度可到10MHz(对应了GPIO的高速输出模式);也可以配置为从模式

主从设备角色配置

  • 在配置角色前,先由SPI_CR1MSTR位决定设备是主设备(1)还是从设备(0)

  • 配置的方法有硬配置和软配置两种,具体使用哪种由SPI_CR2SSM位决定

  • 硬配置

    SPI_CR2_SSM为0(默认情况):此时使用NSS引脚来决定主从设备,主设备的SS接高电平,从设备的SS接低电平

    对一主多从的SPI系统,可以每个从设备(MSTR配置为0)的SS引脚各自接到主机的一个GPIO口上,对于需要进行通信的从设备输出低电平(从模式),其余保持为高电平(对配置为从设备的器件来说即禁用该设备)

  • 软配置

    SPI_CR2_SSM为1:此时启用软件从设备管理,NSS引脚用作普通的GPIO功能

    主从设备由SPI_CR2SSI位来决定:1为主设备,0为从设备

数据帧格式

  • 一个系统内的SPI,都要使用同一的数据帧格式才能正常运作

  • 首先了解两个概念:MSB指most significant bit 最高有效位,相对的LSB即数据的最低有效位

  • MSB方式数据帧

    高位在前,低位在后:MSB位于数据帧最左边,为数据帧的第一位,向后第二位是数据的高位D6,然后依次到D5、D4……直到D1,最后第8位是LSB

  • LSB方式数据帧

    与MSB相反,低位在前,高位在后,LSB在最左侧,然后是数据的低位D1在第二位,然后依次D2、D3……直到D6,最后第8位是MSB

SPI通信时序参数

  • 时钟极性CPOL概述

    时钟极性决定了SPI总线空闲时,时钟线SCK保持什么样的电平

    由SPI_CR1的CPOL位决定:配置为0则空闲时SCK保持低电平

  • 时钟相位CPHA概述

    时钟相位CPHA用于确定数据传输发送在时钟信号的哪一个边沿

    由SPI_CR1的CPHA位决定:配置为1则数据采样从SCK的第二个时钟边沿开始,为0时则从第一个时钟边沿开始

  • 数据传输时序

    同步串行通信依赖同步时钟,同步时钟具体如何控制通信过程称为数据传输时序:其由三个要素决定:数据帧格式、时钟极性、时钟相位

    SPI通信一旦建立,这三者便不能修改了,除非把SPI_CR1的SPE位清0以禁止SPI后再修改

    以下是各个时序的情况:

    image

SPI配置流程

SPI配置流程简述

  • 主设备和从设备的配置思路相同,但一些细节存在差异

    主设备需要为整个SPI通信过程提供串行时钟(时钟信号由SCK引脚产生并连接至从设备的SCK引脚),因此SCK引脚为输出模式,并且要配置其SPI通信速率参数,从设备则不必

  1. 引脚初始化
    • SCK(PC5):时钟引脚:主设备为快速推挽输出/从设备为无中断上拉输入
    • NSS(PE5):片选引脚:硬配置时应该选择无中断上拉输入
    • MISO(PC7):主设备无中断上拉输入,从设备快速推挽输出
    • MOSI(PC6):主设备快速推挽输出,从设备无中断上拉输入
  2. 确定SPI通信参数

    配置SPI_CR1寄存器,确定串行数据帧格式、SPI通信速率、时钟极性和相位

    主设备和从设备的配置必须是一致的,从设备无需配置SPI通信速率

  3. 配置设备的角色
    • 硬件方式:主设备的NSS连接到高电平/从设备连接低电平

    • 软件方式:主设备的SPI_CR2寄存器的SSM和SSI位都置1/从设备的SPI_CR2寄存器的SSM位为1,SSI位置0

  4. 按需开启SPI的各项功能

    按需设置数据模式、全双工、输入使能(只接收模式)、CRC校验以及各个标志位的中断等功能

  5. 使能SPI

    设置SPI_CR1的SPE位为1

SPI相关寄存器

  • SPI控制寄存器1 SPI_CR1

    配置SPI通信时序参数的寄存器,包括:

    • 时钟相位CPHA:0为第一个时钟边沿开始;1为第二个

    • 时钟极性CPOL:0为空闲时SCK低电平;1为高电平

    • 主设备选择MSTR:决定被配置的设备是主设备还是从设备

    • 波特率控制BR[2:0]:波特率为fMASTER/2BR+1

    • SPI使能SPE:0时禁止SPI设备,1时开启

    • 帧格式:0为先发送MSB,1为发送LSB

    image

  • SPI控制寄存器2 SPI_CR2

    CR2是对CR1的补充,可以配置SPI的传输模式(双向数据模式等)以及使能校验功能等,常用的是SSM与SSI位进行主从模式配置
    SSM:当此位置1时启用从设备软件管理,这时SSI位替代片选引脚SS对从设备进行控制
    SSI:当这一位为0时,设备配置为从模式,置1时为主模式,这实际上对应了引脚SS的作用:从设备的SS引脚为低电平时才是有效的从设备,而主设备的SS引脚应该接高电平,如果将从设备的SS接高电平就相当于禁用了该设备

    image

  • SPI中断控制寄存器SPI_ICR

    包括各种状态标志位中断的使能,在此启动对应中断时,当相应标志位置1(对应的事件发生)时将会进入中断

    image

  • SPI状态寄存器SPI_SR

    各类SPI事件的相关标志位存放在该寄存器中,通过检测对应标志位就能直到某件事是否发生或者完成

    image

  • 数据寄存器SPI_DR

    存放想要发送的数据或者接收到的数据

    对应有两个缓冲区:一个用于写(发送缓冲区),另一个用于读(接收缓冲区)

    这两个缓冲区是否为空对应了SPI_DR的TXE与RXNE

  • SPI可以配置使用CRC校验机制,剩下三个寄存器都用于CRC校验,先简单介绍CRC校验法:类似奇偶校验,是一种能检查数据是否出错的机制

    原理:先约定一个生成多项式(最高位和最低位都必须是1),用它去除原始数据(生成多项式有几阶,就要在原始数据后填几个0),得到余数(余数不足阶数则要在左补0),将这个余数附加在原始信息后

    接收方收到信息后再用这个约定好的多项式来除,得到的余数为0则表示信息无错

  • 多项式寄存器SPI_CRCPR

    使用该寄存器来保存CRC校验过程中使用的多项式值

  • 接收数据多项式寄存器SPI_RXCRCR

    用来装载接收数据计数的CRC数值,配合SPI_CRCPR进行CRC校验

  • 发送数据多项式寄存器SPI_TXCRCR

    装载发送数据计算的CRC数值

代码实现

  • 初始化主设备

    本例程使用软件从设备管理,无需使用VSS引脚

    void SPI_Master_init(void)
    {
    	PC_DDR_DDR5 = 1;//SPI_SCK/PC5配置为高速推挽输出
    	PC_CR1_C15 = 1;
    	PC_CR2_C25 = 1;
    	PC_DDR_DDR6 = 1;//SPI_MOSI/PC6配置为高速推挽输出
    	PC_CR1_C16 = 1;
    	PC_CR2_C26 = 1;
    	PC_DDR_DDR7 = 0;//SPI_MISO/PC7配置为弱上拉模式
    	PC_CR1_C17 = 1;
    	PC_CR2_C27 = 0;
    
    	SPI_CR1 = 0x07;//0000 0111,作用如下
    	// LSBFIRST=0 设置为MSB格式
    	// SPE=0 先禁止SPI设备
    	// BR[2:0]=000 波特率时钟配置为主频/2,从设备此位无效
    	//MSTR=1 配置为主设备
    	//CPOL=1 空闲状态SCK为高电平
    	//CPHA=1 数据采样从第二个时钟边沿开始
    	SPI_CR2 = 0x03;//0000 0011,作用如下
    	//BDM=0 双线单向数据模式
    	//BDOE=0 输入使能(只接收模式)
    	//CRCEN=0 关闭CRC校验
    	//CRCNEXT=0 下一个发送数据来自Tx缓冲区
    	//RXOnly=0 全双工
    	//SSM=1 软件配置主从设备角色
    	//SSI=1 主模式
    	SPI_ICR = 0;//按需配置SPI_ICR,启动中断功能,此处不使用
    	SPI_CR1_SPE = 1;//使能SPI
    }
    
  • 初始化SPI从设备

    由前文原理可知,主从设备的SPI参数必须是相同的,因此除了引脚GPIO配置以及主从设备的软件配置外其余不变

    void SPI_Slave_init(void)
    {
    	PC_DDR_DDR5 = 0;//SPI_SCK/PC5配置为弱上拉输入
    	PC_CR1_C15 = 1;
    	PC_CR2_C25 = 0;
    	PC_DDR_DDR6 = 0;//SPI_MOSI/PC6配置为弱上拉输入
    	PC_CR1_C16 = 1;
    	PC_CR2_C26 = 0;
    	PC_DDR_DDR7 = 1;//SPI_MISO/PC7配置为低速推挽输出
    	PC_CR1_C17 = 1;
    	PC_CR2_C27 = 0;
    
    	SPI_CR1 = 0x03;//0000 0011,除了MSTR位外其他与主设备保持一致
    	//MSTR=0 配置为从设备
    
    	SPI_CR2 = 0x02;//0000 0010,除了SSI与主设备不同外其他一致
    	//SSM=1 软件配置主从设备角色
    	//SSI=0 从模式
    	SPI_ICR = 0;//按需配置SPI_ICR,启动中断功能,此处不使用
    	SPI_CR1_SPE = 1;//使能SPI
    }
    
  • SPI收发实验代码

    硬件上:使用两个单片机,甲为主设备、乙为从设备,两机之间三线(SCK/MISO/MOSI)连接

    设计的实验现象:在甲按下PA0连接的按键,让乙控制的数码管显示数加1,如果按下PA1连接的按键则减去1

    //甲的代码如下,为了简便直接使用查询法法
    PA_DDR_DDR0 = 0;
    PA_CR1_C10 = 1;
    PA_CR2_C20 = 0;
    PA_DDR_DDR1 = 0;
    PA_CR1_C11 = 1;
    PA_CR2_C21 = 0;
    SPI_Master_init();
    while(1)
    {
    	if(PA_IDR_IDR0==0)//如果按键被按下
    	{
    		delay(10);//延时消抖
    		if(PA_IDR_IDR0==0)
    		{
    			while(SPI_SR_TXE==0);//发送前等待发送缓冲区为空
    			SPI_DR = 0xF0;//发送数据F0
    			while(PA_IDR_IDR0==0);//松手检测
    		}
    	}
    	if(PA_IDR_IDR1==0)//如果按键被按下
    	{
    		delay(10);//延时消抖
    		if(PA_IDR_IDR1==0)
    		{
    			while(SPI_SR_TXE==0);//发送前等待发送缓冲区为空
    			SPI_DR = 0x0F;//发送数据0F
    			while(PA_IDR_IDR1==0);//松手检测
    		}
    	}
    }
    
    //乙设备代码如下,乙设备连接数码管,为简便假设数码管使用静态显示
    u8 com=0;//变量存放接收数据
    u8 num=0;//变量存放本机使用的数据
    SPI_Slave_init();
    while(1)
    {
    	while(SPI_SR_RXNE==0);//如果接收数据缓冲区非空
    	com=SPI_DR;//取数据
    	if(com==0xF0)//如果接收到的数据是甲发送的对应数据
    	{
    		num = num+1;//执行加1
    		if(num>9)
    			num=0;
    	}
    	if(com==0x0F)
    	{
    		if(num=0)
    			num=10;
    		num = num-1;
    	}
    	Display(0,num);//为简便假设数码管使用静态显示
    	//如果需要进行动态显示,启动SPI的接收中断,将num的变化放在中断服务程序中
    }
    

posted on   无术师  阅读(19)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?

统计

点击右上角即可分享
微信分享提示