SPI通信协议

一、简介

SPI(Serial Peripheral interface)串行外围设备接口,该通信协议由Motorola首先在其MC68HCxx系列处理器上定义的。主要用于EEPROM、FLASH、实时时钟、AD转换器,还有数字信号处理器和数字信号解码器之间。

SPI是一种高速的、全双工、同步的通信总线,并且在芯片的管脚上只占用四根线,节省了芯片管脚,同时为PCB的布局上节省空间、提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。

二、物理层

(1)PIN脚接口定义

SPI通信要求有数据线、时钟线、控制信号线即片选信号线进行通信实现数据传输。

  • 数据线

    • SDA(单线数据线时,同时做输入输出)

    • MOSI、MISO(MOSI:Master Output Slave Input;MISO:Master Input Slave Output)

  • 时钟线

    • SCL(主要作用是Master设备往Slave设备传输时钟信号,控制数据交换的时机和速率)
  • 片选信号线

    • CS(用于Master设备片选Slave设备,使被选中的Slave设备能够被Master设备所访问)
  • 命令/数据线

    • A0(Only 4-Line SPI,用于控制输入的数据为命令还是数据)

在这里插入图片描述

(2)硬件特性

  • 支持全双工通信

  • 通信简单

  • 数据传输速率快

  • 无指定的流控制,位于应答机制确认是否接收到数据

三、协议内容

(1)采用主-从模式的控制方式

SPI规定了两个SPI设备之间通信必须有主设备(Master)来控制从设备(Slave)。Master设备可以通过提供Clock以及对Slave设备进行片选(Slave Select) 来控制多个Slave设备,SPI协议还规定Slave设备的Clock由Master设备通过SCL管脚提供给Slave设备,Slave本身不能产生或控制Clock,没有Clock,则Slave设备不能正常工作。

在这里插入图片描述

(2)采用同步方式(Synchronous)传输数据

Master设备会根据要交换的数据来产生相对的时钟脉冲(Clock Pulse),时钟脉冲组成了时钟信号(Clock Signal),时钟信号通过时钟极性(CPOL)和时钟相位(CPHA)控制着两个SPI设备间何时数据交换以及何时对接收到的数据进行采样,来保证数据在两个设备之间的是同步传输的。

(3)数据交换(Data Exchanges)

SPI 设备间的数据传输之所以又称之为数据交换,是因为SPI协议规定一个SPI设备不能在数据通信过程中仅仅只充当一个“发送者(Tranesmitter)”或者“接收者(Receiver)”。在每个Clock周期内SPI设备都会发送并接收一个bit大小的数据(不管主设备还是从设备),相当于该设备有一个bit大小的数据被交换了。一个Slave设备要想能够接收到Master发送过来的控制信号,必须在此之前能被Master设备进行访问(Access)。所以,Master设备必须首先通过SS/CS PIN对Slave设备进行片选,把想要访问的Slave设备选上。在数据传输过程中,每次接收到的数据必须在下一次数据传输前被采样,如果之前接收到的数据没有被读取,那么这些已经接收完成的数据将有可能被丢弃,导致SPI物理模块最终失效。因此,在程序中一般会在SPI传输完数据后,去读取SPI设备的数据,及时这些数据(Dummy Data)在我们的程序都是无用的(虽然发送后紧接着读取是无意义的,但仍然需要从寄存器读取出来)。

四、编程实现

(1)纯逻辑实现

以4-Line SPI为例:

PIN定义:

  • SCL - 时钟线

  • SDA - 数据线

  • CS - 片选控制线

  • A0 - 命令/数据线


volatile void Write_Command(uint8_t cmd)
{
    uint8_t i;
    CS = 0;     // 拉低片选选中Slave
    A0 = 0;     // A0 = 0时写入命令(根据产品规格书定义)
    for(i=0; i<8; i++)
    {
        if( cmd & 0x80 )
            SDA = 1;
        else
            SDA = 0;
           
        SCL = 0;
        SCL = 1;
        cmd <<= 1;
    }
    CS = 1;
}

volatile void Write_Data(uint8_t data)
{
    uint8_t i;
    CS = 0;     // 拉低片选选中Slave
    A0 = 1;     // A0 = 1时写入数据(根据产品规格书定义)
    for(i=0; i<8; i++)
    {
        if( data & 0x80 )
            SDA = 1;
        else
            SDA = 0;
            
        SCL = 0;
        SCL = 1;
        data <<= 1;
    }
    CS = 1;
}

(2)配置单片机SPI外设

以STM32F103C8T6为例:

void SPI1_Init(void)
{
    // 1、定义结构体存储配置
	GPIO_InitTypeDef GPIO_InitStructure;
    SPI_InitTypeDef  SPI_InitStructure;
  
    // 2、使能SPI1时钟源
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOA|RCC_APB2Periph_SPI1, ENABLE );	
 
    // 3、配置SPI引脚模式(复用输出模式)
	GPIO_InitStructure.GPIO_Pin     = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode    = GPIO_Mode_AF_PP;     //复用推挽输出
	GPIO_InitStructure.GPIO_Speed   = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
  
    // 4、配置SPI工作模式
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //设置SPI单向或者双向的数据模式: SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode      = SPI_Mode_Master;		            //设置SPI工作模式: 设置为主SPI
    SPI_InitStructure.SPI_DataSize  = SPI_DataSize_8b;                  //设置SPI的数据大小: SPI发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL      = SPI_CPOL_High;		            //选择了串行时钟的稳态: 时钟悬空高
	SPI_InitStructure.SPI_CPHA      = SPI_CPHA_2Edge;	                //数据捕获于第二个时钟沿
	SPI_InitStructure.SPI_NSS       = SPI_NSS_Soft;		                //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)管理:内部NSS信号有SSI位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_256;//定义波特率预分频的值:波特率预分频值为256
	SPI_InitStructure.SPI_FirstBit          = SPI_FirstBit_MSB;	        //指定数据传输从MSB位还是LSB位开始:数据传输从MSB位开始
    SPI_InitStructure.SPI_CRCPolynomial     = 7;	                    //CRC值计算的多项式
	SPI_Init(SPI1, &SPI_InitStructure);                                 //根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
 
    //5、使能SPI外设
    SPI_Cmd(SPI1, ENABLE);    
} 
// 发送数据
// 使用标准库函数:void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
// 例:
    SPI_I2S_SendData(SPI1, TxData);

    
// 读取数据
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{		
	uint8_t retry = 0;			
		 	
	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) //检查指定的SPI标志位设置与否:发送缓存空标志位
	{
		retry++;
		if(retry > 200)
          return 0;
	}	
			  
	SPI_I2S_SendData(SPI1, TxData); //通过外设SPIx发送一个数据
	retry = 0;

	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET)//检查指定的SPI标志位设置与否:接受缓存非空标志位
	{
		retry++;
		if(retry > 200)
          return 0;
	}	
	  						    
	return SPI_I2S_ReceiveData(SPI1); //返回通过SPIx最近接收的数据					    
}

引用博客:https://zhuanlan.zhihu.com/p/150121520

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值