SPI主从通讯稳定性之解决方法

在使用SPI通讯时,将硬件SPI用作主机的比较多,程序设计也比较容易,但是,若将硬件SPI用作从机了,网上的案例就比较少了,因为大家都有一个习惯,实在实现不了,就用软件模拟SPI来完成通讯。

在测试或程序设计中,经常会有人提出各种各样非常奇怪的想法,以及遇到的问题:

1、SPI自发自收;这种想法,一般用来验证收发是否正常。这是可以理解的。由于是主机,就不举例了。

2、SPI主机和从机同步收发;他的意思是,SPI主机在一开始发送数据的时候,从机就和主机对发,显然这是违背常理的。当然,主机在任何时候发送数据,从机参与发送还是不参与发送,主机都会收到从机的数据。说他违背常理,是因为主机要发多少个字节,从机是不知道的,所以,主从同步发送数据,是很难实现的。为了能实现SPI主机和从机交换数据,就需要SPI的软件通讯协议。其次,SPI从机必须工作在中断方式。由于主机控制着移位时钟,所以很容易写。下面是SPI通讯的软件协议:

1)、SPI主机发送写数据格式:
高8位地址 + 低8位地址 + 写命令 + 数据长度 + 写数据内容
SPI1 Send: 10  02  06  02  A5  A5
2)、SPI从机应答SPI主机写时发送数据格式:
高8位地址 + 低8位地址 + 写命令 + 数据长度 + 写数据内容
SPI2 Send: 10  02  06  02  A5  A5
3)、SPI主机发送读数据格式:
高8位地址 + 低8位地址 + 读命令 + 数据长度
SPI1 Send: 10  00  03  02
4)、SPI主机发送len个字节的时钟格式:
len个0x00,实现读取从机应答数据
SPI1 Send: 00  00  00  00  00  00
5、SPI从机应答SPI主机读时发送数据格式:
高8位地址 + 低8位地址 + 读命令 + 数据长度 + 读数据内容
SPI2 Send: 10  00  03  02  5A  5A

 3、在SPI从机中使用DMA搬运数据;在SPI主机中使用DMA搬运数据是可以的,发送多少个字节,接收多少个字节,主机肯定是可以知道的。但对于从机,要接收多少个字节,才算搬运完成,从机是未知的。即使定义好SPI软件协议,也无法保证通讯的稳定性。例如,主机死了,从机不知道到,DMA接收就会有问题。所以在SPI从机的程序设计中,不建议使用DMA搬运数据。

4、SPI从机要先于主机初始化或者从机先准备好接收,才可以实现正确通讯。这个问题的解决办法,可以仿照RS485通讯的方法来确定接收是否结束,就是使用定时器协助判断SPI接收是否超时,从而实现主从同步。

 从机测试程序如下:

#include "SPI2.h"
#include "SPI_Std_Lib.h" //自定义USART标准库库
#include "stdio.h"  //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
#include "string.h" //使能strcpy(),strlen(),memset()
#include "LED.h"
#include "delay.h"

//SPI2外设用作从机,其接口:将SPI2_SCK映射到PB13,SPI2_MISO映射到PB14,SPI2_MOSI映射到PB15,SPI2_NSS映射到PB12

/*
STM32H474的SPI工作在从机模式,我们令SPI1工作在主机模式,SPI2工作在从机模式中,实现数据数据互传。
SPI1外设用作主机,其接口:将SPI1_SCK映射到PA5,SPI1_MISO映射到PA6,SPI1_MOSI映射到PA7,SPI1_NSS映射到PA4
SPI2外设用作从机,其接口:将SPI2_SCK映射到PB13,SPI2_MISO映射到PB14,SPI2_MOSI映射到PB15,SPI2_NSS映射到PB12
测试方法:
1)、将SPI1_SCK(PA5)和SPI2_SCK(PB13)连接在一起;
2)、将SPI1_MISO(PA6)和SPI2_MISO(PB14)连接在一起,实现SPI2发送,SPI1接收;
3)、将SPI1_MOSI(PA7)和SPI2_MOSI(PB15)连接在一起,实现SPI1发送,SPI2接收;
4)、将SPI1_NSS(PA4)和SPI2_NSS(PB12)连接在一起,实现SPI1选择SPI2外设
为了保证数据传输正确,需要采用SPI中断方式发送和接收数据,否则就要使用DMA传输数据,才能保证数据传输的正确性。

SPI主机发送数据格式:
高8位地址 + 低8位地址 + 写命令 + 数据长度 + 写数据内容
SPI1 Send: 10  02  06  02  A5  A5
SPI从机应答SPI主机写时发送数据格式:
高8位地址 + 低8位地址 + 写命令 + 数据长度 + 写数据内容
SPI2 Send: 10  02  06  02  A5  A5
SPI主机发送数据格式:
高8位地址 + 低8位地址 + 读命令 + 数据长度
SPI1 Send: 10  00  03  02
SPI主机发送len个字节的时钟格式:
len个0x00,实现读取从机应答数据
SPI1 Send: 00  00  00  00  00  00
SPI从机应答SPI主机读时发送数据格式:
高8位地址 + 低8位地址 + 读命令 + 数据长度 + 读数据内容
SPI2 Send: 10  00  03  02  5A  5A
*/

//STM32G474RE使用SPI2中断发送和接收8位数据,其作用:可以提高系统的整体效率和响应速度。

uint8_t SPI2_TX_Buffer[SPI2_TX_Buffer_Size]; //SPI2发送缓冲区数组
uint8_t SPI2_TX_Buffer_Send_Index;          //记录当前发送缓冲区的下标值
uint8_t SPI2_TX_Buffer_Load_Index;          //总共需要发送多少个字节
uint8_t SPI2_TX_Complete_Flag; //SPI2发送完成标志

uint8_t SPI2_RX_Buffer[SPI2_RX_Buffer_Size]; //SPI2接收缓冲区数组
uint8_t SPI2_RX_Buffer_Index;  //记录当前接收缓冲区的下标值
uint8_t SPI2_RX_Complete_Flag; //SPI2接收完成标志
uint8_t SPI2_RX_Time_Count;
//SPI2接收时间计数器;
//若SPI2接收到新数据,则令SPI2_RX_Time_Count=0;否则,该计数器加1
uint8_t SPI2_RX_Buffer_Print_Index;
uint8_t SPI2_RX_Complete_Print_Flag;

uint16_t SPI2_DATA1;
uint16_t SPI2_DATA2;

void SPI2_Init(void);
void TIM8_Init(void);
void Print_SPI2_Receive_Data(void);
void Print_SPI2_Send_Data(void);
void SPI2_RX_END(void);

void SPI2_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	SPI_HandleTypeDef hspi2;

	__HAL_RCC_SPI2_CLK_ENABLE();  //使能SPI2外设时钟
	__HAL_RCC_GPIOB_CLK_ENABLE(); //GPIOB时钟使能

	GPIO_InitStruct.Pin = GPIO_PIN_13;            //选择引脚编号为13
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
//	GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活
	GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;   //PB13映射到SPI2_SCK
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
	//配置“SPI2_SCK引脚”

	GPIO_InitStruct.Pin = GPIO_PIN_14;            //选择引脚编号为14
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
	GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;   //PB14映射到SPI2_MISO
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
	//配置“SPI2_MISO引脚”

	GPIO_InitStruct.Pin = GPIO_PIN_15;            //选择引脚编号为15
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
	GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;   //PB15映射到SPI2_MOSI
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
	//配置“SPI2_MOSI引脚”

	GPIO_InitStruct.Pin = GPIO_PIN_12;            //选择引脚编号为12
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
	GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI2;   //PB12映射到SPI2_NSS
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
	//配置“SPI2_NSS引脚”

  hspi2.Instance = SPI2;//选择SPI2
#if (SPI2_MASTER == 1U)
	hspi2.Init.Mode = SPI_MODE_MASTER;
  //SPIx_CR1寄存器bit2(MSTR),MSTR=1配置SPI外设为主机
