123下一页
返回列表 发新帖我要提问本帖赏金: 100.00元(功能说明)

[技术问答] 【又换MCU】越来越好用系列,新塘031 SPI PDMA通信。

[复制链接]
32991|44
 楼主| 呐咯密密 发表于 2021-7-5 16:20 | 显示全部楼层 |阅读模式
[url=home.php?mod=space&uid=760190]@21小跑堂 #申请原创#[/url]
开篇废话
依旧是开篇的逼逼叨叨。
自从上周换了新塘的M031,现在对这个MCU真的喜欢,写代码巨舒适,简单快速就能上手。上周写了入门的UART的DMA,今天来搞一个SPI 的DMA通信,实用的外设,咱一个都不能少。
M031 SPI概述
SPI接口是全双工同步串行数据通讯接口,可做为主机或从机,用 4 线双向通讯。 M031 包含 1 组 SPI控制器,用于对从外围设备接收到的数据执行串并转换,并对发送到外围设备的数据进行并串转换。 每个SPI控制器可以配置为主设备或从设备,并支持PDMA功能存取数据缓冲区。 每个SPI控制器还支持I2S模式来连接外部音频编解码器。
特征:
– 1组 SPI 控制器
– 支持主机模式和从机模式
– 传输位长可为 8 ~ 32位
– 提供独立的4级32位(或8级16位)收发FIFO缓存,实际数据位长由SPI的设置决定
– 支持高位优先(MSB)或低位优先(LSB)时序
– 支持字节重排功能
– 支持字节或字暂停模式
– 总线时钟主机模式最高可到24 MHz ,从机模式最高可到16 MHz (当芯片工作在 VDD =1.8~3.6V)
– 支持一数据通道半双工传输
– 支持只接收模式
– 支持 PDMA 传输
框图
1476560e2b71957933.png
代码片
时钟以及GPIO初始化
  1. void SYS_Init(void)
  2. {

  3.     /*---------------------------------------------------------------------------------------------------------*/
  4.     /* Init System Clock                                                                                       */
  5.     /*---------------------------------------------------------------------------------------------------------*/
  6.     /* Unlock protected registers */
  7.     SYS_UnlockReg();

  8.     /* Enable HIRC clock (Internal RC 48MHz) */
  9.     CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);

  10.     /* Wait for HIRC clock ready */
  11.     CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);

  12.     /* Select HCLK clock source as HIRC and HCLK source divider as 1 */
  13.     CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1));

  14.     /* Set both PCLK0 and PCLK1 as HCLK */
  15.     CLK->PCLKDIV = CLK_PCLKDIV_APB0DIV_DIV1 | CLK_PCLKDIV_APB1DIV_DIV1;

  16.     /* Select IP clock source */
  17.     /* Select UART0 clock source is HIRC */
  18.     CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC, CLK_CLKDIV0_UART0(1));
  19.     /* Select UART1 clock source is HIRC */
  20.     CLK_SetModuleClock(UART1_MODULE, CLK_CLKSEL1_UART1SEL_HIRC, CLK_CLKDIV0_UART1(1));
  21.     /* Select PCLK1 as the clock source of SPI0 */
  22.     CLK_SetModuleClock(SPI0_MODULE, CLK_CLKSEL2_SPI0SEL_PCLK1, MODULE_NoMsk);
  23.        
  24.     /* Enable UART0 peripheral clock */
  25.     CLK_EnableModuleClock(UART0_MODULE);
  26.     /* Enable UART1 peripheral clock */
  27.     CLK_EnableModuleClock(UART1_MODULE);
  28.     /* Enable PDMA module clock */
  29.     CLK_EnableModuleClock(PDMA_MODULE);
  30.     /* Enable SPI0 peripheral clock */
  31.     CLK_EnableModuleClock(SPI0_MODULE);
  32.        
  33.     /* Update System Core Clock */
  34.     /* User can use SystemCoreClockUpdate() to calculate PllClock, SystemCoreClock and CycylesPerUs automatically. */
  35.     SystemCoreClockUpdate();

  36.     /*---------------------------------------------------------------------------------------------------------*/
  37.     /* Init I/O Multi-function                                                                                 */
  38.     /*---------------------------------------------------------------------------------------------------------*/

  39.     /* Set PB multi-function pins for UART0 RXD=PB.12 and TXD=PB.13 */
  40.     SYS->GPB_MFPH = (SYS->GPB_MFPH & ~(SYS_GPB_MFPH_PB12MFP_Msk | SYS_GPB_MFPH_PB13MFP_Msk))    |       \
  41.                     (SYS_GPB_MFPH_PB12MFP_UART0_RXD | SYS_GPB_MFPH_PB13MFP_UART0_TXD);

  42.     /* Set PB multi-function pins for UART1 RXD(PB.2) and TXD(PB.3) */
  43.     SYS->GPB_MFPL = (SYS->GPB_MFPL & ~(SYS_GPB_MFPL_PB2MFP_Msk | SYS_GPB_MFPL_PB3MFP_Msk))    |       \
  44.                     (SYS_GPB_MFPL_PB2MFP_UART1_RXD | SYS_GPB_MFPL_PB3MFP_UART1_TXD);

  45.     /* Setup SPI0 multi-function pins */
  46.     /* PA.3 is SPI0_SS,   PA.2 is SPI0_CLK,
  47.        PA.1 is SPI0_MISO, PA.0 is SPI0_MOSI*/
  48.     SYS->GPA_MFPL = (SYS->GPA_MFPL & ~(SYS_GPA_MFPL_PA3MFP_Msk |
  49.                                        SYS_GPA_MFPL_PA2MFP_Msk |
  50.                                        SYS_GPA_MFPL_PA1MFP_Msk |
  51.                                        SYS_GPA_MFPL_PA0MFP_Msk)) |
  52.                     (SYS_GPA_MFPL_PA3MFP_SPI0_SS |
  53.                      SYS_GPA_MFPL_PA2MFP_SPI0_CLK |
  54.                      SYS_GPA_MFPL_PA1MFP_SPI0_MISO |
  55.                      SYS_GPA_MFPL_PA0MFP_SPI0_MOSI);
  56.                                          
  57.     /* Lock protected registers */
  58.     SYS_LockReg();
  59. }

