[RISC-V MCU 应用开发] 第五十三章、CH32V103应用教程——SPI-DMA

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

CH32V103应用教程——SPI-DMA

本章教程主要在SPI全双工模式下使用DMA进行SPI通信实验。

1、SPI简介及相关函数介绍
SPI模块支持使用DMA加快数据通讯速度,可以使用DMA向发送缓冲区填写数据,或者使用DMA从接受缓冲区及时取走数据。DMA会以RXNE和TXE为信号及时取走或发来数据。DMA也可以工作在单工或者加CRC校验的模式。
当SPI控制寄存器2(SPI_CTLR2)上的对应使能位(RXDMAEN位和TXDMAEN位)被置1时,可以启用接收缓冲区DMA或者发送缓冲区DMA。
● 发送数据时,当发送缓冲区为空时(即SPI状态寄存器位TXE=1)发出DMA请求,DMA控制器则写数据至SPI数据寄存器(SPIx_DATAR),TXE标志因此而被清除。
● 接收数据时,当接收缓冲区非空时(即SPI状态寄存器位RXNE=1)发出DMA请求,DMA控制器则从SPI数据寄存器(SPIx_DATAR)读出数据,RXNE标志因此而被清除。
当只使用SPI发送数据时,只需使能SPI的发送DMA通道。此时,因为没有读取收到的数据,SPI状态寄存器(SPIx_STATR)OVR位被置1(译注:软件不必理会这个标志)。
当只使用SPI接收数据时,只需使能SPI的接收DMA通道。
在发送模式下,当DMA已经传输了所有要发送的数据(DMA中断状态寄存器(DMA_INTFR)的TCIF标志变为1(传输完成))后,可以通过监视BSY标志以确认SPI通信结束,这样可以避免在关闭SPI或进入停止模式时,破坏最后一个数据的传输。因此软件需要先等待TXE=1,然后等待BSY=0。
注:在不连续的通信中,在写数据到SPIx_DATAR的操作与BSY位被置为’1’之间,有2个APB时钟周期的延迟,因此,在写完最后一个数据后需要先等待TXE=1再等待BSY=0。

2、硬件设计
本章教程主要在SPI全双工模式下使用DMA发送和接收数据,需用到两个开发板,且由于采用全双工模式,因此主设备和从设备均要使用MOSI引脚和MISO引脚以及SCK引脚进行通讯。此处使用外设为SPI1,主设备和从设备MOSI对应引脚均为PA7引脚、MISO对应引脚均为PA6引脚,将主设备PA6、PA7引脚与从设备PA6、PA7引脚一一对应连接起来,此外还需将两个开发板SPI1对应的SCK引脚PA5连接起来。
此外,由于两个开发板需要同时进行上电传输,因此将两个开发板的3.3V引脚和GND引脚进行连接。

