DMA

1、前言

某客户发现修改代码后,STM32U59 SPI DMA 发送未产生传输完成中断,但修改的代码跟 SPI 以及 DMA 毫无关联。

2、调研

回退代码修改后问题消失,可以确认硬件正常。检查 SPI 发送对应的 DMA 句柄的

ErrorCode,发现 HAL_DMA_ERROR_DTE(Date Transfer Error)置位。尝试修改软件,发现调整下某个全局变量初值,或屏蔽某些变量,都能解决问题。

为屏蔽 RTOS 及应用代码可能带来的影响,调整客户 SPI 和 DMA 初始化代码位置到紧跟 MCU 复位后配置时钟,初始化全局变量,并在初始化完成后立即发送一包数据,测试下来问题仍存在,说明问题和后续 RTOS 及应用代码代码无关。

为澄清全局变量初始化对 SPI 和 DMA 的影响,在全局变量初始化完成、初始化 SPI

和 DMA 前、后,分别检查 RCC、SPI 和 DMA 寄存器,和正常时对比无差异。

3、分析

客户使用的是 GPDMA 的 Linked List 模式,此模式下 DMA 传输相关的 8 个寄存器不通过软件直接设置,而是需要软件把待设定的寄存器参数先放入缓冲区,再将缓冲区地址赋到 DMA 寄存器 CxLBAR(高 16 位)和 CxLLR(低 16 位),使能 DMA 时硬件自动将这些参数加载到 DMA 通道对应的寄存器中。

跟踪代码执行,在 DMA 使能前,查看参数缓冲区(地址:0x2001 FFF0)。确认

DMA 参数缓冲区中源地址寄存器参数 0x200 e0618、目标地址寄存器参数 0x4001 3020都是正确的;使能 DMA 后,发现 DMA CxDAR 寄存器数据异常 0x1382 932e。

“DMA不产生传输完成中断"

“DMA不产生传输完成中断"

查阅参考手册,找到其对对缓冲区限制的描述:必须 4 字节对齐,且不能跨 64Kbyte边界;而用户的缓冲区地址 0x2001 FFF0(32 字节)刚好跨了 64Kbyte 边界。链接器分配的地址范围是 0x2001 FFF0 ~ 0x2002 0010,而 GPDMA 实际访问的数据区间是0x2001 FFF0 ~ 0x2001 FFFF 和 0x20010000 ~ 0x2001000F,把 2001 0000 对应的数据加载到了 DAR 中,随即引发了 DTE 错误。

“DMA不产生传输完成中断"

4、处理

处由于增减全局变量、修改变量初值为零或非零,都会影响最终链接结果,故通过设置预编译选项对所有 DMA 链表数据结构变量的地址作出限制,来满足 4 字节对齐且不跨64K Byte 边界这个约束条件,再次编译链接后进行测试,SPI DMA 功能恢复正常。

5、小结

对这种看似怪异的问题,需要仔细观察并记录整个数据传输过程,各个关键操作前后相关寄存器的变化来获取第一手资料,对比正常情况并结合参考手册寻找具体原因。

来源:STM32单片机
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 74

RX系列MCU UART空闲中断的软件实现,对于RX系列MCU,为了提高运行效率,减少CPU的占用,UART的数据接收往往和DMA功能一起使用,这样可以无需CPU的介入即可完成UART接收寄存器的数据向指定的接收缓存区的数据传送。此时,只有当预设的DMA传送buffer被填满,即DMA的传送计数器变为0时,才会产生中断。而在有些Use case中,客户需要UART接收不定长度的数据包,如果没有UART空闲检测机制,系统不会自动判断此时UART接收一帧数据已经完成。本文给出给出一个解决思路(只针对不能ELC触发定时器的UART通道。可以ELC触发定时器的UART通道可以通过接收数据后使用ELC触发定时器清零,通过定时器是否超时来判断是否完成一帧数据接收)。

01、设置一个定时器通道,在UART数据接收开始时打开(如果知道什么时候需要接收),或者一直打开(不知道数据什么时候会过来)。定时器周期判断接收DMA通道的传送数据计数器寄存器Transfer Count Register DMCRA。

02、DMCRA 的值在接收到一个字节时候就会减一,接收完一帧数据后这个值就不再变化了。

03、通过定时器周期函数去比较这个值有没有变化,可以判定一帧数据是否接收完成。建议定时器每隔1ms(这个值可以由客户的波特率设置的实际情况来定制)检查一次DMCRA寄存器里的值去判断是否接收完成。

请参考如下程序流程示意图。

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

下面介绍FIT模块示例配置

UART配置:使能收发by DMAC

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

DMAC配置:接收和发送配置到SCI9,源地址和目标地址此处随意设置,程序中可以调整。

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

定时器设置:

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

以下为实际程序代码,本文中列出关键部分(UART接收完PC发送过来的一帧数据后,回传到PC端)。

重新配置DMAC0接收源地址和目的地址:

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

定时器间隔中断回调函数:

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

UART使用DMA发送:

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

主程序:

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

实际测试结果(‘发’表示该帧数据是PC发给目标MCU的发送帧,‘收’表示该帧数据是MCU传回来的一帧数据):

“RX系列MCU串口+DMA接收数据完成的空闲判断方法"

来源:瑞萨MCU小百科
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 581

今天再给大家分享一些关于STM32串口中断及DMA接收常见的几个问题。

UART串口中断接收

使能UART串口中断之后,有接收到UART数据,进入中断,此时要清除RXNE接收标志位:

1)通过软件向该RXNE标志位写入零来清零;

2)通过对 USART_DR 寄存器执行读入操作将该位清零。

这里可以查看对应《参考手册》,一般我们选择第2种,通过读取UART串口数据来清零。

1、中断接收数据丢失

在UART串口中断函数中,或者更高优先级中断函数中长时间执行,导致接收丢失,所以,请勿在中断函数中长时间执行。

特别有些人,还在中断函数添加延时函数。实际应用中,只要不是特殊情况,比如测试某个功能可以添加延时函数,都不建议在中断函数添加延时函数。

2、ORE上溢错误

ORE上溢错误是什么意思呢? 可能很多人不了解,简单说就是:UART接收到有数据,没有去取,但又来了数据,此时就会产生ORE上溢错误。(请看“参考手册”)

其实,上面这种长时间在占用中断,就会导致UART接收数据上溢。

很多人没有在意这个,如果是使能了中断接收,标志位没有清除,又有ORE上溢错误的话,程序就会不停地进入UART中断。(大家不妨试一下,看一下是不是你代码一直在UART中断里面不停运行)。

3、使能接收中断前,先清除接收标志位

有时候,在程序初始化的时候,就会接收到数据,这个时候建议大家先清除接收标志位再使能接收中断。类似如下:

“STM32串口中断及DMA接收常见的几个问题"

DMA接收串口数据

使用DMA接收串口数据,相信很多朋友都知道。这个可以理解为使用队列,或者FIFO的形式,防止因高优先级中断而打断,导致接收数据丢失。

但很多人都遇到过,接收不到数据,或者数据异常的情况。所以,这里同样有需要注意的地方。

1、使能UART之前,先使能DMA相关配置

