一、功能实现
为了保证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校验值全部发完之后再拉高。文章来源:https://www.toymoban.com/news/detail-502672.html
此文章纯属刚入行小白的学习记录,如有不对之处,望指正,感谢!文章来源地址https://www.toymoban.com/news/detail-502672.html
到了这里,关于STM32的SPI硬件CRC校验(个人学习记录)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!