3软件设计
本章教程主要在第50章基础上进行,通过使用DMA进行数据发送和接收,具体程序如下:
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  18

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

  13. void SPI_FullDuplex_Init(void);
  14. void DMA_Tx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize);
  15. void DMA_Rx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize);

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

  2. /* Global Variable */

  3. u16 TxData[Size] = { 0x0101, 0x0202, 0x0303, 0x0404, 0x0505, 0x0606,
  4.                      0x1111, 0x1212, 0x1313, 0x1414, 0x1515, 0x1616,
  5.                      0x2121, 0x2222, 0x2323, 0x2424, 0x2525, 0x2626 };
  6. u16 RxData[Size];

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

  17.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE );

  18. #if (SPI_MODE == HOST_MODE)
  19.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  20.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //复用推挽输出
  21.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  22.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  23.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  24.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;   //浮空输入
  25.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  26.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  27.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  28.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  29.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  30. #elif (SPI_MODE == SLAVE_MODE)
  31.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
  32.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  33.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  34.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
  35.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  36.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  37.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  38.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;
  39.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
  40.     GPIO_Init( GPIOA, &GPIO_InitStructure );

  41. #endif

  42.     SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;   //全双工模式

  43. #if (SPI_MODE == HOST_MODE)
  44.     SPI_InitStructure.SPI_Mode = SPI_Mode_Master;  //主机模式

  45. #elif (SPI_MODE == SLAVE_MODE)
  46.     SPI_InitStructure.SPI_Mode = SPI_Mode_Slave;   //从机模式

  47. #endif

  48.     SPI_InitStructure.SPI_DataSize = SPI_DataSize_16b; //16位数据格式
  49.     SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;         //时钟低电平有效
  50.     SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;       //数据捕获于第一个时钟沿
  51.     SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;          //软件控制NSS引脚
  52.     SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64;   //64分频
  53.     SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_LSB; //数据传输:LSB
  54.     SPI_InitStructure.SPI_CRCPolynomial = 7;   //CRC计算用到的多项式
  55.     SPI_Init( SPI1, &SPI_InitStructure );

  56. #if (SPI_MODE == HOST_MODE)
  57.     SPI_I2S_DMACmd( SPI1, SPI_I2S_DMAReq_Tx, ENABLE );  //SPI1DMA发送通道使能
  58.     SPI_I2S_DMACmd( SPI1, SPI_I2S_DMAReq_Rx, ENABLE );  //SPI1DMA接收通道使能

  59. #endif

  60.     SPI_Cmd( SPI1, ENABLE );
  61. }

  62. /*******************************************************************************
  63. * Function Name  : DMA_Tx_Init
  64. * Description    : Initializes the SPI1 DMA Channelx configuration.
  65. * Input          : DMA_CHx:
  66. *                    x can be 1 to 7.
  67. *                  ppadr: Peripheral base address.
  68. *                  memadr: Memory base address.
  69. *                  bufsize: DMA channel buffer size.
  70. * Return         : None
  71. *******************************************************************************/
  72. void DMA_Tx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize)
  73. {
  74.     DMA_InitTypeDef DMA_InitStructure;

  75.     RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

  76.     DMA_DeInit(DMA_CHx);

  77.     DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;  //外设基地址
  78.     DMA_InitStructure.DMA_MemoryBaseAddr = memadr;     //内存基地址
  79.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //外设作为数据传输的目的地
  80.     DMA_InitStructure.DMA_BufferSize = bufsize;        //DMA缓存大小
  81.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //指定外设地址不变。
  82.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;          //指定存储器地址递增。
  83.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;  //设置外设数据单位
  84.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;          //设置存储器数据单位
  85.     DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;            //设置对应DMA工作模式为正常模式
  86.     DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;  //设置DMA通道优先级
  87.     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止使能DMA存储器到存储器的传输方式
  88.     DMA_Init( DMA_CHx, &DMA_InitStructure );     //初始化DMA
  89. }

  90. /*******************************************************************************
  91. * Function Name  : DMA_Rx_Init
  92. * Description    : Initializes the SPI1 DMA Channelx configuration.
  93. * Input          : DMA_CHx:
  94. *                    x can be 1 to 7.
  95. *                  ppadr; Peripheral base address.
  96. *                  memadr: Memory base address.
  97. *                  bufsize: DMA channel buffer size.
  98. * Return         : None
  99. *******************************************************************************/
  100. void DMA_Rx_Init( DMA_Channel_TypeDef* DMA_CHx, u32 ppadr, u32 memadr, u16 bufsize )
  101. {
  102.     DMA_InitTypeDef DMA_InitStructure;

  103.     RCC_AHBPeriphClockCmd( RCC_AHBPeriph_DMA1, ENABLE );

  104.     DMA_DeInit(DMA_CHx);

  105.     DMA_InitStructure.DMA_PeripheralBaseAddr = ppadr;
  106.     DMA_InitStructure.DMA_MemoryBaseAddr = memadr;
  107.     DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
  108.     DMA_InitStructure.DMA_BufferSize = bufsize;
  109.     DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
  110.     DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
  111.     DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
  112.     DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
  113.     DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
  114.     DMA_InitStructure.DMA_Priority = DMA_Priority_High;
  115.     DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
  116.     DMA_Init( DMA_CHx, &DMA_InitStructure );
  117. }