这个和清除标志位一样的道理,使能UART之前,先使能DMA相关配置,防止在配置过程中有接收数据。

“STM32串口中断及DMA接收常见的几个问题"

2、ORE上溢错误导致不能使用DMA接收数据

导致不能DMA接收不到数据的根源,有一种可能就是有ORE上溢错误。

若在串口初始化并使能后到 DMA 使能之前有数据来,MCU是不能接收的。如果此时数据寄存器 USART_DR 存在一个数据,再来一个数据,就会导致ORE上溢错误。

一旦产生上溢错误后,就无法再触发 DAM 请求,即使之后再启动 DMA 也不行,无法触发 DMA 请求就无法将数据寄存器内的数据及时转移走,如此陷入死锁。这就是串口无法正常接收的原因。

所以,最后提醒大家,配置时,请一定要注意这些细节。

来源:嵌入式专栏(作者 | strongerHuang)
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 4222

DMA的基本介绍

什么是DMA (DMA的基本定义)

DMA,全称Direct Memory Access,即直接存储器访问。

DMA传输将数据从一个地址空间复制到另一个地址空间,提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。

我们知道CPU有转移数据、计算、控制程序转移等很多功能,系统运作的核心就是CPU,CPU无时不刻的在处理着大量的事务,但有些事情却没有那么重要,比方说数据的复制和存储数据,如果我们把这部分的CPU资源拿出来,让CPU去处理其他的复杂计算事务,是不是能够更好的利用CPU的资源呢?

因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理。

“详解STM32中的DMA原理"

DMA就是基于以上设想设计的,它的作用就是解决大量数据转移过度消耗CPU资源的问题。有了DMA使CPU更专注于更加实用的操作–计算、控制等。

DMA定义

DMA用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU的干预,通过DMA数据可以快速地移动。这就节省了CPU的资源来做其他操作。

DMA传输方式

DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:

  • 外设到内存
  • 内存到外设
  • 内存到内存
  • 外设到外设

DMA传输参数

我们知道,数据传输,首先需要的是1 数据的源地址 2 数据传输位置的目标地址 ,3 传递数据多少的数据传输量 ,4 进行多少次传输的传输模式 DMA所需要的核心参数,便是这四个。

“详解STM32中的DMA原理"

当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。

也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。  

DMA的主要特征

每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置。

  • 在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推);
  • 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐;
  • 支持循环的缓冲器管理;
  • 每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求;
  • 存储器和存储器间的传输、外设和存储器、存储器和外设之间的传输;
  • 闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标;
  • 可编程的数据传输数目:最大为65535。

STM32少个DMA资源?

对于大容量的STM32芯片有2个DMA控制器 两个DMA控制器,DMA1有7个通道,DMA2有5个通道。

每个通道都可以配置一些外设的地址。

①DMA1 controller

从外设(TIMx[x=1、2、3、4]、ADC1、SPI1、SPI/I2S2、I2Cx[x=1、2]和USARTx[x=1、2、3])产生的7个DMA请求,通过逻辑或输入到DMA1控制器 其中每个通道都对应着具体的外设:

“详解STM32中的DMA原理"

“详解STM32中的DMA原理"

② DMA2 controller

从外设(TIMx[5、6、7、8]、ADC3、SPI/I2S3、UART4、DAC通道1、2和SDIO)产生的5个请求,经逻辑或输入到DMA2控制器,其中每个通道都对应着具体的外设:

“详解STM32中的DMA原理"

“详解STM32中的DMA原理"

这些在下方系统框图中也可以清晰地看到。

DMA工作系统框图

“详解STM32中的DMA原理"

上方的框图,我们可以看到STM32内核,存储器,外设及DMA的连接,这些硬件最终通过各种各样的线连接到总线矩阵中,硬件结构之间的数据转移都经过总线矩阵的协调,使各个外设和谐的使用总线来传输数据。

我们对他来进行一点一点的分析:

下面看有与没有DMA的情况下,ADC采集的数据是怎样存放到SRAM中的?

没有DMA

1.如果没有DMA,CPU传输数据还要以内核作为中转站,比如要将ADC采集的数据转移到到SRAM中,这个过程是这样的:

内核通过DCode经过总线矩阵协调,从获取AHB存储的外设ADC采集的数据,

然后内核再通过DCode经过总线矩阵协调把数据存放到内存SRAM中。

“详解STM32中的DMA原理"

有DMA传输

有DMA的话:

1)DMA传输时外设对DMA控制器发出请求。

2)DMA控制器收到请求,触发DMA工作。

3)DMA控制器从AHB外设获取ADC采集的数据,存储到DMA通道中

4)DMA控制器的DMA总线与总线矩阵协调,使用AHB把外设ADC采集的数据经由DMA通道存放到SRAM中,这个数据的传输过程中,完全不需要内核的参与,也就是不需要CPU的参与。

“详解STM32中的DMA原理"

我们把上面的步骤专业一点介绍:

在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。DMA传输结束,如果有更多的请求时,外设可以启动下一个周期。

总之,每次DMA传送由3个操作组成:

  • 从外设数据寄存器或者从当前外设/存储器地址寄存器指示的存储器地址取数据,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
  • 存数据到外设数据寄存器或者当前外设/存储器地址寄存器指示的存储器地址,第一次传输时的开始地址是DMA_CPARx或DMA_CMARx寄存器指定的外设基地址或存储器单元;
  • 执行一次DMA_CNDTRx寄存器的递减操作,该寄存器包含未完成的操作数目。

DMA传输方式

方法1:DMA_Mode_Normal,正常模式,

当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次 

方法2:DMA_Mode_Circular ,循环传输模式

当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。也就是多次传输模式

仲裁器

“详解STM32中的DMA原理"

仲裁器的作用是确定各个DMA传输的优先级。

仲裁器根据通道请求的优先级来启动外设/存储器的访问。

优先权管理分2个阶段:

软件:每个通道的优先权可以在DMA_CCRx寄存器中设置,有4个等级:

  • 最高优先级
  • 高优先级
  • 中等优先级
  • 低优先级;

硬件:如果2个请求有相同的软件优先级,则较低编号的通道比较高编号的通道有较高的优先权。比如:如果软件优先级相同,通道2优先于通道4。

注意:在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。

DMA数据流(仅存在于STM32F4 /M4 内核上)

在设置了DMA的通道之后,还要选择通道对应外设的数据流。

8 个 DMA 控制器数据流都能够提供源和目标之间的单向传输链路。每个数据流配置后都可以执行:

● 常规类型事务:存储器到外设、外设到存储器或存储器到存储器的传输。

● 双缓冲区类型事务:使用存储器的两个存储器指针的双缓冲区传输(当 DMA 正在进行自/至缓冲区的读/写操作时,应用程序可以进行至/自其它缓冲区的写/读操作)。要传输的数据量(多达 65535)可以编程,并与连接到外设 AHB 端口的外设(请求 DMA 传输)的源宽度相关。每个事务完成后,包含要传输的数据项总量的寄存器都会递减。

DMA_SxCR 寄存器控制数据流到底使用哪一个通道,每个数据流有 8 个通道可供选择,每次只能选择其中一个通道进行 DMA 传输。接下来,我们看看 DMA2 的各数据流通道映射表,如表 28.1.1 所示:

