STM32 F1/F4/H7 DMA总结及应用指南
参考
正点原子HAL库教程
http://t.csdnimg.cn/5j1oA
http://t.csdnimg.cn/pOovw
一.介绍
DMA(直接存储器访问):
DMA是一种计算机系统中的重要技术,它允许数据在内存和外设之间直接传输,而无需CPU的直接干预。这种直接传输的方式可以显著提高数据传输的效率,因为它可以在数据传输过程中不占用CPU的时间,从而使CPU能够专注于执行其他任务。
因此:转移数据(尤其是转移大量数据)是可以不需要CPU参与。比如希望外设A的数据拷贝到外设B,只要给两种外设提供一条数据通路,直接让数据由A拷贝到B 不经过CPU的处理,
一些额外的信息包括:
工作原理:
DMA工作原理是在CPU控制下配置好DMA的参数后,DMA控制器会独立地从一个地址空间(通常是内存)中获取数据,并将数据传输到另一个地址空间(可以是内存或者外设)。整个传输过程无需CPU的干预,只有在传输完成或者出错时,DMA才会通过中断或者其他方式通知CPU。
优点:
DMA的主要优点是能够显著减轻CPU的负担,提高系统的整体性能。它可以加速大量数据的传输,同时释放CPU用于执行其他任务,从而提高系统的并行处理能力和效率。
应用场景:
DMA广泛应用于需要大量数据传输的场景,比如网络通信、存储设备读写、音视频处理等。在这些场景中,DMA可以大幅提高系统的数据处理速度,同时降低CPU的负载。
注意事项:
虽然DMA可以提高系统的性能,但在使用时需要注意DMA传输过程中的数据一致性和安全性问题。由于DMA是直接访问内存的,因此需要合理管理数据的读写顺序和保证数据的正确性,以避免出现数据损坏或者不一致的情况。
总的来说,DMA是一种强大的数据传输技术,能够有效提高系统的性能和效率,但在应用中需要合理使用,并注意处理好相关的数据一致性和安全性问题。
二.结构框图
1.STM32F1 DMA框图
STM32F1系列的DMA(Direct Memory Access)结构包括以下关键部分:
DMA请求:
DMA传输数据之前,外设需要向DMA控制器发送请求,以启动数据传输过程。
DMA通道:
DMA通道是DMA控制器中用于处理不同外设请求的通道。STM32F1系列中,DMA1有7个通道,而DMA2有5个通道。每个通道可以与一个外设相连,接收其数据传输请求。
DMA优先级:
当多个DMA通道同时发出请求时,需要确定响应处理的优先级顺序。DMA控制器中的仲裁器负责管理DMA通道的优先级,以确保数据传输的顺序性和有效性。优先级管理可以在软件阶段进行设置,也可以在硬件阶段进行配置。
需要注意的是,STM32F1系列中的DMA2仅存在于某些大容量产品和互联型产品中,而不是所有的STM32F1芯片都具有DMA2。
(1)DMA处理过程
DMA(Direct Memory Access)处理过程大致如下:
1.外设发送请求:
外设希望通过DMA进行数据传输时,会向DMA控制器发送请求。
2.DMA控制器确认请求:
DMA控制器收到外设的请求后,会向外设发送确认信号(ACK),表示已经收到请求。
3.外设释放请求:
在收到DMA控制器的确认信号后,外设会释放请求,允许DMA控制器进行数据传输。
4.数据传输:
DMA控制器根据外设发送的请求,启动数据传输过程。数据传输包括从源地址(通常是外设的数据寄存器)读取数据,并将其写入目标地址(通常是内存或另一个外设的数据寄存器)。
传输的数据量由DMA控制器提前配置好,通常是指定要传输的字节数或数据块的数量。
5.传输完成:
一旦传输完成,DMA控制器会发送信号通知外设和CPU传输已经完成。
这个过程中,CPU不需要直接参与数据传输的过程,而是可以继续执行其他任务,从而提高了系统的效率。DMA对于大量数据的传输以及需要高速响应的应用场景非常有用。
(2)DMA通道
DMA通道在STM32中用于管理外设对存储器访问的请求,每个通道可以处理来自一个或多个外设的请求。DMA通道的数量取决于具体的STM32系列和型号,在STM32中通常有多个DMA通道可供选择。
每个DMA通道都有一个仲裁器(Arbiter),用于处理DMA请求之间的优先级。当多个DMA通道同时收到请求时,仲裁器会根据通道的优先级确定哪个通道优先处理请求。通常,可以通过软件配置DMA通道的优先级,也可以在硬件层面实现对DMA请求的优先级管理。这种方式可以确保DMA传输按照特定的优先级顺序进行,从而更好地满足系统的要求。
(3)DMA优先级
仲裁器的作用是确定各个DMA传输的优先级
仲裁器根据通道请求的优先级来启动外设/存储器的访问。
在STM32中,DMA优先级的管理分为两个阶段:软件阶段和硬件阶段。
软件阶段:在软件阶段,每个DMA通道的优先级可以在相应的DMA_CCRx寄存器中进行设置。通常情况下,有四个优先级等级可供选择:最高优先级、高优先级、中优先级和低优先级。通过设置这些优先级等级,可以确保DMA通道在处理请求时按照指定的优先级顺序进行。
硬件阶段:如果多个DMA通道的请求具有相同的软件优先级,则会进入硬件阶段。在硬件阶段,仲裁器会对DMA请求进行优先级排序。通常情况下,如果两个请求具有相同的软件优先级,则具有较低编号的DMA通道会被赋予较高的优先级。这意味着较低编号的DMA通道会优先获得DMA控制器的处理权。
需要注意的是,多个DMA请求通过逻辑或门输入到DMA控制器时,只能有一个请求能够被有效处理。因此,在设置DMA优先级时,需要确保合适的优先级设置以及适当的DMA请求触发条件,以便系统能够按照预期的方式处理DMA传输请求。在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。
(4)数据宽度与对齐
(5)DMA传输方式
DMA的作用就是实现数据的直接传输,而去掉了传统数据传输需要CPU寄存器参与的环节,主要涉及四种情况的数据传输,但本质上是一样的,都是从内存的某一区域传输到内存的另一区域(外设的数据寄存器本质上就是内存的一个存储单元)。四种情况的数据传输如下:外设到内存;内存到外设;内存到内存;外设到外设。
方法1:DMA_Mode_Normal,正常模式,
当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次
方法2:DMA_Mode_Circular ,循环传输模式
当传输结束时,硬件自动会将传输数据量寄存器进行重装,进行下一轮的数据传输。 也就是多次传输模式
指针递增模式
根据 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 不支持。
存储器到存储器模式不能与循环模式同时使用。
(6)DMA传输参数
数据传输,首先需要的是1 数据的源地址 2 数据传输位置的目标地址 ,3 传递数据多少的数据传输量 ,4 进行多少次传输的传输模式 DMA所需要的核心参数,便是这四个。
当用户将参数设置好,主要涉及源地址、目标地址、传输数据量这三个,DMA控制器就会启动数据传输,当剩余传输数据量为0时 达到传输终点,结束DMA传输 ,当然,DMA 还有循环传输模式 当到达传输终点时会重新启动DMA传输。
也就是说只要剩余传输数据量不是0,而且DMA是启动状态,那么就会发生数据传输。
(7)DMA的主要特征
每个通道都直接连接专用的硬件DMA请求,每个通道都同样支持软件触发。这些功能通过软件来配置;
1.在同一个DMA模块上,多个请求间的优先权可以通过软件编程设置(共有四级:很高、高、中等和低),优先权设置相等时由硬件决定(请求0优先于请求1,依此类推);
2.独立数据源和目标数据区的传输宽度(字节、半字、全字),模拟打包和拆包的过程。源和目标地址必须按数据传输宽度对齐;
3.支持循环的缓冲器管理;
4.每个通道都有3个事件标志(DMA半传输、DMA传输完成和DMA传输出错),这3个事件标志逻辑或成为一个单独的中断请求;
5.存储器和存储器间的传输、外设和存储器、存储器和外设之间的传输;
闪存、SRAM、外设的SRAM、APB1、APB2和AHB外设均可作为访问的源和目标;
6.可编程的数据传输数目:最大为65535。
(7)DMA中断
每个DMA通道都可以在DMA传输过半、传输完成和传输错误时产生中断。为应用的灵活性考虑,通过设置寄存器的不同位来打开这些中断。
使没开启,我们也可以通过查询这些位来获得当前 DMA 传输的状态。这里我们常用的是 TCIFx位,即数据流 x 的 DMA 传输完成与否标志。
2.F4 / F7 / H7 DMA结构框图
(1)系统架构图
(2)F4/F7/H7 DMA功能
在STM32F4和F7系列中,DMA控制器主要是通过DMA1和DMA2来实现数据传输。它们支持以下功能:
DMA1:
内存到外设(Memory to Peripheral)传输
外设到内存(Peripheral to Memory)传输
DMA2:
内存到内存(Memory to Memory)传输
内存到外设(Memory to Peripheral)传输
外设到内存(Peripheral to Memory)传输
而在STM32H7系列中,DMA功能更加强大,具有更多的功能和灵活性:
D1域(MDMA):
内存到内存(Memory to Memory)传输
内存到外设(Memory to Peripheral)传输
外设到内存(Peripheral to Memory)传输
D2域(DMA1和DMA2):
内存到内存(Memory to Memory)传输
内存到外设(Memory to Peripheral)传输
外设到内存(Peripheral to Memory)传输
D3域(BDMA):
内存到内存(Memory to Memory)传输
内存到外设(Memory to Peripheral)传输
外设到内存(Peripheral to Memory)传输
外设到外设(Peripheral to Peripheral)传输
此外,H7系列的DMA控制器还具有双缓冲模式、突发传输等功能,以提高数据传输的效率和性能。通道请求的灵活性也得到了增强,使得DMA在处理各种数据传输任务时更加灵活和高效。
(3)F4/F7 DMA框图
在STM32F4/F7的DMA结构中,主要包括以下几个关键部分:
AHB从器件编程接口:
DMA相关的控制寄存器通过这个接口设置,用于配置DMA的工作模式、数据传输方向等。
存储器端口和外设端口:
DMA控制器有两个端口,一个用于存储器访问,另一个用于外设访问。这两个端口分别用于处理从存储器到外设或从外设到存储器的数据传输。
FIFO(First In, First Out):
DMA控制器内部有一个FIFO,通常包含4级32位存储器缓冲区。这个FIFO用作源数据传输到目标地址前的临时存储区。可以配置为FIFO模式或直接模式。在FIFO模式下,软件设置阈值,当FIFO达到阈值时触发数据传输;在直接模式下,立即启动对存储器的传输。
DMA优先级(数据流优先级):
DMA控制器中的每个数据流都有一个优先级。在软件阶段,可以通过寄存器配置优先级,通常分为非常高、高、中、低四个等级。在硬件阶段,较低编号的数据流具有较高的优先级。
DMA请求:
每个数据流与一个DMA请求相关联,可以从8个通道中选出DMA请求。这些请求来自于外设,表示外设需要进行数据传输操作。
通过以上结构,DMA控制器能够高效地管理数据传输,减轻CPU的负担,提高系统性能。
(4)F4/F7 DMA请求
在STM32F4/F7中,DMA请求管理主要涉及以下几个关键点:
DMA_SxCR控制数据流使用通道:
DMA控制寄存器(如DMA_SxCR)用于配置数据流的工作模式、数据传输方向、数据大小等参数。每个数据流都对应一个通道,通过配置这些寄存器,可以控制数据流的行为。
外设请求占用通道:
每个外设请求都会占用一个DMA通道,当外设需要进行数据传输时,它会发送一个请求,并占用一个特定的DMA通道。相同外设的不同请求可以占用不同的数据流通道。
请求占用通道的限制:
当某个外设请求使用了某个数据流的通道时,该数据流的其他通道就不能被选择,变为不可用状态。这意味着如果一个外设需要使用多个通道进行数据传输,就需要考虑通道的分配和冲突问题。
通过DMA请求的管理,可以有效地分配和利用DMA通道,确保各个外设之间的数据传输能够顺利进行,提高系统的整体性能。
(5)H7 的结构框图
在STM32H7中,DMA请求管理主要由DMAMUX(DMA Multiplexer)模块负责,具体结构如下:
1.DMA请求的分配:
数据流0~7的通道请求由DMAMUX1管理,每个数据流都有多达115个通道请求可供选择。
H7具有两个DMAMUX模块,其中DMAMUX1负责DMA1/2,而DMAMUX2负责BDMA(Basic DMA)。
2.DMAMUX的作用:
DMAMUX用于管理DMA通道的请求,它能够将外设的请求与DMA通道进行有效地映射,以满足不同外设对DMA传输的需求。通过DMAMUX的灵活配置,可以实现不同数据流通道与外设请求之间的映射关系,从而灵活地分配DMA资源,提高系统的数据传输效率。
3.多通道请求选择:
每个数据流通道可以选择多达115个通道请求,这意味着系统可以同时处理多个外设的数据传输请求,从而实现并行传输,提高系统整体性能。通过DMAMUX的灵活配置和管理,STM32H7能够高效地处理来自多个外设的DMA请求,并且能够满足复杂应用场景下的数据传输需求。
(6)DMAMUX(DMA Multiplexer)
DMAMUX(DMA Multiplexer)DMA请求复用器在STM32H7中起着关键的作用,其主要特点和功能如下:
1.外设请求:
DMAMUX接收来自外设的DMA请求信号,这些请求信号可能来自STM32H7芯片内部的不同外设模块,共有107个外设请求信号可供选择。
2.通道选择:
DMAMUX具有用于选择DMA请求通道的功能,通过配置DMAMUX,可以将外设的DMA请求信号映射到DMA控制器的具体通道上。
3.同步控制:
DMAMUX还提供了同步控制功能,用于同步DMA请求信号。这个功能可以在一定程度上优化DMA传输的效率,但在某些情况下可能不需要使用,可以关闭以节省系统资源。
4.请求信号输出:
DMAMUX最终将经过选择和同步的DMA请求信号输出给DMA控制器,由DMA控制器根据接收到的信号进行数据传输操作。
5.通道映射:
对于DMAMUX1而言,共有15个通道可供选择,分别对应DMA1的通道0-7和DMA2的通道0-7,共计15个通道。通道0-7用于DMA1,通道8-15用于DMA2。
对于DMAMUX2,通道数和通道映射情况可能有所不同,具体取决于其管理的DMA模块(比如BDMA)。
通过DMAMUX的灵活配置,可以有效管理和优化系统中的DMA请求信号,实现不同外设模块之间的数据传输需求,并且提高系统整体的性能和效率。
三.DMA相关寄存器介绍
1.DMA相关寄存器 F1
以下是STM32F1系列中与DMA相关的寄存器介绍:
1.DMA_CCRx:DMA通道x配置寄存器。用于配置DMA通道的工作模式、传输方向、数据大小、内存和外设增量模式、循环模式等参数。每个DMA通道都有一个对应的CCR寄存器。
2.DMA_ISR:DMA中断状态寄存器。用于查询当前DMA传输的状态,包括传输完成、半传输完成、传输错误等状态。
3.DMA_IFCR:DMA中断标志清除寄存器。用来清除DMA_ISR寄存器中的中断标志位,以便重新开始新的DMA传输。
4.DMA_CNDTRx:DMA通道x传输数量寄存器。用于设置DMA通道x每次传输的数据量,即传输的数据数量。
5.DMA_CPARx:DMA通道x外设地址寄存器。用于存储与DMA通道x相关联的外设的地址,指定数据的来源或目的地。
6.DMA_CMARx:DMA通道x存储器地址寄存器。用于存放与DMA通道x相关联的存储器的地址,指定数据的来源或目的地。
7.USART_CR3:USART控制寄存器3。用于配置USART串口的工作模式,其中包括使能串口DMA发送功能,允许USART通过DMA进行数据发送。
这些寄存器在DMA控制和配置过程中起着关键作用,通过合理设置这些寄存器的值,可以实现灵活高效的DMA数据传输操作。
2.F4 / F7 / H7 DMA相关寄存器
以下是F4/F7/H7系列的DMA相关寄存器介绍:
F4/F7/H7 DMA寄存器:
1.DMA_SxCR(DMA数据流x配置寄存器):
用于配置DMA数据流的控制参数,包括传输方向、数据大小、传输模式、循环模式等。
2.DMA_(H/L)ISR(DMA(低/高)中断状态寄存器):
用于查询当前DMA传输状态和中断标志,包括传输完成、传输错误等。
3.DMA_(H/L)IFCR(DMA(低/高)中断标志清除寄存器):
用来清除DMA_(H/L)ISR中对应的中断标志位。
4.DMA_SxNDTR(DMA数据流x传输数量寄存器):
用于设置DMA数据流x每次传输的数据量,即传输的数据大小。
5.DMA_SxPAR(DMA数据流x外设地址寄存器):
用于存储外设的地址,DMA将从这个地址读取数据进行传输。
6.DMA_SxM0AR(DMA数据流x存储器地址寄存器):
用于存放存储器的地址,DMA将把数据传输到这个地址。
H7 DMAMUX寄存器:
对于H7系列的DMAMUX,只需要了解DMAMUX请求发生器通道x配置寄存器的使用即可,具体寄存器名称和作用可能与DMA控制器的寄存器有所不同,但作用是用于配置DMAMUX通道和DMA控制器之间的连接关系,从而实现DMA请求的管理和分配。
四.DMA相关HAL库驱动介绍
1.DMA相关HAL库驱动 F1
以下是STM32F1系列中与DMA相关的HAL库驱动介绍:
1.__HAL_RCC_DMAx_CLK_ENABLE(…): 该函数用于使能DMAx时钟,其中x表示DMA控制器的编号。通过该函数可以启用对应DMA控制器的时钟。
2.HAL_DMA_Init(…): 该函数用于初始化DMA传输通道。通过配置DMA_InitTypeDef结构体中的参数,可以初始化DMA通道的工作模式、传输方向、数据大小、内存和外设增量模式等。
3.HAL_DMA_Start_IT(…): 该函数用于启动DMA传输,并通过中断方式进行传输。它需要配置DMA传输通道的相关寄存器,包括CCR寄存器、CPAR寄存器、CMAR寄存器和CNDTR寄存器。
4.__HAL_LINKDMA(…): 该函数用于将DMA传输通道与外设句柄连接起来,以便外设能够通过DMA进行数据传输。
5.HAL_UART_Transmit_DMA(…): 该函数用于启用USART串口的DMA发送功能,并启动数据传输。它需要配置DMA传输通道的相关寄存器以及USART的CR3寄存器,以使能DMA发送。
6.__HAL_DMA_GET_FLAG(…): 该函数用于查询DMA传输通道的状态,可以检查DMA_ISR寄存器中的中断标志位,以确定DMA传输是否完成或出现错误。
7.__HAL_DMA_ENABLE(…) 和 __HAL_DMA_DISABLE(…): 这两个函数分别用于使能和失能DMA外设。它们通过设置DMA通道的CCR寄存器中的EN位来控制DMA外设的使能状态。
此外,还有两个与DMA相关的结构体:DMA_HandleTypeDef 和 DMA_InitTypeDef。DMA_HandleTypeDef结构体用于存储DMA传输通道的相关信息,而DMA_InitTypeDef结构体用于配置DMA传输通道的初始化参数。
2.F4 / F7 / H7 DMA相关HAL库驱动
以下是F4/F7/H7系列的DMA相关HAL库驱动函数介绍:
1.__HAL_RCC_DMAx_CLK_ENABLE(…):
关联寄存器:RCC_AHBENR
功能描述:使能指定的DMA时钟。
2.HAL_DMA_Init(…):
关联寄存器:DMA_SxCR
功能描述:初始化DMA数据流配置,包括传输方向、数据大小、传输模式等参数的设置。
3.HAL_DMA_Start_IT(…):
关联寄存器:DMA_SxCR、DMA_SxPAR、DMA_SxM0AR、DMA_SxNDTR
功能描述:开始DMA传输,并启用中断进行传输完成等事件的处理。
4.__HAL_LINKDMA(…):
功能描述:用来连接DMA和外设句柄,建立DMA传输的关联关系。
5.HAL_UART_Transmit_DMA(…):
关联寄存器:DMA_SxCR、DMA_SxPAR、DMA_SxM0AR、DMA_SxNDTR、USART_CR3
功能描述:使能DMA发送,并启动传输,通常用于串口数据传输。
6.__HAL_DMA_GET_FLAG(…):
关联寄存器:DMA_ISR
功能描述:查询DMA传输通道的状态,例如传输完成标志等。
7.__HAL_DMA_ENABLE(…):
关联寄存器:DMA_CCR(EN)
功能描述:使能指定的DMA外设。
8.__HAL_DMA_DISABLE(…):
关联寄存器:DMA_CCR(EN)
功能描述:失能指定的DMA外设。
9.HAL_UART_DMAStop(…):
关联寄存器:USART_CR3
功能描述:停止DMA传输,通常用于串口DMA传输的停止。
五、DMA配置步骤
1.以DMA方式传输串口数据配置步骤 F1
以下是使用DMA方式传输串口数据的配置步骤:
1.使能DMA时钟:
使用 __HAL_RCC_DMA1_CLK_ENABLE() 函数使能DMA1的时钟,确保DMA功能正常运行。
2.初始化DMA:
使用 HAL_DMA_Init() 函数初始化DMA传输通道,配置DMA_InitTypeDef结构体中的参数,包括DMA模式、数据方向、数据大小、传输地址等。
3.连接DMA和外设:
使用 __HAL_LINKDMA() 函数将DMA传输通道与串口外设连接起来,以确保外设能够通过DMA进行数据传输。
4.使能串口的DMA发送并启动传输:
使用 HAL_UART_Transmit_DMA() 函数使能串口的DMA发送功能,并启动数据传输。该函数需要配置DMA传输通道的相关寄存器以及串口的CR3寄存器,以启用DMA发送。
5.查询DMA传输状态:
使用 __HAL_DMA_GET_FLAG() 函数查询DMA传输通道的状态,以确定传输是否完成或出现错误。使用 __HAL_DMA_GET_COUNTER() 函数获取当前传输剩余数据量,以便监测传输进度。
6.DMA中断使用:
使用 HAL_NVIC_EnableIRQ() 函数使能DMA传输通道的中断,并设置优先级。
编写中断服务函数,例如 xxx_IRQHandler(),用于处理DMA传输完成或错误的中断事件。
通过以上步骤,可以实现串口数据的DMA传输,并通过中断方式进行传输状态的监测和处理。
2.F4 / F7/ H7 DMA配置步骤
以下是F4/F7/H7系列的DMA配置步骤:
1.使能DMA时钟:
__HAL_RCC_DMAx_CLK_ENABLE(); //使用该宏使能所需的DMA时钟
2.初始化DMA:
HAL_DMA_Init(&hdma); // hdma为DMA_HandleTypeDef类型的结构体变量,其中包含DMA的配置参数
使用HAL_DMA_Init函数初始化DMA相关参数,包括传输方向、数据大小、传输模式等。
3.连接DMA和外设:
__HAL_LINKDMA(huart, hdmatx, hdma_tx); // huart为UART_HandleTypeDef类型的结构体变量,hdmatx为外设的DMA传输句柄,hdma_tx为DMA的句柄
使用__HAL_LINKDMA函数连接DMA和外设,建立DMA传输的关联关系。
4.使能串口的DMA发送,启动传输:
HAL_UART_Transmit_DMA(&huart, pData, Size); // huart为UART_HandleTypeDef类型的结构体变量,pData为发送数据的指针,Size为发送数据的大小
使用HAL_UART_Transmit_DMA函数使能串口的DMA发送,并启动数据传输。
5.查询DMA传输状态:
__HAL_DMA_GET_FLAG(hdma, DMA_FLAG_TC); // hdma为DMA_HandleTypeDef类型的结构体变量,DMA_FLAG_TC为传输完成标志
使用__HAL_DMA_GET_FLAG函数查询DMA传输通道的状态,例如传输完成标志等。
__HAL_DMA_GET_COUNTER(hdma); // hdma为DMA_HandleTypeDef类型的结构体变量
使用__HAL_DMA_GET_COUNTER函数获取当前传输剩余数据量。
6.DMA中断使用:
HAL_NVIC_EnableIRQ(DMAx_Streamx_IRQn); // DMAx_Streamx_IRQn为DMA中断的中断号
HAL_NVIC_SetPriority(DMAx_Streamx_IRQn, 0, 0); // DMAx_Streamx_IRQn为DMA中断的中断号,0为抢占优先级,0为子优先级
使用HAL_NVIC_EnableIRQ函数使能DMA中断。
使用HAL_NVIC_SetPriority函数配置DMA中断的优先级。
编写中断服务函数:
void DMAx_Streamx_IRQHandler(void)
{
HAL_DMA_IRQHandler(&hdma); // hdma为DMA_HandleTypeDef类型的结构体变量
}
编写DMA中断服务函数,处理DMA中断事件。
六、相关实验
1.F1
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/DMA/dma.h"
const uint8_t TEXT_TO_SEND[] = {"正点原子 STM32 DMA 串口实验"}; /* 要循环发送的字符串 */
#define SEND_BUF_SIZE (sizeof(TEXT_TO_SEND) + 2) * 200 /* 发送数据长度, 等于sizeof(TEXT_TO_SEND) + 2的200倍. */
uint8_t g_sendbuf[SEND_BUF_SIZE]; /* 发送数据缓冲区 */
extern DMA_HandleTypeDef g_dma_handle; /* DMA句柄 */
extern UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
int main(void)
{
uint8_t key = 0;
uint16_t i, k;
uint16_t len;
uint8_t mask = 0;
float pro = 0; /* 进度 */
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(RCC_PLL_MUL9); /* 设置时钟, 72Mhz */
delay_init(72); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
dma_init(DMA1_Channel4); /* 初始化串口1 TX DMA */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);
len = sizeof(TEXT_TO_SEND);
k = 0;
for (i = 0; i < SEND_BUF_SIZE; i++) /* 填充ASCII字符集数据 */
{
if (k >= len) /* 入换行符 */
{
if (mask)
{
g_sendbuf[i] = 0x0a;
k = 0;
}
else
{
g_sendbuf[i] = 0x0d;
mask++;
}
}
else /* 复制TEXT_TO_SEND语句 */
{
mask = 0;
g_sendbuf[i] = TEXT_TO_SEND[k];
k++;
}
}
i = 0;
while (1)
{
key = key_scan(0);
if (key == KEY0_PRES) /* KEY0按下 */
{
printf("\r\nDMA DATA:\r\n");
lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE);
lcd_show_string(30, 150, 200, 16, 16, " %", BLUE); /* 显示百分号 */
HAL_UART_Transmit_DMA(&g_uart1_handle, g_sendbuf, SEND_BUF_SIZE);
/* 等待DMA传输完成,此时我们来做另外一些事情,比如点灯
* 实际应用中,传输数据期间,可以执行另外的任务
*/
while (1)
{
if ( __HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TC4)) /* 等待 DMA1_Channel4 传输完成 */
{
__HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TC4);
HAL_UART_DMAStop(&g_uart1_handle); /* 传输完成以后关闭串口DMA */
break;
}
pro = DMA1_Channel4->CNDTR; /* 得到当前还剩余多少个数据 */
len = SEND_BUF_SIZE; /* 总长度 */
pro = 1 - (pro / len); /* 得到百分比 */
pro *= 100; /* 扩大100倍 */
lcd_show_num(30, 150, pro, 3, 16, BLUE);
}
lcd_show_num(30, 150, 100, 3, 16, BLUE); /* 显示100% */
lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE); /* 提示传送完成 */
}
i++;
delay_ms(10);
if (i == 20)
{
LED0_TOGGLE(); /* LED0闪烁,提示系统正在运行 */
i = 0;
}
}
}
dma.c
#include "./BSP/DMA/dma.h"
#include "./SYSTEM/delay/delay.h"
DMA_HandleTypeDef g_dma_handle; /* DMA句柄 */
extern UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
/**
* @brief 串口TX DMA初始化函数
* @note 这里的传输形式是固定的, 这点要根据不同的情况来修改
* 从存储器 -> 外设模式/8位数据宽度/存储器增量模式
*
* @param dmax_chy : DMA的通道, DMA1_Channel1 ~ DMA1_Channel7, DMA2_Channel1 ~ DMA2_Channel5
* 某个外设对应哪个DMA, 哪个通道, 请参考<<STM32中文参考手册 V10>> 10.3.7节
* 必须设置正确的DMA及通道, 才能正常使用!
* @retval 无
*/
void dma_init(DMA_Channel_TypeDef* DMAx_CHx)
{
if ((uint32_t)DMAx_CHx > (uint32_t)DMA1_Channel7) /* 大于DMA1_Channel7, 则为DMA2的通道了 */
{
__HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */
}
else
{
__HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
}
__HAL_LINKDMA(&g_uart1_handle, hdmatx, g_dma_handle); /* 将DMA与USART1联系起来(发送DMA) */
/* Tx DMA配置 */
g_dma_handle.Instance = DMAx_CHx; /* USART1_TX使用的DMA通道为: DMA1_Channel4 */
g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* DIR = 1 , 存储器到外设模式 */
g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */
g_dma_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */
g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 外设数据长度:8位 */
g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 存储器数据长度:8位 */
g_dma_handle.Init.Mode = DMA_NORMAL; /* 外设流控模式 */
g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */
HAL_DMA_Init(&g_dma_handle);
}
2.H750
dma.c
#include "./BSP/DMA/dma.h"
#include "./SYSTEM/delay/delay.h"
DMA_HandleTypeDef g_dma_handle; /* DMA句柄 */
extern UART_HandleTypeDef g_uart1_handle; /* UART句柄 */
/**
* @brief 串口TX DMA初始化函数
* @note 这里的传输形式是固定的, 这点要根据不同的情况来修改
* 从存储器 -> 外设模式/8位数据宽度/存储器增量模式
*
* @param dma_stream_handle : DMA数据流,DMA1_Stream0~7/DMA2_Stream0~7
* @retval 无
*/
void dma_init(DMA_Stream_TypeDef *dma_stream_handle, uint32_t ch)
{
if ((uint32_t)dma_stream_handle > (uint32_t)DMA2) /* 得到当前stream是属于DMA2还是DMA1 */
{
__HAL_RCC_DMA2_CLK_ENABLE(); /* DMA2时钟使能 */
}
else
{
__HAL_RCC_DMA1_CLK_ENABLE(); /* DMA1时钟使能 */
}
__HAL_LINKDMA(&g_uart1_handle,hdmatx, g_dma_handle); /* 将DMA与USART1联系起来(发送DMA) */
/* Tx DMA配置 */
g_dma_handle.Instance = dma_stream_handle; /* 数据流选择 */
g_dma_handle.Init.Request = ch; /* DMA通道选择 */
g_dma_handle.Init.Direction = DMA_MEMORY_TO_PERIPH; /* 存储器到外设 */
g_dma_handle.Init.PeriphInc = DMA_PINC_DISABLE; /* 外设非增量模式 */
g_dma_handle.Init.MemInc = DMA_MINC_ENABLE; /* 存储器增量模式 */
g_dma_handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; /* 外设数据长度:8位 */
g_dma_handle.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; /* 存储器数据长度:8位 */
g_dma_handle.Init.Mode = DMA_NORMAL; /* 外设流控模式 */
g_dma_handle.Init.Priority = DMA_PRIORITY_MEDIUM; /* 中等优先级 */
g_dma_handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; /* 关闭FIFO模式 */
g_dma_handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL; /* FIFO阈值配置 */
g_dma_handle.Init.MemBurst = DMA_MBURST_SINGLE; /* 存储器突发单次传输 */
g_dma_handle.Init.PeriphBurst = DMA_PBURST_SINGLE; /* 外设突发单次传输 */
HAL_DMA_DeInit(&g_dma_handle); /* 取消DMA句柄的初始化 */
HAL_DMA_Init(&g_dma_handle); /* 重新对DMA句柄进行初始化 */
}
main.c
#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./USMART/usmart.h"
#include "./BSP/MPU/mpu.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./BSP/DMA/dma.h"
const uint8_t TEXT_TO_SEND[] = {"正点原子 MiniPRO STM32H7 DMA 串口实验"}; /* 要循环发送的字符串 */
#define SEND_BUF_SIZE (sizeof(TEXT_TO_SEND) + 2) * 200 /* 发送数据长度, 等于sizeof(TEXT_TO_SEND) + 2的200倍. */
uint8_t g_sendbuf[SEND_BUF_SIZE]; /* 发送数据缓冲区 */
int main(void)
{
uint8_t key = 0;
uint16_t i, k;
uint16_t len;
uint8_t mask = 0;
float pro = 0; /* 进度 */
sys_cache_enable(); /* 打开L1-Cache */
HAL_Init(); /* 初始化HAL库 */
sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
delay_init(480); /* 延时初始化 */
usart_init(115200); /* 串口初始化为115200 */
mpu_memory_protection(); /* 保护相关存储区域 */
led_init(); /* 初始化LED */
lcd_init(); /* 初始化LCD */
key_init(); /* 初始化按键 */
dma_init(DMA2_Stream7, DMA_REQUEST_USART1_TX); /* 初始化DMA */
lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
lcd_show_string(30, 70, 200, 16, 16, "DMA TEST", RED);
lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
lcd_show_string(30, 110, 200, 16, 16, "KEY0:Start", RED);
len = sizeof(TEXT_TO_SEND);
k = 0;
for (i = 0; i < SEND_BUF_SIZE; i++) /* 填充ASCII字符集数据 */
{
if (k >= len) /* 入换行符 */
{
if (mask)
{
g_sendbuf[i] = 0x0a;
k = 0;
}
else
{
g_sendbuf[i] = 0x0d;
mask++;
}
}
else /* 复制TEXT_TO_SEND语句 */
{
mask = 0;
g_sendbuf[i] = TEXT_TO_SEND[k];
k++;
}
}
i = 0;
while (1)
{
key = key_scan(0);
if (key == KEY0_PRES) /* KEY0按下 */
{
printf("\r\nDMA DATA:\r\n");
lcd_show_string(30, 130, 200, 16, 16, "Start Transimit....", BLUE);
lcd_show_string(30, 150, 200, 16, 16, " %", BLUE); /* 显示百分号 */
HAL_UART_Transmit_DMA(&g_uart1_handle,g_sendbuf,SEND_BUF_SIZE); /* 开始一次DMA传输! */
/* 等待DMA传输完成,此时我们来做另外一些事情,比如点灯
* 实际应用中,传输数据期间,可以执行另外的任务
*/
while (1)
{
if (__HAL_DMA_GET_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7)) /* 等待DMA2_Stream7传输完成 */
{
__HAL_DMA_CLEAR_FLAG(&g_dma_handle, DMA_FLAG_TCIF3_7); /* 清除DMA2_Stream7传输完成标志 */
HAL_UART_DMAStop(&g_uart1_handle); /* 传输完成以后关闭串口DMA */
break;
}
pro = __HAL_DMA_GET_COUNTER(&g_dma_handle); /* 得到当前还剩余多少个数据 */
len = SEND_BUF_SIZE; /* 总长度 */
pro = 1 - (pro / len); /* 得到百分比 */
pro *= 100; /* 扩大100倍 */
lcd_show_num(30, 150, pro, 3, 16, BLUE);
}
lcd_show_num(30, 150, 100, 3, 16, BLUE); /* 显示100% */
lcd_show_string(30, 130, 200, 16, 16, "Transimit Finished!", BLUE); /* 提示传送完成 */
}
i++;
delay_ms(10);
if (i == 20)
{
LED0_TOGGLE(); /* LED0闪烁,提示系统正在运行 */
i = 0;
}
}
}
作者:孤芳剑影