5.13 DMA设备
2026/7/19大约 6 分钟设备驱动驱动DMA直接内存访问环形缓冲区
5.13 DMA设备
📚 本节导读
学习时长: 约 35 分钟
难度级别: ⭐⭐⭐⭐☆
前置知识: C 语言基础、MCU DMA 基础知识、5.1 设备驱动框架、5.4 Serial 设备
🎯 学习目标
- 理解 OneOS Soft DMA 抽象层的设计理念
- 掌握 DMA 环形缓冲区的使用方法
- 理解半满/全满/超时三种中断回调机制
- 了解 DMA 硬件抽象接口(hard_dma_ops)
一、概述
DMA(Direct Memory Access)是 MCU 中用于外设与内存之间高速数据传输的硬件模块,无需 CPU 干预。OneOS 提供了 Soft DMA 软件抽象层,位于 drivers/dma/ 目录下,核心头文件为 soft_dma.h 和 dma.h。
DMA 框架的特点:
- Soft DMA 抽象:在硬件 DMA 之上封装一层软件抽象,提供环形缓冲区管理
- 环形缓冲区:内置
dma_ring_t环形缓冲区,支持高效的数据读写 - 三种中断回调:半满中断、全满中断、超时中断
- 硬件适配:通过
hard_dma_ops_t接口适配不同 MCU 的 DMA 控制器
二、核心数据结构
2.1 环形缓冲区 dma_ring_t
typedef struct dma_ring
{
uint8_t *buff; /* 缓冲区指针 */
int size; /* 缓冲区总大小 */
int head; /* 写指针(生产者) */
int tail; /* 读指针(消费者) */
} dma_ring_t;环形缓冲区操作函数:
int ring_count(dma_ring_t *ring); /* 获取缓冲区中数据量 */
int ring_space(dma_ring_t *ring); /* 获取缓冲区剩余空间 */
void copy_ring_to_line(dma_ring_t *ring, uint8_t *line, int count); /* 环形缓冲区 → 线性缓冲区 */
void copy_line_to_ring(dma_ring_t *ring, uint8_t *line, int count); /* 线性缓冲区 → 环形缓冲区 */
void copy_ring_to_ring(dma_ring_t *dst, dma_ring_t *src, int count); /* 环形缓冲区 → 环形缓冲区 */2.2 硬件 DMA 信息 hard_dma_info_t
typedef struct hard_info
{
uint32_t mode; /* 传输模式:HARD_DMA_MODE_NORMAL(普通)或 HARD_DMA_MODE_CIRCULAR(循环) */
uint32_t max_size; /* 单次最大传输大小(如 64KB) */
uint32_t flag; /* 中断标志:HARD_DMA_FLAG_HALF_IRQ | HARD_DMA_FLAG_FULL_IRQ | HARD_DMA_FLAG_TIMEOUT_IRQ */
uint32_t data_timeout; /* 数据超时时间(us/Byte) */
} hard_dma_info_t;2.3 硬件 DMA 操作接口 hard_dma_ops_t
typedef struct hard_dma_ops
{
uint32_t (*get_index)(soft_dma_t *dma); /* 获取当前 DMA 传输位置 */
os_err_t (*dma_init)(soft_dma_t *dma); /* 初始化 DMA 硬件 */
os_err_t (*dma_start)(soft_dma_t *dma, void *buff, uint32_t size); /* 启动 DMA 传输 */
uint32_t (*dma_stop)(soft_dma_t *dma); /* 停止 DMA 传输,返回已传输字节数 */
} hard_dma_ops_t;2.4 Soft DMA 回调 soft_dma_callbacks_t
typedef struct soft_dma_callbacks
{
void (*dma_half_callback)(soft_dma_t *dma); /* 半满中断回调 */
void (*dma_full_callback)(soft_dma_t *dma); /* 全满中断回调 */
void (*dma_timeout_callback)(soft_dma_t *dma); /* 超时中断回调 */
} soft_dma_callbacks_t;2.5 Soft DMA 主体 soft_dma_t
struct soft_dma
{
hard_dma_info_t hard_info; /* 硬件 DMA 信息 */
hard_dma_ops_t ops; /* 硬件 DMA 操作接口 */
soft_dma_callbacks_t cbs; /* 回调函数 */
os_bool_t timer_inited; /* 超时定时器是否已初始化 */
os_hrtimer_t timer; /* 超时定时器(高精度) */
soft_dma_rx_buffer_t buffer; /* 接收缓冲区 */
uint32_t status; /* 状态标志 */
uint32_t irq_mask; /* 中断屏蔽 */
os_spinlock_t lock; /* 自旋锁保护 */
};三、API 详解
3.1 初始化和反初始化
os_err_t soft_dma_init(soft_dma_t *dma);
os_err_t soft_dma_deinit(soft_dma_t *dma);3.2 启动和停止
os_err_t soft_dma_start(soft_dma_t *dma, dma_ring_t *ring);
os_err_t soft_dma_stop(soft_dma_t *dma);3.3 中断控制
void soft_dma_irq_enable(soft_dma_t *dma, os_bool_t enable);3.4 中断处理(HAL 层调用)
void soft_dma_half_irq(soft_dma_t *dma); /* 半满中断处理 */
void soft_dma_full_irq(soft_dma_t *dma); /* 全满中断处理 */
void soft_dma_timeout_irq(soft_dma_t *dma); /* 超时中断处理 */HAL 层在 DMA 硬件中断中调用这些函数,由 Soft DMA 层管理环形缓冲区并触发回调。
四、中断回调机制
Soft DMA 支持三种中断回调,对应不同的传输状态:
| 中断类型 | 触发条件 | 典型用途 |
|---|---|---|
| 半满中断 | DMA 传输完成缓冲区的一半 | 处理前半部分数据,实现乒乓缓冲 |
| 全满中断 | DMA 传输完成整个缓冲区 | 处理完整数据块 |
| 超时中断 | 在指定时间内未收到新数据 | 处理不完整的数据帧(如 UART 空闲中断) |
DMA 传输过程(循环模式):
┌──────────────────────────────────────────────────┐
│ 环形缓冲区 (dma_ring_t) │
│ ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐│
│ │ D0 │ D1 │ D2 │ D3 │ D4 │ D5 │ D6 │ D7 ││
│ └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘│
│ ↑ 半满中断 ↑ 全满中断 │
│ └─ 缓冲区前一半完成 ─┘ │
└──────────────────────────────────────────────────┘五、代码示例
5.1 基本使用:初始化与启动
#include <oneos_config.h>
#include <dma.h>
/* 定义 DMA 环形缓冲区 */
static uint8_t g_dma_buffer[1024];
static dma_ring_t g_dma_ring;
static soft_dma_t g_uart_dma;
/* 全满中断回调 */
void dma_full_handler(soft_dma_t *dma)
{
int count = ring_count(&g_dma_ring);
if (count > 0)
{
uint8_t data[128];
int read_len = (count > 128) ? 128 : count;
copy_ring_to_line(&g_dma_ring, data, read_len);
os_kprintf("DMA full: received %d bytes\r\n", read_len);
}
}
/* 超时中断回调 */
void dma_timeout_handler(soft_dma_t *dma)
{
int count = ring_count(&g_dma_ring);
if (count > 0)
{
uint8_t data[64];
int read_len = (count > 64) ? 64 : count;
copy_ring_to_line(&g_dma_ring, data, read_len);
os_kprintf("DMA timeout: %d bytes (frame end)\r\n", read_len);
}
}
void dma_basic_example(void)
{
/* 初始化环形缓冲区 */
g_dma_ring.buff = g_dma_buffer;
g_dma_ring.size = sizeof(g_dma_buffer);
g_dma_ring.head = 0;
g_dma_ring.tail = 0;
/* 配置硬件 DMA 信息 */
g_uart_dma.hard_info.mode = HARD_DMA_MODE_CIRCULAR;
g_uart_dma.hard_info.max_size = 65536;
g_uart_dma.hard_info.flag = HARD_DMA_FLAG_FULL_IRQ | HARD_DMA_FLAG_TIMEOUT_IRQ;
g_uart_dma.hard_info.data_timeout = 90; /* 90us/Byte */
/* 注册回调 */
g_uart_dma.cbs.dma_full_callback = dma_full_handler;
g_uart_dma.cbs.dma_timeout_callback = dma_timeout_handler;
/* 初始化 Soft DMA */
soft_dma_init(&g_uart_dma);
/* 启动 DMA 传输 */
soft_dma_start(&g_uart_dma, &g_dma_ring);
os_kprintf("DMA started with circular mode\r\n");
}5.2 环形缓冲区基本操作
void ring_buffer_operations(void)
{
uint8_t buffer[256];
dma_ring_t ring;
uint8_t data[] = "Hello DMA!";
uint8_t read_buf[32];
/* 初始化环形缓冲区 */
ring.buff = buffer;
ring.size = sizeof(buffer);
ring.head = 0;
ring.tail = 0;
/* 写入数据 */
copy_line_to_ring(&ring, data, strlen((char *)data));
os_kprintf("After write: count=%d, space=%d\r\n",
ring_count(&ring), ring_space(&ring));
/* 读取数据 */
int len = ring_count(&ring);
copy_ring_to_line(&ring, read_buf, len);
read_buf[len] = '\0';
os_kprintf("Read: %s\r\n", read_buf);
/* 验证空状态 */
os_kprintf("After read: count=%d\r\n", ring_count(&ring));
}5.3 乒乓缓冲模式
static uint8_t g_ping_buffer[512];
static uint8_t g_pong_buffer[512];
void dma_pingpong_half_handler(soft_dma_t *dma)
{
os_kprintf("Processing PING buffer (first half)\r\n");
}
void dma_pingpong_full_handler(soft_dma_t *dma)
{
os_kprintf("Processing PONG buffer (second half)\r\n");
}
void dma_pingpong_example(void)
{
soft_dma_t dma;
dma_ring_t ring;
dma.hard_info.mode = HARD_DMA_MODE_CIRCULAR;
dma.hard_info.max_size = 65536;
dma.hard_info.flag = HARD_DMA_FLAG_HALF_IRQ | HARD_DMA_FLAG_FULL_IRQ;
dma.cbs.dma_half_callback = dma_pingpong_half_handler;
dma.cbs.dma_full_callback = dma_pingpong_full_handler;
soft_dma_init(&dma);
ring.buff = g_ping_buffer;
ring.size = 1024; /* ping + pong = 512 + 512 */
ring.head = 0;
ring.tail = 0;
soft_dma_start(&dma, &ring);
os_kprintf("Ping-pong DMA started\r\n");
}5.4 停止和反初始化
void dma_stop_example(void)
{
uint32_t remaining;
/* 停止 DMA 传输 */
remaining = soft_dma_stop(&g_uart_dma);
os_kprintf("DMA stopped, %d bytes not transferred\r\n", remaining);
/* 反初始化 */
soft_dma_deinit(&g_uart_dma);
os_kprintf("DMA deinitialized\r\n");
}六、注意事项
- 缓冲区大小:环形缓冲区大小需是 2 的幂次,且不能超过
hard_info.max_size - 中断上下文:回调函数在中断上下文中执行,不能进行阻塞操作
- 循环模式:
HARD_DMA_MODE_CIRCULAR模式下 DMA 会持续传输,无需手动重启 - 数据超时:
data_timeout用于检测帧结束,配合超时中断实现不定长数据接收 - 自旋锁保护:
soft_dma_t.lock用于多核/中断保护,HAL 层实现需正确使用
📝 本节小结
- Soft DMA 是 OneOS 在硬件 DMA 之上的软件抽象层,提供环形缓冲区管理
dma_ring_t环形缓冲区支持高效的无锁数据读写- 三种中断回调(半满/全满/超时)覆盖不同传输场景
- HAL 层通过
hard_dma_ops_t适配不同 MCU 的 DMA 控制器 - 典型应用:串口 DMA 接收、SPI DMA 传输、ADC DMA 采样