“详解STM32中的DMA原理"

DMA 传输通道

每个通道都可以在有固定地址的外设寄存器和存储器地址之间执行DMA传输。DMA传输的数据 量是可编程的,大达到65535。包含要传输的数据项数量的寄存器,在每次传输后递减。

可编程的数据量:

外设和存储器的传输数据量可以通过DMA_CCRx寄存器中的PSIZE和MSIZE位编程。

指针递增模式

根据 DMA_SxCR 寄存器中 PINC 和 MINC 位的状态,外设和存储器指针在每次传输后可以自动向后递增或保持常量。当设置为增量模式时,下一个要传输的地址将是前一个地址加上增量值。

通过单个寄存器访问外设源或目标数据时,禁止递增模式十分有用。

如果使能了递增模式,则根据在 DMA_SxCR 寄存器 PSIZE 或 MSIZE 位中编程的数据宽度,下一次传输的地址将是前一次传输的地址递增 1个数据宽度、2个数据宽度或 4个数据宽度。

存储器到存储器模式

DMA通道的操作可以在没有外设请求的情况下进行,这种操作就是存储器到存储器模式。

当设置了DMA_CCRx寄存器中的MEM2MEM位之后,在软件设置了DMA_CCRx寄存器中的EN位启动DMA通道时,DMA传输将马上开始。当DMA_CNDTRx寄存器变为0时,DMA传输结束。存储器到存储器模式不能与循环模式同时使用。

这里要注意仅 DMA2 的外设接口可以访问存储器,所以仅 DMA2 控制器支持存储器到存储器的传输,DMA1 不支持。

存储器到存储器模式不能与循环模式同时使用。

DMA中断

每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。

“详解STM32中的DMA原理"

使没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx位,即数据流 x 的 DMA 传输完成与否标志。

可编程的数据传输宽度、对齐方式和数据大小端。

当PSIZE和MSIZE不相同时,DMA模块按照下图进行数据对齐。

注意:在大容量产品中, DMA2 通道 4 和 DMA2 通道 5 的中断被映射在同一个中断向量上。在互联型产品 中, DMA2 通道 4 和 DMA2 通道 5 的中断分别有独立的中断向量。所有其他的 DMA 通道都有自己的中断向量。

DMA的内存占用

在STM32控制器中,芯片采用Cortex-MX架构,总线结构有了很大的优化,DMA占用另外的地址总线,并不会与CPU的系统总线发生冲突。也就是说,DMA的使用不会影响CPU的运行速度。

但是要注意:

DMA控制器和Cortex-M3核共享系统数据总线执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求可能会停止 CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。

DMA配置部分

此部分我们分为DMA寄存器和DMA库函数分别介绍:

DMA寄存器

DMA配置参数包括:通道地址、优先级、数据传输方向、存储器/外设数据宽度、存储器/外设地址是否增量、循环模式、数据传输量。

DMA中断状态寄存器(DMA_ISR)

“详解STM32中的DMA原理"

我们如果开启了 DMA_ISR 中这些中断,在达到条件后就会跳到中断服务函数里面去,即使 没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx, 即通道 DMA 传输完成与否的标志。

注意此寄存器为只读寄存器,所以在这些位被置位之后,只 能通过其他的操作来清除。

DMA中断标志清除寄存器(DMA_IFCR)

“详解STM32中的DMA原理"

DMA_IFCR 的各位就是用来清除 DMA_ISR 的对应位的,通过写 0 清除。在 DMA_ISR 被置位后, 我们必须通过向该位寄存器对应的位写入 0 来清除。

DMA通道x配置寄存器(DMA_CCRx)

“详解STM32中的DMA原理"

该寄存器控制着 DMA 的很多相关 信息,包括数据宽度、外设及存储器的宽度、通道优先级、增量模式、传输方向、中断允许、 使能等都是通过该寄存器来设置的。所以 DMA_CCRx 是 DMA 传输的核心控制寄存器。

DMA通道x传输数量寄存器(DMA_CNDTRx)(x = 1…7)

“详解STM32中的DMA原理"

这个寄存器控制 DMA 通道 x 的每次 传输所要传输的数据量。其设置范围为 0~65535。并且该寄存器的值会随着传输的进行而减少, 当该寄存器的值为 0 的时候就代表此次数据传输已经全部发送完成了。所以可以通过这个寄存 器的值来知道当前DMA 传输的进度。

DMA通道x外设地址寄存器(DMA_CPARx)(x = 1…7)

“详解STM32中的DMA原理"

该寄存器用来存储 STM32 外设的地 址,比如我们使用串口 1,那么该寄存器必须写入 0x40013804(其实就是&USART1_DR)。如果使 用其他外设,就修改成相应外设的地址就行了。

DMA通道x配置寄存器(DMA_CMARx)

“详解STM32中的DMA原理"

该寄存器和 DMA_CPARx 差不多, 但是是用来放存储器的地址的。比如我们使用 SendBuf[5200]数组来做存储器,那么我们在 DMA_CMARx 中写入&SendBuff 就可以了。

DMA寄存器配置流程

通道配置过程 下面是配置DMA通道x的过程(x代表通道号):

1、在DMA_CPARx寄存器中设置外设寄存器的地址。发生外设数据传输请求时,这个地址将 是数据传输的源或目标。

2、在DMA_CMARx寄存器中设置数据存储器的地址。发生外设数据传输请求时,传输的数 据将从这个地址读出或写入这个地址。

3、在DMA_CNDTRx寄存器中设置要传输的数据量。在每个数据传输后,这个数值递减。

4、在DMA_CCRx寄存器的PL[1:0]位中设置通道的优先级。

5、在DMA_CCRx寄存器中设置数据传输的方向、循环模式、外设和存储器的增量模式、外 设和存储器的数据宽度、传输一半产生中断或传输完成产生中断。

6、设置DMA_CCRx寄存器的ENABLE位,启动该通道。

一旦启动了DMA通道,它既可响应连到该通道上的外设的DMA请求。当传输一半的数据后,半传输标志(HTIF)被置1,当设置了允许半传输中断位(HTIE)时,将产生 一个中断请求。在数据传输结束后,传输完成标志(TCIF)被置1,当设置了允许传输完成中断位 (TCIE)时,将产生一个中断请求。

DMA库函数

1.DMA初始化函数

DMA_DeInit(DMAX_ChannelX);

功能:将DMAyChannelx寄存器的初始化为其默认值

注释:RCC_ResetCmd中对DMA无定义,因此采用的直接操纵DMA寄存器的方式

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,  DMA_InitTypeDef* DMA_InitStruct)

功能:设置要开启的通道,还有一些参数,包括外设基地址,存储器基地址,传输的数据量,增量模式,数据宽度等。

具体看下方结构体代码介绍:

