开篇废话
依旧是开篇的逼逼叨叨。
自从上周换了新塘的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 传输

框图

代码片
时钟以及GPIO初始化

void SYS_Init(void){/*---------------------------------------------------------------------------------------------------------*//* Init System Clock                                                                                       *//*---------------------------------------------------------------------------------------------------------*//* Unlock protected registers */SYS_UnlockReg();/* Enable HIRC clock (Internal RC 48MHz) */CLK_EnableXtalRC(CLK_PWRCTL_HIRCEN_Msk);/* Wait for HIRC clock ready */CLK_WaitClockReady(CLK_STATUS_HIRCSTB_Msk);/* Select HCLK clock source as HIRC and HCLK source divider as 1 */CLK_SetHCLK(CLK_CLKSEL0_HCLKSEL_HIRC, CLK_CLKDIV0_HCLK(1));/* Set both PCLK0 and PCLK1 as HCLK */CLK->PCLKDIV = CLK_PCLKDIV_APB0DIV_DIV1 | CLK_PCLKDIV_APB1DIV_DIV1;/* Select IP clock source *//* Select UART0 clock source is HIRC */CLK_SetModuleClock(UART0_MODULE, CLK_CLKSEL1_UART0SEL_HIRC, CLK_CLKDIV0_UART0(1));/* Select UART1 clock source is HIRC */CLK_SetModuleClock(UART1_MODULE, CLK_CLKSEL1_UART1SEL_HIRC, CLK_CLKDIV0_UART1(1));/* Select PCLK1 as the clock source of SPI0 */CLK_SetModuleClock(SPI0_MODULE, CLK_CLKSEL2_SPI0SEL_PCLK1, MODULE_NoMsk);/* Enable UART0 peripheral clock */CLK_EnableModuleClock(UART0_MODULE);/* Enable UART1 peripheral clock */CLK_EnableModuleClock(UART1_MODULE);/* Enable PDMA module clock */CLK_EnableModuleClock(PDMA_MODULE);/* Enable SPI0 peripheral clock */CLK_EnableModuleClock(SPI0_MODULE);/* Update System Core Clock *//* User can use SystemCoreClockUpdate() to calculate PllClock, SystemCoreClock and CycylesPerUs automatically. */SystemCoreClockUpdate();/*---------------------------------------------------------------------------------------------------------*//* Init I/O Multi-function                                                                                 *//*---------------------------------------------------------------------------------------------------------*//* Set PB multi-function pins for UART0 RXD=PB.12 and TXD=PB.13 */SYS->GPB_MFPH = (SYS->GPB_MFPH & ~(SYS_GPB_MFPH_PB12MFP_Msk | SYS_GPB_MFPH_PB13MFP_Msk))    |       \(SYS_GPB_MFPH_PB12MFP_UART0_RXD | SYS_GPB_MFPH_PB13MFP_UART0_TXD);/* Set PB multi-function pins for UART1 RXD(PB.2) and TXD(PB.3) */SYS->GPB_MFPL = (SYS->GPB_MFPL & ~(SYS_GPB_MFPL_PB2MFP_Msk | SYS_GPB_MFPL_PB3MFP_Msk))    |       \(SYS_GPB_MFPL_PB2MFP_UART1_RXD | SYS_GPB_MFPL_PB3MFP_UART1_TXD);/* Setup SPI0 multi-function pins *//* PA.3 is SPI0_SS,   PA.2 is SPI0_CLK,PA.1 is SPI0_MISO, PA.0 is SPI0_MOSI*/SYS->GPA_MFPL = (SYS->GPA_MFPL & ~(SYS_GPA_MFPL_PA3MFP_Msk |SYS_GPA_MFPL_PA2MFP_Msk |SYS_GPA_MFPL_PA1MFP_Msk |SYS_GPA_MFPL_PA0MFP_Msk)) |(SYS_GPA_MFPL_PA3MFP_SPI0_SS |SYS_GPA_MFPL_PA2MFP_SPI0_CLK |SYS_GPA_MFPL_PA1MFP_SPI0_MISO |SYS_GPA_MFPL_PA0MFP_SPI0_MOSI);/* Lock protected registers */SYS_LockReg();}