#else
	hspi2.Init.Mode = SPI_MODE_SLAVE;
	//SPIx_CR1寄存器bit2(MSTR),MSTR=0配置SPI外设为从机
#endif
	hspi2.Init.Direction = SPI_DIRECTION_2LINES;
	//SPIx_CR1寄存器bit15(BIDIMODE),BIDIMODE=0选择“双线单向数据模式” 
	hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
	//SPIx_CR1寄存器bit0(CPHA)
	//CPHA=0,表示从SPI_SCK的空闲位开始,第1个边沿用来采集第1个位
	//CPHA=1,表示从SPI_SCK的空闲位开始,第2个边沿用来采集第1个位
	hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
	//SPIx_CR1寄存器bit1(CPOL)
	//CPOL=0,表示SPI_SCK的空闲位为低电平
	//CPOL=1,表示SPI_SCK的空闲位为高电平

#if (SPI2_MASTER == 1U)
//	hspi2.Init.NSS = SPI_NSS_SOFT;
  //配置spi在master下,NSS作为普通IO,由用户自己写代码控制片选,可以1主多从
	hspi2.Init.NSS = SPI_NSS_HARD_OUTPUT;
	//配置spi在master下,NSS作为SPI专用IO,由MCU自动控制片选,只能1主1从
	//SPIx_CR1寄存器bit9(SSM),SSM=1,NSS引脚的输入将被替换为来自SSI位的值
#else
	hspi2.Init.NSS = SPI_NSS_HARD_INPUT;
  //仅当配置spi在slave下,作为从机片选输入
#endif

	hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
	//SPIx_CR1寄存器bit5(BR[2:0])
	//BR[2:0]=000b,SCK的时钟频率为fPCLK/2
	//BR[2:0]=001b,SCK的时钟频率为fPCLK/4
	//BR[2:0]=010b,SCK的时钟频率为fPCLK/8
	//BR[2:0]=011b,SCK的时钟频率为fPCLK/16
	//BR[2:0]=100b,SCK的时钟频率为fPCLK/32
	//BR[2:0]=101b,SCK的时钟频率为fPCLK/64
	//BR[2:0]=110b,SCK的时钟频率为fPCLK/128
	//BR[2:0]=111b,SCK的时钟频率为fPCLK/256
	hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
	//SPIx_CR1寄存器bit7(LSBFIRST)
	//LSBFIRST=0,表示先传送最高位
	//LSBFIRST=1,表示先传送最低位

	hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
	//SPIx_CR1寄存器bit13(CRCEN),CRCEN=0不使能CRC校验
	hspi2.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
	//SPIx_CR1寄存器bit11(CRCL)
	//CRCL=0表示CRC的长度为8位
	//CRCL=1表示CRC的长度为16位
	hspi2.Init.CRCPolynomial = 7;
	//SPIx_CRCPR,这个寄存器包含了CRC计算的多项式
	//CRC多项式(0x0007)是该寄存器的默认值。可以根据需要,配置自己的“CRC多项式”。 

#if (SPI2_HalfWord == 0U)
	hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
	//SPIx_CR2寄存器bit11:8(DS[3:0])
	//DS[3:0]=0011b,表示SPI传输的一帧的数据长度为4位
	//DS[3:0]=0100b,表示SPI传输的一帧的数据长度为5位
	//DS[3:0]=0101b,表示SPI传输的一帧的数据长度为6位
	//DS[3:0]=0110b,表示SPI传输的一帧的数据长度为7位
	//DS[3:0]=0111b,表示SPI传输的一帧的数据长度为8位,这里读写8位数据
	//DS[3:0]=1000b,表示SPI传输的一帧的数据长度为9位
	//DS[3:0]=1001b,表示SPI传输的一帧的数据长度为10位
	//DS[3:0]=1010b,表示SPI传输的一帧的数据长度为11位
	//DS[3:0]=1011b,表示SPI传输的一帧的数据长度为12位
	//DS[3:0]=1100b,表示SPI传输的一帧的数据长度为13位
	//DS[3:0]=1101b,表示SPI传输的一帧的数据长度为14位
	//DS[3:0]=1110b,表示SPI传输的一帧的数据长度为15位
	//DS[3:0]=1111b,表示SPI传输的一帧的数据长度为16位
#else
  hspi2.Init.DataSize = SPI_DATASIZE_16BIT;//这里读写16位数据
#endif

	hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
	//SPIx_CR2寄存器bit4(FRF)
	//FRF=0,帧格式为SPI Motorola mode,这里使用“Motorola模式”
	//FRF=1,帧格式为SPI TI mode
#if (SPI2_MASTER == 1U)
	hspi2.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
	//SPIx_CR2寄存器bit3(NSSP)
	//NSSP=1,在主机模式中,NSS引脚输出使能
#else
	hspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
	//SPIx_CR2寄存器bit3(NSSP)
	//NSSP=0,在从机模式中,NSS引脚输出不使能
#endif

	HAL_SPI_Init(&hspi2);

	_HAL_SPI_ENABLE(SPI2);
	//SPIx_CR1寄存器bit6(SPE),SPE=1令SPI外设使能

  _HAL_SPI_ENABLE_IT(SPI2,SPI_IT_ERR);
  _HAL_SPI_DISABLE_IT(SPI2,SPI_IT_TXE);//不使能发送缓冲区为空而产生中断
	_HAL_SPI_DISABLE_IT(SPI2,SPI_IT_RXNE);//不使能接收中断
	HAL_NVIC_SetPriority(SPI2_IRQn, 6, 0);
	//设置NVIC中断分组4:4位抢占优先级,0位响应优先级
	//选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
	//这里设置SPI2的抢占优先级为6,响应优先级为0
	HAL_NVIC_EnableIRQ(SPI2_IRQn);

	SPI2_RX_Time_Count=0;
	SPI2_RX_Complete_Flag=0;
	SPI2_RX_Buffer_Index=0;//为下次接收做准备
	_HAL_SPI_ENABLE_IT(SPI2,SPI_IT_RXNE);//使能接收中断

  SPI2_TX_Complete_Flag=0;
	SPI2_TX_Buffer_Send_Index=0;
	_HAL_SPI_DISABLE_IT(SPI2,SPI_IT_TXE);//不使能发送缓冲区为空而产生中断

	TIM8_Init();
}