typedef struct {  
 uint32_t DMA_PeripheralBaseAddr;   
 /*设置DMA源地址*/
 uint32_t DMA_MemoryBaseAddr;       
 /*设置DMA目的地址*/
 uint32_t DMA_DIR; 
 /* 设置数据传输方向,决定是从外设读取数据到内存还送从内存读取数 据发送到外设,也就是外设是源地还是目的地
 */                       
 uint32_t DMA_BufferSize;      
 /*设置传输大小*/    
 uint32_t DMA_PeripheralInc;       
 /*设置ReceiveBuff地址是否自增*/      
 uint32_t DMA_MemoryInc; 
 /*设置传输数据时候内存地址是否递,需要开启*/       
 uint32_t DMA_PeripheralDataSize;   
 /*外设的数据长度是为字节传输(8bits),半 字传输(16bits) 还是字传输(32bits) */    
 uint32_t DMA_MemoryDataSize;    
   /*设置内存的数据长度*/
 uint32_t DMA_Mode;      
 /*设置DMA的模式,正常模式/循环模式  是否循环发送*/        
 uint32_t DMA_Priority; 
  /*设置 DMA 通道的优先级,有低,中,高,超高四种模式*/        
 uint32_t DMA_M2M;    
  /*设置是否是存储器到存储器模式传输,*/       }
 DMA_InitTypeDef; 

2.DMA使能函数

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState  NewState);

功能:使能或者失能DMA外设

例如:DMA_Cmd(DMA1_Channel1 , ENABLE);

3.DMA中断使能函数

void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT,  FunctionalState NewState);
//DMA_IT_TC:传输完成 DMA_IT_HT:传输一半 DMA_IT_TE:传输错误

功能:配置指定的DMAy通道x的中断

例如:DMA_ITConfig(DMA1_Channel1 , DMA_IT_TC , ENABLE);

4.设置CNDTRx和读CNDTRx函数 (通道传输数据量)

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t  DataNumber);
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);

作用:前者设置DMA通道的传输数据量(DMA处于关闭状态);后者获取当前DMA通道传输剩余数据量(DMA处于开启状态)。

DMA库函数配置过程:

  • 使能DMA时钟:RCC_AHBPeriphClockCmd();
  • 初始化DMA通道:DMA_Init();
    • //设置通道;传输地址;传输方向;传输数据的数目;传输数据宽度;传输模式;优先级;是否开启存储器到存储器。
  • 使能外设DMA
    • 以串口为例:使能串口DMA发送,串口DMA使能函数。调用函数:USART_DMACmd();
  • 使能DMA通道传输;函数:DMA_Cmd();
  • 查询DMA传输状态。函数:DMA_GetFlagStatus();
  • 获取当前剩余数据量大小 函数: DMA_GetCurrDataCounter(DMA1_Channel4);

UART DMA传输

DMA就是一个搬运工,可以将数据从一个位置搬运到另一个位置。

以UART为例,如果要接收数据,会触发UART中断,然后CPU介入,在中断中通过CPU将UART输入寄存器的值读出来,存放到内存中;

而DMA方式,产生UART中断后,DMA直接参与,把UART输入寄存器的值搬运到内存中,CPU只需要在去检查内存的值就好了,这样提高了CPU的效率。

DMA代码配置

①DMA初始化配置

void dma_init()
{

DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);


/*DMA配置*/


DMA_InitStructure.DMA_PeripheralBaseAddr = USART1_DR_Base;//串口数据寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff; //内存地址(要传输的变量的指针)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //方向(从内存到外设)
DMA_InitStructure.DMA_BufferSize = 500; //传输内容的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址不增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址自增
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Byte ; //外设数据单位
DMA_InitStructure.DMA_MemoryDataSize =
DMA_MemoryDataSize_Byte ; //内存数据单位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ; //DMA模式:一次传输,循环
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium ; //优先级:高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //禁止内存到内存的传输

DMA_Init(DMA1_Channel4, &DMA_InitStructure); //配置DMA1的4通道
DMA_Cmd(DMA1_Channel4,ENABLE);
DMA_SetCurrDataCounter(DMA_CH4,DMA1_MEM_LEN);//DMA通道的DMA缓存的大小
DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);//配置DMA发送完成后产生中断

}

DMA中断

void DMA1_Channel4_IRQHandler(void)
{
  if(DMA_GetFlagStatus(DMA1_FLAG_TC4)==SET)
  {

    DMA_ClearFlag(DMA1_FLAG_TC4);
  }
}

main函数

#define SEND_BUF_SIZE 500  //发送数据长度,最好等于sizeof(TEXT_TO_SEND)+2的整数倍.

u8 SendBuff[SEND_BUF_SIZE];  //发送数据缓冲区
const u8 TEXT_TO_SEND[]={"STM32F1 DMA 串口实验"};
 uint16_t i;
int main(void)
{     
  uart_init(115200);     //串口初始化为115200

  for(i=0;i<500;i++)
  {
  SendBuff[i] =0xaf;
  }  
  USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口dma传输 

  while(1);
}

————————————————
版权声明:本文为CSDN博主「Z小旋」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:
https://blog.csdn.net/as480133937/article/details/104927922
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 1420

本文我们就来学习 STM32F1 的DMA 使用。要实现的功能是:通过 K_UP 按键控制 DMA 串口 1 数据的传送,在传送过程中让 D2 指示灯不断闪烁,直到数据传送完成。D1 指示灯闪烁提示系统正常运行。学习时可以参考《STM32F10x 中文参考手册》-10 DMA 控制器(DMA)章节。

DMA 简介

DMA,全称是 Direct Memory Access,中文意思为直接存储器访问。DMA 可用于实现外设与存储器之间或者存储器与存储器之间数据传输的高效性。之所以称为高效, 是因为 DMA 传输数据移动过程无需 CPU 直接操作, 这样节省的 CPU 资源就可供其它操作使用。从硬件层面来理解,DMA 就好像是 RAM 与 I/O 设备间数据传输的通路, 外设与存储器之间或者存储器与存储器之间可以直接在这条通路上进行数据传输。这里说的外设一般指外设的数据寄存器, 比如 ADC、SPI、I2C、DCMI等外设的数据寄存器, 存储器一般是指片内 SRAM、 外部存储器、 片内 Flash等。

STM32F1 最多有 2 个 DMA 控制器 ( DMA2 仅存在大容量产品中) ,DMA1 有7 个通道。DMA2 有 5 个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个 DMA 请求的优先权。

STM32F1 的 DMA 有以下主要特性:

● 12 个独立的可配置的通道(请求):DMA1 有 7 个通道, DMA2 有 5 个通道

● 每个通道都直接连接专用的硬件 DMA 请求,每个通道都同样支持软件触发。这些功能通过软件来配置。

● 在同一个 DMA 模块上, 多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求 0 优先于请求1,依此类推) 。

● 独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐。

● 支持循环的缓冲器管理

● 每个通道都有 3 个事件标志(DMA 半传输、 DMA 传输完成和 DMA 传输出错),这3 个事件标志逻辑或成为一个单独的中断请求。

● 存储器和存储器间的传输

● 外设和存储器、存储器和外设之间的传输

● 闪存、 SRAM、外设的 SRAM、 APB1、 APB2 和 AHB 外设均可作为访问的源和目标。

● 可编程的数据传输数目:最大为 65535

DMA 结构框图

DMA 控制器独立于内核,属于一个单独的外设,结构比较简单,从编程的角度来看,我们只需掌握结构框图中的三部分内容即可。如图所示(大家也可以查看《STM32F10x中文参考手册》-10 DMA 控制器(DMA)章节内容)。

“STM32实例-DMA

我们把 DMA 结构框图分成3个子模块,按照顺序依次进行简单介绍。

(1)标号 1:DMA 请求

如果外设要想通过 DMA 来传输数据, 必须先给 DMA 控制器发送 DMA 请求,DMA 收到请求信号之后, 控制器会给外设一个应答信号, 当外设应答后且 DMA 控制器收到应答信号之后,就会启动 DMA 的传输,直到传输完毕。

根据前面介绍我们知道,DMA 含有 DMA1 和 DMA2 两个控制器,其中 DMA1 含有 7个通道,DMA2 含有 5 个通道,不同的 DMA 控制器的通道对应着不同的外设请求。

从DMA 请求映射图中可以知道各通道所对应的外设请求,如图分别是DMA1和DMA2请求映射图:

“STM32实例-DMA

“STM32实例-DMA

DMA2 请求通道中 ADC3、 SDIO 和 TIM8 的 DMA 请求只在大容量产品中存在,这个在具体项目时要注意。

(2)标号 2:DAM 通道

DMA 具有 12 个独立可编程的通道,其中 DMA1 有 7 个通道, DMA2 有 5个通道,每个通道对应不同的外设的 DMA 请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。

(3)标号 3:仲裁器

当发生多个 DMA 通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器也管理。仲裁器管理 DMA 通道请求分为两个阶段。第一阶段属于软件阶段,可以在 DMA_CCRx 寄存器中设置,有 4 个等级:非常高、高、中和低四个优先级。第二阶段属于硬件阶段,如果两个或以上的 DMA 通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 0高于通道 1。在大容量产品和互联型产品中,DMA1 控制器拥有高于 DMA2 控制器的优先级。

DMA 数据配置

使用 DMA, 最核心就是配置要传输的数据, 包括数据从哪里来, 要到哪里去,传输的数据的单位是什么,要传多少数据,是一次传输还是循环传输等。

(1)从哪里来到哪里去

我们知道 DMA 传输数据的方向有三个:从外设到存储器,从存储器到外设,从存储器到存储器。具体的方向 DMA_CCR 位 4 DIR 配置:0 表示从外设到存储器, 1 表示从存储器到外设。这里面涉及到的外设地址由 DMA_CPAR 配置,存储器地址由 DMA_CMAR配置。

外设到存储器

当我们使用从外设到存储器传输时,以 ADC 采集为例。DMA 外设寄存器的地址对应的就是 ADC 数据寄存器的地址,DMA 存储器的地址就是我们自定义的变量(用来接收存储 AD 采集的数据)的地址。方向我们设置外设为源地址。

存储器到外设

当我们使用从存储器到外设传输时, 以串口向电脑端发送数据为例。DMA 外设寄存器的地址对应的就是串口数据寄存器的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储通过串口发送到电脑的数据)的地址。方向我们设置外设为目标地址。

存储器到存储器

当我们使用从存储器到存储器传输时,以内部 FLASH 向内部 SRAM 复制数据为例。DMA 外设寄存器的地址对应的就是内部 FLASH(我们这里把内部 FALSH当作一个外设来看)的地址, DMA 存储器的地址就是我们自定义的变量(相当于一个缓冲区,用来存储来自内部 FLASH 的数据)的地址。方向我们设置外设(即内部 FLASH)为源地址。跟上面两个不一样的是,这里需要把 DMA_CCR 位14:MEM2MEM:存储器到存储器模式配置为 1,启动 M2M 模式。

(2)要传多少,单位是什么

当我们配置好数据要从哪里来到哪里去之后, 我们还需要知道我们要传输的数据是多少,数据的单位是什么。

以串口向电脑发送数据为例,我们可以一次性给电脑发送很多数据,具体多少由 DMA_CNDTR 配置,这是一个 32 位的寄存器,一次最多只能传输 65535 个数据。

要想数据传输正确,源和目标地址存储的数据宽度还必须一致,串口数据寄存器是 8 位的,所以我们定义的要发送的数据也必须是 8 位。外设的数据宽度由 DMA_CCR 的 PSIZE[1:0]配置,可以是 8/16/32 位,存储器的数据宽度由DMA_CCR 的 MSIZE[1:0]配置,可以是 8/16/32 位。

在 DMA 控制器的控制下,数据要想有条不紊的从一个地方搬到另外一个地方,还必须正确设置两边数据指针的增量模式。外设的地址指针由 DMA_CCRx 的PINC 配置,存储器的地址指针由 MINC 配置。以串口向电脑发送数据为例,要发送的数据很多,每发送完一个,那么存储器的地址指针就应该加 1,而串口数据寄存器只有一个,那么外设的地址指针就固定不变。具体的数据指针的增量模式由实际情况决定。

(3)什么时候传输完成

数据什么时候传输完成,我们可以通过查询标志位或者通过中断的方式来判断。每个 DMA 通道在 DMA 传输过半、传输完成和传输错误时都会有相应的标志位,如果使能了该类型的中断后,则会产生中断。有关各个标志位的详细描述请参考 DMA 中断状态寄存器DMA_ISR 的详细描述。

传输完成还分两种模式,是一次传输还是循环传输,一次传输很好理解,即传输一次之后就停止,要想再传输的话,必须关断 DMA 使能后再重新配置后才能继续传输。循环传输则是一次传输完成之后又恢复第一次传输时的配置循环传输,不断的重复。具体的由 DMA_CCR 寄存器的 CIRC 循环模式位控制。

DMA 配置步骤

接下来我们介绍下如何使用库函数对 DMA 进行配置。这个也是在编写程序中必须要了解的。具体步骤如下:(DMA 相关库函数在 stm32f10x_dma.c 和stm32f10x_dma.h 文件中)

(1)使能 DMA 控制器(DMA1 或 DMA2)时钟

要使能 DMA 时钟,需通过AHB1ENR 寄存器来控制,使能 DMA时钟库函数为:

void RCC_AHBPeriphClockCmd(uint32_t RCC_AHBPeriph, FunctionalState
NewState);

例如使能 DMA1 时钟,函数如下:

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

(2)初始化 DMA 通道,包括配置通道、外设和内存地址、传输数据量等要使用 DMA,必须对其相关参数进行设置,包括通道选择、外设和内存地址、

通道优先级、传输数据量的配置等。该部分设置通过 DMA 初始化函数 DMA_Init完成的:

void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef*
DMA_InitStruct);

函数中第一个参数是用来确定 DMA 通道,参数范围为:

DMA1_Channel_0~DMA1_Channel_7(DMA2 是DMA2_Channel_0-DMA2_Channel_5)

第二个参数是一个结构体指针变量,结构体类型是 DMA_InitTypeDef,其内包含了 DMA 相关参数的设置。下面我们简单介绍下它的成员:

typedef struct
{
uint32_t DMA_PeripheralBaseAddr; // 外设地址
uint32_t DMA_MemoryBaseAddr; // 存储器地址
uint32_t DMA_DIR; // 传输方向
uint32_t DMA_BufferSize; // 传输数目
uint32_t DMA_PeripheralInc; // 外设地址增量模式
uint32_t DMA_MemoryInc; // 存储器地址增量模式
uint32_t DMA_PeripheralDataSize; // 外设数据宽度
uint32_t DMA_MemoryDataSize; // 存储器数据宽度
uint32_t DMA_Mode; // 模式选择
uint32_t DMA_Priority; // 通道优先级
uint32_t DMA_M2M; // 存储器到存储器模式
} DMA_InitTypeDef;

