查看: 1858|回复: 0
收起左侧

第五十二章:CH32V103应用教程——SPI-CRC校验

[复制链接]

  离线 

  • TA的每日心情
    慵懒
    2021-7-23 17:16
  • 签到天数: 17 天

    [LV.4]

    发表于 2021-4-29 19:28:11 | 显示全部楼层 |阅读模式

    有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    本帖最后由 草帽王子 于 2021-9-10 17:43 编辑

    本章教程主要在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、下载验证

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

    主机打印:
    CH32V CH573单片机芯片-第五十二章:CH32V103应用教程——SPI-CRC校验risc-v单片机中文社区(1)
    从机打印:
    CH32V CH573单片机芯片-第五十二章:CH32V103应用教程——SPI-CRC校验risc-v单片机中文社区(2)


    51、SPI-CRC.rar

    CH32V CH573单片机芯片-第五十二章:CH32V103应用教程——SPI-CRC校验risc-v单片机中文社区(3) 51、SPI-CRC.rar (467.53 KB, 下载次数: 11)
    链接:https://pan.baidu.com/s/14dC407_qxwYiItx1qGf-xQ
    提取码:w5fz
    复制这段内容后打开百度网盘手机App,操作更方便哦







    上一篇:第五十一章:SPI-全双工通信,硬件控制NSS模式
    下一篇:五十三章:CH32V103应用教程——SPI-DMA
    RISCV作者优文
    全球首家只专注于RISC-V单片机行业应用的中文网站
    回复

    使用道具 举报

    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    关闭

    RISC-V单片机中文网上一条 /2 下一条



    版权及免责声明|RISC-V单片机中文网 |网站地图

    GMT+8, 2024-4-25 17:01 , Processed in 0.633368 second(s), 48 queries .

    快速回复 返回顶部 返回列表