[RISC-V MCU 应用开发] 第五十二章、CH32V103应用教程——SPI-CRC校验

[复制链接]
 楼主| RISCVLAR 发表于 2021-1-13 17:21 | 显示全部楼层 |阅读模式
spi, CRC, IO, ni, ST
本帖最后由 RISCVLAR 于 2021-1-13 17:21 编辑

CH32V103应用教程——SPI-CRC校验

本章教程主要在SPI通信方式下使用CRC校验以保证通信的可靠性。

1、SPI简介及相关函数介绍
关于SPI CRC校验,其用于保证全双工通信的可靠性。数据的发送和接收分别使用单独的CRC计算器。通过对每一个接收位进行可编程的多项式运算来计算CRC。CRC的计算是在由SPI控制寄存器1(SPIx_CTLR1)中的CPHA和CPOL位定义的采样时钟边沿进行的。
注意:该SPI接口提供了两种CRC计算方法,取决于所选的发送和/或接收的数据帧格式:8位数据帧采用CR8;16位数据帧采用CRC16。
CRC计算是通过设置SPI控制寄存器1(SPIx_CTLR1)中的CRCEN位启动的。设置CRCEN位时同时复位SPI接收CRC寄存器和SPI发送CRC寄存器(SPIx_RCRCR和SPI_TCRCR)。当设置了SPI控制寄存器1(SPIx_CTLR1)的CRCNEXT位, SPI_TCRCR的内容将在当前字节发送之后发出。
在传输SPI_TCRCR的内容时,如果在移位寄存器中收到的数值与SPI_RCRCR的内容不匹配,则SPI_SR寄存器的CRCERR标志位被置1。
如果在TX缓冲器中还有数据, CRC的数值仅在数据字节传输结束后传送。在传输CRC期间,CRC计算器关闭,寄存器的数值保持不变。

SPI通信可以通过以下步骤使用CRC:
● 设置CPOL、CPHA、LSBFIRST、BR、SSM、SSI和MSTR的值;
● 在SPI_CRCR寄存器输入多项式;
● 通过设置SPIx_CTLR1寄存器CRCEN位使能CRC计算,该操作也会清除寄存器SPI_RCRCR和SPI_TCRCR;
● 设置SPIx_CTLR1寄存器的SPE位启动SPI功能;
● 启动通信并且维持通信,直到只剩最后一个字节或者半字;
● 在把最后一个字节或半字写进发送缓冲器时,设置SPIx_CTLR1的CRCNEST位,指示硬件在发送完成最后一个数据之后,发送CRC的数值。在发送CRC数值期间,停止CRC计算;
● 当最后一个字节或半字被发送后,SPI发送CRC数值, CRCNEST位被清除。同样,接收到的CRC与SPI_RCRCR值进行比较,如果比较不相配,则设置SPI_SR上的CRCERR标志位,当设置了SPIx_CTLR2寄存器的ERRIE时,则产生中断。
注意:当SPI模块处于从设备模式时,请注意在时钟稳定之后再使能CRC计算,否则可能会得到错误的CRC计算结果。事实上,只要设置了CRCEN位,只要在SCK引脚上有输入时钟,不管SPE位的状态,都会进行CRC的计算。
当SPI时钟频率较高时,用户在发送CRC时必须小心。在CRC传输期间,使用CPU的时间应尽可能少;为了避免在接收最后的数据和CRC时出错,在发送CRC过程中应禁止函数调用。必须在发送/接收最后一个数据之前完成设置CRCNEXT位的操作。
当SPI时钟频率较高时,因为CPU的操作会影响SPI的带宽,建议采用DMA模式以避免SPI降低的速度。
当CH32V103配置为从模式并且使用了NSS硬件模式,NSS引脚应该在数据传输和CRC传输期间保持为低。
当配置SPI为从模式并且使用CRC的功能,即使NSS引脚为高时仍然会执行CRC的计算(译注:当NSS信号为高时,如果SCK引脚上有时钟脉冲,则CRC计算会继续执行)。例如:当主设备交替与多个从设备进行通信时,将会出现这种情况(译注:此时要想办法避免CRC的误操作)。
在不选中一个从设备(NSS信号为高)转换到选中一个新的从设备(NSS信号为低)的时候,为了保持主从设备端下次CRC计算结果的同步,应该清除主从两端的CRC数值。
按照下述步骤清除CRC数值:
1. 关闭SPI模块(SPE=0);
2. 清除CRCEN位为’0’;
3. 设置CRCEN位为’1’;
4. 使能SPI模块(SPE=1)。

2、硬件设计
本章教程主要在第46章基础上进行,需用到两个开发板,主设备使用MOSI引脚,从设备使用MISO引脚进行通讯。此处使用外设为SPI1,主设备MOSI对应引脚为PA7引脚,从设备MISO对应引脚为PA6引脚,将主设备PA7引脚与从设备PA6引脚连接起来,此外还需将两个开发板SPI1对应的SCK引脚PA5连接起来。
此外,由于两个开发板需要同时进行上电传输,因此将两个开发板的3.3V引脚和GND引脚进行连接。

