一、功能实现

        为了保证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校验值全部发完之后再拉高。

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

好文阅读

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: