相关推荐recommended
STM32-SPI通信协议
作者:mmseoamin日期:2024-04-29

串行外设接口SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线。

        在某些芯片上,SPI接口可以配置为支持SPI协议或者支持I2S音频协议。 SPI接口默认工作在SPI方式,可以通过软件把功能从SPI模式切换到I2S模式,具体需参考操作手册

        串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方式通信。此接口可以被配置成主模式,并为外部从设备提供通信时钟(SCK)。接口还能以多主配置方式工作。

它可用于多种用途,包括使用一条双向数据线的双线单工同步传输,还可使用CRC校验的可靠通信。

        I2S也是一种3引脚的同步串行接口通讯协议。它支持四种音频标准,包括飞利浦I2S标准, MSB和LSB对齐标准,以及PCM标准。它在半双工通讯中,可以工作在主和从2种模式下。当它作为主设备时,通过接口向外部的从设备提供时钟信号。

SPI特点

  1. 四根通信线:SCK、MOSI、MISO、SS
  2. 支持全双工同步传输
  3. 支持总线挂载多设备(一主多从)
  4. 支持多主机模型,主或从操作
  5. 支持DMA
  6. 可配置8位/16位数据帧
  7. 时钟频率:f(PCLK)/(2, 4, 8, 16, 32, 64, 128, 256)
  8. 兼容I2S协议等