DMA_PeripheralBaseAddr:外设地址,通过 DMA_CPAR 寄存器设置,一般设置为外设的数据寄存器地址,比如要进行串口 DMA 传输,那么外设基地址为串口接受发送数据存储器 USART1->DR 的地址,表示方法为&USART1->DR。如果是存储器到存储器模式则设置为其中一个存储区地址。

DMA_Memory0BaseAddr:存储器地址,通过 DMA_CMAR 寄存器设置,一般设置为我们自定义存储区的首地址,即我们存放 DMA 传输数据的内存地址。比如我们定义一个 u32 类型数组,将数组首地址(直接使用数组名即可)赋值给DMA_Memory0BaseAddr,在DMA传输的时候就可以把数组内的数据发送或接收。

DMA_DIR:数据传输方向选择,可选择外设到存储器、存储器到外设以及存

储器到存储器。通过设定 DMA_CCR 寄存器的 DIR[1:0]位的值决定。比如本章实验是从内存读取数据发送到串口,所以数据传输方向为存储器到外设,配置为DMA_DIR_MemoryToPeripheral。

DMA_BufferSize:用来设置一次传输数据的大小,通过 DMA_CNDTR 寄存器设置。

DMA_PeripheralInc:用来设置外设地址是递增还是不变,通过 DMA_CCR寄存器的 PINC 位设置,如果设置为递增,那么下一次传输的时候地址加 1。通常外 设 只 有 一 个 数 据 寄 存 器 , 所 以 一 般 不 会 使 能 该 位 , 即 配 置 为DMA_PeripheralInc_Disable。

DMA_MemoryInc:用来设置内存地址是否递增,通过 DMA_CCR 寄存器的MINC位设置。我们自定义的存储区一般都是存放多个数据的,所以需要使能存储器地址自动递增功能,即配置为 DMA_MemoryInc_Enable。

DMA_PeripheralDataSize:外设数据宽度选择,可以为字节(8 位)、半字

(16位)、字(32 位),通过 DMA_CCR 寄存器的 PSIZE[1:0]位设置。例如本实验数据是按照 8 位字节传输,所以配置为DMA_PeripheralDataSize_Byte。

DMA_MemoryDataSize:存储器数据宽度选择,可以为字节(8 位)、半字(16位)、字(32 位),通过 DMA_CCR 寄存器的 MSIZE[1:0]位设置。本章实验同样设置为 8 位字节传输,这个要和我们定义的数组对应,所以配置为DMA_MemoryDataSize_Byte。

DMA_Mode:DMA 传输模式选择, 可选择一次传输或者循环传输, 通过DMA_CCR寄存器的 CIRC 位来设定。比如我们要从内存 (存储器) 中传输 64 个字节到串口,如果设置为循环传输,那么它会在 64 个字节传输完成之后继续从内存的第一个地址传输,如此循环。这里我们设置为一次传输完成之后不循环。所以设置值为DMA_Mode_Normal。

DMA_Priority:用来设置 DMA 通道的优先级,有低,中,高,超高四种级别,可通过 DMA_CCR 寄存器的PL[1:0]位来设定。DMA 优先级只有在多个 DMA 通道同时使用时才有意义,本章实验我们只使用了一个 DMA 通道,所以可以任意设置DMA 优先级,这里我们就设置为中等优先级,配置参数为DMA_Priority_Medium。

DMA_Priority:用来设置 DMA 通道的优先级,有低,中,高,超高四种级别,可通过 DMA_CCR 寄存器的PL[1:0]位来设定。DMA 优先级只有在多个 DMA 通道同时使用时才有意义,本章实验我们只使用了一个 DMA 通道,所以可以任意设置DMA 优先级,这里我们就设置为中等优先级,配置参数为DMA_Priority_Medium。

DMA_M2M:用来设置存储器到存储器模式,使用存储器到存储器时用到,设定 DMA_CCR 的位 14 MEN2MEN 即可启动存储器到存储器模式。

了解结构体成员功能后,就可以进行配置,本实验配置代码如下:

DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外
设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_PeripheralInc =
DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器
增量模式
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Byte;//外设数据长度:8 位
DMA_InitStructure.DMA_MemoryDataSize =
DMA_MemoryDataSize_Byte;//存储器数据长度:8 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先
级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道 x 没有

设置为内存到内存传输

DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA

(3)使能外设 DMA功能(DMA 请求映射图对应的外设)

配置好 DMA 后,我们就需要使能外设 DMA 功能,例如我们要使能串口的DMA发送功能,调用的库函数为:

USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1 的 DMA发送

如果是要使能串口DMA接受, 那么第二个参数修改为USART_DMAReq_Rx即可。

如果是其他的外设需开启DMA功能, 只需要在对应的标准外设库函数中查找到对应的外设 DMA 使能函数。

(4)开启 DMA 的通道传输

初始化 DMA 后,要使用DMA还必须开启它,开启 DMA 通道传输的库函数为:

void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalStateNewState);

第一个参数为外设所对应的 DMA 通道,例如本章使用的是 USART1_TXDMA请求,因此它对应的是 DMA1_Channel4,可通过前面DMA 请求映射图选择。第二个参数相信不说也知道,就是使能或失能。

本实验使能 DMA1_Channel4函数为:

DMA_Cmd(DMA1_Channel4,ENABLE);

(5)查询 DMA 传输状态

通过以上 4 步设置,我们就可以启动一次 DMA 传输了。但是在 DMA 传输过程中,我们还需要查询 DMA传输通道的状态,使用的库函数是:

FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);

例如我们要查询 DMA1通道4 传输是否完成,方法是:

DMA_GetFlagStatus(DMA1_FLAG_TC4);

标准库中,还提供了获取当前剩余数据量大小的函数:

uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef*DMAy_Channelx);

例如我们要获取 DMA1通道4 还有多少个数据没有传输,方法是:

DMA_GetCurrDataCounter(DMA1_Channel4);

同样,标准库中还提供了设置传输数据量大小的函数:

void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx,uint16_t DataNumber);

将以上几步全部配置好后,我们就可以使用 DMA 来传输对应外设的数据了。

硬件设计

本实验使用到硬件资源如下:

(1)D1 和 D2 指示灯

(2)K_UP 按键

(3)串口 1

(4)DMA

D1和 D2 指示灯、K_UP 按键、串口 1 电路在前面章节都介绍过,这里就不多说,至于 DMA 它属于 STM32F1 芯片内部的资源,只要通过软件配置好 DMA即可使用。D1指示灯用来提示系统运行状态,K_UP 按键用来控制 DMA发送,每按一次K_UP键,DMA 就将内存(自定义的一个数组)内数据发送 USART1,并通过串口 1将发送的内容打印出来,在 DMA 数据传输的过程中让 D2 指示灯不断闪烁,直到数据传输完成。D2 指示灯闪烁表示 CPU在执行其他的任务,说明 DMA 传输是不需要占用 CPU 的。

