资讯详情

STM32定时器触发ADC采集:硬件触发与DMA传输实战

📅 2026/10/26 8:48:48 | 华诺云谱 👁 阅读
STM32定时器触发ADC采集:硬件触发与DMA传输实战
简介这份资源面向嵌入式开发初学者与STM32实践者围绕STM32F103的定时器触发ADC采集与串口上传链路提供一套可直接编译运行的完整工程帮助读者理解定时器周期触发、ADC通道采样与UART数据发送之间的协同关系。压缩包共189个文件、约6.62MB以c与h源码、o与d编译中间文件、crf与lst链接列表为主另含sct分散加载、uvprojx与uvoptx工程配置、hex与axf烧录产物及bat清理脚本覆盖固件库、硬件驱动、系统配置与用户代码等模块目录层次清晰便于按功能定位与二次修改。该资源已有4891人学习下载读者可从中获得定时器中断配置、ADC初始化与转换结果处理、串口协议发送的完整参考实现并借助现成工程快速验证与排错适合作为嵌入式入门练习或项目原型的基础模板。1. 定时器触发ADC采集为什么“软件轮询”在采样率上永远打不过硬件触发做STM32采集类项目最先翻车的往往不是代码写错而是采样节奏根本不受控。软件轮询ADC主循环里读一次算一次采样间隔被中断、串口打印、其他任务拉扯得忽长忽短做出来的波形看着像那么回事一算频谱全是毛刺。这套方案要解决的就是这件事用定时器的硬件更新事件去触发ADC规则组转换把“什么时候采”从CPU手里拿走交给定时器硬件决定CPU只负责在转换完成后把数据搬走、打包、发往上位机。适合正在做STM32毕设、课程设计或者要给某个传感器做稳定数据采集链路的同学和从业者。整条链路是定时器触发、ADC转换、DMA搬运、串口上传四段每一段都有明确的职责边界后面按这个顺序拆开讲。2. 需求与选型采样率、分辨率、通道数三个参数怎么定死2.1 先把采样指标翻译成硬件参数动手写代码之前必须把“我要采多快、采多准、采几路”翻译成定时器和ADC的具体配置否则后面调参数全靠试。通常的做法是先列一张指标表把需求侧的语言换成寄存器侧的语言再反推时钟树和分频系数。需求项典型取值对应硬件参数影响采样率10 kSPS定时器更新频率 10 kHz决定定时器 PSC/ARR分辨率12 位ADC 分辨率 12 bit决定量化台阶 3.3V/4096通道数14 路ADC 规则组序列长度决定 DMA 缓冲区大小数据吞吐10k×2 字节/秒串口波特率 ≥ 115200决定上传是否丢包实时性单次转换 100 µsADC 采样时间 转换时间决定采样时间档位这张表是整份方案的锚点。采样率一旦定死定时器的预分频和自动重装值就唯一确定了通道数定死DMA 的传输长度和上位机的解析格式也就定了。我一般会先把这张表填满再碰代码省得写到一半发现波特率不够、缓冲区溢出回头重来。2.2 为什么选定时器触发而不是软件触发或外部中断软件触发的问题第 1 章已经说了节奏不可控。外部中断触发看似精确但中断响应有延迟抖动而且引脚上一点毛刺就可能误触发做传感器采集时这是血泪经验。定时器触发的好处是触发源在芯片内部更新事件和ADC触发输入之间是硬件直连抖动在纳秒级CPU 忙不忙都不影响采样时刻。代价是配置链路长一点时钟使能、时基配置、触发源选择、ADC外部触发使能四步缺一不可少配一步就是“定时器在跑、ADC不动”的经典现象。2.3 时钟树与触发链路的对应关系以常见的 STM32F1 系列为例ADC 时钟来自 PCLK2 分频最高 14 MHz定时器时钟来自 APB1 或 APB2 的倍频。配置时要注意两件事一是 ADC 时钟不能超频超了转换结果会飘二是定时器更新频率要算准公式是f_update f_tim_clk / ((PSC1) × (ARR1))。比如 72 MHz 定时器时钟、要 10 kHz 更新频率取 PSC71、ARR99正好 72M/72/100 10 kHz。这个算式建议写在注释里半年后回来看还能对上号。3. 总体设计四段链路与三个缓冲区的职责划分3.1 链路分段触发、转换、搬运、上传整条数据链拆成四段每段只干一件事。定时器负责产生节拍ADC 负责把模拟量变成数字量DMA 负责在转换完成后把结果从数据寄存器搬到内存串口负责把内存里的数据打包发出去。这样分的好处是每段都能单独验证先确认定时器有更新事件再确认 ADC 有转换完成标志再确认 DMA 计数在减最后确认串口有数据出来。哪一段断了问题就锁在那一段不用整条链路一起猜。3.2 双缓冲与半满中断让搬运和上传不打架单缓冲区最怕的是“DMA 正在写、串口正在读”读到一半数据被覆盖上位机画出来的波形中间断一截。通常的做法是用双缓冲或者半满/全满中断DMA 配成循环模式缓冲区长度设为 2N半满中断时处理前半段全满中断时处理后半段处理期间 DMA 继续往另一半写。这样读写永远不碰同一块内存代价是内存多占一倍换来的是数据连续性。对于 10 kSPS 这种量级N 取 256 或 512 都够用缓冲区太小中断太频繁太大会增加延迟这个值要按实际波特率和处理耗时调。3.3 数据帧格式上位机解析的契约上传的数据不能是裸的 ADC 值得带帧头、通道号、序号和校验否则上位机没法判断丢包和错位。我一般用这样的帧结构帧头 2 字节0xAA 0x55、序号 1 字节、通道号 1 字节、数据 2 字节小端、校验 1 字节前面所有字节异或。一帧 7 字节10 kSPS 单通道就是 70 KB/s115200 波特率约 11.5 KB/s明显不够这时候要么降采样率、要么提波特率到 460800 或 921600要么做数据压缩只传变化量。这个账必须在设计阶段算清楚不然联调时上位机收到的全是残帧。4. 模块实现定时器、ADC、DMA、串口的配置与代码4.1 定时器时基配置把采样率钉死在硬件上/* TIM2 时基配置72MHz 时钟PSC71ARR99更新频率 10kHz */ void TIM2_Init_10kHz(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef tim; tim.TIM_Prescaler 71; /* 72分频计数时钟 1MHz */ tim.TIM_Period 99; /* 计满100次溢出即10kHz */ tim.TIM_CounterMode TIM_CounterMode_Up; tim.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, tim); /* 关键更新事件作为ADC外部触发源不产生中断 */ TIM_SelectOutputTrigger(TIM2, TIM_TRGO_Update); TIM_Cmd(TIM2, ENABLE); }这段代码的重点不在时基本身而在TIM_SelectOutputTrigger这一行。它把定时器的更新事件映射到 TRGO 输出ADC 的外部触发输入再接到这个 TRGO 上整条触发链路才算通。PSC 和 ARR 的取值直接对应第 2 章那张表里的 10 kHz改采样率只改这两个数别的地方不用动。注意这里没有开定时器中断触发是纯硬件行为开中断反而会引入不必要的 CPU 开销。4.2 ADC 外部触发与规则组配置/* ADC1 通道0外部触发源为 TIM2_TRGO12位分辨率 */ void ADC1_Init_ExtTrigger(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); /* 72M/6 12MHz不超14M上限 */ GPIO_InitTypeDef gpio; gpio.GPIO_Pin GPIO_Pin_0; gpio.GPIO_Mode GPIO_Mode_AIN; /* 模拟输入必须配成AIN */ GPIO_Init(GPIOA, gpio); ADC_InitTypeDef adc; adc.ADC_Mode ADC_Mode_Independent; adc.ADC_ScanConvMode DISABLE; /* 单通道不扫描 */ adc.ADC_ContinuousConvMode DISABLE; /* 关键单次转换靠触发 */ adc.ADC_ExternalTrigConv ADC_ExternalTrigConv_T2_TRGO; adc.ADC_DataAlign ADC_DataAlign_Right; adc.ADC_NbrOfChannel 1; ADC_Init(ADC1, adc); ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_ExternalTrigConvCmd(ADC1, ENABLE); /* 使能外部触发漏了这句ADC不动 */ ADC_Cmd(ADC1, ENABLE); /* ADC校准上电必须做否则首几个采样值偏差明显 */ ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); }两个容易漏的点ADC_ContinuousConvMode必须是 DISABLE否则第一次触发后 ADC 自己连续转定时器就白配了ADC_ExternalTrigConvCmd必须使能这是“定时器在跑、ADC 纹丝不动”的头号原因。采样时间选 55.5 个周期对应 12 MHz ADC 时钟约 4.6 µs加上转换时间总共不到 6 µs远小于 100 µs 的采样间隔留足了余量。校准那几行别省省了之后低两位数据基本是噪声。4.3 DMA 循环搬运与半满中断#define BUF_LEN 512 static uint16_t adc_buf[BUF_LEN]; void DMA1_Init_ADC(void) { RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitTypeDef dma; DMA_DeInit(DMA1_Channel1); dma.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; dma.DMA_MemoryBaseAddr (uint32_t)adc_buf; dma.DMA_DIR DMA_DIR_PeripheralSRC; dma.DMA_BufferSize BUF_LEN; dma.DMA_PeripheralInc DMA_PeripheralInc_Disable; dma.DMA_MemoryInc DMA_MemoryInc_Enable; dma.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; dma.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; dma.DMA_Mode DMA_Mode_Circular; /* 循环模式永不停止 */ dma.DMA_Priority DMA_Priority_High; DMA_Init(DMA1_Channel1, dma); DMA_ITConfig(DMA1_Channel1, DMA_IT_HT | DMA_IT_TC, ENABLE); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE); /* ADC到DMA的请求使能漏了DMA收不到数据 */ }循环模式加半满/全满中断就是第 3 章说的双缓冲思路的落地。DMA_BufferSize设 512半满中断在写到 256 个点时来此时前半段已满、后半段正在写处理前半段安全全满中断反过来处理后半段。ADC_DMACmd这句同样容易漏漏了现象是 DMA 计数一直是 0ADC 数据寄存器在更新但内存里没东西。中断服务函数里只做“置标志位”真正的打包和发送放到主循环避免在中断里做耗时操作把采样节奏带偏。4.4 串口打包上传与上位机解析/* 把半缓冲区打包成帧通过USART1发出帧长7字节 */ void Pack_And_Send(uint16_t *src, uint16_t count) { uint8_t frame[7]; static uint8_t seq 0; for (uint16_t i 0; i count; i) { frame[0] 0xAA; frame[1] 0x55; frame[2] seq; frame[3] 0x01; /* 通道号 */ frame[4] src[i] 0xFF; /* 小端低字节 */ frame[5] (src[i] 8) 0xFF; /* 小端高字节 */ frame[6] frame[0]^frame[1]^frame[2]^frame[3]^frame[4]^frame[5]; USART_SendBlocking(USART1, frame, 7); /* 简化写法实际可用DMA发送 */ } }上位机侧按同样的契约解析找 0xAA 0x55、读序号判断丢包、异或校验、拼出 16 位数据、按通道号分路。序号不连续就说明中间丢了帧通常是波特率不够或者发送阻塞太久。这里用阻塞发送是为了代码短实际项目里串口发送也建议上 DMA否则 512 个点打包发送的耗时会拖慢主循环间接影响下一轮处理。上位机用 Python 的 pyserial 加 matplotlib 实时画图就够用解析逻辑和这段打包逻辑严格对称改帧格式两边一起改。5. 避坑与排查五个最容易翻车的点5.1 定时器在跑ADC 一个数都不出现象是示波器看定时器引脚有波形但 DMA 计数不动、内存里全是初值。原因基本是两个ADC_ExternalTrigConvCmd没使能或者触发源选错比如选了 T2_TRGO 但实际接的是 T3_TRGO。解决方法是先查触发源枚举值和定时器编号是否对应再确认外部触发使能位最后用调试器看 ADC 的 CR2 寄存器 EXTSEL 和 EXTTRIG 位是否真的置上了。5.2 采样率对不上实际只有设定值的一半现象是配置算出来 10 kHz实测 5 kHz。原因通常是定时器时钟源搞错APB1 定时器有倍频机制PCLK1 分频不为 1 时定时器时钟是 PCLK1 的 2 倍按 PCLK1 直接算就少了一半。解决办法是查参考手册的时钟树图确认 TIMxCLK 的真实频率再代公式别拿 PCLK 硬算。5.3 数据错位波形周期性跳变现象是上位机波形每隔一段出现一个尖峰或者整体平移。原因是 DMA 缓冲区和串口读取之间没有同步读到了正在被写的区域。解决办法是老老实实上双缓冲加半满中断或者读之前先关 DMA、读完再开会丢点不推荐。另外检查DMA_MemoryInc是否使能没使能的话所有数据都写进同一个地址。5.4 串口丢帧序号不连续现象是上位机解析出的序号跳变帧数比预期少。原因是发送速度跟不上采样速度或者发送函数阻塞时间太长。解决办法是先算吞吐账采样率 × 帧长 × 8 位对比波特率留 30% 余量不够就提波特率、降采样率或者改二进制压缩帧。串口发送改 DMA 也能明显缓解。5.5 低两位数据乱跳静态输入也不稳现象是输入接固定电压采样值末两位随机跳。原因是 ADC 没校准、采样时间太短、或者模拟输入引脚配置成了普通输入而非 AIN。解决办法是上电做完整校准、采样时间提到 55.5 周期以上、确认 GPIO 模式是GPIO_Mode_AIN必要时在输入端加 RC 滤波。这属于玄学范畴但按这三步走基本能压下去。6. 验收与答辩怎么证明这条链路真的跑通了验收这件事别等到答辩前一天才想起来测。我一般把验收拆成四层每层都有可量化的判据答辩时直接拿数据说话比嘴上说“实现了”有说服力得多。第一层是触发层。用调试器或者翻转一个 GPIO确认定时器更新频率和设计值一致误差在晶振精度范围内通常 ±50 ppm 以内。判据是实测频率与理论值偏差小于 0.5%。第二层是转换层。输入一个已知电压比如用分压电阻给出 1.65 V看采样值是否落在 2048±5 的区间这一步验证 ADC 校准和参考电压是否正常。第三层是搬运层。让 DMA 跑满缓冲区检查半满和全满中断的触发次数是否与采样点数对得上判据是一分钟内中断次数误差小于 1%。第四层是上传层。上位机连续接收十分钟统计序号连续性丢帧率低于 0.1% 算合格。答辩时最容易被问的是“你怎么保证采样间隔均匀”。这时候不要空谈直接展示两样东西一是定时器触发链路的配置截图说明触发是硬件直连二是上位机记录的相邻帧时间戳算一下标准差。标准差在微秒级就说明节奏是硬件在管不是软件在凑。反过来如果时间戳标准差到了毫秒级那一定是哪里退回了软件触发或者发送阻塞当场就能定位。还有一个技巧准备一份“故障复现”演示。故意把ADC_ExternalTrigConvCmd注释掉让评委看到 ADC 不动的现象再打开恢复。这比只演示成功路径更能说明你理解每个配置项的作用也顺带把第 5 章那些坑讲清楚了。表格里那四层判据建议打印出来贴在工位上每改一次配置就重跑一遍别嫌烦。从那以后我每次做采集类项目都强制自己先把第 2 章那张指标表填满、把四层验收判据写进测试记录再动手写第一行初始化代码。顺序反了后面全是返工。希望帮到你。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