STM32G474的SPI1工作在主机模式,将SPI1_MISO和SPI1_MOSI连接一起,实现自发自收测试。但是在“使用8位数据自发自收时”时,发现接收数据不是同步的。虽然SPI1初始化正确,但是还需要对SPI1_DR寄存器进行强制转换,否则,就会出现接收不同步。我们知道SPI是一个硬件接口,虽然SPI1_DR寄存器是一个16位寄存器,但是触发事件配置的是1/4个8位就可以触发接收了,应该无需软件进行强制转换才是正确的,但实际使用中,使用强制转换,确实令人无法理解。
如果写成下面这样,总是有点问题:
while ( _HAL_SPI_GET_FLAG( SPI1, SPI_FLAG_TXE ) == RESET ){ if(delay++>200) return 0; }
//检查指定的SPI标志位设置与否:发送缓存空标志位
delay=0;
printf("TxData=0x%02X ",TxData);
SPI1->DR=TxData; //通过外设SPIx发送一个数据,写SPI1_DR
while (_HAL_SPI_GET_FLAG(SPI1, SPI_FLAG_RXNE) == RESET){ if(delay++>200)return 0; }
//检查指定的SPI标志位设置与否:接受缓存非空标志位
RxData=SPI1->DR; //返回通过SPIx最近接收的数据,写读SPI1_DR
printf("RxData=0x%02X\r\n",RxData);
这样就会发现接收不同步:
TxData=0x80 RxData=0x80
TxData=0x81 RxData=0x00
TxData=0x82 RxData=0x81
TxData=0x83 RxData=0x00
TxData=0x84 RxData=0x82
TxData=0x85 RxData=0x00
TxData=0x86 RxData=0x83
TxData=0x87 RxData=0x84
TxData=0x88 RxData=0x85
TxData=0x89 RxData=0x86
如果写成下面这样,就没有问题:
while ( _HAL_SPI_GET_FLAG( SPI1, SPI_FLAG_TXE ) == RESET ){ if(delay++>200) return 0; }
//检查指定的SPI标志位设置与否:发送缓存空标志位
delay=0;
printf("TxData=0x%02X ",TxData);
*(__IO uint8_t *)&SPI1->DR=TxData; //通过外设SPIx发送一个数据,写SPI1_DR
while (_HAL_SPI_GET_FLAG(SPI1, SPI_FLAG_RXNE) == RESET){ if(delay++>200)return 0; }
//检查指定的SPI标志位设置与否:接受缓存非空标志位
RxData=*(__IO uint8_t *)&SPI1->DR; //返回通过SPIx最近接收的数据,写读SPI1_DR
printf("RxData=0x%02X\r\n",RxData);
这样就可以打印如下:
TxData=0x80 RxData=0x80
TxData=0x81 RxData=0x81
TxData=0x82 RxData=0x82
TxData=0x83 RxData=0x83
TxData=0x84 RxData=0x84
TxData=0x85 RxData=0x85
TxData=0x86 RxData=0x86
TxData=0x87 RxData=0x87
TxData=0x88 RxData=0x88
TxData=0x89 RxData=0x89
SPI初始化程序如下:
#define HalfWord 0
/** @brief Check whether the specified SPI flag is set or not.
* @param __Instance__ specifies the SPI Handle.
* 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(__Instance__, __FLAG__) (( ( (__Instance__)->SR ) & (__FLAG__) ) == (__FLAG__))
#define _HAL_SPI_ENABLE(__Instance__) SET_BIT((__Instance__)->CR1, SPI_CR1_SPE)
#define _HAL_SPI_DISABLE(__Instance__) CLEAR_BIT((__Instance__)->CR1, SPI_CR1_SPE)
void SPI1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
SPI_HandleTypeDef hspi1;
__HAL_RCC_SPI1_CLK_ENABLE(); //使能SPI1外设时钟
__HAL_RCC_GPIOB_CLK_ENABLE(); //GPIOB时钟使能
GPIO_InitStruct.Pin = GPIO_PIN_3; //选择引脚编号为3
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; //PB3映射到SPI1_SCK
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
//配置“SPI1_SCK引脚”
GPIO_InitStruct.Pin = GPIO_PIN_4; //选择引脚编号为4
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; //PB4映射到SPI1_MISO
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
//配置“SPI1_MISO引脚”
GPIO_InitStruct.Pin = GPIO_PIN_5; //选择引脚编号为5
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; //PB5映射到SPI1_MOSI
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
//根据GPIO_InitStruct结构变量指定的参数初始化GPIOB的外设寄存器
//配置“SPI1_MOSI引脚”
hspi1.Instance = SPI1; //选择SPI1
hspi1.Init.Mode = SPI_MODE_MASTER;
//SPIx_CR1寄存器bit2(MSTR),MSTR=1配置SPI外设为主机
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的空闲位为高电平
hspi1.Init.NSS = SPI_NSS_SOFT;
//SPIx_CR1寄存器bit9(SSM),SSM=1,NSS引脚的输入将被替换为来自SSI位的值
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 (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
hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
//SPIx_CR2寄存器bit3(NSSP)
//NSSP=0,NSS引脚无脉冲,这里不使用NSS引脚
//NSSP=1,NSS引脚能产生脉冲
HAL_SPI_Init(&hspi1);
_HAL_SPI_ENABLE(SPI1);
}
//函数功能:SPI1总线读写一个字节
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
uint8_t delay=0;
uint8_t RxData = 0;
IWDG_Counter_Reload();
//喂狗,按照IWDG重装载寄存器IWDG_RLR的值重装载IWDG计数器,独立看门狗的溢出周期为6400ms
while ( _HAL_SPI_GET_FLAG( SPI1, SPI_FLAG_TXE ) == RESET ){ if(delay++>200) return 0; }
//检查指定的SPI标志位设置与否:发送缓存空标志位
delay=0;
printf("TxData=0x%02X ",TxData);
*( uint8_t *)&SPI1->DR=TxData; //通过外设SPIx发送一个数据,写SPI1_DR
while (_HAL_SPI_GET_FLAG(SPI1, SPI_FLAG_RXNE) == RESET){ if(delay++>200)return 0; }
//检查指定的SPI标志位设置与否:接受缓存非空标志位
RxData=*( uint8_t *)&SPI1->DR; //返回通过SPIx最近接收的数据,写读SPI1_DR
printf("RxData=0x%02X\r\n",RxData);
return RxData;
}
去掉“__IO修饰符”也可以,说明不是优化的问题。
#define __IO volatile /*!< defines 'read / write' permissions */
volatile 的作用就是指示编译器不要因优化而省略此指令,必须每次都直接读写其值。
问题是解决了,但是为什么这么做,确实有点懵逼。HAL库是这么干的,不参考官方的库,还真不知道怎么搞。若你能解释,请给我留言,或者你有更好的解决方法。若使用16位数据传输,就不要留言,那个是可以的。