所要实现的功能是:通过 K_UP 按键控制 DMA 串口 1 数据的传送,在传送过程中让 D2 指示灯不断闪烁,直到数据传送完成。D1 指示灯闪烁提示系统正常运行。程序框架如下:

(1)初始化 USART1_TX对应的 DMA 通道相关参数

(2)编写主函数

前面介绍 DMA 配置步骤时, 就已经讲解如何初始化 DMA。下面我们打开 “DMA实验” 工程, 在 APP 工程组中可以看到添加了dma.c文件(里面包含了 DMA 驱动程序),在 StdPeriph_Driver 工程组中添加了stm32f10x_dma.c 库文件。DMA 操作的库函数都放在 stm32f10x_dma.c 和stm32f10x_dma.h 文件中,所以使用到 DMA 就必须加入 stm32f10x_dma.c文件,同时还要包含对应的头文件路径。

这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。

DMA 初始化函数

要使用 DMA,我们必须先对它进行配置。初始化代码如下:

/****************************************************************
* 函 数 名 : DMAx_Init
* 函数功能 : DMA 初始化函数
* 输 入 :
DMAy_Channelx:DMA 通 道 选 择 ,@ref DMA_channel
DMA_Channel_0~DMA_Channel_7
par:外设地址
mar:存储器地址
ndtr:数据传输量
* 输 出 : 无
*****************************************************************/
void DMAx_Init(DMA_Channel_TypeDef* DMAy_Channelx,u32 par,u32
mar,u16 ndtr)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA1时钟使能
//DMA_DeInit(DMAy_Channelx);
/* 配置 DMA */
DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外设地址
DMA_InitStructure.DMA_MemoryBaseAddr = mar;//DMA 存储器0地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//存储器到外设模式
DMA_InitStructure.DMA_BufferSize = ndtr;//数据传输量
DMA_InitStructure.DMA_PeripheralInc =
DMA_PeripheralInc_Disable;//外设非增量模式
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//存储器增量模式
DMA_InitStructure.DMA_PeripheralDataSize =
DMA_PeripheralDataSize_Byte;//外设数据长度:8 位
DMA_InitStructure.DMA_MemoryDataSize =DMA_MemoryDataSize_Byte;//存储器数据长度:8 位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;// 使用普通模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;//中等优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA 通道 x 没有设置为内存到内存传输
DMA_Init(DMAy_Channelx, &DMA_InitStructure);//初始化DMA
}

在 DMAx_Init()函数中,首先使能 DMA1 时钟,然后初始化 DMA 各参数,即配置 DMA_InitStructure结构体,初始化 DMA 通道,这一过程在前面步骤介绍中已经提了。

通道的选择有函数参数DMAy_Channelx传递进来, 函数中还有另外3个形参,par 传递的是外设地址,mar传递的是存储器(内存)地址,ndtr传递的是DMA数据传输量。这样做的好处是方便大家修改。

开启 DMA 传输函数

配置好 DMA 后,我们需要开启它,代码如下:

/****************************************************************
* 函 数 名 : DMAx_Enable
* 函数功能 : 开启一次 DMA 传输
* 输 入 : DMAy_Channelx:DMA 通道选择,@ref DMA_channel
DMA_Channel_0~DMA_Channel_7
ndtr:数据传输量
* 输 出 : 无
*****************************************************************/
void DMAx_Enable(DMA_Channel_TypeDef *DMAy_Channelx,u16 ndtr)
{
DMA_Cmd(DMAy_Channelx, DISABLE); //关闭
DMA 传输
DMA_SetCurrDataCounter(DMAy_Channelx,ndtr); //数据传输量
DMA_Cmd(DMAy_Channelx, ENABLE); //开启DMA传输
}

此函数功能很简单,首先失能 DMA 传输通道,然后设置传输的数据量大小,最后使能 DMA 传输通道。函数带有形参 DMAy_Channelx 和 ndtr,用于方便选择对应外设的通道和数据量。

主函数

编写好 DMA 的初始化和使能函数后, 接下来就可以编写主函数了, 代码如下:

/****************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*****************************************************************/
int main()
{
u8 i=0;
u8 key;
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组
LED_Init();
USART1_Init(9600);
KEY_Init();
DMAx_Init(DMA1_Channel4,(u32)&USART1->DR,(u32)send_buf,send_buf_len);
Send_Data(send_buf);
while(1)
{
key=KEY_Scan(0);
if(key==KEY_UP)
{
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口 1的DMA 发送
DMAx_Enable(DMA1_Channel4,send_buf_len); //开始一次DMA 传输!
//等待 DMA传输完成,此时我们来做另外一些事
//实际应用中,传输数据期间,可以执行另外的任务
while(1)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=0)//判断通道 4传输完成
{
DMA_ClearFlag(DMA1_FLAG_TC4);
break;
}
led2=!led2;
delay_ms(300);
}
}
i++;
if(i%20==0)
{
led1=!led1;
}
delay_ms(10);
}
}

主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括SysTick系统时钟, 中断分组, LED初始化等。然后调用我们前面编写的DMAx_Init函数,由于 USART1_TX 是在 DMA1 的通道 4 中,所以通道选择为 DMA1_Channel4。

外设地址传递的是&USART1->DR,因为 USART1 用来接收数据的寄存器是USART1->DR,加上一个&就转换成地址了。存储器地址为send_buf(数组名就是数组的地址) , 这个是我们自定义的一个u8类型数组, 数组大小为send_buf_len,这个也是我们定义的一个宏,值为 5000。然后调用Send_Data 函数,此函数功能是将数组 send_buf 内所有成员全部赋值为字符 5,代码如下:

#define send_buf_len 5000
u8 send_buf[send_buf_len];
void Send_Data(u8 *p)
{
u16 i;
for(i=0;i<send_buf_len;i++)
{
*p='5';
p++;
}
}

最后进入 while 循环,调用KEY_Scan 函数,不断检测 K_UP按键是否按下,如果 K_UP 按键按下,启动一次 USART_TX 的 DMA 传输,在传输的过程中让 CPU控制 D2指示灯闪烁,直到 DMA 数据传输完成。D1 指示灯间隔200ms 闪烁,提示系统正常运行。

将工程程序编译后下载到开发板内,可以看到 D1 指示灯不断闪烁,表示程序正常运行。当 K_UP 按键按下,DMA 开始将内存数组内的数据传输到串口1 上,同时传输过程中,D2 指示灯闪烁,直到传输完成。如果想在串口调试助手上看到传输信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR 框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住,通过 DTR 状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到串口发送过来的信息。(串口助手上先勾选下标号1 DTR 框,然后再取消勾选)如图所示:

“STM32实例-DMA

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:cathy@eetrend.com)。

围观 131

直接存储器访问(Direct Memory Access),简称DMA。DMA是CPU一个用于数据从一个地址空间到另一地址空间“搬运”(拷贝)的组件,数据拷贝过程不需CPU干预,数据拷贝结束则通知CPU处理。因此,大量数据拷贝时,使用DMA可以释放CPU资源。在STM32控制器中,芯片采用Cortex-M3架构,总线结构有了很大的优化,DMA占用另外的总线,并不会与CPU的系统总线发生冲突。也就是说,DMA的使用不会影响CPU的运行速度。