//函数功能:SPI在中断里发送和接收8位数据
void SPI2_IRQHandler(void)
{
//  HAL_SPI_IRQHandler(&hspi2);
  uint32_t itsource;
  uint32_t itflag;
	uint8_t RX_temp;

	(void)RX_temp;//防止RX_temp不使用而产生警告

	itsource = SPI2->CR2;//读SPIx_CR2寄存器
	itflag   = SPI2->SR; //读SPIx_SR寄存器
	if ( (SPI_CHECK_FLAG(itflag, SPI_FLAG_OVR) == RESET) \
		&& (SPI_CHECK_FLAG(itflag, SPI_FLAG_RXNE) != RESET) \
   	&& (SPI_CHECK_IT_SOURCE(itsource, SPI_IT_RXNE) != RESET) )
	{//SPI2接收中断
		RX_temp=*( uint8_t *)&SPI2->DR; //返回通过SPIx最近接收的数据,写读SPI2_DR
		SPI2_RX_Buffer[SPI2_RX_Buffer_Index]=RX_temp;
		SPI2_RX_Buffer_Index++;
		SPI2_RX_Time_Count = 1;//表示SPI2接收数据
		if(SPI2_RX_Buffer_Index>=SPI2_RX_Buffer_Size-1) SPI2_RX_Buffer_Index=0;
		//若接收缓冲区满,则将SPI2_RX_Buffer_Index设置为0;
	}

  if ( (SPI_CHECK_FLAG(itflag, SPI_FLAG_TXE) != RESET) \
		&& (SPI_CHECK_IT_SOURCE(itsource, SPI_IT_TXE) != RESET) )
  {//SPI2发送中断
		if(SPI2_TX_Buffer_Send_Index < SPI2_TX_Buffer_Load_Index) //未发送完全部数据
		{
	    *( uint8_t *)&SPI2->DR=SPI2_TX_Buffer[SPI2_TX_Buffer_Send_Index]; //通过外设SPIx发送一个数据,写SPI2_DR
	    //发送一个字节
			SPI2_TX_Buffer_Send_Index++;
		}
		else//发送完成
		{
			_HAL_SPI_DISABLE_IT(SPI2,SPI_IT_TXE);//不使能因发送缓冲区为空而产生中断
			SPI2_TX_Complete_Flag=1;

		  SPI2_RX_Time_Count=0;
		  SPI2_RX_Complete_Flag=0;
		  SPI2_RX_Buffer_Index=0;//为下次接收做准备
		  _HAL_SPI_ENABLE_IT(SPI2,SPI_IT_RXNE);//使能接收
		}
  }

  if ( ((SPI_CHECK_FLAG(itflag, SPI_FLAG_MODF) != RESET) \
		|| (SPI_CHECK_FLAG(itflag, SPI_FLAG_OVR) != RESET) \
	  || (SPI_CHECK_FLAG(itflag, SPI_FLAG_FRE) != RESET)) \
	  && (SPI_CHECK_IT_SOURCE(itsource, SPI_IT_ERR) != RESET) )
	{//SPI2错误中断
    if (SPI_CHECK_FLAG(itflag, SPI_FLAG_OVR) != RESET)
    {//SPI Overrun error interrupt occurred 
			_HAL_SPI_CLEAR_OVRFLAG(SPI2);//清除溢出错误
    }

    if (SPI_CHECK_FLAG(itflag, SPI_FLAG_MODF) != RESET)
    {//SPI Mode Fault error interrupt occurred
      _HAL_SPI_CLEAR_MODFFLAG(SPI2);
    }

    if (SPI_CHECK_FLAG(itflag, SPI_FLAG_FRE) != RESET)
    {//SPI Frame error interrupt occurred
      _HAL_SPI_CLEAR_FREFLAG(SPI2);
    }
	}
}

//函数功能:将两个字节合并为一个双字节
uint16_t HEX8_To_HEX16(uint8_t high,uint8_t low)
{
	uint16_t ret;

	ret=high;
	ret=(uint16_t)(ret<<8);
	ret=(uint16_t)(ret|(uint16_t)low);

	return ret;
}

//函数功能:将数据块起始地址为addr中的值通过SPI口发送给主机,数据长度为2
//SPI从机应答SPI主机读时发送数据格式:高8位地址 + 低8位地址 + 读命令 + 数据长度 + 读数据内容
//SPI2 Send: 10  00  03  02  5A  5A
void SPI2_ACK_Read_2_Byte(uint16_t addr,uint16_t d)
{
  _HAL_SPI_DISABLE_IT(SPI2,SPI_IT_TXE);//不使能发送缓冲区为空产生中断
	SPI2_TX_Buffer[0]=(uint8_t)(addr>>8);//高8位地址
	SPI2_TX_Buffer[1]=(uint8_t)(addr);   //低8位地址
	SPI2_TX_Buffer[2]=0x03;//从机回传主机的读命令
	SPI2_TX_Buffer[3]=2; //读2个字节
  SPI2_TX_Buffer[4]=(uint8_t)(d>>8);
	SPI2_TX_Buffer[5]=(uint8_t)d;
	SPI2_TX_Buffer_Load_Index = 6;   //准备发送len+4个字节
	SPI2_TX_Buffer_Send_Index=0;         //发送计数器清零

	_HAL_SPI_DISABLE_IT(SPI2,SPI_IT_RXNE);//不使能接收
启动发送/
	_HAL_SPI_ENABLE_IT(SPI2,SPI_IT_TXE);
	  //使能SPI在TXE标志建立时产生SPI发送中断
}

//函数功能:将数据块起始地址为addr中的值通过SPI口发送给主机,数据长度为2
//SPI从机应答SPI主机写时发送数据格式:高8位地址 + 低8位地址 + 写命令 + 数据长度 + 写数据内容
//SPI2 Send: 10  02  06  02  A5  A5
void SPI2_ACK_Write_2_Byte(uint16_t addr,uint16_t d)
{
	_HAL_SPI_DISABLE_IT(SPI2,SPI_IT_TXE);//不使能发送缓冲区为空产生中断
	SPI2_TX_Buffer[0]=(uint8_t)(addr>>8);//高8位地址
	SPI2_TX_Buffer[1]=(uint8_t)(addr);   //低8位地址
	SPI2_TX_Buffer[2]=0x06;//从机回传主机的写命令
	SPI2_TX_Buffer[3]=2; //写2个字节
	SPI2_TX_Buffer[4]=(uint8_t)(d>>8);
	SPI2_TX_Buffer[5]=(uint8_t)d;
	SPI2_TX_Buffer_Load_Index = 6;   //准备发送6个字节
	SPI2_TX_Buffer_Send_Index=0;     //发送计数器清零

	_HAL_SPI_DISABLE_IT(SPI2,SPI_IT_RXNE);//不使能接收
启动发送/
	_HAL_SPI_ENABLE_IT(SPI2,SPI_IT_TXE);
	  //使能SPI在TXE标志建立时产生SPI发送中断
}

void SPI2_RX_END(void)
{
	uint16_t addr;
	uint8_t  tmp;

	if(SPI2_RX_Complete_Flag==0 && SPI2_RX_Time_Count>0)
	{
		SPI2_RX_Time_Count++;
		if(SPI2_RX_Time_Count>2)
		{
			SPI2_RX_Complete_Flag=1;//建立接收完成标志
			SPI2_RX_Time_Count = 0;
		}
	}

	if(SPI2_RX_Complete_Flag)
	{
    SPI2_RX_Buffer_Print_Index=SPI2_RX_Buffer_Index;//记录打印的字节数
    SPI2_RX_Complete_Print_Flag=1;//记录打印标志
		SPI2_RX_Time_Count=0;
		SPI2_RX_Complete_Flag=0;
		SPI2_RX_Buffer_Index=0;//为下次接收做准备

		addr=HEX8_To_HEX16(SPI2_RX_Buffer[0],SPI2_RX_Buffer[1]);
		if(SPI2_RX_Buffer[2]==0x03)//读命令
		{
			if( addr==SPI2_ADDRESS1 && SPI2_RX_Buffer[3]==2) SPI2_ACK_Read_2_Byte(SPI2_ADDRESS1,SPI2_DATA1);//读16位
			if( addr==SPI2_ADDRESS2 && SPI2_RX_Buffer[3]==2) SPI2_ACK_Read_2_Byte(SPI2_ADDRESS2,SPI2_DATA2);
		}

		if(SPI2_RX_Buffer[2]==0x06)//写命令
		{
			if( addr==SPI2_ADDRESS1 && SPI2_RX_Buffer[3]==2)
			{
				SPI2_DATA1=HEX8_To_HEX16(SPI2_RX_Buffer[4],SPI2_RX_Buffer[5]);
				SPI2_ACK_Write_2_Byte(SPI2_ADDRESS1,SPI2_DATA1);//发送“写应答1”
			}

			if( addr==SPI2_ADDRESS2 && SPI2_RX_Buffer[3]==2)
			{
				SPI2_DATA2=HEX8_To_HEX16(SPI2_RX_Buffer[4],SPI2_RX_Buffer[5]);
				SPI2_ACK_Write_2_Byte(SPI2_ADDRESS2,SPI2_DATA2);//发送“写应答1”
			}
		}
	}
}

