DMA直接内存访问
概述
DMA(Direct Memory Access,直接内存访问)允许外设直接与内存交换数据,无需CPU干预,大幅提高数据传输效率。
DMA优势
- 解放CPU:CPU只需配置DMA,然后可以做其他工作
- 高吞吐量:适合大批量数据传输
- 低延迟:硬件级数据搬运
- 实时性:确定性传输时间
DMA传输模式
| 模式 |
说明 |
适用场景 |
| 内存到内存 |
两个内存区域复制 |
数据搬运 |
| 外设到内存 |
外设读取数据到内存 |
ADC、UART接收 |
| 内存到外设 |
内存数据发送到外设 |
DAC、UART发送 |
| 外设到外设 |
外设间直接传输 |
高级应用 |
STM32 DMA编程
HAL库初始化
| C |
|---|
| #include "stm32f4xx_hal.h"
DMA_HandleTypeDef hdma_usart1_rx;
void DMA_Init(void)
{
__HAL_RCC_DMA2_CLK_ENABLE();
hdma_usart1_rx.Instance = DMA2_Stream2;
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_usart1_rx);
// 关联到UART
__HAL_LINKDMA(&huart1, hdmarx, hdma_usart1_rx);
}
|
UART DMA接收
| C |
|---|
| uint8_t rx_buffer[256];
void start_uart_dma_rx(void)
{
HAL_UART_Receive_DMA(&huart1, rx_buffer, sizeof(rx_buffer));
}
// DMA接收完成回调
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART1) {
// 处理接收数据
process_data(rx_buffer, sizeof(rx_buffer));
// 循环模式会自动重启
}
}
// DMA半传输完成(循环模式)
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef *huart)
{
// 处理前半部分数据
process_data(rx_buffer, sizeof(rx_buffer) / 2);
}
|
ADC DMA采样
| C |
|---|
| uint16_t adc_buffer[1000];
void start_adc_dma(void)
{
// 配置ADC为循环模式
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DMAContinuousRequests = ENABLE;
// 启动ADC DMA
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, 1000);
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
// 1000次采样完成
process_adc_data(adc_buffer, 1000);
}
|
内存到内存DMA
| C |
|---|
| void mem_to_mem_dma(void)
{
uint32_t src[256];
uint32_t dst[256];
DMA_HandleTypeDef hdma_mem;
hdma_mem.Instance = DMA2_Stream0;
hdma_mem.Init.Channel = DMA_CHANNEL_0;
hdma_mem.Init.Direction = DMA_MEMORY_TO_MEMORY;
hdma_mem.Init.PeriphInc = DMA_PINC_ENABLE;
hdma_mem.Init.MemInc = DMA_MINC_ENABLE;
hdma_mem.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD;
hdma_mem.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
hdma_mem.Init.Mode = DMA_NORMAL;
hdma_mem.Init.Priority = DMA_PRIORITY_HIGH;
HAL_DMA_Init(&hdma_mem);
// 启动传输
HAL_DMA_Start(&hdma_mem, (uint32_t)src, (uint32_t)dst, 256);
// 等待完成
HAL_DMA_PollForTransfer(&hdma_mem, HAL_DMA_FULL_TRANSFER, 1000);
}
|
Linux DMA编程
DMA缓冲区分配
| C |
|---|
| #include <linux/dma-mapping.h>
// 一致性DMA缓冲区
void *vaddr;
dma_addr_t dma_handle;
vaddr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
// vaddr: CPU访问的虚拟地址
// dma_handle: DMA控制器使用的物理地址
// 使用完成后释放
dma_free_coherent(dev, size, vaddr, dma_handle);
|
流式DMA映射
| C |
|---|
| // 映射单次传输缓冲区
dma_addr_t dma_handle;
// 外设读取内存(CPU写入 -> DMA读取 -> 外设)
dma_handle = dma_map_single(dev, vaddr, size, DMA_TO_DEVICE);
// 等待DMA完成
dma_unmap_single(dev, dma_handle, size, DMA_TO_DEVICE);
// 外设写入内存(外设 -> DMA写入 -> CPU读取)
dma_handle = dma_map_single(dev, vaddr, size, DMA_FROM_DEVICE);
dma_unmap_single(dev, dma_handle, size, DMA_FROM_DEVICE);
// 双向传输
dma_handle = dma_map_single(dev, vaddr, size, DMA_BIDIRECTIONAL);
|
缓存同步
| C |
|---|
| // CPU访问前同步
dma_sync_single_for_cpu(dev, dma_handle, size, DMA_FROM_DEVICE);
// DMA传输前同步
dma_sync_single_for_device(dev, dma_handle, size, DMA_TO_DEVICE);
|
Scatter-Gather DMA
| C |
|---|
| #include <linux/dmapool.h>
struct scatterlist sg[10];
int nents;
// 设置scatter-gather列表
sg_init_table(sg, 10);
for (i = 0; i < 10; i++) {
sg_set_buf(&sg[i], buffers[i], sizes[i]);
}
// 映射
nents = dma_map_sg(dev, sg, 10, DMA_TO_DEVICE);
// 取消映射
dma_unmap_sg(dev, sg, nents, DMA_TO_DEVICE);
|
DMA配置参数
通道/流选择
| C |
|---|
| // STM32F4系列
DMA1: Stream 0-7
DMA2: Stream 0-7
// 每个流可配置不同通道
hdma.Init.Channel = DMA_CHANNEL_0; // 通道0-7
|
优先级
| C |
|---|
| hdma.Init.Priority = DMA_PRIORITY_LOW; // 低优先级
hdma.Init.Priority = DMA_PRIORITY_MEDIUM; // 中优先级
hdma.Init.Priority = DMA_PRIORITY_HIGH; // 高优先级
hdma.Init.Priority = DMA_PRIORITY_VERY_HIGH; // 最高优先级
|
传输模式
| C |
|---|
| // 正常模式:传输完成后停止
hdma.Init.Mode = DMA_NORMAL;
// 循环模式:传输完成后自动重启
hdma.Init.Mode = DMA_CIRCULAR;
// 双缓冲模式
hdma.Init.Mode = DMA_DOUBLE_BUFFER;
|
数据宽度和对齐
| C |
|---|
| // 外设数据宽度
hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; // 8位
hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 16位
hdma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; // 32位
// 内存数据宽度
hdma.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;
|
地址递增
| C |
|---|
| // 外设地址递增(通常禁用)
hdma.Init.PeriphInc = DMA_PINC_DISABLE; // 外设寄存器固定
hdma.Init.PeriphInc = DMA_PINC_ENABLE; // 外设地址递增
// 内存地址递增(通常启用)
hdma.Init.MemInc = DMA_MINC_ENABLE; // 内存地址递增
hdma.Init.MemInc = DMA_MINC_DISABLE; // 内存地址固定
|
DMA中断
中断类型
| C |
|---|
| // 传输完成中断
void HAL_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma);
// 半传输完成中断
void HAL_DMA_XferHalfCpltCallback(DMA_HandleTypeDef *hdma);
// 错误中断
void HAL_DMA_XferErrorCallback(DMA_HandleTypeDef *hdma);
|
使能中断
| C |
|---|
| // 使能DMA中断
__HAL_DMA_ENABLE_IT(&hdma, DMA_IT_TC); // 传输完成
__HAL_DMA_ENABLE_IT(&hdma, DMA_IT_HT); // 半传输
__HAL_DMA_ENABLE_IT(&hdma, DMA_IT_TE); // 传输错误
|
DMA调试
查看DMA状态
| C |
|---|
| // 检查DMA是否忙
if (HAL_DMA_GetState(&hdma) == HAL_DMA_STATE_BUSY) {
// DMA正在传输
}
// 获取剩余数据量
uint32_t remaining = __HAL_DMA_GET_COUNTER(&hdma);
|
常见问题
- 数据错位
- 检查数据宽度配置
-
检查地址对齐
-
传输不完整
- 检查传输长度配置
-
检查是否被其他中断打断
-
缓冲区溢出
- 使用循环模式时注意处理速度
- 考虑双缓冲模式
参考资料