依据新塘的代码风格,系统的初始化被安排在SYS_Init()中完成所有的系统时钟和各个外设时钟的初始化,在初始化时钟之后初始化外设的引脚,设置各种复用,这里初始化了UART0 和 UART1的外设以及SPI0的外设。因为采用自动的NSS,所以这里将NSS的引脚也复用。
SPI初始化

void SPI_Init(void){/*---------------------------------------------------------------------------------------------------------*//* Init SPI                                                                                                *//*---------------------------------------------------------------------------------------------------------*//* Configure as a master, clock idle low, 32-bit transaction, drive output on falling clock edge and latch input on rising edge. *//* Set IP clock divider. SPI clock rate = 2 MHz */SPI_Open(SPI0, SPI_MASTER, SPI_MODE_0, 8, SPI_CLK_FREQ);/* Enable the automatic hardware slave select function. Select the SS pin and configure as low-active. */SPI_EnableAutoSS(SPI0, SPI_SS, SPI_SS_ACTIVE_LOW);}

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这里做了一个集合。下图为四个可选参数。

下面给一张控制SPI设备的各个寄存器值设定图,

我这里选择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读写数据:

 /* Write to TX register */SPI_WRITE_TX(SPI0, 0x50650F);/* Check SPI0 busy status */while(SPI_IS_BUSY(SPI0));i32Err = SPI_READ_RX(SPI0);        

SPI_WRITE_TX()进行SPI数据的发送,在等待发送完成后用SPI_READ_RX()进行读数据便可完成一次完整的SPI数据读写。
PDMA设置

void SPI_DMAInit(void){/* Reset PDMA module */SYS_ResetModule(PDMA_RST);/* Enable PDMA channels */PDMA_Open(PDMA, SPI_OPENED_CH);/*=======================================================================SPI master PDMA TX channel configuration:-----------------------------------------------------------------------Word length = 32 bitsTransfer Count = DATA_COUNTSource = g_au32MasterToSlaveTestPatternSource Address = IncresingDestination = SPI0->TXDestination Address = FixedBurst Type = Single Transfer=========================================================================*//* Set transfer width (32 bits) and transfer count */PDMA_SetTransferCnt(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_WIDTH_8, DATA_COUNT);/* Set source/destination address and attributes */PDMA_SetTransferAddr(PDMA, SPI_MASTER_TX_DMA_CH, (uint32_t)SPI_TX, PDMA_SAR_INC, (uint32_t)&SPI0->TX, PDMA_DAR_FIX);/* Set request source; set basic mode. */PDMA_SetTransferMode(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_SPI0_TX, FALSE, 0);/* Single request type. SPI only support PDMA single request type. */PDMA_SetBurstType(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_REQ_SINGLE, 0);/* Disable table interrupt */PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL |= PDMA_DSCT_CTL_TBINTDIS_Msk;/*=======================================================================SPI master PDMA RX channel configuration:-----------------------------------------------------------------------Word length = 32 bitsTransfer Count = DATA_COUNTSource = SPI0->RXSource Address = FixedDestination = g_au32MasterRxBufferDestination Address = IncreasingBurst Type = Single Transfer=========================================================================*//* Set transfer width (32 bits) and transfer count */PDMA_SetTransferCnt(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_WIDTH_8, DATA_COUNT);/* Set source/destination address and attributes */PDMA_SetTransferAddr(PDMA, SPI_MASTER_RX_DMA_CH, (uint32_t)&SPI0->RX, PDMA_SAR_FIX, (uint32_t)SPI_RX, PDMA_DAR_INC);/* Set request source; set basic mode. */PDMA_SetTransferMode(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_SPI0_RX, FALSE, 0);/* Single request type. SPI only support PDMA single request type. */PDMA_SetBurstType(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_REQ_SINGLE, 0);/* Disable table interrupt */PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL |= PDMA_DSCT_CTL_TBINTDIS_Msk;/* Enable SPI master DMA function */SPI_TRIGGER_TX_PDMA(SPI0);SPI_TRIGGER_RX_PDMA(SPI0);}