依据新塘的代码风格,系统的初始化被安排在SYS_Init()中完成所有的系统时钟和各个外设时钟的初始化,在初始化时钟之后初始化外设的引脚,设置各种复用,这里初始化了UART0 和 UART1的外设以及SPI0的外设。因为采用自动的NSS,所以这里将NSS的引脚也复用。
SPI初始化
  1. void SPI_Init(void)
  2. {
  3.     /*---------------------------------------------------------------------------------------------------------*/
  4.     /* Init SPI                                                                                                */
  5.     /*---------------------------------------------------------------------------------------------------------*/
  6.     /* Configure as a master, clock idle low, 32-bit transaction, drive output on falling clock edge and latch input on rising edge. */
  7.     /* Set IP clock divider. SPI clock rate = 2 MHz */
  8.     SPI_Open(SPI0, SPI_MASTER, SPI_MODE_0, 8, SPI_CLK_FREQ);

  9.     /* Enable the automatic hardware slave select function. Select the SS pin and configure as low-active. */
  10.         SPI_EnableAutoSS(SPI0, SPI_SS, SPI_SS_ACTIVE_LOW);
  11. }
SPI的初始化依旧是两个函数。
第一个SPI_Open(SPI_T *spi,
                  uint32_t u32MasterSlave,
                  uint32_t u32SPIMode,
                  uint32_t u32DataWidth,
                  uint32_t u32BusClock));
内置5个参数,
SPI_T *spi:选定开启的SPI口。我们选择SPI0。
uint32_t u32MasterSlave:选择主模式或者从模式。这里选择主模式。
uint32_t u32SPIMode:选择SPI的模式,这里有4个待选参数,主要是确定SPI的空闲电平,在上升沿还是下降沿读取数据,在第几个沿读数据等,这个在每个MCU都有该配置,只是M031这里做了一个集合。下图为四个可选参数。
8645760e2bbaae2070.png
下面给一张控制SPI设备的各个寄存器值设定图,
2306660e2bc8ae825a.png
我这里选择SPI_MODE_0,即:将SPI时钟的空闲状态设为低电平,选择数据在SPI总线时钟的下降沿传输,选择数据在SPI总线时钟的上升沿锁存。
uint32_t u32DataWidth:为数据宽度,可在8-32位之间选择,我这里选择最低的8位,这个是比较常见的数据。
uint32_t u32BusClock:设置SPI的时钟,最大24MHz。


第二个函数为SPI_EnableAutoSS(SPI0, SPI_SS, SPI_SS_ACTIVE_LOW);设置硬件使能,自动从机选择功能。如果无需自动从选,可使用SPI_DisableAutoSS(SPI0);进行屏蔽,此时可任选一个通用IO控制从选信号。
SPI读写数据:
  1. /* Write to TX register */
  2.         SPI_WRITE_TX(SPI0, 0x50650F);
  3.         /* Check SPI0 busy status */
  4.         while(SPI_IS_BUSY(SPI0));
  5.         i32Err = SPI_READ_RX(SPI0);       
