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的主机程序容易实现,就省略不写了。

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.xdnf.cn/news/1557394.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

函数式接口在Java中的应用与实践

1. 引言 函数式接口是Java 8引入的一个概念&#xff0c;它是指只有一个抽象方法的接口。函数式接口可以被用作lambda表达式的目标类型。在函数式接口中&#xff0c;除了抽象方法外&#xff0c;还可以有默认方法和静态方法。 函数式接口的引入是为了支持函数式编程&#xff0c…

Java项目: 基于SpringBoot+mybatis+maven+vue网上摄影工作室(含源码+数据库+任务书+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismavenmavenvue网上摄影工作室 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、…

【算法】博弈论(C/C++)

个人主页&#xff1a;摆烂小白敲代码 创作领域&#xff1a;算法、C/C 持续更新算法领域的文章&#xff0c;让博主在您的算法之路上祝您一臂之力 欢迎各位大佬莅临我的博客&#xff0c;您的关注、点赞、收藏、评论是我持续创作最大的动力 目录 博弈论&#xff1a; 1. Grundy数…

【MySQL】-- 表的操作

文章目录 1. 查看所有表1.1 语法 2. 创建表2.1 语法2.2 示例2.3 表在磁盘上对应的文件 3. 查看表结构3.1 语法3.2 示例 4. 查看创建表的语句5. 修改表5.1 语法5.2 示例5.2.1 向表中添加一列5.2.2 修改某列的长度5.2.3 重命名某列5.2.4 删除某个字段5.2.5 修改表名 6. 删除表6.1…

不入耳开放式耳机哪个品牌好?开放式耳机排行榜10强推荐!

不入耳开放式耳机哪个品牌好&#xff1f;开放式耳机排行榜10强推荐&#xff01; 随着开放式耳机的日益流行&#xff0c;市场上的选择愈发多样&#xff0c;这有时会让消费者在挑选时感到迷茫&#xff0c;不知道哪个牌子的开放式耳机最好。为解决这一困扰&#xff0c;我精心筛选…

社区圈子系统 圈子社区系统 兴趣社区圈子论坛系统 圈子系统源码圈子系统的适用领域有哪些?如何打造自己的圈子圈子系统有哪些常见问题

社区圈子系统 圈子社区系统 兴趣社区圈子论坛系统 圈子系统源码圈子系统的适用领域有哪些&#xff1f;如何打造自己的圈子圈子系统有哪些常见问题 圈子系统的适用领域 圈子系统的适用领域广泛&#xff0c;涵盖了多个行业和场景&#xff0c;包括但不限于以下几个方面&#xff1…

Label Studio 半自动化标注

引言 Label Studio ML 后端是一个 SDK,用于包装您的机器学习代码并将其转换为 Web 服务器。Web 服务器可以连接到正在运行的 Label Studio 实例,以自动执行标记任务。我们提供了一个示例模型库,您可以在自己的工作流程中使用这些模型,也可以根据需要进行扩展和自定义。 1…

springboot邮件群发功能的开发与优化策略?

springboot邮件配置指南&#xff1f;如何实现spring邮件功能&#xff1f; SpringBoot框架因其简洁、高效的特点&#xff0c;成为了开发邮件群发功能的理想选择。AokSend将深入探讨SpringBoot邮件群发功能的开发过程&#xff0c;并提出一系列优化策略&#xff0c;以确保邮件发送…

常见的图像处理算法:均值滤波----mean filter

一、什么是均值滤波 均值滤波器是一种常见的图像滤波器&#xff0c;是典型的线性滤波算法。其基本原理是用一个给定的窗口覆盖图像中的每一个像素点&#xff0c;将窗口内的像素值求平均值&#xff0c;然后用这个平均值代替原来的像素值。均值滤波器可以去除噪声、平滑图像、减少…

代码随想录算法训练营Day28 | 39. 组合总和、40.组合总和Ⅱ、131.分割回文串

目录 39. 组合总和 40.组合总和Ⅱ 131.分割回文串 39. 组合总和 题目 39. 组合总和 - 力扣&#xff08;LeetCode&#xff09; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不…

路径规划关于地图的整理

路径规划离不开地图&#xff0c;其中真实地图&#xff0c;栅格地图和RVIZ之间Grid显示之间很混乱&#xff0c;还有各个原点位置显示&#xff0c;不弄清发现map在rviz里显示老是偏的&#xff0c;专门学习记录一下。 RVIZ里Grid的全局坐标系原点&#xff0c;在默认在栅格中间&am…

软考学习笔记

学习资料&#xff1a; 数据库关系模式的范式总结_关系模式范式-CSDN博客 【范式】五大范式所解决的问题及说明_天空_新浪博客 (sina.com.cn) 求函数依赖&#xff1a; 根据函数依赖求候选码_证明存在部分依赖属于候选码-CSDN博客 关系范式&#xff1a; 1NF&#xff1a;若关…

xss-labs靶场第二关测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、注入点寻找 2、使用hackbar进行payload测试 3、绕过结果 四、源代码分析 五、结论 一、测试环境 1、系统环境 渗透机&#xff1a;本机(127.0.0.1) 靶 机&#xff1a;本机(127.0.0.…

刷题 二叉树

面试经典 150 题 - 二叉树 104. 二叉树的最大深度 广度优先遍历 class Solution { public:// 广度优先遍历int maxDepth(TreeNode* root) {if (root nullptr) return 0;queue<TreeNode*> que;que.push(root);int result 0;while (!que.empty()) {result;int num que…

看《米小圈动画汉字》轻松掌握汉字的起源、演变和应用!

在这个充满探索与发现的年纪&#xff0c;孩子刚刚从幼儿园的温暖怀抱中走出&#xff0c;踏入了小学的大门。对于每一个小学生而言&#xff0c;这不仅是一个新环境的适应&#xff0c;更是知识大门的开启。汉字&#xff0c;这一古老而美丽的文字&#xff0c;承载着丰富的文化与历…

【JAVA基础】集合类之Hash的原理及应用

近期几期内容都是围绕该体系进行知识讲解&#xff0c;以便于同学们学习Java集合篇知识能够系统化而不零散。 本文将介绍HashSet的基本概念&#xff0c;功能特点&#xff0c;使用方法&#xff0c;以及优缺点分析和应用场景案例。 一、概念 HashSet是 Java 集合框架中的一个重…

思科防火墙:ASA中Object-group在ACL中的应用

一、实验拓扑&#xff1a; 二、实验要求&#xff1a; 先定义几个小的&#xff0c;然后用大的包在一起&#xff1b;打包在一起&#xff0c;这就是所谓的嵌套&#xff0c;嵌套在编程里是很长用的东西&#xff0c;叫做Object-group&#xff1b; Object-group比较强大&#xff0c;可…

【JAVA开源】基于Vue和SpringBoot的师生共评作业管理系统

本文项目编号 T 071 &#xff0c;文末自助获取源码 \color{red}{T071&#xff0c;文末自助获取源码} T071&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

学习threejs,模拟窗户光源

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言二、&#x1f340;绘制任意字体模型…

性能测试度量指标的多种收集环境

目录 一、技术环境 二、业务环境 三、操作环境 在用卷尺测量某一物体的长度时&#xff0c;长度就是该场景下的度量指标&#xff0c;我们可以用分米、米或者更精确的厘米甚至毫米来描述这个长度&#xff0c;具体取决于使用场景。 与其他形式的测量一样&#xff0c;对性能进行…