这里PDMA设置和UART差不多,不罗里吧嗦的胡扯了,主要就是一个SPI的使能,这里用SPI_TRIGGER_TX_PDMA()和SPI_TRIGGER_RX_PDMA()。这个是UART的SPI没有的。
在这个初始化完成后会进行一次发送和接收,如果不需要请删除                    SPI_TRIGGER_TX_PDMA(SPI0); SPI_TRIGGER_RX_PDMA(SPI0);
如果想要重复PDMA 的发送可做如下处理:

                    /* Re-trigger *//* Master PDMA TX channel configuration *//* Set transfer width (32 bits) and transfer count */PDMA_SetTransferCnt(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_WIDTH_32, DATA_COUNT);/* Set request source; set basic mode. */PDMA_SetTransferMode(PDMA, SPI_MASTER_TX_DMA_CH, PDMA_SPI0_TX, FALSE, 0);/* Master PDMA RX channel configuration *//* Set transfer width (32 bits) and transfer count */PDMA_SetTransferCnt(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_WIDTH_32, DATA_COUNT);/* Set request source; set basic mode. */PDMA_SetTransferMode(PDMA, SPI_MASTER_RX_DMA_CH, PDMA_SPI0_RX, FALSE, 0);/* Enable master's DMA transfer function */SPI_TRIGGER_TX_PDMA(SPI0);SPI_TRIGGER_RX_PDMA(SPI0);

可以封装为一个函数,每次需要启动DMA就调用一次该函数。但是这里又是老问题,启动DMA的速度慢,示波器测试需要8个微秒左右吧,时间过长。同样我们可以混搭寄存器操作,加快代码执行效率,这个技巧非常实用;

void SPI_DMA_WRITE(void){PB1 = 0;/* Master PDMA TX channel configuration */PDMA->CHCTL |= (1 << SPI_MASTER_TX_DMA_CH); /* Enable PDMA channel */PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL =(PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL & (~(PDMA_DSCT_CTL_TXCNT_Msk | PDMA_DSCT_CTL_OPMODE_Msk))) |(DATA_COUNT - 1) << PDMA_DSCT_CTL_TXCNT_Pos | /* Transfer count */1 << PDMA_DSCT_CTL_OPMODE_Pos;/* Master PDMA RX channel configuration */PDMA->CHCTL |= (1 << SPI_MASTER_RX_DMA_CH); /* Enable PDMA channel */PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL =(PDMA->DSCT[SPI_MASTER_RX_DMA_CH].CTL & (~(PDMA_DSCT_CTL_TXCNT_Msk | PDMA_DSCT_CTL_OPMODE_Msk))) |(DATA_COUNT - 1) << PDMA_DSCT_CTL_TXCNT_Pos | /* Transfer count */1 << PDMA_DSCT_CTL_OPMODE_Pos;/* Enable master's DMA transfer function */SPI0->PDMACTL = (SPI_PDMACTL_RXPDMAEN_Msk | SPI_PDMACTL_TXPDMAEN_Msk);while (PDMA->DSCT[SPI_MASTER_TX_DMA_CH].CTL & PDMA_DSCT_CTL_TXCNT_Msk) ;PB1 = 1;}

这样,M031的DMA就完成了,如果需要启动DMA传输,就调用SPI_DMA_WRITE()函数便可。
---------------------
作者:呐咯密密
链接:https://bbs.21ic.com/icview-3145428-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载