SPI_WRITE_TX()进行SPI数据的发送,在等待发送完成后用SPI_READ_RX()进行读数据便可完成一次完整的SPI数据读写。
PDMA设置
  1. void SPI_DMAInit(void)
  2. {
  3.     /* Reset PDMA module */
  4.     SYS_ResetModule(PDMA_RST);

  5.     /* Enable PDMA channels */
  6.     PDMA_Open(PDMA, SPI_OPENED_CH);

  7.     /*=======================================================================
  8.       SPI master PDMA TX channel configuration:
  9.       -----------------------------------------------------------------------
  10.         Word length = 32 bits
  11.         Transfer Count = DATA_COUNT
  12.         Source = g_au32MasterToSlaveTestPattern
  13.         Source Address = Incresing
  14.         Destination = SPI0->TX
  15.         Destination Address = Fixed
  16.         Burst Type = Single Transfer
  17.     =========================================================================*/
  18.     /* Set transfer width (32 bits) and transfer count */
  19.     PDMA_SetTransferCnt(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_WIDTH_8, DATA_COUNT);
  20.     /* Set source/destination address and attributes */
  21.     PDMA_SetTransferAddr(PDMA, SPI_MASTER_TX_DMA_CH, (uint32_t)SPI_TX, PDMA_SAR_INC, (uint32_t)&SPI0->TX, PDMA_DAR_FIX);
  22.     /* Set request source; set basic mode. */
  23.     PDMA_SetTransferMode(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_SPI0_TX, FALSE, 0);
  24.     /* Single request type. SPI only support PDMA single request type. */
  25.     PDMA_SetBurstType(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_REQ_SINGLE, 0);
  26.     /* Disable table interrupt */
  27.     PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL |= PDMA_DSCT_CTL_TBINTDIS_Msk;

  28.     /*=======================================================================
  29.       SPI master PDMA RX channel configuration:
  30.       -----------------------------------------------------------------------
  31.         Word length = 32 bits
  32.         Transfer Count = DATA_COUNT
  33.         Source = SPI0->RX
  34.         Source Address = Fixed
  35.         Destination = g_au32MasterRxBuffer
  36.         Destination Address = Increasing
  37.         Burst Type = Single Transfer
  38.     =========================================================================*/
  39.     /* Set transfer width (32 bits) and transfer count */
  40.     PDMA_SetTransferCnt(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_WIDTH_8, DATA_COUNT);
  41.     /* Set source/destination address and attributes */
  42.     PDMA_SetTransferAddr(PDMA, SPI_MASTER_RX_DMA_CH, (uint32_t)&SPI0->RX, PDMA_SAR_FIX, (uint32_t)SPI_RX, PDMA_DAR_INC);
  43.     /* Set request source; set basic mode. */
  44.     PDMA_SetTransferMode(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_SPI0_RX, FALSE, 0);
  45.     /* Single request type. SPI only support PDMA single request type. */
  46.     PDMA_SetBurstType(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_REQ_SINGLE, 0);
  47.     /* Disable table interrupt */
  48.     PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL |= PDMA_DSCT_CTL_TBINTDIS_Msk;

  49.     /* Enable SPI master DMA function */
  50.     SPI_TRIGGER_TX_PDMA(SPI0);
  51.     SPI_TRIGGER_RX_PDMA(SPI0);
  52. }
这里PDMA设置和UART差不多,不罗里吧嗦的胡扯了,主要就是一个SPI的使能,这里用SPI_TRIGGER_TX_PDMA()和SPI_TRIGGER_RX_PDMA()。这个是UART的SPI没有的。
在这个初始化完成后会进行一次发送和接收,如果不需要请删除                    SPI_TRIGGER_TX_PDMA(SPI0); SPI_TRIGGER_RX_PDMA(SPI0);
如果想要重复PDMA 的发送可做如下处理:
  1.                     /* Re-trigger */
  2.                     /* Master PDMA TX channel configuration */
  3.                     /* Set transfer width (32 bits) and transfer count */
  4.                     PDMA_SetTransferCnt(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_WIDTH_32, DATA_COUNT);
  5.                     /* Set request source; set basic mode. */
  6.                     PDMA_SetTransferMode(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_SPI0_TX, FALSE, 0);

  7.                     /* Master PDMA RX channel configuration */
  8.                     /* Set transfer width (32 bits) and transfer count */
  9.                     PDMA_SetTransferCnt(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_WIDTH_32, DATA_COUNT);
  10.                     /* Set request source; set basic mode. */
  11.                     PDMA_SetTransferMode(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_SPI0_RX, FALSE, 0);

  12.                     /* Enable master's DMA transfer function */
  13.                     SPI_TRIGGER_TX_PDMA(SPI0);
  14.                     SPI_TRIGGER_RX_PDMA(SPI0);
