恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
STM32定时器触发ADC DMA采样:原理、配置与避坑实践
首页
资讯中心
/
STM32定时器触发ADC DMA采样:原理、配置与避坑实践
STM32定时器触发ADC DMA采样:原理、配置与避坑实践
发布时间:2026/10/19 4:38:23
简介面向STM32F1嵌入式开发者这份压缩包聚焦TIMER3的TRGO事件触发ADC采样并通过DMA自动将转换结果存入缓存适用于需要高效、低CPU占用实现定时采集的工程场景。包内共6个文件包含3个C源文件与3个H头文件分别实现ADC、TIMER3、DMA的初始化和中断服务整体仅3KB结构精简便于直接移植学习。已有4106人学习下载配置思路和代码模块得到了较多关注。资源中给出了如init_ADC、init_TIMER3、init_DMA等初始化函数以及DMA传输完成中断处理与缓存切换的参考写法可帮助读者快速掌握外设联动配置理解TRGO触发、ADC转换结束与DMA搬运之间的时序关系节省底层调试时间。对于希望提升系统实时性、减少CPU干预的STM32开发者是一份实用的参考代码。1. 定时器触发 ADC DMA 这条链路到底解决什么问题电机控制、电网谐波分析、音频采样这类场景里CPU 给 ADC 发“开始转换”命令是最容易出错的做法——中断响应延迟在几百纳秒到几微秒之间漂移采样间隔只要抖动一次后面做 FFT 算出来的相位、谐波分量全是错的。STM32 的 TIMER3 TRGO 触发 ADC、再由 DMA 自动把结果搬进内存核心价值就是让采样时序脱离软件控制定时器溢出那一刻硬件发出触发信号ADC 在下一个时钟周期启动转换转换完成硬件拉起 DMA 请求数据落内存后 CPU 完全不知情。整个过程不需要进中断、不需要软件干预采样周期由定时器的 PSC 和 ARR 两个寄存器唯一决定。这份资源适合刚把标准库点亮的开发者也适合被采样抖动折腾过的老手——本篇就把寄存器配置、周期计算和最容易翻车的几个边界一次讲透。2. TIM3 TRGO 信号链配置先让定时器“广播”触发事件2.1 TRGO 不是随便一根输出线主模式选择位 MMS 决定事件源STM32 定时器的 TRGOTrigger Output触发输出是内部信号不经过 GPIO它的用途是把定时器内部事件“广播”给其他外设。ADC、DAC、另一个定时器都可以监听这根线。很多人第一次写代码时只配置了定时器的 PSC 和 ARR结果 ADC 纹丝不动因为根本没告诉定时器“你要把哪个事件送出去”。TRGO 的事源选择在定时器的 CR2 寄存器里对应控制位叫 MMSMaster Mode Selection。标准库封装成了TIM_SelectOutputTrigger()函数它有四个常用选项// 把 TIM3 的更新事件作为 TRGO 输出 TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); // 把 TIM3 的使能信号作为 TRGO 输出 // TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Enable); // 把 TIM3 的 OC1REF 信号作为 TRGO 输出 // TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_OC1Ref);绝大多数 ADC 定时触发场景用更新事件Update Event。所谓更新事件就是计数器向上计数溢出、准备回零重载那一刻产生的脉冲。TIM_TRGOSource_Enable 是另一个容易误用的选项——它输出的是定时器使能电平不是脉冲如果选它ADC 只会在定时器刚打开时触一次之后再也不触发。OC1Ref 选项适合要在特定比较点触发 ADC 的场景比如 PWM 中心对齐采样但这个资源里不需要保持默认的更新事件就行。配置完 MMS 之后还要注意一个细节定时器必须处于使能状态才会产生更新事件也就是TIM_Cmd(TIM3, ENABLE)不能漏。这句漏了ADC 还是完全不触发。很多入坑者在这里卡半天查了 ADC、DMA 寄存器都没问题最后发现定时器没开。2.2 采样周期计算PSC 和 ARR 不是随便填的定时器触发 ADC 的采样周期由公式T_sample (PSC 1) × (ARR 1) / TIM_CLK决定。以一个主频 72 MHz 的芯片为例TIM3 挂载在 APB1 总线上通常 TIM_CLK 也是 72 MHzAPB1 分频不为 1 时定时器时钟会翻倍。如果想得到 10 kHz 的采样率即 100 μs 触发一次 ADC有无数种 PSC/ARR 组合采样率PSCARR实际采样周期频率误差10 kHz07199100.00 μs0%10 kHz7899100.00 μs0%10 kHz7199100.00 μs0%50 kHz0143920.00 μs0%50 kHz717920.00 μs0%1 kHz719991000.00 μs0%那到底怎么选 PSC 和 ARR我的习惯是先把 ARR 压到足够小让计数器的分辨率足够高但不要小到更新事件频率超过 ADC 的转换能力。ADC 完成一次转换需要的时间是采样周期加转换周期比如采样周期 1.5 个 ADC 时钟、转换 12.5 个 ADC 时钟ADC 时钟 12 MHz 的话单次转换大约 1.17 μs。如果定时器 1 μs 就触一次ADC 根本转不完DMA 搬运就会出现丢数据。所以确定采样率之前先把 ADC 的时钟和采样周期算清楚确保T_sample 单次转换时间这是硬件链路能稳定工作的前提。代码里通常用宏定义把周期参数跟实际采样率对应起来#define TIM3_PSC 71 // 72 MHz / (711) 1 MHz 计数频率 #define TIM3_ARR 99 // 1 MHz / (991) 10 kHz 触发频率 void TIM3_Init(void) { TIM_TimeBaseInitTypeDef tim_base; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); tim_base.TIM_Prescaler TIM3_PSC; tim_base.TIM_CounterMode TIM_CounterMode_Up; tim_base.TIM_Period TIM3_ARR; tim_base.TIM_ClockDivision TIM_CKD_DIV1; tim_base.TIM_RepetitionCounter 0; TIM_TimeBaseInit(TIM3, tim_base); TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); TIM_Cmd(TIM3, ENABLE); }这段代码的关键逻辑是先把 PSC 设为 71把计数频率从 72 MHz 降到 1 MHz再去计 ARR99也就是每 100 个计数脉冲产生一次更新事件。为什么要先分频再计数因为如果 PSC0、ARR7199计数器每一步是 13.9 nsARR 寄存器里写 7199 也完全可行没有本质区别。但当你需要临时改采样率做实验时固定 PSC71、只改 ARR改动更直观也更不容易算错。TIM_RepetitionCounter 只有高级定时器才用TIM3 是通用定时器标准库里写上也不会生效但保留它是向上兼容的习惯。2.3 开启更新中断不这个场景不需要新手容易惯性思维定时器产生事件就要开中断。但在 TRGO 触发 ADC 的场景里默认不开定时器更新中断因为中断会抢占 ADC 的 DMA 传输反而引入时序抖动。那如果确实想知道定时器触发频率是否正常怎么办资源里给的做法是用一个 IO 翻转来观测不是进中断// 用于观测触发频率的调试方法PA8 翻转 void TIM3_Update_Event_Probe(void) { static uint8_t toggle 0; // 读取 SR 寄存器中的 UIF 标志 if (TIM_GetFlagStatus(TIM3, TIM_FLAG_Update) SET) { // 手动清除标志不开启中断仅供调试时查询 TIM_ClearFlag(TIM3, TIM_FLAG_Update); toggle !toggle; GPIO_WriteBit(GPIOA, GPIO_Pin_8, toggle); } }这段代码放在主循环里轮询配合示波器看 PA8 的波形周期就能确认定时器更新事件的实际频率。查询 UIF 标志后要立即手动清除因为 TRGO 输出的是脉冲信号但 UIF 标志是电平锁存的不清除的话下一次轮询仍然读到 SET测出来的频率就是错的。3. ADC 触发源与扫描模式外部触发边沿和采样周期决定数据质量3.1 把 ADC1 的触发源指向 TIM3_TRGO定时器那边把事件广播出来了ADC 这边还得“同意收听”。ADC 的控制寄存器 CR2 里有两个位EXTTRIG 和 EXTSEL。EXTTRIG 是外部触发使能位EXTSEL 选择具体是哪个外设的触发信号。每个芯片型号的 EXTSEL 映射表略有不同以常见的 F1 系列为例EXTSEL 值规则组触发源适用定时器000TIM1_CC1TIM1 比较通道 1001TIM1_CC2TIM1 比较通道 2010TIM1_CC3TIM1 比较通道 3011TIM2_CC2TIM2 比较通道 2100TIM3_TRGOTIM3 触发输出101TIM4_CC4TIM4 比较通道 4也就是说TIM3_TRGO 对应的是 EXTSEL100不是默认值。标准库初始化代码里要显式指定ADC_InitTypeDef adc_init; // 选择 TIM3_TRGO 作为规则组触发源 adc_init.ADC_ExternalTrigConv ADC_ExternalTrigConv_T3_TRGO; // 上升沿触发 adc_init.ADC_ExternalTrigConvEdge ADC_ExternalTrigConvEdge_Rising; // 关闭连续转换只靠外部触发逐个转 adc_init.ADC_ContinuousConvMode DISABLE; // 多通道扫描模式 adc_init.ADC_ScanConvMode ENABLE;这几个参数有个先后逻辑关闭连续转换是必须的因为一旦开了连续转换ADC 会自己不停地转外部触发信号根本插不进来扫描模式打开是让 ADC 在收到一次触发后把规则序列里的所有通道依次转完。如果只采一个通道扫描模式开不开都行但多通道下忘记打开 ScanConvModeADC 就只采第一个通道。ADC_ExternalTrigConvEdge 选上升沿还是下降沿跟 TRGO 的脉冲极性有关。更新事件作为 TRGO 输出时默认是一个正脉冲所以用上升沿触发。初始化顺序上还有一个隐藏依赖先配 ADC再使能 ADC最后等 ADC 稳定然后打开定时器。顺序反了会漏掉第一轮采样导致 DMA 缓冲区开头出现几个 0 值。3.2 采样周期不是越大越好从 AC 采样精度说起ADC 的采样周期SAMPLING TIME决定采样保持电容充电的时间。外部信号源阻抗越高需要的采样时间越长。对于普通的电压采样1.5 个周期通常够用但如果信号源是分压电阻网络电阻值超过 10 kΩ1.5 个周期的采样值会偏低转换结果产生幅值误差。这个误差不像非线性和偏移那样好校准——它会随输入电压变化而改变在交流采样里表现为波形削顶或幅值压缩。// ADC 1.5 cycle 采样周期 vs 239.5 cycle 采样周期 // 12 MHz ADC 时钟下 // 1.5 cycle - 125 ns 采样时间 // 239.5 cycle- 19.96 us 采样时间 void ADC_Channel_SampleTime_Config(void) { // 采样时间按通道单独配置 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_1Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_1Cycles5); // 高阻信号源通道加大采样时间 ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_239Cycles5); }这段配置里通道 0 和通道 1 都是低阻信号源1.5 周期采样足够通道 2 接的是高阻分压网络选择 239.5 周期。要注意的是加大采样时间会拉长单次转换的总耗时进而影响最大可支持的触发频率。算一下ADC 时钟 12 MHz采样 239.5 周期加转换 12.5 周期单通道总耗时 21 μs。如果定时器触发频率是 50 kHz20 μs 周期ADC 上一次还没转完下一次触发就来了DMA 会搬走上一轮的数据导致采样序列错位。遇到这种情况要么降低采样率要么换低阻抗信号源没有别的捷径。3.3 校准序列规则组通道顺序和采样率的配合多通道采集时规则组的序列寄存器SQR决定了一次触发后按什么顺序转换哪些通道。序列长度和通道顺序直接影响每次触发的时间开销。比如配置了 3 个通道一次触发后要转 3 个通道总时间就是 3 个通道的采样时间加转换时间之和这决定了最大触发频率。// 规则组序列配置3 个通道顺序 0-1-2 void ADC_RegularSequence_Config(void) { // SQR1 寄存器设置规则组序列长度为 3 ADC_InitTypeDef adc_init; adc_init.ADC_NbrOfChannel 3; // 规则组序列长度 3 adc_init.ADC_ExternalTrigConv ADC_ExternalTrigConv_T3_TRGO; adc_init.ADC_ExternalTrigConvEdge ADC_ExternalTrigConvEdge_Rising; adc_init.ADC_ContinuousConvMode DISABLE; adc_init.ADC_ScanConvMode ENABLE; adc_init.ADC_DataAlign ADC_DataAlign_Right; ADC_Init(ADC1, adc_init); // 序列 1: 通道 0 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_1Cycles5); // 序列 2: 通道 1 ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_1Cycles5); // 序列 3: 通道 2 ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_1Cycles5); }如果触发频率高于单轮序列转换总耗时规则组会出现丢转换的情况ADC 会在忙的时候把新的触发请求挂起但不会排队等直接丢弃。等定时器频率降下来数据又会恢复正常。调试时看到波形周期性少点优先怀疑这个原因不要先怀疑 DMA。4. DMA 循环存取用半传输中断做无缝双缓冲4.1 DMA 通道映射别想当然ADC 的 DMA 请求不是随便挂到哪个 DMA 通道都能响应的。标准库和参考手册里都有一张映射表ADC1 的 DMA 请求在 F1 系列上固定走 DMA1 的通道 1ADC3 则走 DMA2 的通道 0如果有 DMA2 的话。把 DMA 配置到其他通道初始化时不报错但 DMA 永远不会收到请求这是典型的“代码能跑但不干活”的问题。#define ADC_BUF_LEN 1024 uint16_t adc_buf[ADC_BUF_LEN]; // 1024 次采样结果的存放区 volatile uint8_t half_complete 0; // 半传输标志 volatile uint8_t full_complete 0; // 全传输标志 void DMA_ADC1_Init(void) { DMA_InitTypeDef dma_init; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); dma_init.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; // 外设地址ADC 数据寄存器 dma_init.DMA_MemoryBaseAddr (uint32_t)adc_buf; // 内存地址用户缓冲区 dma_init.DMA_DIR DMA_DIR_PeripheralSRC; // 外设是源读取 ADC 数据 dma_init.DMA_BufferSize ADC_BUF_LEN; // 传输次数要素 dma_init.DMA_PeripheralInc DMA_PeripheralInc_Disable; // 外设地址不递增 dma_init.DMA_MemoryInc DMA_MemoryInc_Enable; // 内存地址递增 dma_init.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; // 16 位 dma_init.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; // 16 位 dma_init.DMA_Mode DMA_Mode_Circular; // 循环模式 dma_init.DMA_Priority DMA_Priority_High; dma_init.DMA_M2M DMA_M2M_Disable; // 不是内存到内存 DMA_Init(DMA1_Channel1, dma_init); DMA_ITConfig(DMA1_Channel1, DMA_IT_TC | DMA_IT_HT, ENABLE); DMA_Cmd(DMA1_Channel1, ENABLE); }外设地址写ADC1-DR而不是某个数组名是因为 DMA 需要的是地址值。DMA_PeripheralDataSize_HalfWord对应 ADC 的 12 位数据右对齐后刚好是 16 位这里用 HalfWord 两边都对齐避免数据宽度不一致导致的高低位错乱。DMA_Mode_Circular 是循环模式跑完一轮自动从首地址重新开始整个缓冲区的写入不会中断。M2M 必须禁用因为这是外设到内存的传输不是内存到内存拷贝M2M 模式下 DMA 会忽略触发请求让 DMA 误以为是一次性内存拷贝ADC 的数据就不会再进来。4.2 用半传输中断替代双缓冲读写分离的关键DMA 循环模式配好了但 CPU 读数据时不能直接去数组里拿——如果你刚好读到一半下一次 DMA 写入已经把另外一半覆盖了数据就是旧一半混新一半。常见做法是用双缓冲但 STM32 的 DMA 还可以用半传输中断 循环模式实现更简洁的双缓冲效果。当 DMA 传输完一半缓冲区512 个 16 位数据时触发 HT 中断CPU 去处理前一半传输完整个缓冲区时触发 TC 中断CPU 去处理后一半。因为 DMA 始终在循环CPU 处理前半段时 DMA 正在写后半段处理后半段时 DMA 又从头开始写前半段两边互不干扰。void DMA1_Channel1_IRQHandler(void) { if (DMA_GetITStatus(DMA1_IT_HT1)) // 半传输中断 { DMA_ClearITPendingBit(DMA1_IT_HT1); half_complete 1; // 置标志主循环里处理 } if (DMA_GetITStatus(DMA1_IT_TC1)) // 全传输中断 { DMA_ClearITPendingBit(DMA1_IT_TC1); full_complete 1; } }中断服务函数里不直接做数据处理只置标志位数据处理放在主循环里。为什么因为 DMA 中断里做 FFT、串口打印这类耗时操作会延长中断占用时间下一次 DMA 中断就进不来了严重时直接进 HardFault。主循环里通过标志位轮流访问缓冲区的前后两半while (1) { if (half_complete) { half_complete 0; // 处理 adc_buf[0] ~ adc_buf[511] Process_ADC_Half(adc_buf, 512); } if (full_complete) { full_complete 0; // 处理 adc_buf[512] ~ adc_buf[1023] Process_ADC_Half(adc_buf[512], 512); } }这里有一个隐含的边界问题全传输中断标志置位后DMA 不会立刻从头开始写而是继续写完后才重新回到首地址。所以full_complete 1的时刻缓冲区后半段的数据是完整的前半段正要被覆盖。主循环处理后半段时DMA 正在写前半段——这个时序刚好错开。如果主循环太慢处理 512 个点的时间超过 DMA 写满另一半的时间取决于采样率数据还是会被覆盖。解决办法是缩小半区大小或者用更大的 DMA 缓冲区。这个权衡没有绝对标准只能按采样率和主循环耗时来调节。4.3 ADC 使能 DMA 请求需要单独的一行ADC 的 DMA 请求不是配好 DMA 通道就能自动使能的还要在 ADC 的 CR2 寄存器里打开 ADC DMA 使能位。标准库对应的函数是ADC_DMACmd(ADC1, ENABLE)。漏掉这一句DMA 配置写得再完整也收不到数据。void ADC1_Init_For_DMA(void) { // ... 前述 ADC 初始化代码 ... ADC_DMACmd(ADC1, ENABLE); // 开启 ADC 的 DMA 请求输出 ADC_Cmd(ADC1, ENABLE); // 使能 ADC // 校准等待校准完成确保 ADC 内部电容状态正确 ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); }使能 ADC 之后不要立刻启动定时器先做校准。校准期间如果定时器触发已经开始ADC 会忙于校准而忽略外部触发第一轮采样数据全部丢失。这个顺序问题在资源给出的源码里是单独成章节的因为它太容易被忽略了。校准结束后再TIM_Cmd(TIM3, ENABLE)这样第一个触发脉冲进来时ADC 已经处于完全就绪状态。5. 避坑排查定时器触发 ADC DMA 的五个典型翻车现场5.1 现象一ADC 完全不转换DMA 缓冲区全是 0按顺序排查先查定时器有没有产生更新事件用示波器量 PA8 翻转波形再查 MMS 是否设置为 Update 事件然后查 ADC 的 EXTSEL确认ADC_ExternalTrigConv真的是ADC_ExternalTrigConv_T3_TRGO最后查 ADC 的 DMA 请求有没有使能。这个链路任何一环断了都是静默失败因为寄存器全是可读的但功能就是不走。原因里最常见的是 EXTSEL 用默认值默认值对应 TIM1_CC1ADC 在等一个永远不来的信号。解决显式将ADC_ExternalTrigConvEdge设置为上升沿并将ADC_ExternalTrigConv设置为T3_TRGO一个都不能省。我见过有人在标准库里把 edge 参数漏掉结果 ADC 配置函数直接用了库默认值触发信号来了也当没看见。5.2 现象二DMA 数据每隔固定长度错位出现周期性的跳变点数据不是每次都错而是每 N 个点错一次且 N 与 ADC 通道数或规则组序列长度有关。根因基本是单次触发的转换总时间超过了定时器触发周期。比如序列长度 3、每个通道 1.5 周期采样但定时器周期只比序列总时间短那么几百纳秒偶尔一次触发落在 ADC 忙着的窗口里被丢弃后续所有序列都往后挪一个位置。解决一是加长定时器触发周期优先把 ARR 往上调二是缩短 ADC 采样周期把 1.5 周期改成 1.5 周期以外更大的值时要重新核算三是减少规则组通道数量。排查办法是在数据起始位置放一个已知的基准电压比如通道 0 接 1.5 V 参考检查数据流里基准值出现的间隔是否与规则组长度一致。5.3 现象三上半区数据正常下半区数据全 0这是半传输中断 循环模式的经典坑。全传输中断触发后CPU 去处理后半段但 DMA 可能在中断还没执行完时已经重新从首地址开始写。主循环处理后半段的速度赶不上 DMA 写前半段的速度等 CPU 去读后半段里面已经被新数据部分覆盖。解决把 DMA 缓冲区加大一倍或者把半区的点数量减少。另一个思路是关闭循环模式改用普通模式每次 ADC 数据满缓冲区就产生 TC 中断在中断里重新启动 DMA缓冲区地址不变。但这样会损失循环模式的零开销优势更推荐的做法是调高 DMA 中断优先级数据处理主循环里不要做阻塞操作只有 Copy 操作计算、打印全部放到另一层。5.4 现象四ADC 数据整体偏小或波形削顶信号源阻抗过高、采样时间不够时采样电容还没充满就被切断了12 位 ADC 读到的值比实际输入低。这一条容易被误判成分压电阻算错了。解决提高该通道的采样时间把ADC_SampleTime_1Cycles5改为ADC_SampleTime_71Cycles5或ADC_SampleTime_239Cycles5。改完采样时间后要重新核算最大触发频率防止转化时间超限。5.5 现象五HAL 库环境下启动顺序不对数据总是丢前几帧标准库的初始化时序相对可控但使用 HAL 库时HAL_ADC_Start_DMA()和HAL_TIM_Base_Start()的先后顺序不同行为差异明显。先启动定时器再启动 ADC DMA会漏掉早期触发先启动 ADC 再启动定时器又可能因为 ADC 未校准而丢数据。解决强制按“ADC 校准完成 → ADC DMA 启动 → 定时器启动”的顺序执行。这个顺序在 HAL 里没有文档明确写但调过的都知道它是血泪经验。6. 快速验证这套链路的两个技巧先说一个最直接的验证技巧把定时器的更新事件同时配置为输出比较翻转用逻辑分析仪直接量触发频率。具体做法是在 TIM3 的更新事件产生时同步翻转一个 GPIO这不需要额外中断只要把同一个更新事件映射到 TRGO 的同时再配置一个输出比较通道让它也在更新事件时翻转。代码里就多几行TIM_OCInitTypeDef tim_oc; tim_oc.TIM_OutputState TIM_OutputState_Enable; tim_oc.TIM_Pulse 50; // 占空比设定无关紧要 tim_oc.TIM_OCMode TIM_OCMode_Toggle; TIM_OC1Init(TIM3, tim_oc);然后示波器探头夹在 GPIO 上数脉冲波形就能验证实际触发频率和理论值差多少。这个方法比进中断打印时间戳要干净得多不会影响采样时序。第二个技巧是验证 DMA 数据链路的完整性在 ADC 输入引脚接一个已知频率的方波或正弦波比如用另一个定时器输出 PWM 通过低通滤波后灌进 ADC 输入采集完在串口或屏幕上观察波形。如果波形上的采样点数等于定时器频率除以信号频率链路就是通的如果点数偏少或波形出现重影排查方向就锁定在 ADC 规则组序列长度和 DMA 缓冲区的边界对齐上。我一般会用 1 kHz 正弦波和 10 kHz 采样率组合一个周期 10 个点看起来特别清晰。从那以后我每次做定时器触发 ADC 的工程都强制先过一遍这个验证流程先量定时器波形再灌已知信号看采集结果两步都通过才继续往下做算法。这套流程救过我好几次——有一次就是示波器波形完全正常但采集到的正弦波周期点数偏了一个后来查出来是规则组序列长度配置多了 1 个通道导致每轮数据里混进了一个不存在的通道值。希望帮到你。本文还有配套的精品资源点击获取