//使用TIM8中断,判断SPI2接收是否完成///
void TIM8_Init(void)
{
	TIM_HandleTypeDef htim8;  //TIM8句柄
	RCC_ClkInitTypeDef    clkconfig;
	uint32_t              uwTimclock8 = 0;
	uint32_t              pFLatency;
	uint32_t              uwPrescalerValue8 = 0;
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};

  __HAL_RCC_TIM8_CLK_ENABLE();//使能“定时器8”的时钟,Enable TIM8 clock
  HAL_RCC_GetClockConfig(&clkconfig, &pFLatency);//Get clock configuration
  uwTimclock8 = HAL_RCC_GetPCLK2Freq();
	//读取PCLK2的时钟频率,Return the PCLK2 frequency
	//若PCLK2的分频器值为1,则和SystemCoreClock的值相等
	uwPrescalerValue8 = (uint32_t) ((uwTimclock8 / 1000000U) - 1U);
	//uwPrescalerValue8=170

  htim8.Instance = TIM8;
  htim8.Init.Period = (1000000U / 1000U) - 1U;
	//定时器周期999
  htim8.Init.Prescaler = uwPrescalerValue8;
	//设置TIM8预分频器为uwPrescalerValue8
  htim8.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
	//设置时钟分频系数,TIM8_CR1中的CKD[9:8]=00b,tDTS=ttim_ker_ck;
	//溢出时间为(999+1)*1*170/170000000/1=1毫秒
  htim8.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim8.Init.RepetitionCounter = 0;//重复计数(1-0),产生一次中断
  htim8.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;//TIM_AUTORELOAD_PRELOAD_DISABLE;
  HAL_TIM_Base_Init(&htim8);

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
	//TIM8_TRGO是adc_ext_trg9,用来触发ADC1/2/3/4/5
//  sMasterConfig.MasterOutputTrigger2 = TIM_TRGO2_RESET;
	//TIM8_TRGO2是adc_ext_trg10,用来触发ADC1/2/3/4/5
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  HAL_TIMEx_MasterConfigSynchronization(&htim8, &sMasterConfig);
	//Configures the TIM in master mode.

   HAL_TIM_Base_Start_IT(&htim8);
   HAL_NVIC_EnableIRQ(TIM8_UP_IRQn);//使能TIM8产生中断
   HAL_NVIC_SetPriority(TIM8_UP_IRQn, 2, 0U);
	 //设置NVIC中断分组4:4位抢占优先级,0位响应优先级
	 //选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
	//这里设置TIM8中断优先级为2
}

//TIM8更新中断,每1ms中断一次
//为了节省内存,该中断处理程序使用“寄存器”处理,该死的HAL库,就是这么屌;
void TIM8_UP_IRQHandler(void)
{
	if( (TIM8->SR & TIM_FLAG_UPDATE) == TIM_FLAG_UPDATE)
  {//读取TIM8状态寄存器TIMx_SR的bit0(UIF),UIF=1表示产生了“TIM8更新事件”
		if( (TIM8->DIER & TIM_IT_UPDATE)  == TIM_IT_UPDATE )
    {//读取TIM8中断使能寄存器TIMx_DIER的bot0(UIE),查看UIE=1?
      TIM8->SR = ~(TIM_IT_UPDATE);
			SPI2_RX_END();
      //若SPI2接收到数据,则令SPI2_RX_Time_Count=1;
      //若SPI2没有接收到新数据,则SPI2_RX_Time_Count计数器会加1
    }
  }
}

void Print_SPI2_Send_Data(void)
{
	uint8_t i,temp;

	if(SPI2_TX_Complete_Flag)
	{
	  printf("SPI2 Send:");    //将"SPI2 Send:"发送到调试串口,由PC显示;
	  for(i=0;i<SPI2_TX_Buffer_Load_Index;i++)
	  {
		  temp=0;
		  if( ( (SPI2_TX_Buffer[i]==0x0D)||(SPI2_TX_Buffer[i]==0x0A) ) )
		  {
			  printf("%c",SPI2_TX_Buffer[i]);
			  temp=1;
      }
		  if(temp==0)
		  {
//			  if( ( (SPI2_TX_Buffer[i]>=' ')&&(SPI2_TX_Buffer[i]<='~') ) ) printf("%c",SPI2_TX_Buffer[i]);
//			  else
//			  {
				  printf(" ");
				  printf("%02X",SPI2_TX_Buffer[i]);
				  printf(" ");
//        }
      }
    }
	  printf("\r\n");//将"\r\n"发送到调试串口,由PC显示;
		SPI2_TX_Complete_Flag=0;
  }
}

void Print_SPI2_Receive_Data(void)
{
	uint8_t i,temp;

	if(SPI2_RX_Complete_Print_Flag)
	{
	  printf("SPI2 Rece:");    //将"\r\nSPI2 Receive:"发送到调试串口,由PC显示;
	  for(i=0;i<SPI2_RX_Buffer_Print_Index;i++)
	  {
		  temp=0;
		  if( ( (SPI2_RX_Buffer[i]==0x0D)||(SPI2_RX_Buffer[i]==0x0A) ) )
		  {
			  printf("%c",SPI2_RX_Buffer[i]);
			  temp=1;
      }
		  if(temp==0)
		  {
//			  if( ( (SPI2_RX_Buffer[i]>=' ')&&(SPI2_RX_Buffer[i]<='~') ) ) printf("%c",SPI2_RX_Buffer[i]);
//			  else
//			  {
				  printf(" ");
				  printf("%02X",SPI2_RX_Buffer[i]);
				  printf(" ");
//        }
      }
    }
	  printf("\r\n");//将"\r\n"发送到调试串口,由PC显示;
		SPI2_RX_Complete_Print_Flag=0;SPI2_RX_Buffer_Print_Index=0;
  }
}

SPI2.h程序如下:

#ifndef __SPI2_H__
#define __SPI2_H__

#include "stm32g4xx_hal.h"
//使能int8_t,int16_t,int32_t,int64_t
//使能uint8_t,uint16_t,uint32_t,uint64_t
#include "stm32g4xx_hal_spi.h"

#define SPI2_MASTER  0    //将SPI2设置为从机
//#define SPI2_MASTER  1  //将SPI2设置为主机
#define SPI2_HalfWord  0  //SPI使用8位数据收发

#define SPI2_TX_Buffer_Size 		      125
extern uint8_t SPI2_TX_Buffer[SPI2_TX_Buffer_Size]; //SPI2发送缓冲区数组
extern uint8_t SPI2_TX_Buffer_Send_Index;          //记录当前发送缓冲区的下标值
extern uint8_t SPI2_TX_Buffer_Load_Index;          //总共需要发送多少个字节
extern uint8_t SPI2_TX_Complete_Flag; //SPI2发送完成标志

#define SPI2_RX_Buffer_Size  125
extern uint8_t SPI2_RX_Buffer[SPI2_RX_Buffer_Size]; //SPI2接收缓冲区数组
extern uint8_t SPI2_RX_Buffer_Index;           //记录当前接收缓冲区的下标值
extern uint8_t SPI2_RX_Complete_Flag; //SPI2接收完成标志
extern uint8_t SPI2_RX_Time_Count;
//SPI2接收时间计数器;
//若SPI2接收到新数据,则令SPI2_RX_Time_Count=0;否则,该计数器加1

#define SPI2_ADDRESS1  0x1000  //存储区1
#define SPI2_ADDRESS2  0x1002  //存储区2

extern void SPI2_Init(void);
extern void Print_SPI2_Receive_Data(void);
extern void Print_SPI2_Send_Data(void);
extern void SPI2_RX_END(void);
#endif /*__ SPI2_H__ */

SPI的主机程序

#include "SPI1.h"
#include "SPI_Std_Lib.h" //自定义USART标准库库
#include "stdio.h"  //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
#include "string.h" //使能strcpy(),strlen(),memset()
#include "LED.h"
#include "delay.h"

//SPI1外设用作主机,其接口:将SPI1_SCK映射到PA5,SPI1_MISO映射到PA6,SPI1_MOSI映射到PA7,SPI1_NSS映射到PA4
//STM32G474RE使用SPI1中断发送和接收8位数据,其作用:可以提高系统的整体效率和响应速度。

