STM32 SPI硬件CRC校验详解及个人学习记录

一、功能实现

        为了保证SPI通信数据的准确性,需要通过对每个数据进行CRC校验,保证设备运行正常。

二、基本原理

SPI通信可以通过以下步骤使用CRC:
● 设置CPOL、CPHA、LSBFirst、BR、SSM、SSI和MSTR的值;
● 在SPI_CRCPR寄存器输入多项式;
● 通过设置SPI_CR1寄存器CRCEN位使能CRC计算,该操作也会清除寄存器SPI_RXCRCR 和SPI_TXCRC;
● 设置SPI_CR1寄存器的SPE位启动SPI功能;
● 启动通信并且维持通信,直到只剩最后一个字节或者半字;
● 在把最后一个字节或半字写进发送缓冲器时,设置SPI_CR1的CRCNext位,指示硬件在发送完成最后一个数据之后,发送CRC的数值。在发送CRC数值期间,停止CRC计算;
● 当最后一个字节或半字被发送后,SPI发送CRC数值,CRCNext位被清除。同样,接收到的CRC与SPI_RXCRCR值进行比较,如果比较不相配,则设置SPI_SR上的CRCERR标志位,当设置了SPI_CR2寄存器的ERRIE时,则产生中断。

按照下述步骤清除CRC数值:
1. 关闭SPI模块(SPE=0);(实测可以不需要)
2. 清除CRCEN位为’0’;
3. 设置CRCEN位为’1’;
4. 使能SPI模块(SPE=1)。(实测可以不需要)

三、硬件配置

主机选用STM32F407VGT6,SPI2,从机选用STM32F103ZET6,SPI3,进行SPI通信并通过硬件CRC校验;

硬件接线:       

                                     主机————从机

CS:                            PB12———–PA15

CLOCK:                     PB13———–PB3

MISO:                        PB14———–PB4

MOSI:                        PB15———–PB5

四、主机代码

#define SPI2_CS_ACTIVE() GPIO_ResetBits(GPIOB,SPI2_CS_PIN) //CS低电平时SPI2数据传输开始
#define SPI2_CS_INACTIVE() GPIO_SetBits(GPIOB,SPI2_CS_PIN) //CS高电平时SPI2数据传输截止
u16 TxData[4] = { 0x0001, 0x0002, 0x0003, 0x0004};

void SPI2_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
   	SPI_InitTypeDef  SPI_InitStructure;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能GPIOB时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//使能SPI2时钟
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 ;
  	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  	GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
  	GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
    GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化
	
  	GPIO_PinAFConfig(GPIOB,GPIO_PinSource13,GPIO_AF_SPI2); //PB13复用为 SPI2
  	GPIO_PinAFConfig(GPIOB,GPIO_PinSource14,GPIO_AF_SPI2); //PB14复用为 SPI2
    GPIO_PinAFConfig(GPIOB,GPIO_PinSource15,GPIO_AF_SPI2); //PB15复用为 SPI2

	
	//这里只针对SPI口初始化
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,ENABLE);//复位SPI2
	RCC_APB1PeriphResetCmd(RCC_APB1Periph_SPI2,DISABLE);//停止复位SPI2
	
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //全双工模式
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		    //主SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;		//16位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步时钟的空闲状态为高电平
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步时钟的第二个跳变沿被采样
  	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信号由软件(使用SSI位)管理
  	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;	//预分频值为4
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//数据传输从MSB位LSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值计算的多项式
	SPI_Init(SPI2, &SPI_InitStructure);  //初始化外设SPIx寄存器
	

	SPI_CalculateCRC(SPI2,ENABLE);    //开启硬件CRC校验
	SPI_Cmd(SPI2, ENABLE);         //使能SPI外设
}

void SPI2_WriteByte(u16 txData)
{		 			 
	unsigned int crcval;

	SPI2_CS_ACTIVE();//拉低CS信号
	
	SPI_TransmitCRC( SPI2 );//开启CRC计算
	while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET){}//等待发送区空  
	SPI_I2S_SendData(SPI2, txData); //通过外设SPIx发送一个byte  数据
	
	delay_us(3);        //用逻辑分析仪测了数据发送总时间约为2.5us左右
	SPI2_CS_INACTIVE();//拉高CS信号


	crcval = SPI_GetCRC( SPI2, SPI_CRC_Tx );  //获取SPI1发送CRC寄存器的值
	printf( "CRC:%04x\r\n", crcval );        //打印输出CRC校验值

	SPI_CalculateCRC(SPI2,DISABLE);    //清除CRC校验值
	SPI_CalculateCRC(SPI2,ENABLE);
}

