尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32 ADC/DAC与DMA协同:构建高效实时数据流处理系统

STM32 ADC/DAC与DMA协同:构建高效实时数据流处理系统 1. 从“感知”到“行动”ADC、DAC与DMA的角色定位在嵌入式开发尤其是基于STM32这类MCU的项目里我们常常会听到三个高频词ADC、DAC和DMA。新手工程师很容易把它们看作是三个独立、并列的外设模块然后分别去学习它们的寄存器配置。但如果你真的这么做了往往会陷入“只见树木不见森林”的困境配置起来总觉得磕磕绊绊效率低下。今天我想从一个更贴近实战的系统视角来聊聊这三者到底是如何协同工作共同构成一个高效、实时数据流处理系统的。你可以把整个系统想象成一个智能工厂的生产线。ADC模数转换器就是这条生产线的“质检传感器”。它的任务是把来自外部世界的、连续变化的模拟信号比如温度、压力、声音、光照强度转换成MCU能够理解和处理的离散数字量。没有它MCU就是个“聋子”和“瞎子”无法感知真实的物理世界。DAC数模转换器则恰恰相反它是生产线的“执行器”或“信号发生器”。MCU内部运算产生的数字结果需要通过DAC转换回连续的模拟电压或电流信号才能去驱动扬声器发出声音、控制电机转速、或者生成特定的波形。没有它MCU的决策就无法作用于外部世界成了一个“哑巴”。那么DMA直接内存访问在这个比喻里扮演什么角色它就是那条连接传感器、中央控制室CPU和执行器之间的高速自动化传送带。在传统模式下ADC转换完一个数据需要触发一个中断CPU停下手中的活上下文切换跑过来把数据从ADC数据寄存器“搬”到内存里DAC需要新数据时CPU又得跑过去“喂”数据。CPU大量时间浪费在了这种简单重复的“搬运工”角色上。DMA的出现就是为了解放CPU。它可以在外设如ADC、DAC、串口、SPI和内存之间直接建立数据通道一旦ADC转换完成或者DAC需要新数据DMA控制器会自动完成数据传输完全不需要CPU干预。CPU只需要在数据搬运开始前配置好DMA搬运完成后或搬运了足够多的数据后处理一下即可从而可以专注于更复杂的逻辑运算和决策。所以一个典型的闭环数据流可能是这样的外部模拟信号 - ADC采样 - DMA将采样数据搬运到内存数组 - CPU对数组数据进行滤波、算法处理 - CPU将处理结果写入另一个内存数组 - DMA将结果数组自动搬运给DAC - DAC输出模拟信号。在这个流程中CPU只参与了“处理”环节繁重的“搬运”工作全部由DMA接管系统效率和实时性得到极大提升。接下来我们就深入STM32的内部看看如何让这条“生产线”高效运转起来。2. STM32 ADC实战从单次采样到DMA连续搬运STM32的ADC模块功能相当丰富支持多种分辨率如12位、10位、多种扫描模式、多种触发源。但万变不离其宗其核心工作流程可以概括为启动转换 - 等待转换完成 - 读取数据。我们直接从最常见的应用场景入手。2.1 基础单通道轮询采样这是最简单的模式适合对采样速率要求不高的场景比如每隔几百毫秒读取一次电池电压。// 以HAL库为例初始化一个ADC通道进行单次转换 ADC_HandleTypeDef hadc1; void ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ScanConvMode DISABLE; // 单通道禁用扫描模式 hadc1.Init.ContinuousConvMode DISABLE; // 单次转换模式 hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; // 软件触发启动 hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; // 数据右对齐 hadc1.Init.NbrOfConversion 1; // 1个转换序列 HAL_ADC_Init(hadc1); // 配置通道 ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_0; // 假设使用通道0 (PA0) sConfig.Rank 1; // 序列中的第1个 sConfig.SamplingTime ADC_SAMPLETIME_15CYCLES; // 采样时间 HAL_ADC_ConfigChannel(hadc1, sConfig); } uint16_t Read_ADC_Value(void) { HAL_ADC_Start(hadc1); // 启动转换 if (HAL_ADC_PollForConversion(hadc1, 10) HAL_OK) { // 等待转换完成超时10ms return HAL_ADC_GetValue(hadc1); // 读取转换结果 } return 0; }这段代码清晰展示了单次采样的过程。但这里有个关键细节采样时间 (SamplingTime)的配置。它决定了ADC内部的采样保持电容对输入信号充电的时间。时间太短电容电压跟不上信号变化会导致采样不准确时间太长虽然精度高但限制了总的采样速率。这个值需要根据信号源的内阻来估算。对于大多数传感器输出阻抗在几kΩ以内ADC_SAMPLETIME_15CYCLES或28CYCLES通常是安全的起点。2.2 多通道扫描与DMA搬运当需要同时采集多个模拟信号比如四轴飞行器的多个电机电流时单通道轮询的效率就太低了。这时就需要用到ADC的扫描模式和DMA。扫描模式下ADC会按照预先配置好的通道顺序Rank自动依次转换多个通道。而DMA则可以在每个通道转换完成后自动将数据搬运到指定的内存数组中形成一个“流水线”。#define ADC_CONVERTED_DATA_BUFFER_SIZE 4 // 假设采集4个通道 uint16_t adc_buffer[ADC_CONVERTED_DATA_BUFFER_SIZE]; // DMA搬运的目标数组 DMA_HandleTypeDef hdma_adc1; ADC_HandleTypeDef hadc1; void ADC1_DMA_Init(void) { // 1. 初始化DMA __HAL_RCC_DMA2_CLK_ENABLE(); // 大多数STM32的ADC1使用DMA2 hdma_adc1.Instance DMA2_Stream0; // 具体Stream和Channel需查数据手册映射表 hdma_adc1.Init.Channel DMA_CHANNEL_0; hdma_adc1.Init.Direction DMA_PERIPH_TO_MEMORY; // 方向从外设到内存 hdma_adc1.Init.PeriphInc DMA_PINC_DISABLE; // 外设地址固定ADC数据寄存器 hdma_adc1.Init.MemInc DMA_MINC_ENABLE; // 内存地址递增存入数组 hdma_adc1.Init.PeriphDataAlignment DMA_PDATAALIGN_HALFWORD; // 外设数据宽度半字16位 hdma_adc1.Init.MemDataAlignment DMA_MDATAALIGN_HALFWORD; // 内存数据宽度半字 hdma_adc1.Init.Mode DMA_CIRCULAR; // 循环模式缓冲区填满后从头开始实现连续采集 hdma_adc1.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_adc1); __HAL_LINKDMA(hadc1, DMA_Handle, hdma_adc1); // 将DMA与ADC句柄关联 // 2. 初始化ADC扫描模式连续转换 hadc1.Instance ADC1; hadc1.Init.ScanConvMode ENABLE; // 启用扫描模式 hadc1.Init.ContinuousConvMode ENABLE; // 启用连续转换模式 hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; // 仍可用软件触发但启动后便连续运行 hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 4; // 4个转换序列 HAL_ADC_Init(hadc1); // 3. 配置多个通道及其在序列中的顺序Rank ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_0; // 通道0 sConfig.Rank 1; // 序列1 sConfig.SamplingTime ADC_SAMPLETIME_15CYCLES; HAL_ADC_ConfigChannel(hadc1, sConfig); sConfig.Channel ADC_CHANNEL_1; // 通道1 sConfig.Rank 2; // 序列2 HAL_ADC_ConfigChannel(hadc1, sConfig); // ... 配置通道2为Rank3通道3为Rank4 // 4. 启动带DMA的ADC转换 HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buffer, ADC_CONVERTED_DATA_BUFFER_SIZE); }配置完成后ADC就会在DMA的配合下自动、连续地工作。adc_buffer数组的内容会不断被刷新其数据排列顺序就是按照你配置的Rank来的adc_buffer[0]是通道0的数据adc_buffer[1]是通道1的数据以此类推。CPU可以随时去读取这个数组进行后续处理而无需关心数据搬运过程。注意DMA通道和Stream的映射这是STM32 DMA配置中最容易出错的地方。ADC1、USART1_TX等每个外设的DMA请求都固定映射到特定的DMA控制器DMA1或DMA2的特定Stream和Channel上。这个映射关系必须严格参照对应型号的《参考手册》中的“DMA请求映射”表格配错了DMA就无法正常工作。例如STM32F4系列ADC1通常使用DMA2的Stream0/Channel0。2.3 高级触发与定时器联动对于需要精确采样间隔的应用比如音频采集44.1kHz软件触发或自由运行模式ContinuousConvMode ENABLE的精度和稳定性是不够的。这时就需要使用外部触发而最理想的触发源就是定时器。你可以配置一个定时器如TIM2以固定的频率比如10kHz产生一个更新事件UEV并将这个事件连接到ADC的触发输入端。这样ADC的每一次转换都由硬件定时器精确触发采样间隔的抖动极小非常适合构建数字信号处理DSP系统的前端。在CubeMX中配置这个功能非常直观在ADC参数设置里将External Trigger Conversion Source选为某个定时器的触发输出如TIM2_TRGO然后在对应定时器的配置中开启主模式输出Master Mode选择Update Event作为触发输出TRGO。在代码中你只需要启动定时器然后启动ADC它们就会自动同步工作。3. STM32 DAC实战从静态输出到DMA波形生成STM32的DAC模块相对ADC简单一些但配合DMA后能实现非常强大的功能比如播放音频、生成任意波形。3.1 基础电压输出最简单的用法就是输出一个固定的直流电压。DAC_HandleTypeDef hdac; void DAC1_Init(void) { hdac.Instance DAC; HAL_DAC_Init(hdac); DAC_ChannelConfTypeDef sConfig {0}; sConfig.DAC_Trigger DAC_TRIGGER_NONE; // 不使用硬件触发软件直接写数据 sConfig.DAC_OutputBuffer DAC_OUTPUTBUFFER_ENABLE; // 使能输出缓冲提高驱动能力 HAL_DAC_ConfigChannel(hdac, sConfig, DAC_CHANNEL_1); // 配置通道1 (PA4) } void Set_DAC_Voltage(uint16_t value) { // value的范围取决于DAC分辨率如12位DAC范围为0-4095 // 对应输出电压 (value / 4095) * Vref Vref通常为3.3V HAL_DAC_SetValue(hdac, DAC_CHANNEL_1, DAC_ALIGN_12B_R, value); HAL_DAC_Start(hdac, DAC_CHANNEL_1); // 启动DAC输出 }这种模式下你改变一次valueDAC输出就更新一次。但如果你想输出一个正弦波难道要用一个循环不断调用HAL_DAC_SetValue吗这会让CPU完全被占用而且输出频率极不稳定。此时DMA就该登场了。3.2 DMA驱动波形生成思路是预先在内存中计算好一个波形周期或数个周期的数据点数组然后让DMA自动、匀速地将这个数组中的数据“喂”给DAC。#define WAVE_BUFFER_SIZE 256 uint16_t sine_wave[WAVE_BUFFER_SIZE]; DMA_HandleTypeDef hdma_dac1; DAC_HandleTypeDef hdac; void DAC1_DMA_Wave_Init(void) { // 1. 生成正弦波表一个周期12位分辨率 for (int i 0; i WAVE_BUFFER_SIZE; i) { sine_wave[i] (uint16_t)((sin(2 * 3.1415926f * i / WAVE_BUFFER_SIZE) 1.0f) * 2047.5f); // 0-4095范围 } // 2. 初始化DMA方向内存到外设 // ... DMA初始化代码注意 Direction DMA_MEMORY_TO_PERIPH, PeriphInc DISABLE, MemInc ENABLE, Mode DMA_CIRCULAR // 3. 初始化DAC关键使用定时器触发 hdac.Instance DAC; HAL_DAC_Init(hdac); DAC_ChannelConfTypeDef sConfig {0}; sConfig.DAC_Trigger DAC_TRIGGER_T2_TRGO; // 使用TIM2作为触发源 sConfig.DAC_OutputBuffer DAC_OUTPUTBUFFER_ENABLE; HAL_DAC_ConfigChannel(hdac, sConfig, DAC_CHANNEL_1); // 4. 初始化一个定时器用于设定波形更新频率即DAC采样率 // 假设需要生成1kHz正弦波一个周期256个点则定时器频率应为 1kHz * 256 256kHz // 根据系统时钟配置定时器分频和重载值使其产生256kHz的更新事件作为TRGO输出。 // 5. 启动DMA传输 HAL_DAC_Start_DMA(hdac, DAC_CHANNEL_1, (uint32_t*)sine_wave, WAVE_BUFFER_SIZE, DAC_ALIGN_12B_R); // 6. 启动定时器 // HAL_TIM_Base_Start(htim2); }一旦定时器启动DAC就会在定时器的每个触发事件到来时自动将sine_wave数组中的下一个数据加载到输出寄存器从而在引脚上产生一个连续、平滑的正弦波。CPU只在初始化时参与波形生成过程完全由定时器和DMA硬件完成效率极高。实操心得缓冲区与波形失真使用循环DMA模式时sine_wave数组首尾的数据点必须平滑衔接否则会在波形周期切换点产生一个阶跃导致输出信号含有高频谐波失真。对于标准正弦波、三角波这很容易。如果你想动态改变波形比如软件合成音乐就需要使用双缓冲区Double Buffer技术DMA在传输缓冲区A时CPU填充缓冲区B当A传输完DMA自动切换到B同时产生一个半传输完成中断通知CPU去填充已传输完的A。HAL库的HAL_DAC_Start_DMA函数本身就支持设置双缓冲区模式只需注意数据对齐和缓冲区大小。4. DMA深度配置模式、中断与数据对齐陷阱DMA是提升系统性能的利器但配置不当也是“坑”最多的地方。除了前面提到的通道映射还有几个关键点需要深刻理解。4.1 传输模式普通模式 vs 循环模式普通模式 (DMA_NORMAL)DMA按照设定的数据量传输一次然后就停止并产生传输完成中断。适用于一次性、非连续的数据块传输比如从内存搬运一大段配置数据到SPI外设。循环模式 (DMA_CIRCULAR)DMA在传输完设定的数据量后会自动重置传输计数器从头开始下一轮传输周而复始。这是ADC连续采集和DAC波形生成的必备模式。它形成了一个“环形缓冲区”数据在缓冲区中被循环覆盖。4.2 中断的正确使用DMA提供了多种中断事件合理使用可以高效同步数据。传输完成中断 (TC)在普通模式下全部数据传完时产生在循环模式下每完成一轮一个缓冲区传输时产生。这是最常用的中断用于通知CPU“一批数据准备好了”或“缓冲区已空可以填充新数据了”。半传输中断 (HT)当传输完成一半数据量时产生。这是实现双缓冲区 (Double Buffer)技术的核心。结合循环模式你可以让DMA在前半缓冲区传输时CPU处理后半缓冲区反之亦然实现数据生产和消费的无缝衔接避免竞争。传输错误中断 (TE)在传输过程中发生错误如访问非法地址时产生用于错误处理。在HAL库中你需要先调用HAL_DMA_Start_IT()启动带中断的DMA传输然后在中断回调函数HAL_DMA_XferCpltCallback()或HAL_DMA_XferHalfCpltCallback()中编写你的处理逻辑。4.3 数据宽度与对齐的“隐形坑”这是DMA配置中最隐蔽的bug来源之一。PeriphDataAlignment和MemDataAlignment必须根据外设数据寄存器的宽度和内存中数据的实际宽度来设置。经典错误案例你想用DMA把ADC的12位数据存放在16位寄存器的低12位右对齐搬运到一个uint16_t数组中。你可能会想两边都是16位对齐方式都设为DMA_PDATAALIGN_HALFWORD和DMA_MDATAALIGN_HALFWORD总没错吧大部分时候确实可以。但是如果你错误地将ADC配置为左对齐 (ADC_DATAALIGN_LEFT)而DMA仍按半字搬运就会导致数据错位。更复杂的场景是内存到外设比如将uint8_t类型的字节数组通过DMA发送到USART的DR寄存器一个8位/16位寄存器。此时外设数据宽度应为字节 (DMA_PDATAALIGN_BYTE)内存数据宽度也应为字节 (DMA_MDATAALIGN_BYTE)。如果内存数组是uint32_t类型但你想每次发送一个字节就需要将内存数据宽度设为字节并且使能内存地址递增。DMA会从该uint32_t变量的最低字节开始搬运。黄金法则配置DMA时反复确认三点1. 外设数据寄存器的有效宽度查数据手册2. 你访问内存时使用的指针类型决定了编译器如何看待那块内存3. 你希望DMA以何种粒度搬运数据。三者必须匹配。5. 综合案例基于ADC-DMA-DAC的音频环回系统为了将ADC、DAC、DMA的知识串联起来我们设计一个简单的音频环回系统通过麦克风接入ADC采集音频信号经过一个简单的增益处理再通过耳机接口接DAC实时播放出来。这相当于一个数字化的音频直通通道。系统架构设计ADC配置使用一个音频ADC或普通ADC以44.1kHz或48kHz的采样率工作。配置为扫描模式如果有多路、连续转换模式并由一个定时器如TIM8精确触发。启用DMA方向为外设到内存循环模式目标是一个较大的双缓冲区如两个1024点的int16_t数组。DAC配置使用一个音频DAC或普通DAC同样以44.1kHz的采样率工作。由同一个定时器TIM8的另一路输出触发以确保与ADC同步。启用DMA方向为内存到外设循环模式源是另一个双缓冲区。数据处理在ADC的DMA半传输/传输完成中断中获取到刚填满的半个缓冲区。在这里可以加入数字增益调节、简单滤波等算法。处理完成后将数据复制到DAC对应的输出缓冲区中。由于ADC和DAC由同一时钟触发且缓冲区大小一致只要处理延时在一个缓冲区长度以内就能实现极低延迟的实时环回。同步核心整个系统的节拍器是那个定时器。它同时触发ADC采样和DAC更新保证了采样和回放的时钟同步避免了因时钟漂移导致的缓冲区上溢或下溢。关键代码片段示意// 定义缓冲区 #define BUFFER_SIZE 1024 int16_t adc_buffer[2][BUFFER_SIZE]; // ADC双缓冲区 int16_t dac_buffer[2][BUFFER_SIZE]; // DAC双缓冲区 volatile uint8_t adc_buffer_index 0; // 当前ADC写缓冲区索引 volatile uint8_t dac_buffer_index 0; // 当前DAC读缓冲区索引 // ADC DMA半传输完成中断回调 void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc) { // 此时adc_buffer[adc_buffer_index] 的前半部分已满 process_audio_buffer(adc_buffer[adc_buffer_index], BUFFER_SIZE/2, 0); // 将处理后的数据拷贝到DAC对应的前半缓冲区 memcpy(dac_buffer[dac_buffer_index], adc_buffer[adc_buffer_index], sizeof(int16_t) * BUFFER_SIZE/2); } // ADC DMA传输完成中断回调 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { // 此时adc_buffer[adc_buffer_index] 的后半部分已满 process_audio_buffer(adc_buffer[adc_buffer_index], BUFFER_SIZE/2, BUFFER_SIZE/2); // 将处理后的数据拷贝到DAC对应的后半缓冲区 memcpy(dac_buffer[dac_buffer_index] BUFFER_SIZE/2, adc_buffer[adc_buffer_index] BUFFER_SIZE/2, sizeof(int16_t) * BUFFER_SIZE/2); // 切换DAC缓冲区索引准备输出下一帧 dac_buffer_index (dac_buffer_index 1) % 2; // 注意ADC缓冲区的切换由DMA循环模式自动完成我们只需处理数据 } // DAC DMA传输完成中断回调用于双缓冲区切换 void HAL_DAC_ConvCpltCallbackCh1(DAC_HandleTypeDef* hdac) { // 当DAC完成一个完整缓冲区的输出后我们可以做一些状态更新或指示 }这个案例涵盖了多外设DMA协同、双缓冲区、定时器同步触发、中断处理等核心概念是检验你对ADC、DAC、DMA理解深度的绝佳试金石。在实际实现时还需要仔细计算定时器参数以满足采样率处理可能的直流偏置并注意ADC/DAC的输入输出范围匹配。6. 性能优化与常见问题排查当你的系统涉及到高速、连续的ADC/DAC数据流时性能瓶颈和奇怪的问题就会接踵而至。以下是一些实战中总结的要点。6.1 优化DMA和CPU缓存一致性在带有D-Cache数据缓存的Cortex-M7/M4等高性能STM32型号上如STM32H743DMA和CPU共同访问同一块内存区域时会存在缓存一致性问题。问题现象CPU计算出的数据DMA读到的却是旧值或者DMA刚从外设搬运来的新数据CPU读到的却是缓存中的旧数据。根本原因DMA直接访问物理内存DDR或SRAM而CPU访问的是缓存中的数据副本。两者不同步。解决方案使用非缓存内存可以通过链接脚本或MPU内存保护单元配置将DMA缓冲区所在的内存区域设置为“Non-Cacheable”或“Write-Through”。这是最简单可靠的方法但会损失该区域数据的缓存加速效益。手动维护缓存一致性在CPU写入数据后、启动DMA传输前调用SCB_CleanDCache_by_Addr()函数将缓存中已修改的数据“清洗”回主存。在DMA传输完成后、CPU读取数据前调用SCB_InvalidateDCache_by_Addr()函数使CPU缓存失效强制从主存重新加载数据。HAL库中DMA传输完成中断回调函数是进行失效操作的合适位置。6.2 ADC采样速率与系统时钟的权衡ADC的采样速率不是可以无限提高的。它受到两个主要因素限制ADC时钟 (ADCCLK)由APB2时钟分频得到必须在数据手册规定的范围内例如STM32F4通常不超过36MHz。总转换时间总转换时间 采样时间 逐次逼近转换时间固定为若干周期。采样时间越短允许的ADC时钟就可以越高理论上采样速率也越高但精度会下降。你需要根据信号频率和精度要求在数据手册的图表中找到平衡点。例如ADCCLK30MHz采样时间为3个周期12位转换时间为15个周期则一次转换总时间为(315) / 30MHz 0.6us理论最高采样率约为1.67 MSPS。但这是单通道的情况。如果是4通道扫描且使用DMA连续传输那么每个通道的采样率是1.67 / 4 ≈ 0.417 MSPS。6.3 接地、参考电压与噪声抑制模拟电路的性能很大程度上取决于PCB布局和电源质量。参考电压 (VREF)这是ADC/DAC精度的心脏。务必使用一个干净、稳定的电源作为参考电压最好使用独立的基准电压芯片如REF3033。即使使用MCU的AVDD作为VREF也要确保其经过良好的LC滤波。模拟地与数字地正确的单点接地至关重要。建议将MCU的VSSA模拟地和VSS数字地在芯片下方通过磁珠或0欧电阻单点连接并在电源入口处将模拟电源和数字电源分开滤波。采样保持电容ADC输入端对高频噪声非常敏感。在ADC输入引脚就近放置一个几十到几百皮法的小电容到地可以有效地滤除高频噪声提高采样精度。这个电容与信号源内阻共同构成了一个低通滤波器。6.4 DMA传输停滞问题排查如果DMA启动后没有数据传输可以按照以下流程排查检查时钟相关外设ADC/DAC/USART的时钟、DMA控制器的时钟、GPIO端口的时钟是否全部使能检查映射DMA的Stream和Channel是否与目标外设的请求完全对应查数据手册确认这是最高频的错误。检查触发DMA是工作在“外设到内存”还是“内存到外设”触发源是什么如果是外设触发如ADC转换完成确保该外设确实能产生DMA请求例如ADC的DMA使能位ADC_CR2.DMA是否置1。检查中断如果使用了中断NVIC中对应的DMA Stream中断是否使能中断服务函数名称是否正确有时会因Stream不同而不同检查缓冲区地址内存缓冲区的地址是否有效是否因为缓存一致性问题导致DMA访问了错误的数据使用调试器查看DMA控制寄存器 (DMA_SxCR)、数据流寄存器 (DMA_SxNDTR) 和中断标志位。NDTR寄存器会随着传输递减这是判断DMA是否在工作的最直接标志。调试DMA问题时逻辑分析仪或示波器是必不可少的工具。你可以直接测量ADC/DAC的引脚看是否有信号活动或者测量与DMA传输相关的GPIO如果可用来间接判断DMA是否在搬运数据。
返回列表