可以封装为一个函数,每次需要启动DMA就调用一次该函数。但是这里又是老问题,启动DMA的速度慢,示波器测试需要8个微秒左右吧,时间过长。同样我们可以混搭寄存器操作,加快代码执行效率,这个技巧非常实用;
  1. void SPI_DMA_WRITE(void)
  2. {
  3.         PB1 = 0;
  4.         /* Master PDMA TX channel configuration */
  5.         PDMA->CHCTL |= (1 << SPI_MASTER_TX_DMA_CH); /* Enable PDMA channel */
  6.         PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL =
  7.                 (PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL & (~(PDMA_DSCT_CTL_TXCNT_Msk | PDMA_DSCT_CTL_OPMODE_Msk))) |
  8.                 (DATA_COUNT - 1) << PDMA_DSCT_CTL_TXCNT_Pos | /* Transfer count */
  9.                 1 << PDMA_DSCT_CTL_OPMODE_Pos;
  10.         /* Master PDMA RX channel configuration */
  11.         PDMA->CHCTL |= (1 << SPI_MASTER_RX_DMA_CH); /* Enable PDMA channel */
  12.         PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL =
  13.                 (PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL & (~(PDMA_DSCT_CTL_TXCNT_Msk | PDMA_DSCT_CTL_OPMODE_Msk))) |
  14.                 (DATA_COUNT - 1) << PDMA_DSCT_CTL_TXCNT_Pos | /* Transfer count */
  15.                 1 << PDMA_DSCT_CTL_OPMODE_Pos;
  16.         /* Enable master's DMA transfer function */
  17.         SPI0->PDMACTL = (SPI_PDMACTL_RXPDMAEN_Msk | SPI_PDMACTL_TXPDMAEN_Msk);
  18.         while (PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL & PDMA_DSCT_CTL_TXCNT_Msk) ;
  19.         PB1 = 1;
  20. }
这样,M031的DMA就完成了,如果需要启动DMA传输,就调用SPI_DMA_WRITE()函数便可。

打赏榜单

21小跑堂 打赏了 100.00 元 2021-07-06
理由:恭喜通过原创奖文章审核!请多多加油哦!

aspoke 发表于 2021-7-7 21:19 | 显示全部楼层
新唐全新M031/32微控制器  
232321122 发表于 2021-7-7 21:19 | 显示全部楼层
有测评的吗   
ghuca 发表于 2021-7-7 21:20 | 显示全部楼层
M031原理图有吗  
soodesyt 发表于 2021-7-7 21:20 | 显示全部楼层
            
mnynt121 发表于 2021-7-7 21:20 | 显示全部楼层
M031BT?            
plsbackup 发表于 2021-7-7 21:21 | 显示全部楼层
希望越来越好用系列  
kmzuaz 发表于 2021-7-7 21:21 | 显示全部楼层
这个供货量怎么样   
qiufengsd 发表于 2021-7-7 21:22 | 显示全部楼层
硬件spi吗   
jstgotodo 发表于 2021-7-7 21:22 | 显示全部楼层
            
quickman 发表于 2021-7-7 21:23 | 显示全部楼层
最大的传输速度是多少     
soodesyt 发表于 2021-7-7 21:23 | 显示全部楼层
跟着楼主多多学习了。   
ghuca 发表于 2021-7-7 21:23 | 显示全部楼层
来个相关 资料学习一下。   
232321122 发表于 2021-7-7 21:23 | 显示全部楼层
性能怎么样
aspoke 发表于 2021-7-7 21:23 | 显示全部楼层
基于 Arm® Cortex®-M0 CPU 的 32 位 微控制器  
jstgotodo 发表于 2021-7-7 21:23 | 显示全部楼层
谢谢楼主分享的。   
quickman 发表于 2021-7-7 21:23 | 显示全部楼层
可以到4Mhz吗?
qiufengsd 发表于 2021-7-7 21:23 | 显示全部楼层
这个硬件spi有bug吗?  
kmzuaz 发表于 2021-7-7 21:23 | 显示全部楼层
会不会断货呢            
plsbackup 发表于 2021-7-7 21:23 | 显示全部楼层
价格怎么样   
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:苏州澜宭自动化科技嵌入式工程师
简介:本人从事磁编码器研发工作,负责开发2500线增量式磁编码器以及17位、23位绝对值式磁编码器,拥有多年嵌入式开发经验,精通STM32、GD32、N32等多种品牌单片机,熟练使用单片机各种外设。

568

主题

4085

帖子

56

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