【又换MCU】越来越好用系列,新塘031 SPI PDMA通信相关推荐

  1. [应用方案]【又换MCU】越来越好用系列,新塘031 串口PDMA通信。

    老规矩,发帖先吐槽!!! 从入职新公司后,一个项目我折腾了两年,看了一下自己的帖子,都是泪,每次以为开发完了,就会出事故,这次保守点,用新塘一定没问题,一定会成功量产,一定不要重新来过.这一个项目我从 ...

  2. fullcalendar 显示的时间间隔只有四十五分钟_NHR系列智能显示控制仪表RS485通信中应用...

    请点击上方蓝字关注我们! 01 摘要 NHR系列智能显示控制仪表是经过多年开发制造经验而设计生产,集诸多全新功能于一身的新一代智能显示控制仪表.针对现场温度.压力.液位.速度.流量等各种信号进行采集. ...

  3. 数字接口系列文章:SPI 总线

    数字接口系列文章:SPI 总线 SPI总线 SPI接口是在CPU和外围低速器件之间进行同步串行数据传输,在主器件的移位脉冲下,数据按位传输,高位在前,地位在后,为全双工通信,数据传输速度总体来说比I2 ...

  4. 三菱FX5U系列PLC与汇川IT6000系列触摸屏进行MODBUS TCP通信的具体方法

    三菱FX5U系列PLC与汇川IT6000系列触摸屏进行MODBUS TCP通信的具体方法 本次和大家分享三菱FX5U系列PLC与汇川IT6000系列触摸屏进行MODBUS TCP通信的具体方法,由于汇 ...

  5. 重磅!兆易创新推出中国首款Cortex®-M7内核超高性能MCU GD32H737/757/759系列

    关注.星标公众号,精彩内容每日送达 来源:网络素材 中国北京(2023年5月11日)--业界领先的半导体器件供应商兆易创新GigaDevice (股票代码 603986) 今日宣布,正式推出中国首款基 ...

  6. 苹果6换屏多钱_iPhone12系列屏幕维修价格公布 苹果12换屏多少钱

    iPhone12系列屏幕维修价格公布.虽然iPhone12 全系都使用了超瓷晶面板,苹果公司称其"比任何智能手机玻璃都要坚韧",抗跌落损坏的能力是其四倍,但依然有碎屏的风险.苹果1 ...

  7. 树莓派ubuntu18.04server安装摄像头不成换ubuntu mate继续安装系列

    因为这个版本没有自带raspi-config,因此需要先安装 64位内核树莓派安装raspi-config 和 语言设置 在上面帖子中的raspi的版本换为新版本 http://archive.ras ...

  8. STM32 基础系列教程 15 - SPI

    前言 学习stm32 SPI通信接口使用,学会用SPI接口收发数据. 示例详解 基于硬件平台: STM32F10C8T6最小系统板, MCU 的型号是 STM32F103c8t6, 使用stm32cu ...

  9. 视频会议系统EasyRTC常见的几种架构方式及应用场景:MCU/SFU、视频会议、应急指挥、即时通信

    我们这里常说的RTC可以理解为WebRTC技术,因为WebRTC技术是目前使用最广泛的即时通信技术,虽然在早期我们提到WebRTC.提到视频通话就会想到P2P的方式,但实际的视频通话方式背后的逻辑有很 ...

最新文章

  1. #6279. 数列分块入门 3(区间修改,查询权值前驱)
  2. 第十六届全国大学生智能车比赛掠影
  3. 我和阿里巴巴的孽缘(三)
  4. e-mobile帐号状态存在异常_企业微信添加好友提示”操作异常”怎么办?
  5. java实现单链表常见操作
  6. 从xls或者txt等格式的文件中读取编码码率以及PSNR
  7. velocity 将数字转为以万为单位,保留2位小数
  8. diff与patch操作
  9. 大道至简,凯里亚德酒店成为酒店投资圈万众瞩目的“新”星
  10. 3. Carla导入openDRIVE地图
  11. 服务器CPU与家用个人电脑CPU的区别详解
  12. 网站SQL注入漏洞检测
  13. 请高手指点,简单的几个数组操作方法不知道是否可以有更好的改进方法或者更简单的方法?
  14. iPhone 苹果手机尺寸大全
  15. 什么是智能颈部按摩仪低频脉冲电流?它会对人体有何影响?
  16. 亚马逊跟卖还能做吗?
  17. foxmai邮件服务器pop,全球邮企业邮箱Foxmail POP3/IMAP协议设置方法
  18. GIS空间分析实验教程期末重点91012
  19. 使用开源软件Prometheus监控企业内部应用
  20. 洛谷千题详解 | P1010 [NOIP1998 普及组] 幂次方【C++、Java、Python、Pascal语言】

热门文章

  1. IIS服务器(Windows)远程修改ftp密码方法
  2. 52岁的周鸿祎,还年轻吗?
  3. 用Python演奏《太阳照常升起》
  4. 跑跑卡丁车连接不上服务器无响应,跑跑卡丁车无法连接服务器怎么办?
  5. 魔兽世界怀旧服服务器信息,魔兽世界怀旧服服务器类型有哪些_怀旧服服务器类型介绍...
  6. 【C++修行之路】C/C++内存管理
  7. Linux gtypist
  8. 访问HTTPS请求遇到SSL信任问题
  9. Windows Server 2008 R2 MSDN ISO镜像简体中文版 英文版下载
  10. 如何查看win10系统的异常关机日志