3软件设计
本章教程主要在第46章基础上进行,增加了CRC校验步骤,具体程序如下:
spi.h文件
  1. #ifndef __SPI_H
  2. #define __SPI_H

  3. #include "ch32v10x_conf.h"

  4. /* SPI Mode Definition */
  5. #define HOST_MODE    0
  6. #define SLAVE_MODE   1

  7. /* SPI Communication Mode Selection */
  8. #define SPI_MODE   HOST_MODE
  9. //#define SPI_MODE   SLAVE_MODE

  10. #define  Size  4

  11. extern u16 TxData[Size];
  12. extern u16 RxData[Size];

  13. void SPI_1Lines_HalfDuplex_Init(void);

  14. #endif
spi.c文件
  1. #include "spi.h"

  2. /* Global Variable */

  3. u16 TxData[Size] = { 0x01, 0x02, 0x03, 0x04};
  4. u16 RxData[Size];

  5. /*******************************************************************************
  6. * Function Name  : SPI_1Lines_HalfDuplex_Init
  7. * Description    : Configuring the SPI for half-duplex communication.
  8. * Input          : None
  9. * Return         : None
  10. *******************************************************************************/
  11. void SPI_1Lines_HalfDuplex_Init(void)
  12. {
  13.     GPIO_InitTypeDef GPIO_InitStructure;
  14.     SPI_InitTypeDef SPI_InitStructure;

  15.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE );

  16. #if (SPI_MODE == HOST_MODE)
  17.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  18.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  19.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  20.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  21.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  22.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  23.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  24.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  25. #elif (SPI_MODE == SLAVE_MODE)
  26.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  27.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  28.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  29.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  30.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  31.     GPIO_Init( GPIOA, &GPIO_InitStructure );
  32. #endif


  33. #if (SPI_MODE == HOST_MODE)
  34.     SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
  35.     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;

  36. #elif (SPI_MODE == SLAVE_MODE)
  37.     SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Rx;
  38.     SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;

  39. #endif

  40.     SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b;
  41.     SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
  42.     SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
  43.     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
  44.     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;
  45.     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
  46.     SPI_InitStructure.SPI_CRCPolynomial = 7;
  47.     SPI_Init( SPI1, &SPI_InitStructure );

  48.     SPI_CalculateCRC( SPI1, ENABLE );  //开启CRC计算
  49.     SPI_Cmd( SPI1, ENABLE );
  50. }
