- 无人机
- 嵌入式
- 智能硬件
【免费下载链接】inav
INAV: Navigation-enabled flight control software
导读
在 INAV 目标板(target)开发中,定时器与 DMA(Direct Memory Access)冲突是导致电机输出失效的最常见原因之一:STM32 的 DMA 流/通道资源有限,而每个定时器通道又被硬件固定映射到特定的 DMA 流。本文以 INAV 官方目标开发文档 timer-dma-conflicts.md 为主体,结合仓库源码中的 DMA 映射表、运行时占用检测逻辑与真实目标板配置,完整讲解 DMA 冲突的产生原理、检测手段与三种解决策略。读完本文,你将能够在编写target.c时独立核查DEF_TIM()的 DMA 分配、利用dmaopt切换备用 DMA 映射化解冲突,并掌握 H7 系列 DMAMUX 的优势与边界。
什么是 DMA 冲突
DMA(Direct Memory Access,直接内存访问)允许定时器等外设在无需 CPU 介入的情况下直接与内存交换数据。在 INAV 中,电机与舵机的 PWM/DShot 信号正是依赖定时器通道的 DMA 输出才能以极高的频率刷新(例如 DShot600 需要逐比特周期搬运数据)。
冲突发生的条件:多个定时器通道试图使用同一个 DMA 流(stream)/通道(channel)。当两个DEF_TIM()条目解析到相同的 DMA 流且dmaopt相同时,二者便会竞争同一个硬件资源。
典型后果:一个或多个电机输出失效——具体表现为有的电机不响应油门、只有部分电机转动,且这种失败往往不是编译错误,而是静默的运行时故障,排查起来极具迷惑性。
源码视角:冲突为何是"静默"的
从源码结构看,INAV 在运行时并不会为 DMA 冲突报编译错误,而是在初始化阶段做占用检测。以标准外设库(stdperiph)实现为例,timer_impl_stdperiph.c 中的impl_timerPWMConfigChannelDMA()执行:
tch->dma = dmaGetByTag(tch->timHw->dmaTag); if (tch->dma == NULL) { return false; } // If DMA is already in use - abort if (tch->dma->owner != OWNER_FREE) { return false; }HAL 驱动的对应实现在 timer_impl_hal.c 中逻辑相同。当目标 DMA 已被占用时函数返回false,进而导致 pwm_output.c 中该输出端口无法被标记为configured:
if (timerPWMConfigChannelDMA(port->tch, port->dmaBuffer, sizeof(port->dmaBuffer[0]), DSHOT_DMA_BUFFER_SIZE)) { ZERO_FARRAY(port->dmaBuffer); port->configured = true; }也就是说,冲突通道在固件中表现为"从未被成功配置",电调自然收不到正常信号——这就是"电机没反应"的底层原因。
STM32 DMA 架构:固定映射与 DMAMUX
不同 MCU 系列的 DMA 路由能力差异巨大,这也直接决定了冲突概率的高低。
F4/F7:固定的 DMA 请求映射
在 STM32F4/F7 上,每个定时器通道拥有 1~3 个可用的 DMA 流,这是由 MCU 的 DMA 请求映射表硬件固定的(参见 F4 参考手册 RM0090 的 DMA 章节、F7 参考手册 RM0385 的 DMA 章节)。INAV 将这些映射硬编码在目标板相关的头文件中:
- F4 映射表:timer_def_stm32f4xx.h
- F7 映射表:timer_def_stm32f7xx.h
映射条目使用D(dma, stream, channel)三元组表示,例如:
#define DEF_TIM_DMA__BTCH_TIM3_CH1 D(1, 4, 5) // TIM3_CH1 → DMA1_Stream4 #define DEF_TIM_DMA__BTCH_TIM2_CH4 D(1, 7, 3),D(1, 6, 3) // TIM2_CH4 → DMA1_Stream7 或 DMA1_Stream6 #define DEF_TIM_DMA__BTCH_TIM1_CH1 D(2, 6, 0),D(2, 1, 6),D(2, 3, 6) #define DEF_TIM_DMA__BTCH_TIM1_CH3 D(2, 6, 0),D(2, 6, 6)注意同一个通道的多个映射变体(variant)之间,DMA 流可能相同(如 TIM1_CH3 的两个变体都是 DMA2_Stream6,只是请求通道号不同),也可能不同(如 TIM2_CH4、TIM1_CH1)。这正是"更换dmaopt可换用不同 DMA 流"能够解决冲突的硬件基础。
冲突示例:
DEF_TIM(TIM3, CH1, PB4, TIM_USE_OUTPUT_AUTO, 0, 0), // 解析为 DMA1_Stream4 DEF_TIM(TIM2, CH3, PB10, TIM_USE_OUTPUT_AUTO, 0, 0), // 若同样解析到 DMA1_Stream4,则冲突! // ^ CONFLICT - 两者只能有一个正常工作需要强调:具体通道到底落在哪个 DMA 流上,必须逐一核对 MCU 参考手册或 timer_def_stm32f4xx.h、timer_def_stm32f7xx.h 中的真实映射表,不能想当然。文档中的示例用于说明概念,实际开发时以映射表和目标 MCU 型号为准。
H7:DMAMUX 柔性路由
STM32H7 系列引入了DMAMUX外设,它允许任意定时器通道路由到任意 DMA 流。从 timer_def_stm32h7xx.h 可以看到这一设计在源码中的体现:
// H7 has DMAMUX that allow arbitrary assignment of peripherals to streams. #define DEF_TIM_DMA_FULL(req) \ D(1, 0, req), D(1, 1, req), ..., D(2, 6, req), D(2, 7, req)每个定时器通道都被赋予了 DMA1/DMA2 全部 16 个流的完整候选集,因此 F4/F7 上典型的"撞流"问题在 H7 上几乎绝迹。
但 DMAMUX 并非万能:如果某个外设根本没有 DMA 请求线,DMAMUX 也无从路由。一个典型例子是H7 的TIM15_CH2没有 DMA 请求线——在 timer_def_stm32h7xx.h 中明确标注为:
#define DEF_TIM_DMA__BTCH_TIM15_CH1 DEF_TIM_DMA_FULL(DMA_REQUEST_TIM15_CH1) #define DEF_TIM_DMA__BTCH_TIM15_CH2 NONETIM15_CH2的 DMA 映射为NONE,这是硬件约束,属于"无法路由",而不是可通过换流解决的冲突。同理,TIM9~TIM14 的多数通道在 H7 上也标注为NONE。开发者在 H7 上遇到某个通道完全不工作(即使换了任意dmaopt)时,应优先检查映射表中是否为NONE。
顺带一提,INAV 同样支持具有 DMAMUX 的国产 AT32F43x 系列,其映射表 timer_def_at32f43x.h 采用相同的"全流候选"策略,可供参考。
在 target.c 中控制 DMA 分配:DEF_TIM 的六个参数
所有定时器输出都定义在目标板的target.c中,核心宏是DEF_TIM()。以 F4 为例,宏的真实签名为(timer_def_stm32f4xx.h):
#define DEF_TIM(tim, ch, pin, usage, flags, dmavar) { tim, IO_TAG(pin), DEF_TIM_CHNL_ ## ch, DEF_TIM_OUTPUT(ch) | flags, IOCFG_AF_PP, GPIO_AF_ ## tim, usage, DEF_TIM_DMAMAP(dmavar, tim ## _ ## ch) }对应到写法上的六个参数:
DEF_TIM(TIM, CH, PIN, USE, flags, dmavar) ^^^^^^ ^^^^^^| 参数 | 含义 | 说明 |
|---|---|---|
TIM | 定时器外设 | 如TIM1、TIM3 |
CH | 通道 | CH1~CH4,以及CH1N/CH2N/CH3N(互补输出) |
PIN | 引脚 | INAV 端口命名,如PB4 |
USE | 用途标志 | 常用TIM_USE_OUTPUT_AUTO(自动分配为电机/舵机)、TIM_USE_LED |
flags | 输出标志 | 通常为 0;组合TIMER_OUTPUT_INVERTED、TIMER_OUTPUT_N_CHANNEL等(见 timer.h) |
dmavar | DMA 映射变体(即文档所称 dmaopt) | 选第几个 DMA 映射,0、1、2…(见下) |
文档中所说的dmaopt(DMA Option)指的就是选择哪个备用 DMA 映射(0/1/2...),在源码中由第 6 个参数dmavar承担;宏展开时通过 timer_def.h 的DEF_TIM_DMAMAP(variant, timch)机制,从该通道的候选 DMA 列表(如D(1,4,5),D(2,1,6),...)中取第variant个条目。注意:现代 target 中第 6 参数绝非恒为 0 的占位符——它是解决冲突的关键旋钮。第 5 参数flags才是通常保持 0 的输出标志位。
F405/F411 实例对照
以文档给出的 F405/F411 场景为例,结合映射表验证:
// TIM3_CH1 只有一个 DMA 选项:DMA1_Stream4(映射表 D(1, 4, 5)) DEF_TIM(TIM3, CH1, PB4, TIM_USE_OUTPUT_AUTO, 0, 0), // dmavar=0 // TIM2_CH4 有两个 DMA 选项:DMA1_Stream7 或 DMA1_Stream6(映射表 D(1, 7, 3), D(1, 6, 3)) DEF_TIM(TIM2, CH4, PB11, TIM_USE_OUTPUT_AUTO, 0, 0), // dmavar=0 → DMA1_Stream7 DEF_TIM(TIM2, CH4, PB11, TIM_USE_OUTPUT_AUTO, 0, 1), // dmavar=1 → DMA1_Stream6其中DMA_TAG的编码方式(DMA 编号、流、请求通道打包进一个 32 位 tag)定义在 dma.h,运行时dmaGetByTag()依据 DMA 编号与流号匹配描述符(参见 dma_stm32f4xx.c)。
检测 DMA 冲突
典型症状
- 部分电机不响应油门,其余正常;
- 同一套 timer 表在某些配置下正常、在另一些配置下失效(例如启用了占用同一 DMA 流的 UART/SPI/ADC 后);
- CLI
status命令输出中出现 "DMA conflict" 类警告——这一排查路径在 troubleshooting-guide.md 的"Motors Not Working"一节亦有说明(status命令注册于 fc/cli.c)。
查找冲突的步骤
- 取出目标板
target.c中全部DEF_TIM()条目; - 对照 MCU 的 DMA 请求映射表(参考手册或 STM32CubeMX 的 pinout 视图,或直接对照仓库中的 timer_def_stm32f4xx.h、timer_def_stm32f7xx.h);
- 检查是否存在任意两条条目在相同
dmaopt下解析到相同的 DMA 流/通道; - 同时留意与外设的竞争:DMA 流不只被定时器使用,UART 收发、SPI、ADC、SDIO 等同样会占用。真实案例如 SPRACINGF4EVO/target.c 的注释:
DEF_TIM(TIM4, CH1, PB6, TIM_USE_OUTPUT_AUTO, 0, 0), // ESC 5 / Conflicts with USART5_RX / SPI3_RX - SPI3_RX can be mapped to DMA1_ST3_CH0 DEF_TIM(TIM4, CH2, PB7, TIM_USE_OUTPUT_AUTO, 0, 0), // ESC 6 / Conflicts with USART3_RX解决冲突的三种策略
策略一:改用不同的定时器
与其让同一定时器的多个通道挤在一起,不如把输出分散到多个定时器上,从源头摊薄 DMA 流占用:
// 之前(潜在冲突) DEF_TIM(TIM3, CH1, PB4, TIM_USE_OUTPUT_AUTO, 0, 0), DEF_TIM(TIM3, CH2, PB5, TIM_USE_OUTPUT_AUTO, 0, 0), DEF_TIM(TIM3, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), DEF_TIM(TIM3, CH4, PB1, TIM_USE_OUTPUT_AUTO, 0, 0), // 之后(分散到不同定时器) DEF_TIM(TIM3, CH1, PB4, TIM_USE_OUTPUT_AUTO, 0, 0), DEF_TIM(TIM2, CH3, PB0, TIM_USE_OUTPUT_AUTO, 0, 0), DEF_TIM(TIM4, CH1, PB6, TIM_USE_OUTPUT_AUTO, 0, 0), DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 0, 0),前提是这些引脚在物理上支持对应的定时器复用功能(见下文"引脚到定时器映射")。这也是 creating-targets.md 中建议的目标板 timer 表常规布局方式。
策略二:切换 DMA Option(dmavar)
当同一通道存在多个 DMA 候选流时,通过改变第 6 参数选择备用映射即可错开冲突:
// 之前(冲突) DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 0, 0), // dmavar=0 → DMA2_Stream6 DEF_TIM(TIM1, CH3, PA10, TIM_USE_OUTPUT_AUTO, 0, 0), // dmavar=0 → DMA2_Stream6 - CONFLICT! // 之后(CH1 改用备用映射) DEF_TIM(TIM1, CH1, PA8, TIM_USE_OUTPUT_AUTO, 1, 0), // dmavar=1 → DMA2_Stream1 DEF_TIM(TIM1, CH3, PA10, TIM_USE_OUTPUT_AUTO, 0, 0), // dmavar=0 → DMA2_Stream6 - OK!对照 timer_def_stm32f4xx.h 的映射表,TIM1_CH1的候选为D(2,6,0), D(2,1,6), D(2,3,6),dmavar=1确实解析到DMA2_Stream1;而TIM1_CH3的两个变体均为DMA2_Stream6。这一示例与真实映射表完全一致。需要提醒:若某通道所有变体都落在同一个 DMA 流(如本例的TIM1_CH3),那么仅靠换dmavar无法解决该通道自身的冲突,只能考虑策略一或策略三。
真实世界中还有更精细的用法:例如 DAKEFPVH743_SLIM/target.c 为 LED 灯带选择dmavar=9,注释写明"moved off DMA2 Stream0 to avoid ADC1 DMA conflict"(避开与 ADC1 共享的 DMA 流):
DEF_TIM(TIM3, CH3, PB0, TIM_USE_LED, 0, 9), // LED_2812 - moved off DMA2 Stream0 to avoid ADC1 DMA conflict策略三:检查硬件设计
如果冲突无法通过软件调整化解,问题可能出在硬件设计本身。此时应回头审查:
- 引脚能否改接到其他定时器通道?(需查 AF 表)
- 所有电机输出是否都是必要的?
- LED 灯带、摄像头控制等次要功能能否换用别的引脚/定时器?
引脚到定时器映射:AF 复用表
并不是所有引脚都能驱动所有定时器。每个 STM32 引脚只支持有限的"复用功能(AF)",需要查阅芯片数据手册的Alternate Functions 表(INAV 源码中 AF 映射表也逐引脚列出,见 timer_def_stm32f7xx.h 等)。
PA8 的示例:
- 可复用为
TIM1_CH1(AF1) - 可复用为
TIM8_CH1(AF3) - 不可复用为 TIM2、TIM3、TIM4
查找备用功能的操作步骤:
- 打开 STM32 数据手册的 Pinout 章节;
- 定位目标引脚(如 PA8);
- 查看 "AF"(Alternate Function)列;
- 寻找其中的
TIMx_CHy条目,确认该引脚能驱动哪个定时器通道。
在 F4 上宏直接引用GPIO_AF_ ## tim(timer_def_stm32f4xx.h),F7/H7 则通过DEF_TIM_AF查找每个 (引脚, 定时器通道) 组合对应的 AF 编号(timer_def_stm32f7xx.h),写法上二者不同,但都需要确保 (引脚, 定时器, 通道) 组合真实存在于数据手册。
最佳实践
- 始终对照参考手册核查——在敲定
target.c之前,把每一条DEF_TIM()的 DMA 解析结果都与 MCU 的 DMA 请求映射表核对一遍; - 分散定时器——避免把电机全部压在同一枚定时器上;
- 为 UART/SPI 预留 DMA——UART 收发、SPI、SDIO、ADC 等外设同样需要 DMA 流,别让 timer 表把它们挤占殆尽;
- 测试全部输出——定义完 timer 后逐一验证每个电机/舵机输出;
- 写好注释——在
DEF_TIM()行尾标注 DMA 分配结果,便于他人与未来的自己审查:
// Good: Shows DMA usage DEF_TIM(TIM3, CH1, PB4, TIM_USE_OUTPUT_AUTO, 0, 0), // DMA1_S4 DEF_TIM(TIM3, CH2, PB5, TIM_USE_OUTPUT_AUTO, 0, 0), // DMA1_S5此外,参考 common-issues.md 的"Timer Configuration"一节,还应使用正确的用途标志:电机/舵机一律用TIM_USE_OUTPUT_AUTO,LED 灯带用TIM_USE_LED,TIM_USE_ANY已被标记为 DEPRECATED——其枚举值为 0、不设置任何用途位,编译可通过但通道会沦为无效条目(详见 troubleshooting-guide.md)。
测试流程:如何验证无冲突
完成 timer 定义后,按以下流程验证:
- 编译固件(目标板构建方式见 creating-targets.md);
- 刷写飞控;
- 打开 INAV Configurator;
- 进入Motors(电机)选项卡;
- 逐个推动电机滑条——所有输出都应有响应;
- 打开 CLI 输入
status——留意是否存在 DMA 相关警告(命令定义于 fc/cli.c)。
结合源码可以进一步明确:若某通道因 DMA 被占用而在 timer_impl_stdperiph.c 的检查中返回false,对应端口在 pwm_output.c 中不会被标记为已配置——此时"电机滑条无反应"就是 DMA 冲突的直接证据,应回到DEF_TIM()表按本文三种策略调整。
H7 的优势与边界
STM32H7 的 DMAMUX 消除了绝大多数 DMA 冲突:
- 任意定时器可路由到任意 DMA 通道;
- 灵活性远高于 F4/F7 的固定映射;
- 目标板开发者的头痛问题大幅减少。
但需要记住边界:冲突依然可能(只是更罕见),且存在 DMAMUX 无法绕过的硬件约束——例如 timer_def_stm32h7xx.h 中TIM15_CH2与多个 TIM9~TIM14 通道的映射为NONE(根本没有 DMA 请求线)。遇到这类通道,换dmavar无用,只能改走其他定时器。
相关文档与资源
- overview.md —— INAV 目标板系统基础与文件结构
- common-issues.md —— 常见目标板问题清单,"Timer Configuration"一节含真实提交示例
- creating-targets.md —— 新目标板创建流程中的 timer 表搭建
- troubleshooting-guide.md —— 系统性调试方法(含 CLI
status检查 DMA 冲突) - STM32 参考手册—— 完整的 DMA 请求映射表(F4: RM0090 第 10 章 DMA;F7: RM0385 第 9 章 DMA;H7: RM0433 第 17 章 DMAMUX)
- 仓库 DMA 映射表源码:timer_def_stm32f4xx.h、timer_def_stm32f7xx.h、timer_def_stm32h7xx.h、timer_def_at32f43x.h
- 无人机
- 嵌入式
- 智能硬件
【免费下载链接】inav
INAV: Navigation-enabled flight control software
相关推荐
INAV 中的 AnyFC F4 目标板:硬件规格、引脚定义与固件构建指南
INAV 中的 AnyFC F4 目标板:硬件规格、引脚定义与固件构建指南 本篇技术指南以 INAV 开源仓库中 AnyFC 目标板说明 https://lin
无人机嵌入式智能硬件INAV 飞控板指南:FlyingRC F4 Wing Mini(FLYINGRCF4WINGMINI)目标板特性、规格与限制
INAV 飞控板指南:FlyingRC F4 Wing Mini(FLYINGRCF4WINGMINI)目标板特性、规格与限制 FlyingRC F4 Wing
无人机嵌入式智能硬件INAV 板级指南:Airbot F4 / Flip32 F4 飞控硬件资源与接线配置详解
INAV 板级指南:Airbot F4 / Flip32 F4 飞控硬件资源与接线配置详解 本指南以 INAV 官方板级文档为主体,结合当前仓库中 AIRBOT
无人机嵌入式智能硬件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考