做嵌入式开发的朋友,估计对DMA是又爱又恨。爱是因为它能解放CPU,重负载场景下配合中断和回调,可以让外设数据吞吐完全不需要CPU操心;恨的是出了问题你往往抓不住它到底卡在哪里。最近在STM32U5上调试一个ADC多通道扫描循环采样DMA的方案,就遇到了一模一样的现象:DMA初始化一切正常,可一启动传输,状态就变成了busy,中断不触发,回调也不执行。翻遍寄存器、查遍CubeMX,最后才发现问题出在一个特别容易忽略的细节上。这篇文章就基于这个案例聊一聊“U5 DMA not setting interrupt or executing callback, stays busy”这类问题:为什么会卡在busy,传输完成标志为什么没有置位、中断链路断在哪里,以及应该按什么顺序排查。如果你现在正被同样的问题折磨,或者刚用U5准备做ADC、串口、SPI的DMA传输,这篇文章应该能帮你少走不少弯路。
1. 问题现象与初步判断
1.1 打开工程时的第一眼:一切正常,但就是不工作
我遇到的情况很典型。使用STM32CubeMX生成工程,ADC配置了多通道扫描,DMA配成循环模式,外设到内存,缓冲区和数据宽度都检查过,初始化代码也调用了。执行HAL_ADC_Start_DMA()之后,返回值是HAL_OK,看起来一切正常。但程序就是没有反应:没有ADC转换数据更新,DMA中断没有触发,HAL_ADC_ConvCpltCallback也没有被调用。
更奇怪的是,如果直接读DMA状态,通道的BSY位始终是1,也就是它认为自己一直在忙。状态寄存器里如果有TCIF标志,读出来又像是没有发生过任何传输完成事件。HAL_DMA_GetState()返回的也是HAL_DMA_STATE_BUSY,整个DMA通道就像被锁死了一样。
这类问题之所以折磨人,是因为它既不是崩溃,也不是编译错误,它只是“不干事”。如果你的程序里还有别的外设中断在跑,甚至系统表面上是活的,只是DMA数据不更新,那排查起来就更考验耐心。
1.2 遇到问题先别急着怀疑芯片,理清排查方向
很多朋友遇到DMA卡死会第一时间怀疑芯片坏了,或者怀疑HAL库有bug。实际上,绝大多数“stays busy”案例都是配置层面的问题,而且往往不是一处错误,而是“配置组合”出来的怪现象。我一般会按下面这几个方向去理思路:
- DMA通道本身是否处于可启动状态,是不是上一次传输没结束;
- DMA的中断标志、中断使能位,以及NVIC中断通道是否真正配置正确;
- 外设发出的DMA请求信号是否已经产生,有没有选对DMA请求映射;
- 中断服务例程里是否调用了HAL库的
HAL_DMA_IRQHandler,回调函数是否在用户文件里被正确定义; - Cache、MPU、总线矩阵等U5新特性是否对DMA产生了额外影响。
把这几个方向一步步查完,绝大多数问题都能浮出水面。下面我先把U5的DMA工作机制拆开讲清楚,你理解了状态机和中断链路之后,排查起来就会有方向感,而不是靠瞎试。
2. DMA工作原理解析:读懂busy标志与中断回调的关系
2.1 U5的DMA通道与请求映射机制
STM32U5系列用的DMA和老的F1、F4系列不太一样,它有着更灵活的请求映射方式,通常是通过DMAMUX或者通道控制寄存器来把外设请求连接到对应的DMA通道。一个DMA通道可以服务于多个外设请求,关键在于你怎么配这个“请求选择”。比如ADC的转换完成请求可以被连接到DMA1的Channel0,也可以被连接到另一个通道,具体取决于寄存器里的请求选择位。
很多人只记得配置DMA通道的方向、地址、数据宽度,却忘了配置“这个通道到底听谁的指挥”。如果你在外设请求源那里选错了,或者干脆没选,那么即使DMA通道被使能,它也不会开始搬运数据。外设产生请求时,DMA压根不知道自己需要响应,最终表现就是NDTR不变、BSY不变、中断不触发。
所以排查这类问题时,我第一件事就是确认“DMA请求映射”。在STM32CubeMX里面,你可以在DMA设置里看到“Request”下拉选项,这里必须选对实际使用的外设。比如ADC1就是ADC1,不能选成TIM1或者别的什么。U5的寄存器手册里会有详细的映射表,实在不确定就翻手册看一下。
2.2 busy标志到底什么时候会被清除
DMA通道控制寄存器里有一个BSY位,表示当前通道是否正在传输。按照STM32的DMA设计,当一次传输完成、半传输或者错误事件发生时,BSY位并不会自动清除,而是需要你先处理对应的事件标志,也就是要写IFCR寄存器把TCIF、HTIF、TEIF等标志清除。只有清除了这些标志,DMA控制器才会认为这个通道“空闲”,从而允许下一条传输启动。
这就是“stays busy”最核心的机制之一。如果DMA传输已经发生并且完成,但是中断没有执行、标志没有清除,DMA就会一直认为通道正在忙。后续你再调用HAL_DMA_Start_IT(),HAL库发现通道状态是BUSY,直接返回HAL_BUSY,连启动都启动不了。哪怕你用寄存器操作绕过HAL层强行启动,BSY位因为旧标志没清,也会导致行为异常。
还有一点要注意:U5的中断标志和早期M3内核的DMA不太一样,有些系列把标志分成了多个通道独立的中断标志寄存器,读写方式也有差异。我建议拿到新芯片后,先仔细看参考手册里对DMA_ISR和DMA_IFCR的位定义,不要直接照搬F1/F4的老代码。
2.3 从外设请求到回调函数,完整中断链路是什么
要理解“中断不触发、回调不执行”,得先把整条链路看清楚。一次正常的DMA中断流程如下:
- 外设产生DMA请求,比如ADC规则组转换完成、USART接收寄存器非空、SPI收发准备好;
- DMA控制器收到请求后,按照配置好的通道参数进行搬运,并把数据写到目的地址;
- 当传输长度计数NDTR减到0,或者发生半传输/传输错误时,DMA控制器会置位对应的中断标志,比如传输完成标志TCIF;
- 如果DMA通道控制寄存器的TCIE、HTIE、TEIE相应中断使能位为1,DMA就会向NVIC发出中断请求;
- NVIC需要对应DMA通道的中断被使能,且优先级分组配置正确,CPU才会跳转到中断向量表,进入DMA中断服务函数;
- 在中断服务函数里,通常要调用HAL库的
HAL_DMA_IRQHandler(),这个函数会根据标志位情况,自动调用用户重写的回调函数,比如HAL_DMA_XferCpltCallback()。
如果你看到的现象是“中断没反应”,就要沿着这条链路一段一段查:外设有没有发请求?DMA有没有接受到请求?标志有没有置位?标志置位后有没有触发NVIC?CPU有没有进中断服务函数?中断服务函数有没有调用HAL库处理函数?HAL库处理函数有没有找到用户回调?
2.4 HAL库返回HAL_BUSY背后的状态机逻辑
HAL库的DMA实现里,每个DMA通道对应一个DMA_HandleTypeDef结构体,其中State成员记录当前状态。启动传输前,HAL库会检查这个State,如果是HAL_DMA_STATE_BUSY,直接返回HAL_BUSY。所以如果调用HAL_DMA_Start_IT()或HAL_ADC_Start_DMA()时返回HAL_BUSY,说明DMA通道的软件状态还停在忙状态,没有恢复。
这个状态通常和硬件标志是联动的。HAL库在传输完成中断里会更新State,然后调用回调;如果你关了中断,或者中断没有正确执行,HAL库就永远没有机会去更新State。于是下一次启动自然就是HAL_BUSY。
所以排查的时候不要只看返回值,还要看底层的标志位。有时候HAL_OK不代表硬件正常,它只是说软件配置参数合法;同样HAL_BUSY也未必是硬件卡住,可能只是软件状态没同步。理解了这层机制,排查思路就清晰了。
3. 系统化排查与修复实操
3.1 第一时间读取DMA寄存器的“现场状态”
遇到问题不要盲目改配置,第一步应该把DMA的寄存器状态抓出来。你需要重点看这几个字段:
- 通道控制寄存器CCR:看看通道是否使能,传输方向、模式、地址递增、中断使能位有没有配对;
- 传输数量寄存器NDTR:如果传输已经开始,这个值应该随搬运而递减;如果一直是初始值,说明DMA根本没有在搬数据;
- 中断状态寄存器DMA_ISR或对应通道的标志位:看TCIF、HTIF、TEIF有没有置位,这能告诉你硬件到底发生了什么事件;
- 中断清除寄存器DMA_IFCR:通过它清除标志后,看BSY位是否会变化。
我在调试时,习惯在HAL_ADC_Start_DMA()之前和之后各抓一次寄存器状态,脚本或调试器直接看,比较方便。也可以用串口打印这些寄存器的值,但注意打印本身也可能干扰时序,生产代码里不要留。
3.2 检查中断是否从DMA一路送到了NVIC
先检查DMA通道的CCR里有没有使能传输完成中断。比如:
if (hdma->Instance->CCR & DMA_CCR_TCIE) { // 传输完成中断已在DMA层使能 }如果这一位是0,说明就算外设请求来了、DMA搬完了,也不会产生中断,回调自然永远不会执行。接下来要检查NVIC层。在HAL里通常在CubeMX生成的MX_DMA_Init()函数里会调用HAL_NVIC_SetPriority()和HAL_NVIC_EnableIRQ(),你要确认DMA通道对应的IRQn是哪一个,并且在启动文件里存在对应的中断处理函数。
有一个容易踩的坑:STM32U5系列有些DMA通道的中断向量名和F系列不完全一样,如果工程是从旧型号复制过来的,很可能中断服务函数名对不上。链接器不会报错,但中断永远不会进你的函数。检查方式是在启动文件里搜DMA相关IRQHandler,确认名字与你调用HAL_DMA_IRQHandler的地方一致。
3.3 确认外设请求信号真的来了
DMA是“被动”的,它靠外设请求来触发搬运。所以如果NDTR一直不动,大概率是外设根本没发出DMA请求。下面列几个常见外设的检查点:
- ADC的DMA请求:在ADC配置里必须使能DMA请求。有些系列是ADC_CR2的DMA位,有些是多通道配置里的DMAContinuousRequest;启动时还要调用带DMA的启动函数,比如
HAL_ADC_Start_DMA(),而不是单纯HAL_ADC_Start(); - 串口的DMA发送:往发送数据寄存器写数据后,才会产生DMA发送请求。如果你用
HAL_UART_Transmit_DMA(),它本身会触发,但如果底层串口没使能或者波特率配置异常,请求也可能不产生; - SPI的DMA收发:SPI的DMA请求需要在SPI初始化后使能,且SPI主模式下时钟不启动就不会有数据请求,需要注意极性和相位是否匹配;
- 定时器触发:如果用定时器触发ADC,还得确认定时器确实在跑,并产生了PWM或者比较更新事件,否则ADC不会启动转换,自然也没有DMA请求。
如果外设请求一直没有产生,你可以临时用逻辑分析仪抓外设事件引脚,或者在调试器里读外设状态寄存器,确认转换是否完成。不要凭空假设“外设肯定在工作”。
3.4 回调函数为何“人间蒸发”
HAL库的机制是中断服务函数里调用回调,但回调函数本身是弱函数,需要你在用户代码里重新定义。很多人写了回调,但写错了文件,或者没注意前缀。正确的做法是,在使用DMA的源文件里定义一个全局函数:
void HAL_DMA_XferCpltCallback(DMA_HandleTypeDef *hdma) { // 判断是哪个DMA通道 if (hdma->Instance == DMA1_Channel0) { // 处理你的逻辑 } }如果你用的是ADC的DMA,通常不是直接重写DMA的HAL_DMA_XferCpltCallback,而是重写ADC相关的回调,比如HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)。这两个回调的触发链路不一样,前者是DMA中断里调用的,后者是ADC中断或DMA回调里进一步调用的。如果名字写错了,编译不会报错,但回调不会执行,尤其是在用了HAL_ADC_Start_DMA时,很多人只在ADC的回调里处理数据,却忽略了底层DMA回调的存在。
我在项目里的习惯是,先用一个简单的全局标志或串口打印,在HAL_DMA_IRQHandler()入口和出口打点,确认入口有没有进、出口调用的是哪个回调。只要这一层通了,再往上找数据问题就容易了。
3.5 中断标志残留,堪称“stays busy”的头号元凶
前面说到BSY位在事件标志没清除前不会复位,这个情形我在调试中碰到的概率最高。典型场景是:第一次DMA传输正常完成,但因为某种原因中断没有执行,TCIF标志留在那里;第二次启动DMA时,DMA控制器发现TCIF还没清除,认为上一个传输过程还没结束,忙标志一直置位,新请求根本不会处理。
特别是在配置了Normal模式,却希望循环传输时,最容易触发这个现象。Normal模式传完一次就停下来了,NDTR归零,TCIF置位;如果你没有在中断里清除标志,下一次调用HAL_DMA_Start_IT()就可能拿到HAL_BUSY。处理方式很简单:启动前先清除所有残留标志,必要时先调用HAL_DMA_Abort()。
__HAL_DMA_CLEAR_FLAG(&hdma, DMA_FLAG_TC | DMA_FLAG_HT | DMA_FLAG_TE);这行代码在每次启动DMA前执行,能避免很多“第二次就卡住”的问题。如果你用的是LL库,也别忘了类似操作。
4. 常见配置错误案例分析
4.1 CubeMX里DMA请求源选错,编译不报错但运行诡异
STM32CubeMX里配置DMA时,会要求你为每个DMA通道选择一个“Request来源”。比如你要用UART1接收DMA,就应该在UART1的DMA设置里添加通道,并让CubeMX自动生成对应的请求选择。如果你手工改过配置,或者从别的工程复制过来,很容易出现DMA通道选对了、但请求源还是之前外设的情况。
这种错误最麻烦的地方在于:初始化代码是正常的,寄存器配置也没毛病,但DMA收不到正确外设的请求,所有数据都不会动。检查方法很简单,看生成的代码里,比如hdma_uart1_rx.Init.Request = DMA_REQUEST_UART1_RX;,这个值一定要和实际外设匹配。一旦发现这里不匹配,CubeMX里重新生成,或者手动改成对应值即可。
4.2 Normal模式被当成循环模式用
很多新手用DMA,以为DMA搬完一次后会自己从头再来。实际上在Normal模式下,搬运指定长度的数据后,通道就停止工作了,NDTR变成0,通道使能位也会被清零。如果你在主循环里只调用了一次HAL_ADC_Start_DMA,之后就等着数据持续刷新,根本等不到。
这种情况下,DMA不是“卡住”了,而是已经正常结束,只是你没有重新启动它。处理办法要么改成Circular模式实现自动回绕,要么在传输完成回调里再次调用启动函数。
U5的DMA在Circular模式下,搬运完一组数据之后NDTR会自动重载,从而持续搬运。像ADC多通道扫描循环采样这种场景,Circular模式是标配,除非你要处理的是“一次性的搬运任务”。
4.3 地址递增方向配反,数据乱飞或根本不更新
DMA配置里有两个地址相关选项:外设地址是否递增、内存地址是否递增。外设寄存器往往固定地址,比如ADC的数据寄存器地址固定不变,所以外设地址递增要关闭;而内存缓冲区通常是一段连续地址,需要开启内存地址递增。
如果这两个搞反了,DMA可能把外设数据写到同一个地址反复覆盖,或者从内存往外的地址递增得乱七八糟,最后表现为数据不对、内存被写花、甚至触发HardFault。某些情况下还会导致DMA传输提前结束或异常,中断标志可能会设置错误,反而让busy状态更混乱。
配置这类参数时,我习惯在脑子里过一遍“数据流方向”:外设地址固定还是递增?内存缓冲是一段数组还是单个变量?传输方向是从外设到内存,还是从内存到外设?这样逐项确认基本不会错。
4.4 数据宽度不一致,NDTR计数和实际搬运量对不上
很多情况下,NDTR寄存器的值是传输“数据单元”的数量,而不是字节数。如果外设数据寄存器是32位,你配置的DMA数据宽度却是16位,那么实际搬运的数据量就会翻倍,导致缓冲区溢出,或者只搬了一半数据。
在ADC多通道采样中,如果ADC分辨率是12位或14位,数据寄存器通常是32位,建议DMA数据宽度配成Word,内存缓冲区用uint32_t数组。如果你用uint16_t数组配Word,寻址空间虽然也能容纳,但每次搬运会跨越两个数组元素,最后数据错位,很难查。
数据宽度不一致也可能会让DMA产生错误事件,触发TEIF标志,如果错误中断没处理好,一样会卡在busy。
4.5 开启Cache之后,DMA与CPU看到的数据不一致
STM32U5部分型号内置Cache,如果开启了D-Cache,而DMA直接搬运内存,CPU读到的可能是Cache里的旧数据,或者DMA写到内存时绕过了Cache,导致数据不同步。这类问题不一定让DMA卡死,但会让程序逻辑看起来像是DMA没工作。
解决方法是合理配置MPU,把DMA缓冲区标记为non-cacheable,或者在访问数据前后做Cache clean/invalidate操作。在HAL中可以用SCB_CleanDCache()、SCB_InvalidateDCache()等函数。如果只是验证DMA功能,最简单的方法是在CubeMX里先不开启D-Cache,或者把缓冲区放到non-cacheable区域,先把问题范围缩小。
U5和F系列不同,很多例程里没有提前处理Cache的问题,我建议刚接触U5的朋友先确认自己用的型号有没有Cache、工程有没有开启,再往下查DMA。
5. 实战案例:U5 ADC多通道扫描循环采样DMA的完整排查过程
5.1 需求拆解与初始配置
我当时的应用需求很普通:ADC1采集三路模拟电压,分别是CH0、CH1、CH2,使用扫描模式,连续转换,DMA循环传输到内存数组。CubeMX里大致配置是:ADC1开启Scan Conversion Mode,Number of Conversion设为3,采样时间拉长避免阻抗问题,DMA请求选择ADC1,方向Peripheral To Memory,模式Circular,数据宽度Word,内存地址递增,同时使能DMA中断。
初始化代码生成后,我手动调用HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buf, 3)。调试时发现程序没有进回调,adc_buf一直是初始值。
5.2 我按什么顺序一步步查的
第一步看DMA状态寄存器,发现BSY位为1,TCIF没有置位。这说明DMA本身没完成任何传输,忙状态是“假忙”,可能是启动前就卡住了。
第二步看CCR寄存器,通道使能、外设到内存、内存递增、循环模式、TCIE都看起来正常,说明DMA通道本身的配置没有明显问题。
第三步看NDTR,发现数值始终是3,完全没有递减。DMA没有搬运数据,那问题很可能在于没有请求到来,或者请求映射不对。
第四步回到ADC侧。检查ADC状态寄存器,发现ADC已经启动,扫描模式也开了,但是规则通道转换完成后,DMA请求没有拉起来。对照参考手册发现,ADC的DMA请求使能位没有设置成功。重新检查CubeMX配置,发现DMA配置页面虽然选了ADC1作为请求源,但ADC自身的DMA请求使能被隐藏在另一个配置项里,没有勾选上。生成代码里也因此没有写入对应的寄存器位。
5.3 修复方式与最终代码
在CubeMX里找到ADC DMA相关配置,确保ADC的DMA请求被使能,然后重新生成代码。如果不用CubeMX,也可以手动在ADC初始化后加入设置请求使能的代码。修改后,我再启动DMA,NDTR开始递减,TCIF置位,中断触发,进到HAL_DMA_IRQHandler()之后ADC回调被调用,数据正常更新。
一个简化版的关键配置逻辑大概是:
hadc1.Instance = ADC1; hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DMAContinuousRequests = ENABLE; // 这个必须开 // ... hdma_adc1.Init.Request = DMA_REQUEST_ADC1; hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc1.Init.MemInc = DMA_MINC_ENABLE; hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_WORD; hdma_adc1.Init.Mode = DMA_CIRCULAR; // ...注意DMAContinuousRequests这个参数,在STM32U5的HAL库里,它决定了ADC是否连续产生DMA请求。如果这个没开,即使你在DMA层配置了循环模式,ADC只会在第一组转换结束后产生一次请求,之后因为请求被关闭,DMA不会再收到新的触发源,看起来就像DMA卡住了。其实DMA没卡,是ADC没有继续请求。
5.4 案例总结:全局视角比单独看DMA更重要
这个案例最有价值的点在于,DMA本身没问题,问题在ADC侧。如果你只盯着DMA寄存器和中断,要排查到天荒地老。所以排查DMA问题时,一定要把“请求源”和“DMA数据流”当成一个整体看,而不是只研究DMA一个模块。外设没有产生请求、请求没有映射到DMA通道、DMA通道没有使能中断、中断没有进NVIC,任何一个环节出问题,最终表象都一样:interrupt not set, callback not executed, stays busy。
6. 常见问题速查表与排错经验
6.1 一张表快速定位DMA卡死问题
下面这个表格是我这些年做DMA调试时总结出来的速查表,遇到问题可以直接对着找:
| 现象 | 可能原因 | 排查方法 | 解决办法 |
|---|---|---|---|
| 启动函数返回HAL_BUSY | DMA通道软件状态未复位 | 查看HAL_DMA_GetState()返回值 | 调用HAL_DMA_Abort(),或清除DMA标志 |
| DMA的BSY位一直为1 | 传输完成/错误标志未清除 | 读中断状态寄存器 | 写IFCR清除TC、HT、TE标志 |
| 传输完成中断不触发 | DMA中断使能位未开,或NVIC没使能 | 检查CCR的TCIE,检查NVIC的IRQ | 打开中断使能位,调用HAL_NVIC_EnableIRQ |
| 中断服务函数执行了但回调不执行 | 未重写回调函数,或回调函数名不对 | 搜索工程里的HAL_DMA_XferCpltCallback | 添加回调函数定义,确认函数原型 |
| NDTR一直不递减 | 外设请求没来,或请求映射错误 | 检查外设DMA请求使能位 | 配置外设DMA请求,重新选择DMA Request |
| 数据搬运错乱 | 地址递增方向、数据宽度配置错误 | 核对DMA Init结构体 | 改PeriphInc、MemInc、PeriphDataAlignment、MemDataAlignment |
| 第二次启动时卡住 | 中断标志残留 | 观察启动前状态寄存器 | 每次启动前清除标志或Abort |
| 开启Cache后数据不更新 | 缓存一致性问题 | 查看MPU配置 | 将缓冲区设为non-cacheable或做clean/invalidate |
这张表不是万能的,但覆盖了论坛里八成以上的“DMA卡死”提问。只要你按这个表对照检查,大部分问题都能自己解决。
6.2 我自己的调试习惯:把寄存器打印做成“标配”
我调试DMA类问题,很少上来就改代码。就算找到了可疑点,也喜欢先用一段调试代码把关键寄存器值打印出来。打印这些寄存器可以帮助你建立“直觉”:什么状态下应该是什么值,什么异常值对应什么问题。下面这段代码是我调试时常用的伪代码,风格很简单:
void dump_dma_state(DMA_HandleTypeDef *hdma) { printf("CCR: 0x%08lx\n", hdma->Instance->CCR); printf("NDTR: 0x%08lx\n", hdma->Instance->NDTR); printf("ISR: 0x%08lx\n", hdma->DMA_ChannelIndex ? /* 取对应标志 */ : 0); }在启动DMA前、启动后、以及几十毫秒后再各打一次,对比三次数据就能看出DMA到底有没有在工作。这个方法比单独看某个变量可靠得多,因为它反映的是硬件真实状态。
实际项目中,打印函数本身可能占用时间,调试完要记得删掉或做成条件编译。还有一点:如果你用RTOS,在中断服务里不要直接调用复杂打印,否则会影响实时性,甚至造成死锁;把寄存器值存到一个全局结构体里,等任务去打印更安全。
6.3 关于U5的DMA,我还踩过这些坑
最后补充一些U5特有的经验。U5的时钟树比老系列更复杂,DMA时钟需要确保在RCC里已使能;CubeMX生成的HAL_RCC_DMA_CLK_ENABLE()一般会自动加上,但如果你从旧工程迁移,很容易漏掉。
U5的DMA描述符、安全和特权级机制也需要留意。有些DMA请求和中断受到TrustZone影响,如果开启了TrustZone但没有正确配置安全分配,非安全状态下的DMA代码可能无法正常工作。这个坑在普通教程里很少提到,但实际项目可能会遇到。
另外,U5的CubeMX代码生成逻辑和F4有一些细微差别,比如有些配置项名字改成了“DMAMUX Request”,不要看到“Request”变了下名字就一脸懵,本质是一样的。拿到芯片后先看手册里的DMA框图,确认信号流向,能省下很多调试时间。我个人体会是,DMA这类问题,只要状态寄存器读得懂、中断链路理得清、请求源找得准,基本没有解决不了的疑难杂症。