在使用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;
}