DMA介绍
直接存储器存取(direct memory access,DMA)用来提供在外设和存储器之间或者存储器和存储器之间的高速数据传输。无须CPU干预,数据可以通过DMA快速地移动,这就节省了CPU的资源来做其他操作。
2个DMA控制器有12个通道(DMA1有7个通道,DMA2有5个通道),每个通道专门用来管理来自于一个或多个外设对存储器访问的请求。还有一个仲裁器来协调各个DMA请求的优先权。
DMA控制器和Cortex™-M3核心共享系统数据总线,执行直接存储器数据传输。当CPU和DMA同时访问相同的目标(RAM或外设)时,DMA请求会暂停CPU访问系统总线达若干个周期,总线仲裁器执行循环调度,以保证CPU至少可以得到一半的系统总线(存储器或外设)带宽。
要注意的是DMA2只存在于大容量产品和互联型产品中。
DMA框图
DMA请求
如果外设要想通过DMA来传输数据,必须先给DMA控制器发送DMA请求,DMA控制器收到请求信号之后,控制器会给外设一个应答信号,当外设得到控制器的应答信号后,外设会立即释放它的请求。
DMA有DMA1和DMA2两个控制器,DMA1有7个通道,DMA2有5个通道,不同DMA控制器的通道对应着不同的外设请求,这决定了我们在软件编程上该怎么设置。
DMA通道
DMA具有12个独立可编程的通道,其中 DMA1有7个通道,DMA2有5个通道,每个通道对应不同的外设的DMA请求。虽然每个通道可以接收多个外设的请求,但是同一时间只能接收一个,不能同时接收多个。
仲裁器
当发生多个DMA通道请求时,就意味着有先后响应处理的顺序问题,这个就由仲裁器管理。仲裁器管理DMA通道请求分为两个阶段。
第一阶段属于软件阶段,可以在DMA_CCRx寄存器中设置,有4个等级:非常高、高、中和低四个优先级。
第二阶段属于硬件阶段,如果两个或以上的DMA通道请求设置的优先级一样,则他们优先级取决于通道编号,编号越低优先权越高,比如通道 1 高于通道 2。
在大容量产品和互联型产品中,DMA1控制器拥有高于DMA2控制器的优先级。
传输方向
存储器到外设,外设到存储器,存储器到存储器。这里的存储器指的是ROM和RAM。注意DMA没有办法把数据从RAM传输到ROM(flash)。
实验-ROM到RAM
需求
使用寄存器操作把ROM中的数据通过DMA传输到RAM,然后把数据通过printf发送到串口验证是否正确。
DMA传输不涉及外设,所以通道随便选。我们选DMA1的1通道。
寄存器分析
RCC时钟
传输方向
[!NOTE]
可以将ROM当做外设
源/目标的基地址
传输数据的宽度
外设和内存地址是否自增
传输数据的长度
传输中断使能
通道传输使能
中断状态® & 中断标志清除(w)
示例代码
dma1.h
#ifndef __DMA1_H__
#define __DMA1_H__
#include "stm32f10x.h"
void DMA1_Init(void);
void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t byteSize);
void DMA1_CH1_TransmitCompleteCallback(void);
#endif /* __DMA1_H__ */
dma1.c
#include "dma1.h"
#include "logger.h"
void DMA1_Init(void) {
// 使能AHB时钟
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
// 配置DMA通道(使用内存到内存模式时,DMA所有通道都可以选择,这里以通道1为例)
// 传输模式:内存到内存
DMA1_Channel1->CCR |= DMA_CCR1_MEM2MEM;
// 传输方向:从外设读取数据(内存到内存模式下,提供数据的内存可以当做外设)
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
// 数据宽度,统一为8字节
DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE;
DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE;
// 地址自动递增
DMA1_Channel1->CCR |= DMA_CCR1_PINC;
DMA1_Channel1->CCR |= DMA_CCR1_MINC;
// 使能传输完成中断 Transmit Complete Interrupt Enable
DMA1_Channel1->CCR |= DMA_CCR1_TCIE;
NVIC_SetPriorityGrouping(3);
NVIC_SetPriority(DMA1_Channel1_IRQn, 3);
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
}
void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t byteSize) {
// 外设地址(内存到内存模式下,为内存中源数据的基地址)
DMA1_Channel1->CPAR = srcAddr;
// 内存地址(内存到内存模式下,为内存中存放数据的目标地址)
DMA1_Channel1->CMAR = destAddr;
// 指定要传输的数据数量
DMA1_Channel1->CNDTR = byteSize;
// 使能通道,开始传输
DMA1_Channel1->CCR |= DMA_CCR1_EN;
LOG_DEBUG("DMA start")
}
void DMA1_Channel1_IRQHandler(void) {
LOG_DEBUG("DMA1_Channel1_IRQHandler")
// 判断是否为传输完成事件对应的中断
if (DMA1->ISR & DMA_ISR_TCIF1) {
// 清除中断标志
DMA1->IFCR |= DMA_IFCR_CTCIF1;
DMA1_CH1_TransmitCompleteCallback();
// 关闭DMA通道
DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
}
}
__weak void DMA1_CH1_TransmitCompleteCallback(void) {
}
main.c
#include "delay.h"
#include "dma1.h"
#include "key.h"
#include "led.h"
#include "logger.h"
#include "stdio.h"
#include "stm32f10x.h"
#include "string.h"
#include "uart.h"
void uart1_received_callback(uint8_t buf[], uint8_t size) {
uart_send_bytes(buf, size);
}
const uint8_t src[] = {0x11, 0x22, 0x33, 0x44};
uint8_t dest[4] = {0};
void DMA1_CH1_TransmitCompleteCallback(void) {
LOG_DUMP("dest data => ", dest, 4)
}
int main() {
uart_init();
DMA1_Init();
LOG_DEBUG("main start")
LOG_DEBUG("src address => %p, dest address => %p", src, dest);
DMA1_Transmit((uint32_t)src, (uint32_t)dest, 4);
while (1) {
}
}
ROM & RAM地址验证
HAL库实现
Cube配置
注册传输完成回调 & 中断方式开启DMA传输
/* USER CODE BEGIN PV */
const uint8_t src[] = {11, 22, 33, 44};
uint8_t dest[4] = {0};
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
void DMA_XferCpltCallback(DMA_HandleTypeDef *_hdma);
/* USER CODE END PFP */
/* USER CODE BEGIN 4 */
void DMA_XferCpltCallback(DMA_HandleTypeDef *_hdma) {
HAL_DMA_Abort_IT(_hdma);
for (uint8_t i = 0; i < 4; i++) {
printf("%d\t", dest[i]);
}
printf("\n");
}
/* USER CODE END 4 */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_DMA_RegisterCallback(&hdma_memtomem_dma1_channel1,
HAL_DMA_XFER_CPLT_CB_ID, DMA_XferCpltCallback);
printf("src => %p, dest => %p\n", src, dest);
HAL_DMA_Start_IT(&hdma_memtomem_dma1_channel1, (uint32_t)src,
(uint32_t)dest, 4);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1) {
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
实验-RAM到UART
外设特定DMA通道
外设使能DMA模式
示例代码
DMA1_CH4
#include "dma1.h"
#include "logger.h"
void DMA1_Init(void) {
// 使能AHB时钟
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
// 配置DMA通道(使用内存到内存模式时,DMA所有通道都可以选择,这里以通道1为例)
// 传输方向:从内存读,发送到外设uart
DMA1_Channel4->CCR |= DMA_CCR4_DIR;
// 数据宽度,统一为8字节
DMA1_Channel4->CCR &= ~DMA_CCR4_PSIZE;
DMA1_Channel4->CCR &= ~DMA_CCR4_MSIZE;
// 内存地址自增,UART缓冲区地址不自增
DMA1_Channel4->CCR |= DMA_CCR4_MINC;
DMA1_Channel4->CCR &= ~DMA_CCR4_PINC;
// 如果开启循环传输模式,那么传输完成中断中不要停止DMA通道
//DMA1_Channel4->CCR |= DMA_CCR4_CIRC;
// 使能传输完成中断 Transmit Complete Interrupt Enable
DMA1_Channel4->CCR |= DMA_CCR4_TCIE;
NVIC_SetPriorityGrouping(3);
NVIC_SetPriority(DMA1_Channel4_IRQn, 3);
NVIC_EnableIRQ(DMA1_Channel4_IRQn);
}
void DMA1_Transmit(uint32_t srcAddr, uint32_t destAddr, uint16_t byteSize) {
DMA1_Channel4->CMAR = srcAddr;
DMA1_Channel4->CPAR = destAddr;
DMA1_Channel4->CNDTR = byteSize;
// 使能通道,开始传输
DMA1_Channel4->CCR |= DMA_CCR4_EN;
}
void DMA1_Channel4_IRQHandler(void) {
// 判断是否为传输完成事件对应的中断
if (DMA1->ISR & DMA_ISR_TCIF4) {
// 清除中断标志
DMA1->IFCR |= DMA_IFCR_CTCIF4;
// 关闭DMA通道
DMA1_Channel4->CCR &= ~DMA_CCR4_EN;
}
}
UART使能DMA发送
// 发送数据使能DMA
USART1->CR3 |= USART_CR3_DMAT;
main
#include "delay.h"
#include "dma1.h"
#include "key.h"
#include "led.h"
#include "logger.h"
#include "stdio.h"
#include "stm32f10x.h"
#include "string.h"
#include "uart.h"
void uart1_received_callback(uint8_t buf[], uint8_t size) {
uart_send_bytes(buf, size);
}
const uint8_t src[] = {'a', 'b', 'c', 'd'};
int main() {
uart_init();
DMA1_Init();
DMA1_Transmit((uint32_t)src, (uint32_t)&(USART1->DR), 4);
while (1) {
}
}
HAL库实现
外设下的DMA设置
以DMA方式驱动外设
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
uint8_t data[] = {'a', 'b', 'c', 'd', 'e'};
HAL_UART_Transmit_DMA(&huart1, data, 5);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
源码分析
MX_USART1_UART_Init
:DMA初始化:HAL_UART_MspInit
HAL_UART_Transmit_DMA
:调用HAL_DMA_Start_IT