STM32 的 DMA 是一种强大的硬件技术,在嵌入式系统中发挥着重要作用。
DMA,全称为 Direct Memory Access,即直接内存访问。它允许数据在内存和外设之间直接传输,而无需 CPU 的干预。在 STM32 微控制器中,DMA 的作用尤为关键,它能够显著提高数据传输的效率,减少 CPU 的负担,从而让 CPU 可以处理其他更重要的任务。
STM32 最多有 2 个 DMA 控制器。其中,DMA1 有 7 个通道,DMA2 有 5 个通道。每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。有一个仲裁器来协调各个 DMA 请求的优先权。在同一个 DMA 模块上,多个请求间的优先权可以通过软件编程设置,共有四级:很高、高、中等和低。优先权设置相等时由硬件决定,例如较低编号的通道比较高编号的通道有较高的优先权。
DMA 传输的数据量是可编程的,最大达到 65535。包含要传输的数据项数量的寄存器,在每次传输后递减。数据传输宽度可设置,外设与存储器的数据传输宽度的设置可根据不同情况进行数据对齐。通过设置 DMA_CCRx 寄存器中的标志位,外设和存储器的指针在每次传输后可以有选择地完成自动增量。
DMA 的数据传输可以是单次传输也可以是循环不间断的传输。循环模式用于处理循环缓冲区和连续的数据传输,如 ADC 的扫描模式。在 DMA_CCRx 寄存器中的 CIRC 位用于开启这一功能。当启动了循环模式,数据传输的数目变为 0 时,将会自动地被恢复成配置通道时设置的初值,DMA 操作将会继续进行。此外,DMA 还可以实现存储器到存储器的传输模式,当设置了 DMA_CCRx 寄存器中的 MEM2MEM 位之后,在软件设置了 DMA_CCRx 寄存器中的 EN 位启动 DMA 通道时,DMA 传输将马上开始。当 DMA_CNDTRx 寄存器变为 0 时,DMA 传输结束。
二、DMA 的组成与特性
(一)DMA 控制器结构
STM32 的 DMA 控制器结构较为复杂但功能强大。其中,最多有两个独立的 DMA 控制器,即 DMA1 和 DMA2。DMA1 通常较为常用,拥有 7 个通道,可用于管理多种外设对存储器的访问请求。而 DMA2 在大容量产品中存在,拥有 5 个通道。
每个通道都具有特定的功能,能够管理来自一个或多个外设对存储器的访问请求。这意味着不同的外设可以通过不同的通道与存储器进行数据传输,提高了系统的灵活性和效率。
为了协调各个通道的请求,STM32 引入了仲裁器。当多个外设同时请求 DMA 传输时,仲裁器会根据预先设置的优先权来决定哪个通道先进行数据传输。这种机制确保了系统在处理多个 DMA 请求时的有序性和高效性。
(二)DMA 的主要特性
- STM32 的 DMA 具有 12 个独立可配置的通道,其中 DMA1 有 7 个通道,DMA2 有 5 个通道。这些通道不仅支持软件触发,还支持特定的硬件触发。通过软件编程,可以设置每个通道的优先权,共有四级:很高、高、中等和低。当多个通道的优先权相同时,低编号的通道具有更高的优先权。例如,在相同优先级的情况下,通道 0 会优先于通道 1 进行数据传输。
- DMA 具有独立的源和目标数据区传输宽度,可以根据实际需求进行设置。它支持循环缓冲管理,这在处理连续数据传输时非常有用。例如,在 ADC 的扫描模式下,可以通过循环缓冲管理实现连续的数据采集和传输。此外,每个通道都有 3 个事件标志,分别是 DMA 半传输、DMA 传输完成和 DMA 传输出错。这 3 个事件标志可以逻辑或成为一个单独的中断请求,当发生相应事件时,可以及时通知 CPU 进行处理。
- STM32 的 DMA 支持多种数据传输模式,包括存储器和存储器间的传输、外设和存储器之间的传输以及存储器和外设之间的传输。这种灵活性使得 DMA 可以满足不同应用场景的需求。同时,可编程的数据传输数目最大为 65535,这意味着可以进行大量数据的连续传输,提高了数据传输的效率。例如,在进行大数据量的存储设备读写操作时,可以利用 DMA 的这一特性,快速地将数据从存储器传输到外设或从外设传输到存储器。
三、DMA 的工作原理与模式
(一)工作原理
当一个事件发生后,外设会向 DMA 控制器发送一个请求信号。DMA 控制器中的仲裁器会根据预先设置的通道优先权来处理这个请求。当 DMA 控制器开始访问发出请求的外设时,会立即发送给它一个应答信号。当从 DMA 控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA 控制器同时撤销应答信号。这样就完成了一个数据传输的周期,当有更多的请求时,外设可以启动下一个周期。
例如,在 ADC 采集数据的过程中,当一次 ADC 转换完成后,ADC 会向 DMA 控制器发送请求信号。DMA 控制器根据优先权处理这个请求,开始从 ADC 的数据寄存器读取数据,并将其存储到指定的存储器地址中。当数据传输完成后,外设释放请求,DMA 控制器撤销应答信号,等待下一次 ADC 转换完成后的请求。
(二)传输模式
- 数据传输宽度可设置外设与存储器不同宽度,通过设置标志位实现指针增量。
STM32 的 DMA 可以设置不同的数据传输宽度,如 8 位、16 位或 32 位。当外设与存储器的数据宽度不同时,可以通过设置 DMA_CCRx 寄存器中的标志位来实现数据的正确传输。同时,外设和存储器的指针在每次传输后可以有选择地完成自动增量,这通过设置 DMA_CCRx 寄存器中的相关标志位来实现。例如,如果设置存储器地址指针自动增量,那么在每次传输完一个数据后,存储器地址指针会自动指向下一个存储单元,方便下一次数据传输。
- 循环模式用于处理循环缓冲区和连续数据传输,当数据传输数目变为 0 时可自动重新加载初始数值继续操作。
循环模式在处理循环缓冲区和连续数据传输时非常有用。例如,在 ADC 的扫描模式下,可以通过循环模式实现连续的数据采集和传输。当数据传输数目变为 0 时,DMA 会自动重新加载初始数值继续操作。假设我们设置了一个缓冲区来存储 ADC 采集的数据,当缓冲区满后,DMA 会自动从缓冲区的开头重新开始存储数据,实现循环缓冲管理。根据资料显示,在某些应用场景中,循环模式可以大大提高数据传输的效率,减少数据丢失的风险。
- 存储器到存储器模式可在没有外设请求下进行传输,设置相应标志位后启动,传输结束时 DMA 传输结束。
存储器到存储器模式是 STM32 DMA 的一个特殊功能。在这种模式下,可以在没有外设请求的情况下进行数据传输。只需要设置 DMA_CCRx 寄存器中的 MEM2MEM 位,然后在软件设置了 DMA_CCRx 寄存器中的 EN 位启动 DMA 通道时,DMA 传输将马上开始。当 DMA_CNDTRx 寄存器变为 0 时,DMA 传输结束。例如,可以使用存储器到存储器模式在内部 FLASH 和内部 SRAM 之间复制数据。在这种情况下,内部 FLASH 的地址可以被视为一个外设地址,内部 SRAM 的地址则是存储器地址。通过设置正确的传输方向、数据宽度和指针增量模式,可以实现高效的数据复制操作。
四、DMA 的配置过程与函数
(一)通道配置过程
以通道 1-DMA1 为例,首先需要使能时钟,通过 RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE) 来开启 DMA1 的时钟。接着设置 DMA 模式,如工作在正常缓存模式还是循环模式。对于外设寄存器地址,可以使用 DMA_InitStructure.DMA_PeripheralBaseAddr 进行设置,例如设置为特定外设的地址。数据寄存地址通过 DMA_InitStructure.DMA_MemoryBaseAddr 来指定,比如指向一个内存区域。传输数据量通过 DMA_InitStructure.DMA_BufferSize 来确定,这个值可以根据实际需求进行调整,最大为 65535。通道优先级可以使用 DMA_InitStructure.DMA_Priority 设置为很高、高、中等和低四个等级之一。数据传输方向由 DMA_InitStructure.DMA_DIR 确定,包括从内存到外设、从外设到内存以及存储器到存储器等方向。循环或非循环模式可通过 DMA_InitStructure.DMA_Mode 进行设置,如果需要连续的数据传输,可以选择循环模式。外设和存储器增量模式分别由 DMA_InitStructure.DMA_PeripheralInc 和 DMA_InitStructure.DMA_MemoryInc 控制,根据实际情况选择是否自动递增地址。数据宽度可以通过 DMA_InitStructure.DMA_PeripheralDataSize 和 DMA_InitStructure.DMA_MemoryDataSize 设置为字节、半字或全字。最后,启动 DMA 通道可以使用 DMA_Init(DMA_CHx, &DMA_InitStructure) 和 DMA_Cmd(DMA_CHx, ENABLE) 来完成配置并使能通道。
(二)常用 DMA 函数
- DMA 初始化函数:如 void DMA_DeInit(DMA_Channel_TypeDef* DMA_Channelx) 将通道寄存器重设为缺省值。这个函数在进行 DMA 配置之前可以使用,确保通道处于初始状态,避免之前的配置影响新的传输任务。例如,在开始一个新的 DMA 传输之前,可以先调用 DMA_DeInit 函数将指定通道的寄存器重置为默认值,然后再进行新的配置。
- DMA 使能函数:void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState) 控制通道使能。这个函数用于开启或关闭特定的 DMA 通道。当 NewState 设置为 ENABLE 时,通道被使能,可以开始数据传输;当设置为 DISABLE 时,通道被关闭,传输停止。例如,在配置完成后,可以使用 DMA_Cmd(DMA_CHx, ENABLE) 来启动 DMA 通道的传输。
- 开启中断函数:void DMA_ITConfig(DMA_Channel_TypeDef* DMA_Channelx, uint32_t DMA_IT, FunctionalState NewState) 设置中断。这个函数可以用来开启特定的 DMA 中断。DMA_IT 参数可以是 DMA_IT_TC(传输完成中断)、DMA_IT_HT(半传输中断)、DMA_IT_TE(传输错误中断)等。通过设置这些中断,可以在特定事件发生时及时通知 CPU 进行处理。例如,如果需要在 DMA 传输完成时触发中断,可以使用 DMA_ITConfig(DMA_CHx, DMA_IT_TC, ENABLE) 来开启传输完成中断。
- 设置和读取 CNDTRx 函数:void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber) 和 uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)。DMA_SetCurrDataCounter 函数用于设置 DMA 通道的当前剩余数据量,而 DMA_GetCurrDataCounter 函数则用于获取当前剩余数据量。这两个函数在需要重新设置传输数据量或者查询传输进度时非常有用。例如,在一次传输完成后,可以使用 DMA_SetCurrDataCounter(DMA_CHx, newDataNumber) 来重新设置传输数据量,以便进行下一次传输。同时,可以使用 DMA_GetCurrDataCounter(DMA_CHx) 来查询当前剩余数据量,以便了解传输进度。
五、DMA 的应用实例
(一)串口通信与 DMA
- 通过 DMA 将数据从内存传输到串口,实现高效数据传输,如配置 DMA 与 USART1 联系起来,设置传输参数并启动传输。
-
- 在实际应用中,我们可以使用 STM32 的 DMA 功能来实现高效的数据传输。例如,将数据从内存传输到串口可以大大提高传输效率,减少 CPU 的参与。首先,我们需要配置 DMA 与 USART1 建立联系。通过设置 DMA 的相关参数,如外设地址、内存地址、传输数据量等,可以确保数据能够准确地从内存传输到串口。根据资料显示,在 STM32CUBEMX 配置中,我们可以选择 USART1,模式选择异步通信,开启 USART1 的串口中断,并添加 RX 和 TX。然后,在代码中进行补充实现,开启对应的帧接收和 DMA 接收函数,并在中断函数中添加相应的代码。最后,通过调用特定的函数启动传输,如 HAL_TIM_Base_Start_IT(&htim2) 用于开启定时器中断,从而触发 DMA 传输。
- 在串口通信中使用 DMA 可提高传输效率,减少 CPU 参与,如通过中断方式判断传输完成并关闭串口 DMA。
-
- 使用 DMA 在串口通信中具有显著的优势。当使用 DMA 进行串口通信时,CPU 可以在数据传输过程中处理其他任务,从而提高系统的整体效率。在传输过程中,可以通过中断方式判断传输是否完成。例如,当 DMA 传输完成时,会触发特定的中断,我们可以在中断服务函数中进行相应的处理。在 STM32 中,可以通过重写中断回调函数来实现对传输完成事件的响应。当检测到传输完成后,可以关闭串口 DMA,释放相关资源。根据实际测试,使用 DMA 进行串口通信可以将传输效率提高数倍,特别是在大数据量传输的情况下效果更加明显。
(二)中断模式编程实验
- 利用中断模式编程实现开关控制 LED,如设置引脚、触发方式和使能外部中断线等。
-
- 在中断模式编程实验中,我们可以利用 STM32 的中断功能来实现开关控制 LED。首先,需要设置相关的引脚。例如,将引脚 PB5 输出模式设置为 GPIO_Output,将引脚 PA1 输出模式设置为 GPIO_EXIT1。然后,设置引脚的触发方式,如设置 PA1 引脚为上升沿触发。接着,使能对应外部中断线,通过在 NVIC 中勾选 Enabled 选项来实现。这样,当外部开关产生上升沿信号时,就会触发中断,从而控制 LED 的状态。
- 通过重写虚函数和配置项目实现功能,如在中断回调函数中翻转 LED 状态并烧录代码进行验证。
-
- 为了实现中断模式编程实验的功能,我们需要重写虚函数并配置项目。在代码中,可以在 main.c 文件中重写 weak 虚函数 void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)。在这个函数中,可以判断外部中断源,如果是特定的中断引脚,如 A1_Pin,则翻转 LED 状态。然后,进行烧录代码进行验证。在烧录之前,要将芯片的 BOOT0 置 1,否则烧录会失败。烧录成功后,可以连接线路进行测试。例如,将 LED 的长脚端连接 3V3,短脚端连接 PB5,PA1 连接 3V3 时,灯光亮起;连接 GND 时,灯光熄灭。通过这种方式,可以验证中断模式编程实验的功能是否正常实现。