/*
1、SPI从机接收的字节数量
对于SPI主机来说,发送多少个字节,它就能接收多少个字节,不管从机是否应答,所接收的字节数量是可以事先知道的。
对于SPI从机来说,主机准备发送多少个字节,从机是无法知道的。

2、SPI接收解决办法
1)、从机接收:每接收到一个字节,都要进入中断服务程序,保存数据,然后设置“超时退出接收”。
当接收超时后,从机才能分析接收到的指令,准备应答数据。
2)、主机接收:由于主机事先知道需要接收多少个字节,因此,可以根据“接收到的字节数量”是否满足条件,判断接收是否结束。

3、SPI数据格式
起始地址 + 命令 + 数据长度 + 数据内容
命令:0x03表示读,0x06表示写;
数据长度:0x01表示要传输两个字节;

主机发送:0x10 0x00 0x03  0x01
从机发送:无
主机接收:数据无效
从机接收:数据有效
从机准好发送数据,关闭接收,处于发送状态;
主机延时1ms,然后发送0xFFFF读取两个字节;

主机发送:0xFFFF
从机发送:0x10FF
*/

uint8_t SPI1_TX_Buffer[SPI1_TX_Buffer_Size]; //SPI1发送缓冲区数组
uint8_t SPI1_TX_Buffer_Send_Index;          //记录当前发送缓冲区的下标值
uint8_t SPI1_TX_Buffer_Load_Index;          //总共需要发送多少个字节
uint8_t SPI1_TX_Complete_Flag; //SPI1发送完成标志

uint8_t SPI1_RX_Buffer[SPI1_RX_Buffer_Size]; //SPI1接收缓冲区数组
uint8_t SPI1_RX_Buffer_Index;  //记录当前接收缓冲区的下标值
uint8_t SPI1_RX_Buffer_Load_Index;  //总共需要接收多少个字节
uint8_t SPI1_RX_Complete_Flag; //SPI1接收完成标志

void SPI1_Init(void);
void SPI1_Write_2_Byte(uint16_t addr,uint16_t d);
void SPI1_SendReadCMD(uint16_t addr,uint8_t len);
void SPI1_ReadData(uint8_t len);
void Print_SPI1_Receive_Data(void);
void Print_SPI1_Send_Data(void);
	
void SPI1_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	SPI_HandleTypeDef hspi1;

	__HAL_RCC_SPI1_CLK_ENABLE();  //使能SPI1外设时钟
	__HAL_RCC_GPIOA_CLK_ENABLE(); //GPIOA时钟使能

	GPIO_InitStruct.Pin = GPIO_PIN_5;            //选择引脚编号为5
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
//	GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活
	GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PA5映射到SPI1_SCK
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	//根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器
	//配置“SPI1_SCK引脚”

	GPIO_InitStruct.Pin = GPIO_PIN_6;            //选择引脚编号为6
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
	GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PA6映射到SPI1_MISO
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	//根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器
	//配置“SPI1_MISO引脚”

	GPIO_InitStruct.Pin = GPIO_PIN_7;            //选择引脚编号为7
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
	GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PA7映射到SPI1_MOSI
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	//根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器
	//配置“SPI1_MOSI引脚”

	GPIO_InitStruct.Pin = GPIO_PIN_4;            //选择引脚编号为4
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
	GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHz
	GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PA4映射到SPI1_NSS
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
	//根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器
	//配置“SPI1_NSS引脚”

  hspi1.Instance = SPI1;//选择SPI1
#if (SPI1_MASTER == 1U)
	hspi1.Init.Mode = SPI_MODE_MASTER;
  //SPIx_CR1寄存器bit2(MSTR),MSTR=1配置SPI外设为主机
#else
	hspi1.Init.Mode = SPI_MODE_SLAVE;
	//SPIx_CR1寄存器bit2(MSTR),MSTR=0配置SPI外设为从机
#endif
	hspi1.Init.Direction = SPI_DIRECTION_2LINES;
	//SPIx_CR1寄存器bit15(BIDIMODE),BIDIMODE=0选择“双线单向数据模式” 
	hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
	//SPIx_CR1寄存器bit0(CPHA)
	//CPHA=0,表示从SPI_SCK的空闲位开始,第1个边沿用来采集第1个位
	//CPHA=1,表示从SPI_SCK的空闲位开始,第2个边沿用来采集第1个位
	hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
	//SPIx_CR1寄存器bit1(CPOL)
	//CPOL=0,表示SPI_SCK的空闲位为低电平
	//CPOL=1,表示SPI_SCK的空闲位为高电平

#if (SPI1_MASTER == 1U)
//	hspi1.Init.NSS = SPI_NSS_SOFT;
  //配置spi在master下,NSS作为普通IO,由用户自己写代码控制片选,可以1主多从
	hspi1.Init.NSS = SPI_NSS_HARD_OUTPUT;
	//配置spi在master下,NSS作为SPI专用IO,由MCU自动控制片选,只能1主1从
	//SPIx_CR1寄存器bit9(SSM),SSM=1,NSS引脚的输入将被替换为来自SSI位的值
#else
	hspi1.Init.NSS = SPI_NSS_HARD_INPUT;
  //仅当配置spi在slave下,作为从机片选输入
#endif

	hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;
	//SPIx_CR1寄存器bit5(BR[2:0])
	//BR[2:0]=000b,SCK的时钟频率为fPCLK/2
	//BR[2:0]=001b,SCK的时钟频率为fPCLK/4
	//BR[2:0]=010b,SCK的时钟频率为fPCLK/8
	//BR[2:0]=011b,SCK的时钟频率为fPCLK/16
	//BR[2:0]=100b,SCK的时钟频率为fPCLK/32
	//BR[2:0]=101b,SCK的时钟频率为fPCLK/64
	//BR[2:0]=110b,SCK的时钟频率为fPCLK/128
	//BR[2:0]=111b,SCK的时钟频率为fPCLK/256
	hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
	//SPIx_CR1寄存器bit7(LSBFIRST)
	//LSBFIRST=0,表示先传送最高位
	//LSBFIRST=1,表示先传送最低位

	hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
	//SPIx_CR1寄存器bit13(CRCEN),CRCEN=0不使能CRC校验
	hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
	//SPIx_CR1寄存器bit11(CRCL)
	//CRCL=0表示CRC的长度为8位
	//CRCL=1表示CRC的长度为16位
	hspi1.Init.CRCPolynomial = 7;
	//SPIx_CRCPR,这个寄存器包含了CRC计算的多项式
	//CRC多项式(0x0007)是该寄存器的默认值。可以根据需要,配置自己的“CRC多项式”。 

#if (SPI1_HalfWord == 0U)
	hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
	//SPIx_CR2寄存器bit11:8(DS[3:0])
	//DS[3:0]=0011b,表示SPI传输的一帧的数据长度为4位
	//DS[3:0]=0100b,表示SPI传输的一帧的数据长度为5位
	//DS[3:0]=0101b,表示SPI传输的一帧的数据长度为6位
	//DS[3:0]=0110b,表示SPI传输的一帧的数据长度为7位
	//DS[3:0]=0111b,表示SPI传输的一帧的数据长度为8位,这里读写8位数据
	//DS[3:0]=1000b,表示SPI传输的一帧的数据长度为9位
	//DS[3:0]=1001b,表示SPI传输的一帧的数据长度为10位
	//DS[3:0]=1010b,表示SPI传输的一帧的数据长度为11位
	//DS[3:0]=1011b,表示SPI传输的一帧的数据长度为12位
	//DS[3:0]=1100b,表示SPI传输的一帧的数据长度为13位
	//DS[3:0]=1101b,表示SPI传输的一帧的数据长度为14位
	//DS[3:0]=1110b,表示SPI传输的一帧的数据长度为15位
	//DS[3:0]=1111b,表示SPI传输的一帧的数据长度为16位