int main()
{
    delay_init(168);
	uint16_t i;
	USART3_Init(115200);
	SPI2_Init();

	printf("host mode\r\n");

	while(1)
	{	
		for(i=0;i<4;i++)
		{
			SPI2_WriteByte(TxData[i]);
	        delay_ms(1000);    //延时一段时间,防止从机数据处理不过来导致发送顺序出错
		}
		while(1);
	}
}

五、从机代码

void SPI3_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	SPI_InitTypeDef  SPI_InitStructure;
	
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_AFIO |RCC_APB2Periph_GPIOB |         
    RCC_APB2Periph_GPIOA, ENABLE );//PORTB时钟使能 
	RCC_APB1PeriphClockCmd(	RCC_APB1Periph_SPI3,  ENABLE );//SPI3时钟使能 	
	/**SPI3配置时需要关闭JTAG **/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
	GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
	
	//片选信号
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;  // PA15 推挽 
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  //上拉输入
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOA, &GPIO_InitStructure);
 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;  //PB13/14/15复用推挽输出 
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);//初始化GPIOB

 	GPIO_SetBits(GPIOB,GPIO_Pin_3|GPIO_Pin_4|GPIO_Pin_5);  //PB13/14/15上拉

	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
	SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;
	SPI_InitStructure.SPI_NSS =SPI_NSS_Hard;
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
	SPI_InitStructure.SPI_CRCPolynomial =7;
	SPI_Init(SPI3, &SPI_InitStructure);  //初始化外设SPIx寄存器

	SPI_CalculateCRC(SPI3,ENABLE);    //使能CRC校验
	SPI_Cmd(SPI3, ENABLE);            //使能SPI外设
	SPI_TransmitCRC( SPI3 );
}

u16 RxData,CRCData,CRCRecieve;

int main(void)
{		
	delay_init();	    	 //延时函数初始化
	USART1_Init(115200);
	SPI3_Init();
	printf("slave mode\r\n");	 
		
    while (1)
	{
		SPI_TransmitCRC( SPI3 );//开启CRC计算
		
		while (SPI_I2S_GetFlagStatus( SPI3, SPI_I2S_FLAG_RXNE ) == RESET){} 
		RxData = SPI_I2S_ReceiveData( SPI3 );    //接收数据 

		while (SPI_I2S_GetFlagStatus(SPI3, SPI_I2S_FLAG_RXNE) == RESET){}
		CRCRecieve=SPI_I2S_ReceiveData(SPI3);      //接收主机的CRC校验值
			
		CRCData = SPI_GetCRC( SPI3, SPI_CRC_Rx );  //获取从机的CRC校验值
		
		printf("RxData:%04x\r\n",RxData);    
		printf("CRCRecieve:%04x\r\n",CRCRecieve);
		printf("CRCData:%04x\r\n",CRCData);
			
		SPI_CalculateCRC(SPI3,DISABLE);    //清除从机CRC校验值
		SPI_CalculateCRC(SPI3,ENABLE);
		
    }
}

六、运行结果

主机串口打印

从机串口打印:

七、总结

不知道为啥,SPI传输的第一个数据总是不正确,后面的数据不会有影响,我不理解。但是其他的数据和CRC都是对的,说明硬件CRC成功开启了。

关于SPI波特率的设置,因为SPI2、SPI3都是挂在APB1线上,最大预分频系数是2。主机的APB1时钟是42MHZ,从机APB1的时钟是36MHZ,所以为了保证从机能够接收正确,预分频系数最小是设置到4。

关于主机片选信号拉高延时时间的确定,可以通过示波器或者逻辑分析仪测量一下,测量一个数据发送的总时长,确保数据和CRC校验值全部发完之后再拉高。

此文章纯属刚入行小白的学习记录,如有不对之处,望指正,感谢!

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 SPI硬件CRC校验详解及个人学习记录

发表评论