返回首页

Zephyr DMA 传输学习笔记

1. DMA 概述

DMA (Direct Memory Access) 允许外设直接与内存交换数据,无需 CPU 介入。

优势

2. Zephyr DMA API

2.1 设备树配置

/* device tree example */
&dma {
    status = "okay";
};

&spi0 {
    dmas = <&dma 0>, <&dma 1>;
    dma-names = "tx", "rx";
};

2.2 Kconfig 配置

CONFIG_DMA=y
# 具体控制器配置取决于 SoC / 板卡
# 例如某些 STM32 平台会启用:
# CONFIG_DMAMUX_STM32=y
# CONFIG_STM32_DMA1=y

2.3 DMA 驱动 API

#include <zephyr/drivers/dma.h>

// 设备句柄
const struct device *dma_dev;

// 初始化 DMA
int dma_init(void) {
    dma_dev = device_get_binding("DMA_0");
    if (!dma_dev) {
        return -ENODEV;
    }
    return 0;
}

3. 内存到内存传输

3.1 配置结构

struct dma_config cfg = {
    .block_size = 256,
    .channel_direction = MEMORY_TO_MEMORY,
    .source_data_size = 1,
    .dest_data_size = 1,
    .source_burst_length = 1,
    .dest_burst_length = 1,
    .dma_slot = 0,
    .channel_priority = 0,
};

3.2 传输示例

uint8_t src_buf[256];
uint8_t dst_buf[256];

// 配置通道
int ret = dma_config(dma_dev, 0, &cfg);
if (ret) {
    return ret;
}

// 启动传输
ret = dma_start(dma_dev, 0);
if (ret) {
    return ret;
}

// 等待完成
k_sem_take(&dma_sem, K_FOREVER);

3.3 回调函数

static void dma_callback(const struct device *dev, void *arg,
                         uint32_t channel, int status) {
    if (status == 0) {
        printk("DMA transfer complete\n");
    }
    k_sem_give(&dma_sem);
}

// 在配置中设置回调
cfg.callback = dma_callback;

4. SPI DMA 传输

4.1 设备树配置

&spi0 {
    status = "okay";
    dmas = <&dma 1>, <&dma 2>;
    dma-names = "tx", "rx";
};

4.2 SPI DMA 使用

// SPI 发送数据(DMA 模式)
int spi_dma_send(const struct device *spi, uint8_t *tx_buf, 
                 size_t len) {
    struct spi_buf buf = {
        .buf = tx_buf,
        .len = len
    };
    struct spi_buf_set tx = {
        .buffers = &buf,
        .count = 1
    };
    
    return spi_write(spi, &spi_cfg, &tx);
}

5. UART DMA 传输

5.1 设备树配置

&uart1 {
    status = "okay";
    dmas = <&dma 3>, <&dma 4>;
    dma-names = "tx", "rx";
};

5.2 UART DMA 接收

uint8_t rx_buf[256];

// 配置 DMA 接收
struct dma_config dma_cfg = {
    .block_size = sizeof(rx_buf),
    .channel_direction = PERIPHERAL_TO_MEMORY,
    .dest_data_size = 1,
    .source_data_size = 1,
};

// 启动 DMA 接收
dma_config(dma_dev, rx_channel, &dma_cfg);
memcpy(rx_buf, rx_dma_buf, sizeof(rx_buf));
dma_start(dma_dev, rx_channel);

6. ADC DMA 采样

6.1 配置

&adc {
    status = "okay";
    dmas = <&dma 5>;
    dma-names = "rx";
};

6.2 ADC DMA 读取

int16_t adc_buf[256];

// 读取 ADC(DMA 模式)
adc_read_async(adc_dev, &sequence, adc_buf, sizeof(adc_buf));

7. Nordic nRF DMA 特性

7.1 EasyDMA

nRF 系列使用 EasyDMA,简化 DMA 配置:

7.2 PPI 配置

/* 较新的 Nordic 平台通常会优先考虑 DPPI / nrfx 触发链路;
 * 具体写法依 SoC 家族而定,不建议把旧 PPI 调用当成通用模板。
 */

8. 调试与问题排查

8.1 检查 DMA 状态

# 查看 DMA 设备
ls /dev/dma*

# 内核日志
dmesg | grep dma

8.2 常见问题

9. 最佳实践

  1. 使用双缓冲避免数据覆盖
  2. 确保内存对齐(4/8 字节)
  3. 正确处理回调中的错误
  4. 释放不再使用的 DMA 通道

参考资料