#else
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;//这里读写16位数据
#endif

	hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
	//SPIx_CR2寄存器bit4(FRF)
	//FRF=0,帧格式为SPI Motorola mode,这里使用“Motorola模式”
	//FRF=1,帧格式为SPI TI mode
#if (SPI1_MASTER == 1U)
	hspi1.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
	//SPIx_CR2寄存器bit3(NSSP)
	//NSSP=1,在主机模式中,NSS引脚输出使能
#else
	hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
	//SPIx_CR2寄存器bit3(NSSP)
	//NSSP=0,在从机模式中,NSS引脚输出不使能
#endif

	HAL_SPI_Init(&hspi1);

	_HAL_SPI_ENABLE(SPI1);
	//SPIx_CR1寄存器bit6(SPE),SPE=1令SPI外设使能

  _HAL_SPI_ENABLE_IT(SPI1,SPI_IT_ERR);
  _HAL_SPI_DISABLE_IT(SPI1,SPI_IT_TXE);
	_HAL_SPI_DISABLE_IT(SPI1,SPI_IT_RXNE);
	HAL_NVIC_SetPriority(SPI1_IRQn, 5, 0);
	//设置NVIC中断分组4:4位抢占优先级,0位响应优先级
	//选择中断优先级组4,即抢占优先级为4位,取值为0~15,响应优先级组为0位,取值为0
	//这里设置SPI1的抢占优先级为5,响应优先级为0
	HAL_NVIC_EnableIRQ(SPI1_IRQn);
}

//函数功能:将d的值通过SPI口发送给从机,保存数据块起始地址为addr,数据长度为2
//SPI发送数据格式:高8位地址 + 低8位地址 + 写命令 + 数据长度 + 写数据内容
//SPI1 Send: 10  02  06  02  A5  A5
void SPI1_Write_2_Byte(uint16_t addr,uint16_t d)
{
	_HAL_SPI_DISABLE_IT(SPI1,SPI_IT_TXE);//不使能发送缓冲区为空产生中断
	delay_ms(1);//等待发送完成
	SPI1_TX_Buffer[0]=(uint8_t)(addr>>8);//高8位地址
	SPI1_TX_Buffer[1]=(uint8_t)(addr);   //低8位地址
	SPI1_TX_Buffer[2]=0x06;//写命令
	SPI1_TX_Buffer[3]=2; //写2个字节
	SPI1_TX_Buffer[4]=(uint8_t)(d>>8);
  SPI1_TX_Buffer[5]=(uint8_t)d;

	SPI1_TX_Buffer_Load_Index = 6;   //准备发送6个字节
	SPI1_TX_Buffer_Send_Index=0;     //发送计数器清零
	SPI1_TX_Complete_Flag=0;

	_HAL_SPI_ENABLE_IT(SPI1,SPI_IT_RXNE);//不使能接收
	SPI1_RX_Buffer_Load_Index=0;//准备接收0个字节
	SPI1_RX_Buffer_Index=0;//接收计数器清零
	SPI1_RX_Complete_Flag=0;

启动发送/
	_HAL_SPI_ENABLE_IT(SPI1,SPI_IT_TXE);
	//使能SPI在TXE标志建立时产生SPI发送中断
}

//函数功能:发送读命令
//SPI发送数据格式:高8位地址 + 低8位地址 + 读命令 + 数据长度
//SPI1 Send: 10  00  03  02
void SPI1_SendReadCMD(uint16_t addr,uint8_t len)
{
	_HAL_SPI_ENABLE_IT(SPI1,SPI_IT_TXE);//不使能发送缓冲区为空产生中断
	delay_ms(1);//等待发送完成
	SPI1_TX_Buffer[0]=(uint8_t)(addr>>8);//高8位地址
	SPI1_TX_Buffer[1]=(uint8_t)(addr);   //低8位地址
	SPI1_TX_Buffer[2]=0x03;//读命令
	SPI1_TX_Buffer[3]=len; //读len个字节
	SPI1_TX_Buffer_Load_Index = 4;   //准备发送4个字节
	SPI1_TX_Buffer_Send_Index=0;     //发送计数器清零

	_HAL_SPI_ENABLE_IT(SPI1,SPI_IT_RXNE);//不使能接收
	SPI1_RX_Buffer_Load_Index=0;//准备接收0个字节
	SPI1_RX_Buffer_Index=0;//接收计数器清零
	SPI1_RX_Complete_Flag=0;
启动发送/
	_HAL_SPI_ENABLE_IT(SPI1,SPI_IT_TXE);
	//使能SPI在TXE标志建立时产生SPI发送中断
}

//函数功能:SPI1发送len个字节的时钟,使能“从机发送len个字节数据”
//SPI发送len个字节的时钟格式:len个0x00,实现读取从机应答数据
//SPI1 Send: 00  00  00  00  00  00
void SPI1_ReadData(uint8_t len)
{
	uint8_t i;

	for(i=0;i<len;i++) SPI1_TX_Buffer[i]=0x00;
	SPI1_TX_Buffer_Load_Index = len; //准备发送len个字节
	SPI1_TX_Buffer_Send_Index=0;     //发送计数器清零

	SPI1_RX_Buffer_Load_Index=len;//准备接收len个字节
	SPI1_RX_Buffer_Index=0;//接收计数器清零
	SPI1_RX_Complete_Flag=0;
启动发送/
	_HAL_SPI_ENABLE_IT(SPI1,SPI_IT_TXE|SPI_IT_RXNE);
	//使能SPI在TXE标志建立时产生SPI发送中断,并使能接收
}

void Print_SPI1_Send_Data(void)
{
	uint8_t i,temp;

	if(SPI1_TX_Complete_Flag)
	{
	  printf("SPI1 Send:");    //将"SPI1 Send:"发送到调试串口,由PC显示;
	  for(i=0;i<SPI1_TX_Buffer_Load_Index;i++)
	  {
		  temp=0;
		  if( ( (SPI1_TX_Buffer[i]==0x0D)||(SPI1_TX_Buffer[i]==0x0A) ) )
		  {
			  printf("%c",SPI1_TX_Buffer[i]);
			  temp=1;
      }
		  if(temp==0)
		  {
//			  if( ( (SPI1_TX_Buffer[i]>=' ')&&(SPI1_TX_Buffer[i]<='~') ) ) printf("%c",SPI1_TX_Buffer[i]);
//			  else
//			  {
				  printf(" ");
				  printf("%02X",SPI1_TX_Buffer[i]);
				  printf(" ");
//        }
      }
    }
	  printf("\r\n");//将"\r\n"发送到调试串口,由PC显示;
		SPI1_TX_Complete_Flag=0;
  }
}

void Print_SPI1_Receive_Data(void)
{
	uint8_t i,temp;

	if(SPI1_RX_Complete_Flag)
	{
		printf("SPI1 Rece:");    //将"SPI1 Receive:"发送到调试串口,由PC显示;
	  for(i=0;i<SPI1_RX_Buffer_Index;i++)
	  {
		  temp=0;
		  if( ( (SPI1_RX_Buffer[i]==0x0D)||(SPI1_RX_Buffer[i]==0x0A) ) )
		  {
			  printf("%c",SPI1_RX_Buffer[i]);
			  temp=1;
      }
		  if(temp==0)
		  {
//			  if( ( (SPI1_RX_Buffer[i]>=' ')&&(SPI1_RX_Buffer[i]<='~') ) ) printf("%c",SPI1_RX_Buffer[i]);
//			  else
//			  {
				  printf(" ");
				  printf("%02X",SPI1_RX_Buffer[i]);
				  printf(" ");
//        }
      }
    }
		printf("\r\n");//将"\r\n"发送到调试串口,由PC显示;

		SPI1_RX_Complete_Flag=0;SPI1_RX_Buffer_Index=0;
  }
}