“STM32串口收发数据为什么要使用DMA?"

DMA数据拷贝过程,典型的有:

  • 内存—>内存,内存间拷贝

  • 外设—>内存,如uart、spi、i2c等总线接收数据过程

  • 内存—>外设,如uart、spi、i2c等总线发送数据过程

串口有必要使用DMA吗

串口(uart)是一种低速的串行异步通信,适用于低速通信场景,通常使用的波特率小于或等于115200bps。对于小于或者等于115200bps波特率的,而且数据量不大的通信场景,一般没必要使用DMA,或者说使用DMA并未能充分发挥出DMA的作用。

对于数量大,或者波特率提高时,必须使用DMA以释放CPU资源,因为高波特率可能带来CPU资源过度浪费的问题。

举个例子

对于发送,使用循环发送,可能阻塞线程,需要消耗大量CPU资源“搬运”数据,浪费CPU。对于发送,使用中断发送,不会阻塞线程,但需浪费大量中断资源,CPU频繁响应中断。以115200bps波特率,1s传输11520字节,大约69us需响应一次中断,如波特率再提高,将消耗更多CPU资源。

对于接收,如仍采用传统的中断模式接收,同样会因为频繁中断导致消耗大量CPU资源。

因此,在高波特率传输场景下,串口非常有必要使用DMA。

来源:STM32嵌入式开发
免责声明:本文为转载文章,转载此文目的在于传递更多信息,版权归原作者所有。本文所用视频、图片、文字如涉及作品版权问题,请联系小编进行处理(联系邮箱:
cathy@eetrend.com)。

围观 438

一、DMA功能简介

首先唠叨一下DMA的基本概念,DMA的出现大大减轻了CPU的工作量。在硬件系统中,主要由CPU(内核)、外设、内存(SRAM)、总线等结构组成,数据经常要在内存和外设之间,外设和外设之间转移。例如:CPU需要处理从外设采集回来的数据,CPU需要先将数据从ADC外设的寄存器读取到内存中(变量)去,然后进行运算处理,这是一般的解决方法。CPU的资源是非常宝贵的,我们可以设法把转移的工作交给其他部件来完成,CPU把更多的资源用于数据运算和中断响应上,如此DMA便登场了。DMA正是为CPU分担数据转移工作,因为DMA的存在,CPU才被解放出来,它可以在数据转移的同时进行数据运算,相应中断,大大提高了效率。

二、DMA的主要特性
stm32之串口通信DMA传输完成中断

三、DMA中断特性

stm32之串口通信DMA传输完成中断

四、DMA之串口通信

我们实现一个简单的功能,在DMA中处理串口通信,把数据转移的工作交给DMA,DMA把数据从内存(数组)到外设(串口)的转移,在main函数中不断进行闪灯操作,这样我们可以看到DMA在工作的时候CPU也在工作。非常有必要复习一下DMA的对应关系,我们知道stm32总共有2个DMA控制器(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自一个或多个外设对存储器访问的请求,还有一个仲裁器来协调DMA请求的优先级(优先级分:很高、高、中等、低),这可不是随便对应的。
stm32之串口通信DMA传输完成中断
1、LED初始化程序如下:
stm32之串口通信DMA传输完成中断
这个地方地方没什么要注意的,唯一要注意的就是输入输出模式,我们按需求这样配就好了。

2、串口初始化
stm32之串口通信DMA传输完成中断

3、DMA初始化
stm32之串口通信DMA传输完成中断
在这里我们要注意以下几点:

(1)DMA_InitStructure.DMA_PeripheralBaseAddr = USART3_DR_Base;这里对应USART数据寄存器地址,这个地址我们是这样定义的:#define USART3_DR_Base 0x40004804,这个值是怎么算出来的呢?我们可以查看stm32存储器映射表:
stm32之串口通信DMA传输完成中断
USART3的起始地址是0x40004800,我们查看stm32串口数据寄存器偏移地址为0x04
stm32之串口通信DMA传输完成中断
因此我们可以计算到USART3数据寄存器地址为0x40004804

(2)我们数据传输方向内存(变量)到外设(串口),所以DMA方向为内存到外设

(3)DMA传输模式有两种:DMA_Mode_Normal(普通模式),DMA只传输一次;DMA_Mode_Circular(循环模式),DMA循环传输,比如在AD采集时要配置成循环模式。

4、主函数
stm32之串口通信DMA传输完成中断
这个函数很简单,我们很容易就可以实现,达到效果,这里就不贴图片了。

五、串口通信DMA传输完成中断

我们知道DMA可以在传输过半,传输完成,传输错误时产生中断。我们实现的功能是,DMA工作在普通模式下即只传输一次,LED灯初始化是关闭的,DMA传输完成后产生一个中断,在中断中我们做点灯操作。这个程序调了一天才调了出来,并不是因为它很难,而是有一些要注意的地方没有注意到,从而到时耽误了好长时间才调出来。不过有错误就会有进步嘛。

我先贴出正确的代码,然后在讨论我犯的错误,由于和上一个程序好多都是一样的,这里我们只贴出不同的地方。

(1)DMA初始化程序:
stm32之串口通信DMA传输完成中断
注意我们在这里打开了DMA传输完成中断。

(2)NVIC初始化
stm32之串口通信DMA传输完成中断
(3)中断处理程序

我们在stm32f10x_it.c中编写我们的中断处理程序:
stm32之串口通信DMA传输完成中断
我们也可以这样写中断处理程序:
stm32之串口通信DMA传输完成中断
这两种写法都行,我们在库开发文档可以查看。都代表DMA的通道2传输完成中断。  

(4)主函数
stm32之串口通信DMA传输完成中断
这样我们实验便可以看到,LED灯初始化是关闭的,当串口发送完40000字节的‘A’后,LED等亮。

(5)补充

原意是测试DMA发送完成中断指的是每次指定字节发送完成后便产生一个中断还是最终都传输完成触发一次中断,刚开始中断处理函数写的程序如下:
stm32之串口通信DMA传输完成中断
通过测试,我发现LED灯并没有像试想的那样每次发送完成后便触发一次中断,然后灯会间隔闪烁,而实际是第一次传输完成后灯点亮,之后就一直保持亮的状态。刚开始我还以为DMA只会触发第一次中断,后来仔细分析后才发现了问题。正确的代码应该如下。
stm32之串口通信DMA传输完成中断
在这里n是一个局部变量,如果不定义成静态变量,每次出中断时后n所占的内存(栈)便会释放,这样再次进入后n还是会初始化为0.与我们要达到的效果不符。因此,在这里我们把它指定为静态变量,那么内存就不会释放,它会保持上一次的的值,修改之后达到了效果,每次传输完成3000个字节后灯的状态就会改变一次。  

在这里我们整理一下变量:

全局动态变量:作用范围为整个工程,不释放内存,会保持上一次的值。

全局静态变量:作用范围为当前文件,不释放内存,会保持上一次的值。

局部动态变量:作用范围为当前函数,每次函数执行结束释放内存,不会保持上一次的值。

局部静态变量:作用范围为当前函数,不释放内存,会保持上一次的值。

本文转载自: xtusir

围观 809

页面

订阅 RSS - DMA