spi.c文件主要包括1个函数:SPI_1Lines_HalfDuplex_Init函数。SPI_1Lines_HalfDuplex_Init函数主要进行主机发送从机接收配置。在这种配置下,主机需要用到SCK引脚和MOSI引脚,从机需要用到SCK引脚和MISO引脚,此处,由于用到SPI1,对应SCK引脚为PA5,MOSI引脚为PA7,MISO引脚为PA6,因此,在SPI_1Lines_HalfDuplex_Init函数中首先对主机和从机对应GPIO引脚进行初始化配置,此处需要注意,由于是主机发送从机接收,需要将PA7设置为复用推挽输出模式,PA6设置为浮空输入模式。此外,由于主机作为发送,从机作为接收,此处在SPI_1Lines_HalfDuplex_Init函数中需要对SPI进行主机发送和从机接收初始化配置,此配置可根据CH32V103应用手册主模式和从模式配置步骤进行,主要对SPI通信的通信方向、主从模式、数据帧大小、时钟极性、时钟相位、NSS引脚使用方式、波特率等进行配置,可对照手册参考标准库函数ch32v10x_spi.c文件中SPI_Init函数进行配置。
main.c文件
  1. /********************************** (C) COPYRIGHT *******************************
  2. * File Name          : main.c
  3. * Author             : WCH
  4. * Version            : V1.0.0
  5. * Date               : 2020/04/30
  6. * Description        : Main program body.
  7. *******************************************************************************/

  8. #include "debug.h"
  9. #include "spi.h"

  10. /*
  11. *@Note
  12. 使用CRC错误校验,Master/Slave 模式收发例程:
  13. Master:SPI1_SCK(PA5)、SPI1_MOSI(PA7)。
  14. Slave :SPI1_SCK(PA5)、SPI1_MISO(PA6)。

  15. 本例程演示使用CRC错误校验,Master 发,Slave 收。
  16. 注:两块板子分别下载 Master 和 Slave 程序,同时上电。
  17.        硬件连线:PA5 —— PA5
  18.             PA7 —— PA6

  19. */
  20. /*******************************************************************************
  21. * Function Name  : main
  22. * Description    : Main program.
  23. * Input          : None
  24. * Return         : None
  25. *******************************************************************************/
  26. int main(void)
  27. {
  28.     u8 i=0,crcval;

  29.     NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
  30.     Delay_Init();
  31.     USART_Printf_Init(115200);
  32.     printf("SystemClk:%d\r\n",SystemCoreClock);

  33.     SPI_1Lines_HalfDuplex_Init();

  34. #if (SPI_MODE == SLAVE_MODE)
  35.     printf("SLAVE Mode\r\n");
  36.     Delay_Ms(1000);

  37. #endif

  38. #if (SPI_MODE == HOST_MODE)
  39.     printf("HOST Mode\r\n");
  40.     Delay_Ms(2000);

  41. #endif

  42.     while(1)
  43.     {
  44. #if (SPI_MODE == HOST_MODE)
  45.         while( i<4 )
  46.         {
  47.             if( i<3 )
  48.             {
  49.                 if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET )  //发送缓冲区非空
  50.                 {
  51.                     SPI_I2S_SendData( SPI1, TxData[i] );  //发送数据
  52.                     i++;
  53.                 }
  54.             }
  55.       else
  56.             {
  57.                 if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET )  //发送缓冲区非空
  58.                 {

  59.                     //SPI_TransmitCRC函数通过控制SPIx_CTLR1寄存器的CRCNEXT位实现,当将CRCNEXT位置1,SPIx_TCRCR寄存器的内容将在当前字节发送之后发出。
  60.                     SPI_TransmitCRC( SPI1 );

  61.                     SPI_I2S_SendData( SPI1, TxData[i] );  //发送数据
  62.                     i++;
  63.                 }
  64.             }
  65.         }

  66.         while( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET )  //发送缓冲区非空
  67.         {
  68.             if(SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_BSY ) == RESET)  //SPI不在通讯状态
  69.             {
  70.                 SPI_Cmd( SPI1, DISABLE );
  71.                 crcval = SPI_GetCRC( SPI1, SPI_CRC_Tx );  //获取SPI1发送CRC寄存器的值
  72.                 printf( "CRC:%02x\r\n", crcval );
  73.                 while(1);
  74.             }
  75.         }

  76. #elif (SPI_MODE == SLAVE_MODE)
  77.         while( i<4 )
  78.         {

  79.             if( i<3 )
  80.             {
  81.                 if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET )  //接收缓冲区为空
  82.                 {
  83.                     RxData[i] = SPI_I2S_ReceiveData( SPI1 );  //接收数据
  84.                     i++;
  85.                 }
  86.             }
  87.             else if( i == 3)
  88.             {
  89.                 //在发送或者接收最后一个数据之前需将SPI控制寄存器1(SPIx_CTLR1)CRCNEXT位置1
  90.                 SPI_TransmitCRC( SPI1 );

  91.                 if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET )
  92.                 {
  93.                     RxData[i] = SPI_I2S_ReceiveData( SPI1 );
  94.                     i++;
  95.                 }
  96.             }
  97.             else
  98.             {
  99.                 if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET )
  100.                 {
  101.                     RxData[i] = SPI_I2S_ReceiveData( SPI1 );
  102.                     i++;
  103.                 }
  104.             }
  105.         }

  106.         crcval = SPI_GetCRC( SPI1, SPI_CRC_Rx );  //获取SPI1接收CRC寄存器的值
  107.         printf( "CRC:%02x\r\n", crcval );

  108.         for( i=0; i<4; i++ )
  109.         {
  110.             printf( "Rxdata:%02x\r\n", RxData[i] );
  111.         }

  112.         while(1);

  113. #endif
  114.     }
  115. }
main.c文件主要进行主机和从机下的数据发送和接收。并将接收数据与发送数据进行对比,当发送数据与接收数据相同,输出same,若不同,输出different。

4下载验证
将编译好的程序分别在主机模式和从机模式下下载到两个开发版,并将主机的引脚与从机的引脚一一对应进行连接,开发板上电后,串口打印如下:
主机打印:
图片2.png
从机打印:
图片3.png


  

51、SPI-CRC.rar

467.53 KB, 下载次数: 228

gejigeji521 发表于 2021-2-22 12:01 | 显示全部楼层
工作以来,还没用过CRC,看看怎么用。
单片小菜 发表于 2021-2-23 15:26 | 显示全部楼层
CRC是8还是CRC16的?
真爱吴迪迪 发表于 2021-2-23 15:44 | 显示全部楼层
说实话,第一次看见SPI的CRC校验的。
 楼主| RISCVLAR 发表于 2021-2-23 15:56 | 显示全部楼层

SPI接口提供了两种CRC计算方法,取决于所选的发送或接收的数据帧格式:8位数据帧采用CRC8;16位数据帧采用CRC16。
hezzz 发表于 2023-2-6 11:42 | 显示全部楼层
很少见spi crc feature 测试的,学习一下。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

133

主题

296

帖子

45

粉丝
快速回复 在线客服 返回列表 返回顶部