//函数功能:SPI在中断里发送和接收8位数据
void SPI1_IRQHandler(void)
{
//  HAL_SPI_IRQHandler(&hspi1);
  uint32_t itsource;
  uint32_t itflag;
	uint8_t RX_temp;

	(void)RX_temp;//防止RX_temp不使用而产生警告

	itsource = SPI1->CR2;//读SPIx_CR2寄存器
	itflag   = SPI1->SR; //读SPIx_SR寄存器
	if ( (SPI_CHECK_FLAG(itflag, SPI_FLAG_OVR) == RESET) \
		&& (SPI_CHECK_FLAG(itflag, SPI_FLAG_RXNE) != RESET) \
   	&& (SPI_CHECK_IT_SOURCE(itsource, SPI_IT_RXNE) != RESET) )
//	if ( SPI_CHECK_FLAG(itflag, SPI_FLAG_RXNE) != RESET )
	{//SPI1接收中断
		RX_temp=*( uint8_t *)&SPI1->DR; //返回通过SPIx最近接收的数据,写读SPI1_DR
		SPI1_RX_Buffer[SPI1_RX_Buffer_Index]=RX_temp;
		SPI1_RX_Buffer_Index++;
		if(SPI1_RX_Buffer_Index>=SPI1_RX_Buffer_Size-1) SPI1_RX_Buffer_Index=0;
		//若接收缓冲区满,则将SPI1_RX_Buffer_Index设置为0;
		if(SPI1_RX_Buffer_Index >= SPI1_RX_Buffer_Load_Index)
		{
			SPI1_RX_Complete_Flag=1;//接收完成
			_HAL_SPI_ENABLE_IT(SPI1,SPI_IT_RXNE);
		}
	}

  if ( (SPI_CHECK_FLAG(itflag, SPI_FLAG_TXE) != RESET) \
		&& (SPI_CHECK_IT_SOURCE(itsource, SPI_IT_TXE) != RESET) )
//  if ( SPI_CHECK_FLAG(itflag, SPI_FLAG_TXE) != RESET)
  {//SPI1发送中断
		if(SPI1_TX_Buffer_Send_Index < SPI1_TX_Buffer_Load_Index) //未发送完全部数据
		{
	    *( uint8_t *)&SPI1->DR=SPI1_TX_Buffer[SPI1_TX_Buffer_Send_Index]; //通过外设SPIx发送一个数据,写SPI1_DR
	    //发送一个字节
			SPI1_TX_Buffer_Send_Index++;
		}
		else
		{
			_HAL_SPI_DISABLE_IT(SPI1,SPI_IT_TXE);//不允许发送完成中断
			SPI1_TX_Complete_Flag=1;//发送完成
		}
  }

  if ( ((SPI_CHECK_FLAG(itflag, SPI_FLAG_MODF) != RESET) \
		|| (SPI_CHECK_FLAG(itflag, SPI_FLAG_OVR) != RESET) \
	  || (SPI_CHECK_FLAG(itflag, SPI_FLAG_FRE) != RESET)) \
	  && (SPI_CHECK_IT_SOURCE(itsource, SPI_IT_ERR) != RESET) )
	{//SPI1错误中断
    if (SPI_CHECK_FLAG(itflag, SPI_FLAG_OVR) != RESET)
    {//SPI Overrun error interrupt occurred 
			_HAL_SPI_CLEAR_OVRFLAG(SPI1);//清除溢出错误
    }

    if (SPI_CHECK_FLAG(itflag, SPI_FLAG_MODF) != RESET)
    {//SPI Mode Fault error interrupt occurred
      _HAL_SPI_CLEAR_MODFFLAG(SPI1);
    }

    if (SPI_CHECK_FLAG(itflag, SPI_FLAG_FRE) != RESET)
    {//SPI Frame error interrupt occurred
      _HAL_SPI_CLEAR_FREFLAG(SPI1);
    }
	}
}

SPI1.h程序

#ifndef __SPI1_H__
#define __SPI1_H__

#include "stm32g4xx_hal.h"
//使能int8_t,int16_t,int32_t,int64_t
//使能uint8_t,uint16_t,uint32_t,uint64_t
#include "stm32g4xx_hal_spi.h"

//#define SPI1_MASTER  0  //将SPI2设置为从机
#define SPI1_MASTER  1  //将SPI2设置为主机

#define SPI1_HalfWord  0  //SPI使用8位数据收发

#define SPI1_TX_Buffer_Size 		      125
extern uint8_t SPI1_TX_Buffer[SPI1_TX_Buffer_Size]; //SPI1发送缓冲区数组
extern uint8_t SPI1_TX_Buffer_Send_Index;          //记录当前发送缓冲区的下标值
extern uint8_t SPI1_TX_Buffer_Load_Index;          //总共需要发送多少个字节
extern uint8_t SPI1_TX_Complete_Flag; //SPI1发送完成标志

#define SPI1_RX_Buffer_Size  125
extern uint8_t SPI1_RX_Buffer[SPI1_RX_Buffer_Size]; //SPI1接收缓冲区数组
extern uint8_t SPI1_RX_Buffer_Index;           //记录当前接收缓冲区的下标值
extern uint8_t SPI1_RX_Buffer_Load_Index;           //总共需要接收多少个字节
extern uint8_t SPI1_RX_Complete_Flag; //SPI1接收完成标志

extern void SPI1_Init(void);
extern void SPI1_Write_2_Byte(uint16_t addr,uint16_t d);
extern void SPI1_SendReadCMD(uint16_t addr,uint8_t len);
extern void SPI1_ReadData(uint8_t len);
extern void Print_SPI1_Receive_Data(void);
extern void Print_SPI1_Send_Data(void);

#endif /*__ SPI1_H__ */

 main.c程序
 

#include "main.h"
//#include "cmsis_os.h"
#include "stdio.h"  //getchar(),putchar(),scanf(),printf(),puts(),gets(),sprintf()
#include "sys.h"
#include "Clock_Config.h"
#include "SPI1.h"
#include "SPI2.h"
#include "LED.h"
#include "delay.h"
#include "USART1.h"

const char CPU_Reset_REG[]="\r\nCPU reset!\r\n";
int main(void)
{
	STACK_Init();

  HAL_Init();
	//复位所有的外设
	//初始化FLASH接口
	//将SysTick定时器配置1ms中断
	//因为“HRTIM定时器A”的中断优先级为0,所以需要在“stm32g4xx_hal_conf.h”,将TICK_INT_PRIORITY定义为1,设置SysTick中断优先级为1

  SystemClock_Config();
	//Configure the system clock
	HAL_Delay(500);
  USART1_Init(115200);
	printf("%s",CPU_Reset_REG);
	Print_HCLK_PCLK1_PCLK2();

	delay_init();
	delay_ms(1000);

  LED_Init();//配置PC13为输出,无上拉或下拉,输出速度为5MHz
	delay_ms(500);

  
	SPI2_Init();
  SPI1_Init();
  while (1)
  {
		delay_ms(1000);
		LED1_Toggle();

//
    printf("\r\n");
		SPI1_Write_2_Byte(SPI2_ADDRESS1,0x5A5A);
		delay_ms(10);
		Print_SPI1_Send_Data();
		Print_SPI2_Receive_Data();

		SPI1_ReadData(6);//SPI1发送6个字节的时钟,使能“从机发送6个字节数据”
		delay_ms(10);
		Print_SPI1_Send_Data();
		Print_SPI2_Receive_Data();
		Print_SPI2_Send_Data();
		Print_SPI1_Receive_Data();

		SPI1_SendReadCMD(SPI2_ADDRESS1,2);
		delay_ms(10);
		Print_SPI1_Send_Data();
		Print_SPI2_Receive_Data();

		SPI1_ReadData(6);//SPI1发送6个字节的时钟,使能“从机发送6个字节数据”
		delay_ms(10);
		Print_SPI1_Send_Data();
		Print_SPI2_Receive_Data();
		Print_SPI2_Send_Data();
		Print_SPI1_Receive_Data();

//
    printf("\r\n");
		SPI1_Write_2_Byte(SPI2_ADDRESS2,0xA5A5);
		delay_ms(10);
		Print_SPI1_Send_Data();
		Print_SPI2_Receive_Data();

		SPI1_ReadData(6);//SPI1发送6个字节的时钟,使能“从机发送6个字节数据”
		delay_ms(10);
		Print_SPI1_Send_Data();
		Print_SPI2_Receive_Data();
		Print_SPI2_Send_Data();
		Print_SPI1_Receive_Data();

		SPI1_SendReadCMD(SPI2_ADDRESS2,2);
		delay_ms(10);
		Print_SPI1_Send_Data();
		Print_SPI2_Receive_Data();

		SPI1_ReadData(6);//SPI1发送6个字节的时钟,使能“从机发送6个字节数据”
		delay_ms(10);
		Print_SPI1_Send_Data();
		Print_SPI2_Receive_Data();
		Print_SPI2_Send_Data();
		Print_SPI1_Receive_Data();
  }
}