spi.c文件主要包括3个函数:SPI_FullDuplex_Init函数、DMA_Tx_Init函数以及DMA_Rx_Init函数。SPI_FullDuplex_Init函数主要进行SPI1全双工通信模式下的主机和从机配置。首先,由于采用全双工通信方式,且采用软件控制NSS引脚,因此需要对主机和从机的SCK引脚、MOSI引脚和MISO引脚进行GPIO初始化配置。此外,还需要进行主机和从机配置,此配置可根据CH32V103应用手册主模式和从模式配置步骤进行,主要对SPI通信的通信方向、主从模式、数据帧大小、时钟极性、时钟相位、NSS引脚使用方式、波特率等进行配置,可对照手册参考标准库函数ch32v10x_spi.c文件中SPI_Init函数进行配置。DMA_Tx_Init函数主要进行DMA发送数据初始化配置,DMA_Rx_Init函数主要进行DMA接收数据初始化配置。
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. #include "string.h"

  11. /*
  12. *@Note
  13. SPI使用DMA,Master/Slave 模式收发例程:
  14. Master:SPI1_SCK(PA5)、SPI1_MISO(PA6)、SPI1_MOSI(PA7)。
  15. Slave :SPI1_SCK(PA5)、SPI1_MISO(PA6)、SPI1_MOSI(PA7)。

  16. 本例程演示 Master 和 Slave 同时使用 DAM 全双工收发。
  17. 注:两块板子分别下载 Master 和 Slave 程序,同时上电。
  18.      硬件连线:PA5 —— PA5
  19.            PA6 —— PA6
  20.            PA7 —— PA7
  21. */
  22. /*******************************************************************************
  23. * Function Name  : main
  24. * Description    : Main program.
  25. * Input          : None
  26. * Return         : None
  27. *******************************************************************************/
  28. int main(void)
  29. {
  30.     u8 i=0,j=0;

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

  34.     SPI_FullDuplex_Init();  //SPI初始化

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

  38. #endif

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

  42.     DMA_Tx_Init( DMA1_Channel3, (u32)&SPI1->DATAR, (u32)TxData, Size );  //DMA发送通道初始化
  43.     DMA_Rx_Init( DMA1_Channel2, (u32)&SPI1->DATAR, (u32)RxData, Size );  //DMA接收通道初始化
  44.     DMA_Cmd( DMA1_Channel3, ENABLE );  //开启DMA1通道3发送数据
  45.     DMA_Cmd( DMA1_Channel2, ENABLE );  //开启DMA1通道2接收数据

  46. #endif

  47.     while(1)
  48.     {
  49. #if (SPI_MODE == HOST_MODE)

  50.         /* 等待接收通道接收完成和发送通道发送完成 */
  51.         while( (!DMA_GetFlagStatus(DMA1_FLAG_TC2)) &&  (!DMA_GetFlagStatus(DMA1_FLAG_TC3)) );

  52.         for( i=0; i<Size; i++ )
  53.         {
  54.             printf( " RxData:%04x\r\n", RxData[i] );
  55.         }

  56. #elif (SPI_MODE == SLAVE_MODE)
  57.         while( ( i<Size ) || ( j<Size ))
  58.         {
  59.             if( i<Size )
  60.             {
  61.                 if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_TXE ) != RESET )
  62.                 {
  63.                     SPI_I2S_SendData( SPI1, TxData[i] );
  64.                     i++;
  65.                 }
  66.             }

  67.             if( j<Size )
  68.             {
  69.                 if( SPI_I2S_GetFlagStatus( SPI1, SPI_I2S_FLAG_RXNE ) != RESET )
  70.                 {
  71.                     RxData[j] = SPI_I2S_ReceiveData( SPI1 );
  72.                     j++;
  73.                 }
  74.             }
  75.         }

  76.         for( i=0; i<Size; i++ )
  77.         {
  78.             printf( " RxData:%04x\r\n", RxData[i] );
  79.         }

  80. #endif

  81.         while(1);
  82.     }
  83. }

main.c文件主要进行主机和从机下的数据发送和接收。

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






  

52、SPI-DMA.rar

467.25 KB, 下载次数: 159

gejigeji521 发表于 2021-2-22 12:01 | 显示全部楼层
这些教程可以打包下载吗
 楼主| RISCVLAR 发表于 2021-2-22 14:07 | 显示全部楼层
gejigeji521 发表于 2021-2-22 12:01
这些教程可以打包下载吗

目前这些程序还需要点击附件一个个下载,后面可以考虑整合一下放个压缩包
嵌入小菜菜 发表于 2021-2-23 16:26 | 显示全部楼层
只要是有DMA的地方,外围的资源就跟白给一样,设置好了,就可以了。
dongnanxibei 发表于 2021-3-13 15:42 | 显示全部楼层
准备认真学一下DMA。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

133

主题

296

帖子

44

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

133

主题

296

帖子

44

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