当前位置: 首页 > news >正文

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;            //选择引脚编号为13GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
//	GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHzGPIO_InitStruct.Alternate = GPIO_AF5_SPI2;   //PB13映射到SPI2_SCKHAL_GPIO_Init(GPIOB, &GPIO_InitStruct);//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器//配置“SPI2_SCK引脚”GPIO_InitStruct.Pin = GPIO_PIN_14;            //选择引脚编号为14GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHzGPIO_InitStruct.Alternate = GPIO_AF5_SPI2;   //PB14映射到SPI2_MISOHAL_GPIO_Init(GPIOB, &GPIO_InitStruct);//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器//配置“SPI2_MISO引脚”GPIO_InitStruct.Pin = GPIO_PIN_15;            //选择引脚编号为15GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHzGPIO_InitStruct.Alternate = GPIO_AF5_SPI2;   //PB15映射到SPI2_MOSIHAL_GPIO_Init(GPIOB, &GPIO_InitStruct);//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器//配置“SPI2_MOSI引脚”GPIO_InitStruct.Pin = GPIO_PIN_12;            //选择引脚编号为12GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHzGPIO_InitStruct.Alternate = GPIO_AF5_SPI2;   //PB12映射到SPI2_NSSHAL_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外设为主机
#elsehspi2.Init.Mode = SPI_MODE_SLAVE;//SPIx_CR1寄存器bit2(MSTR),MSTR=0配置SPI外设为从机
#endifhspi2.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位的值
#elsehspi2.Init.NSS = SPI_NSS_HARD_INPUT;//仅当配置spi在slave下,作为从机片选输入
#endifhspi2.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/256hspi2.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位
#elsehspi2.Init.DataSize = SPI_DATASIZE_16BIT;//这里读写16位数据
#endifhspi2.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引脚输出使能
#elsehspi2.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;//SPIx_CR2寄存器bit3(NSSP)//NSSP=0,在从机模式中,NSS引脚输出不使能
#endifHAL_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,响应优先级为0HAL_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_DRSPI2_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 clockHAL_RCC_GetClockConfig(&clkconfig, &pFLatency);//Get clock configurationuwTimclock8 = HAL_RCC_GetPCLK2Freq();//读取PCLK2的时钟频率,Return the PCLK2 frequency//若PCLK2的分频器值为1,则和SystemCoreClock的值相等uwPrescalerValue8 = (uint32_t) ((uwTimclock8 / 1000000U) - 1U);//uwPrescalerValue8=170htim8.Instance = TIM8;htim8.Init.Period = (1000000U / 1000U) - 1U;//定时器周期999htim8.Init.Prescaler = uwPrescalerValue8;//设置TIM8预分频器为uwPrescalerValue8htim8.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/5sMasterConfig.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  //存储区2extern 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;            //选择引脚编号为5GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式
//	GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHzGPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PA5映射到SPI1_SCKHAL_GPIO_Init(GPIOA, &GPIO_InitStruct);//根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器//配置“SPI1_SCK引脚”GPIO_InitStruct.Pin = GPIO_PIN_6;            //选择引脚编号为6GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_NOPULL;          //引脚上拉和下拉都没有被激活GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHzGPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PA6映射到SPI1_MISOHAL_GPIO_Init(GPIOA, &GPIO_InitStruct);//根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器//配置“SPI1_MISO引脚”GPIO_InitStruct.Pin = GPIO_PIN_7;            //选择引脚编号为7GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHzGPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PA7映射到SPI1_MOSIHAL_GPIO_Init(GPIOA, &GPIO_InitStruct);//根据GPIO_InitStruct结构变量指定的参数初始化GPIOA的外设寄存器//配置“SPI1_MOSI引脚”GPIO_InitStruct.Pin = GPIO_PIN_4;            //选择引脚编号为4GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;      //复用推挽模式GPIO_InitStruct.Pull = GPIO_PULLUP;           //设置上拉
//	GPIO_InitStruct.Pull = GPIO_PULLDOWN;        //设置下拉GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;//引脚的输出速度为120MHzGPIO_InitStruct.Alternate = GPIO_AF5_SPI1;   //PA4映射到SPI1_NSSHAL_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外设为主机
#elsehspi1.Init.Mode = SPI_MODE_SLAVE;//SPIx_CR1寄存器bit2(MSTR),MSTR=0配置SPI外设为从机
#endifhspi1.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位的值
#elsehspi1.Init.NSS = SPI_NSS_HARD_INPUT;//仅当配置spi在slave下,作为从机片选输入
#endifhspi1.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/256hspi1.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位
#elsehspi1.Init.DataSize = SPI_DATASIZE_16BIT;//这里读写16位数据
#endifhspi1.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引脚输出使能
#elsehspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;//SPIx_CR2寄存器bit3(NSSP)//NSSP=0,在从机模式中,NSS引脚输出不使能
#endifHAL_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,响应优先级为0HAL_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_DRSPI1_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中断优先级为1SystemClock_Config();//Configure the system clockHAL_Delay(500);USART1_Init(115200);printf("%s",CPU_Reset_REG);Print_HCLK_PCLK1_PCLK2();delay_init();delay_ms(1000);LED_Init();//配置PC13为输出,无上拉或下拉,输出速度为5MHzdelay_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;
}


http://www.mrgr.cn/news/45276.html

相关文章:

  • 【算法】博弈论(C/C++)
  • 动态规划——简单的动态规划问题
  • 随机掉落的项目足迹:Vue3中vite.config.ts配置代理服务器解决跨域问题
  • 【Redis】持久化(下)-- AOF
  • 新手投资者如何选择低佣金的证券公司开户
  • FDS-150 土壤氮磷钾传感器 三针式不锈钢探针 可搭配速测仪 响应快
  • 国产气压传感器WF5803 2BAR测试
  • 大模型+向量数据库组合:解决问题还是制造新问题?
  • 建设官方网站怎么建
  • 软件工程相关
  • shiny APP实现xgboost 构建,超参数调节以及后概率校准
  • Qt-QSpacerItem布局相关控件(45)
  • 有限差分方法 - 拉普拉斯算子第二部分
  • Python数据分析库和基本概念
  • Ansys Zemax | 如何使用 Binary2 面型设计衍射光学元件
  • nacos启动报错:Unable to start embedded Tomcat
  • VSCode运行QT界面
  • N1从安卓盒子刷成armbian
  • 《RabbitMQ篇》消息应答和发布确认
  • 昆虫分类与检测系统源码分享