//函数功能:在发生错误时,将执行此函数。
void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
		printf("Error\r\n");
  }
}

 SPI_Std_Lib.h程序

#ifndef __SPI_Std_Lib_H__
#define __SPI_Std_Lib_H__

#include "stm32g4xx_hal.h"
//使能int8_t,int16_t,int32_t,int64_t
//使能uint8_t,uint16_t,uint32_t,uint64_t
#include "stm32g4xx_hal_spi.h"

/** @brief  Check whether the specified SPI flag is set or not.
  * @param  __SPIx__ specifies the SPI peripheral.
  *         This parameter can be SPI where x: 1, 2, or 3 to select the SPI peripheral.
  * @param  __FLAG__ specifies the flag to check.
  *         This parameter can be one of the following values:
  *            @arg SPI_FLAG_RXNE: Receive buffer not empty flag
  *            @arg SPI_FLAG_TXE: Transmit buffer empty flag
  *            @arg SPI_FLAG_CRCERR: CRC error flag
  *            @arg SPI_FLAG_MODF: Mode fault flag
  *            @arg SPI_FLAG_OVR: Overrun flag
  *            @arg SPI_FLAG_BSY: Busy flag
  *            @arg SPI_FLAG_FRE: Frame format error flag
  *            @arg SPI_FLAG_FTLVL: SPI fifo transmission level
  *            @arg SPI_FLAG_FRLVL: SPI fifo reception level
  * @retval The new state of __FLAG__ (TRUE or FALSE).
  */
#define _HAL_SPI_GET_FLAG(__SPIx__, __FLAG__) (( ( (__SPIx__)->SR ) & (__FLAG__) ) == (__FLAG__))

#define _HAL_SPI_ENABLE(__SPIx__)  SET_BIT((__SPIx__)->CR1, SPI_CR1_SPE)
//SPIx_CR1寄存器bit6(SPE),SPE=1令SPI外设使能

#define _HAL_SPI_DISABLE(__SPIx__) CLEAR_BIT((__SPIx__)->CR1, SPI_CR1_SPE)
//SPIx_CR1寄存器bit6(SPE),SPE=0令SPI外设不使能

/** @brief  Enable the specified SPI interrupts.
  * @param  __SPIx__ specifies the SPI Handle.
  *         This parameter can be SPI where x: 1, 2, or 3 to select the SPI peripheral.
  * @param  __INTERRUPT__ specifies the interrupt source to enable.
  *         This parameter can be one of the following values:
  *            @arg SPI_IT_TXE: Tx buffer empty interrupt enable
  *            @arg SPI_IT_RXNE: RX buffer not empty interrupt enable
  *            @arg SPI_IT_ERR: Error interrupt enable
  * @retval None
  */
#define _HAL_SPI_ENABLE_IT(__SPIx__, __INTERRUPT__)   SET_BIT((__SPIx__)->CR2, (__INTERRUPT__))
//__INTERRUPT__ = SPI_IT_TXE,设置SPIx_CR2寄存器bit7(TXEIE位),TXEIE=1表示使能SPI在TXE标志建立时产生SPI发送中断
//__INTERRUPT__ = SPI_IT_RXNE,设置SPIx_CR2寄存器bit6(RXNEIE位),RXNEIE=1表示使能SPI在RXNE标志建立时产生SPI接收中断
//__INTERRUPT__ = SPI_IT_ERR,设置SPIx_CR2寄存器bit5(ERRIE位),ERRIE=1表示使能SPI在CRCERR,OVR和MODF标志建立时产生SPI错误中断

/** @brief  Disable the specified SPI interrupts.
  * @param  __SPIx__ specifies the SPI handle.
  *         This parameter can be SPIx where x: 1, 2, or 3 to select the SPI peripheral.
  * @param  __INTERRUPT__ specifies the interrupt source to disable.
  *         This parameter can be one of the following values:
  *            @arg SPI_IT_TXE: Tx buffer empty interrupt enable
  *            @arg SPI_IT_RXNE: RX buffer not empty interrupt enable
  *            @arg SPI_IT_ERR: Error interrupt enable
  * @retval None
  */
#define _HAL_SPI_DISABLE_IT(__SPIx__, __INTERRUPT__)  CLEAR_BIT((__SPIx__)->CR2, (__INTERRUPT__))

/** @brief  Clear the SPI OVR pending flag.
  * @param  __SPIx__ specifies the SPI Handle.
  *         This parameter can be SPI where x: 1, 2, or 3 to select the SPI peripheral.
  * @retval None
  */
#define _HAL_SPI_CLEAR_OVRFLAG(__SPIx__)        \
  do{                                              \
    __IO uint32_t tmpreg_ovr = 0x00U;              \
    tmpreg_ovr = (__SPIx__)->DR;       \
    tmpreg_ovr = (__SPIx__)->SR;       \
    UNUSED(tmpreg_ovr);                            \
  } while(0U)

/** @brief  Clear the SPI MODF pending flag.
  * @param  __SPIx__ specifies the SPI Handle.
  *         This parameter can be SPI where x: 1, 2, or 3 to select the SPI peripheral.
  * @retval None
  */
#define _HAL_SPI_CLEAR_MODFFLAG(__SPIx__)             \
  do{                                                    \
    __IO uint32_t tmpreg_modf = 0x00U;                   \
    tmpreg_modf = (__SPIx__)->SR;            \
    CLEAR_BIT((__SPIx__)->CR1, SPI_CR1_SPE); \
    UNUSED(tmpreg_modf);                                 \
  } while(0U)

	/** @brief  Clear the SPI FRE pending flag.
  * @param  __SPIx__ specifies the SPI Handle.
  *         This parameter can be SPI where x: 1, 2, or 3 to select the SPI peripheral.
  * @retval None
  */
#define _HAL_SPI_CLEAR_FREFLAG(__SPIx__)        \
  do{                                              \
    __IO uint32_t tmpreg_fre = 0x00U;              \
    tmpreg_fre = (__SPIx__)->SR;       \
    UNUSED(tmpreg_fre);                            \
  }while(0U)

#endif /* __SPI_Std_Lib_H__ */

4、主机采用软件模拟SPI收发

//函数功能:模拟SPI总线读写一个字节
uint8_t Soft_ReadWriteByte(uint8_t dat)
{
	uint8_t i,temp,ret;

	ret=0;
	temp=dat;

	    for(i = 0; i < 8; i++)
	    {
	      CLK_PIN1_Output_Low();//在低电平期间准备数据
	      delay_us(2);

		    ret=(uint8_t)(ret<<1);

		    if( temp&0x80 ) MOSI_PIN1_Output_High();
		    else MOSI_PIN1_Output_Low();
		    temp = (uint8_t)(temp<<1);
		    delay_us(2);//等待数据稳定

		    CLK_PIN1_Output_High();//CPU在上升沿输出数据
		    delay_us(2);
		    if(MISO1) ret=ret+1;//SPI从机输出数据
	    }


  return ret;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值