电路连接

  • 所有SPI设备的SCK、MOSI、MISO、分别连接在一起。
  • 主机另外引出多条SS控制线,分别接到各从机的SS引脚,需要找谁通信就置谁的SS为低电平,同一时间主机只能选择一个从机。
  • 输出引脚配置为推挽输出,输入引脚配置为浮空或上拉输入。
  • 所有设备需要共地。STM32-SPI通信协议,第1张

     SPI移位示意图

     STM32-SPI通信协议,第2张SPI时序

    起始条件:SS从高电平切换到地点哦

    终止条件:SS从低电平切换到高电平

    STM32-SPI通信协议,第3张

    SPI模式

    SPI有四种模式通过CPOL时钟极性和CPHA时钟相位来定义

    • CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
    • CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
      • CPHA=0,在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样。在第2个边沿发送数据
      • CPHA=1,在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样。在第1个边沿发送数据

        STM32-SPI通信协议,第4张 软件SPI设置时序单元

        数据采样==移入数据

        模式0

        STM32-SPI通信协议,第5张

        模式1

        STM32-SPI通信协议,第6张

        模式2STM32-SPI通信协议,第7张
        模式3

        STM32-SPI通信协议,第8张

        软件SPI代码

        以模式0为例,只需对相位和极性修改即可得到其他模式

        //SPI写SS引脚电平
        void MySPI_W_SS(uint8_t BitValue)
        {
        	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);//根据BitValue,设置SS引脚的电平
        }
        //SPI写SCK引脚电平
        void MySPI_W_SCK(uint8_t BitValue)
        {
        	GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);//根据BitValue,设置SCK引脚的电平
        }
        //SPI写MOSI引脚电平
        void MySPI_W_MOSI(uint8_t BitValue)
        {
        	GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);		
            //根据BitValue,设置MOSI引脚的电平,BitValue要实现非0即1的特性
        }
        //SPI读MISO引脚电平
        uint8_t MySPI_R_MISO(void)
        {
        	return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);			//读取MISO电平并返回
        }
        //SPI初始化
        void MySPI_Init(void)
        {
        	/*开启时钟*/
        	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
        	
        	/*GPIO初始化*/
        	GPIO_InitTypeDef GPIO_InitStructure;
        	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
        	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        	GPIO_Init(GPIOA, &GPIO_InitStructure);			//将PA4、PA5和PA7引脚初始化为推挽输出
        	
        	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
        	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
        	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA6引脚初始化为上拉输入
        	
        	/*设置默认电平*/
        	MySPI_W_SS(1);											//SS默认高电平
        	MySPI_W_SCK(0);											//SCK默认低电平
        }
        //SPI起始
        void MySPI_Start(void)
        {
        	MySPI_W_SS(0);				//拉低SS,开始时序
        }
        //SPI终止
        void MySPI_Stop(void)
        {
        	MySPI_W_SS(1);				//拉高SS,终止时序
        }
        //SPI交换传输一个字节,使用SPI模式0
        uint8_t MySPI_SwapByte(uint8_t ByteSend)
        {
        	uint8_t i, ByteReceive = 0x00;		            
            //定义接收的数据,并赋初值0x00,此处必须赋初值0x00,后面会用到
        	
        	for (i = 0; i < 8; i ++)						//循环8次,依次交换每一位数据
        	{
        		MySPI_W_MOSI(ByteSend & (0x80 >> i));		//使用掩码的方式取出ByteSend的指定一位数据并写入到MOSI线
        		MySPI_W_SCK(1);								//拉高SCK,上升沿移出数据
        		if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}	//读取MISO数据,并存储到Byte变量
        																//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
        		MySPI_W_SCK(0);								//拉低SCK,下降沿移入数据
        	}
        	
        	return ByteReceive;								//返回接收到的一个字节数据
        //	for(i=0;i<8;i++)
        //	{
        //		MySPI_W_MOSI(ByteSend & 0x80);
        //		ByteSend<<=1;
        //		MySPI_W_SCK(1);
        //		if(MySPI_R_MISO()==1){ByteSend|=0x01;}
        //		MySPI_W_SCK(0);
        //	}
        //	 return ByteSend;
        }
        

        硬件SPI

        SPI采用高位先行的模式

        SPI框图

        数据寄存器DR分为TDR和RDR

        1.当需要发送数据时,第一个数据写入TDR,当移位寄存器没有数据进行移位时,TDR数据会立刻转入移位寄存器开始移位。

        转入时刻会置状态寄存器的TXE位1,表示发送寄存器空,当检查TXE为1后,下一个数据便可写入TDR等候。

        2.移位寄存器检测到有数据也会自动产生时钟将数据移出,在移出的过程中MISO的数据也会移入。

        3.一旦数据移出完成,数据移入也完成了,这是移位寄存器就会将数据整体转出到RDR,

        这时会置状态寄存器RXNE为1,表示接收寄存器非空。当检测到RXNE为1就需在下一个数据到来之前将数据读出来,不然数据会被覆盖。

        STM32-SPI通信协议,第9张
        SPI时序框图
        连续传输

        模式三

        STM32-SPI通信协议,第10张

        非连续传输 

        STM32-SPI通信协议,第11张

        硬件SPI代码
         
        #include "stm32f10x.h"                  // Device header
        //SPI写SS引脚电平,SS仍由软件模拟
        void MySPI_W_SS(uint8_t BitValue)
        {
        	GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);//根据BitValue,设置SS引脚的电平
        }
        //SPI初始化
        void MySPI_Init(void)
        {
        	/*开启时钟*/
        	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);	//开启GPIOA的时钟
        	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);	//开启SPI1的时钟
        	
        	/*GPIO初始化*/
        	GPIO_InitTypeDef GPIO_InitStructure;
        	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
        	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
        	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA4引脚初始化为推挽输出
        	
        	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
        	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
        	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        	GPIO_Init(GPIOA, &GPIO_InitStructure);				//将PA5和PA7引脚初始化为复用推挽输出
        	
        	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
        	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
        	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
        	GPIO_Init(GPIOA, &GPIO_InitStructure);					//将PA6引脚初始化为上拉输入
        	
        	/*SPI初始化*/
        	SPI_InitTypeDef SPI_InitStructure;						//定义结构体变量
        	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;			//模式,选择为SPI主模式
        	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	//方向,选择2线全双工
        	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//数据宽度,选择为8位
        	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;		//先行位,选择高位先行
        	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;	//波特率分频,选择128分频
        	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;				//SPI极性,选择低极性
        	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;			//SPI相位,选择第一个时钟边沿采样,极性和相位决定选择SPI模式0
        	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;				//NSS,选择由软件控制
        	SPI_InitStructure.SPI_CRCPolynomial = 7;			//CRC多项式,暂时用不到,给默认值7
        	SPI_Init(SPI1, &SPI_InitStructure);				//将结构体变量交给SPI_Init,配置SPI1
        	
        	/*SPI使能*/
        	SPI_Cmd(SPI1, ENABLE);									//使能SPI1,开始运行
        	
        	/*设置默认电平*/
        	MySPI_W_SS(1);											//SS默认高电平
        }
        //SPI起始
        void MySPI_Start(void)
        {
        	MySPI_W_SS(0);				//拉低SS,开始时序
        }
        //SPI终止
        void MySPI_Stop(void)
        {
        	MySPI_W_SS(1);				//拉高SS,终止时序
        }
        //SPI交换传输一个字节,使用SPI模式0
        uint8_t MySPI_SwapByte(uint8_t ByteSend)
        {
        	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);	//等待发送数据寄存器空
        	
        	SPI_I2S_SendData(SPI1, ByteSend);				//写入数据到发送数据寄存器,开始产生时序
        	
        	while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);//等待接收数据寄存器非空
        	
        	return SPI_I2S_ReceiveData(SPI1);							//读取接收到的数据并返回
        }