基于STM32F103C8T6的简易示波器设计:ADC+DMA采样与触发显示
发布时间:2026/11/4 8:27:38 锦皓数字建站

简介这份资源是面向嵌入式初学者与电子竞赛备赛者的STM32F103C8T6简易示波器完整工程Demo围绕ADC采样、定时器触发、串口通信与波形显示等核心环节帮助读者理解如何用Cortex-M3内核微控制器搭建一套可运行的数字示波器原型适合具备一定C语言与单片机基础、希望从理论走向实操的学习者。压缩包共298个文件约8.56MB以c与h源码、Keil工程文件uvprojx、uvoptx、编译产物o、d、axf、hex及少量bat脚本、htm说明为主覆盖从工程配置到固件烧录的完整链路。目前已有2916人学习下载说明该案例在同类项目中具有较高参考价值。读者可从中获得可直接编译运行的工程框架、ADC与定时器协同采样的实现思路、串口数据传输与上位机显示的处理逻辑以及硬件电路与调试排错的相关线索便于对照学习并二次开发。1. 需求先立住为什么用 STM32F103C8T6 做一台“够用”的简易示波器手上只有一块最小系统板、一块 TFT 屏和几根杜邦线却要交出一台能测波形、能调时基、能看幅值的示波器这是很多嵌入式课设和毕设里最真实的起点。选 STM32F103C8T6 不是因为它强恰恰是因为它“刚好够”72MHz 主频、12 位 ADC、多路定时器加 DMA配合一块 320×240 的并口或 SPI 屏就能把采样、触发、显示这条链路跑通。这套简易示波器 Demo 要解决的核心问题是在低成本 MCU 上把“采样—存储—触发—绘制”做成一条稳定闭环适合正在做嵌入式课程设计、想理解 ADC 与定时器协同、又不想被复杂模拟前端拖住的人。先把需求边界划清楚后面所有设计才有落点。2. 总体方案设计采样链路、触发机制与显示刷新怎么分工2.1 从信号到像素的四段式结构一台简易示波器拆开看无非四件事把模拟电压变成数字、按固定节奏存进缓冲区、找到波形起点、把缓冲区画到屏幕上。STM32F103C8T6 的资源决定了这四段不能互相抢时间所以结构上我一般按“定时器触发 ADC、DMA 搬运、主循环做触发判断与绘制”来分。定时器负责节拍ADC 负责转换DMA 负责把转换结果悄悄塞进内存CPU 只在缓冲区填满后介入。这样采样期间 CPU 几乎不参与留给屏幕刷新的时间才够用。具体分工如下TIM2 或 TIM3 作为采样时钟源通过更新事件触发 ADC1 的规则通道转换ADC1 配置为连续转换加 DMA 循环模式把结果写入一个长度为 N 的uint16_t缓冲区主循环检测半满或全满标志后做软件触发判断再把一段数据映射成屏幕坐标。这里的关键认知是采样率由定时器决定不由 ADC 自己决定这是后面时基调节能成立的前提。2.2 采样率与时基的换算关系时基旋钮背后其实是一个除法。假设定时器时钟 72MHz预分频PSC、自动重装ARR则采样率fs 72MHz / ((PSC1)*(ARR1))。屏幕上横向 240 个像素点若一屏显示T个采样点则每格时间 T / fs / 格数。我通常把一屏固定显示 240 点通过改ARR来切换时基档位这样绘制逻辑不用动只换采样节奏。时基档位采样率 fsARR 典型值适用场景1ms/div约 10kSPS7199低频信号、电源纹波100us/div约 100kSPS719音频、PWM 观察10us/div约 1MSPS71中频方波、脉冲1us/div受 ADC 上限约束需评估仅作示意注意混叠提示STM32F103 的 ADC 最高采样率约 1MSPS采样时间取最短时再往上调 ARR 只会让转换来不及完成读到的值是上一次的残留波形会“粘住”不动这是新手最容易误判为程序卡死的地方。2.3 触发方式的选择为什么先做上升沿软件触发硬件触发需要比较器和外部中断配合对 Demo 来说成本偏高。我一般先做软件触发在 DMA 缓冲区里从前往后找第一个满足“前一点低于阈值、当前点高于阈值”的位置把它作为波形起点从该点开始取 240 点送显示。这样每次刷新波形都从同一个相位开始屏幕上才不会左右乱跑。阈值默认取缓冲区中值也可以做成可调。等这套跑稳了再考虑加迟滞比较避免噪声导致触发点抖动。3. 关键模块实现ADCDMA 采样、触发定位与屏幕绘制3.1 ADC 与 DMA 的初始化配置采样链路的初始化顺序不能乱先开时钟再配 GPIO 为模拟输入然后配 ADC 和 DMA最后配定时器并启动。下面这段是核心配置省略了 GPIO 时钟使能等样板代码重点看 ADC 与 DMA 的绑定关系。// ADC1 DMA1_Channel1 循环采样TIM3 更新事件触发 void adc_dma_init(uint16_t *buf, uint32_t len) { RCC-APB2ENR | RCC_APB2ENR_ADC1EN; RCC-AHBENR | RCC_AHBENR_DMA1EN; // PA0 模拟输入 GPIOA-CRL ~(0xF 0); DMA1_Channel1-CPAR (uint32_t)ADC1-DR; // 外设地址ADC 数据寄存器 DMA1_Channel1-CMAR (uint32_t)buf; // 内存地址采样缓冲区 DMA1_Channel1-CNDTR len; // 传输数量 DMA1_Channel1-CCR DMA_CCR_MSIZE_0 // 内存 16 位 | DMA_CCR_PSIZE_0 // 外设 16 位 | DMA_CCR_MINC // 内存地址自增 | DMA_CCR_CIRC; // 循环模式缓冲区自动回卷 DMA1_Channel1-CCR | DMA_CCR_EN; ADC1-CR1 ADC_CR1_SCAN; // 单通道也开扫描位便于扩展 ADC1-CR2 ADC_CR2_CONT // 连续转换 | ADC_CR2_DMA // 使能 DMA | ADC_CR2_EXTSEL_1 | ADC_CR2_EXTSEL_0 // TIM3 TRGO 触发 | ADC_CR2_EXTTRIG; // 允许外部触发 ADC1-SMPR2 ADC_SMPR2_SMP0_2; // 通道 0 采样时间 41.5 周期 ADC1-SQR1 0; // 规则序列长度 1 ADC1-SQR3 0; // 第一个转换通道为 0 ADC1-CR2 | ADC_CR2_ADON; // 开启 ADC }这段代码里有两个容易忽略的点。第一DMA_CCR_CIRC让缓冲区循环回卷主循环永远能读到最近len个点不需要手动重启 DMA。第二ADC_CR2_EXTSEL选的是 TIM3 的 TRGO 事件所以定时器必须配置成“更新事件产生 TRGO”否则 ADC 收不到触发DMA 一个数都不会搬。采样时间SMP0_2对应 41.5 周期是为了让高阻抗信号源有足够建立时间如果信号源阻抗低可以改短以换取更高采样率。3.2 定时器触发节奏的配置定时器这边只做一件事按设定频率产生更新事件并把它映射到 TRGO。下面以 TIM3 为例arr由时基档位查表得到。void tim3_trigger_init(uint16_t psc, uint16_t arr) { RCC-APB1ENR | RCC_APB1ENR_TIM3EN; TIM3-PSC psc; TIM3-ARR arr; TIM3-CR2 TIM_CR2_MMS_1; // 更新事件作为 TRGO 输出 TIM3-CR1 TIM_CR1_CEN; // 启动定时器 }MMS_1这个位组合的含义是“更新事件映射到 TRGO”是 ADC 外部触发能工作的关键。很多人把定时器配好了、ADC 也开了波形却是一条直线问题往往就出在CR2没设TRGO 一直是低电平。参数上psc一般固定为 71把定时器时钟降到 1MHz这样arr直接就是微秒数换算时基时心算就能完成调试阶段省很多事。3.3 触发点定位与波形绘制缓冲区填满后主循环先找触发点再把 240 个点映射到屏幕。映射时要注意 ADC 是 12 位0~4095屏幕纵向只有 240 像素左右必须做缩放并且要留出上下边距否则波峰波谷会贴边被裁掉。// 在 buf 中寻找上升沿触发点返回起始索引找不到则返回 0 uint32_t find_trigger(uint16_t *buf, uint32_t len, uint16_t thresh) { for (uint32_t i 1; i len; i) { if (buf[i-1] thresh buf[i] thresh) { return i; } } return 0; // 未触发从头画屏幕会滚动属正常现象 } // 把 240 个采样点画到屏幕y 轴做缩放并留边距 void draw_wave(uint16_t *buf, uint32_t start, uint32_t n) { for (uint32_t x 0; x n; x) { uint16_t v buf[(start x) % BUF_LEN]; // 4095 - 屏高 200上下各留 20 像素 uint16_t y 220 - (uint32_t)v * 200 / 4095; lcd_draw_point(x, y, COLOR_GREEN); } }find_trigger返回的是缓冲区内的绝对索引绘制时用取模回卷保证起点靠近缓冲区尾部时也能连续取点。draw_wave里的220 - ...是因为屏幕坐标原点在左上角电压越高 y 越小这个减法别写反写反了波形会上下颠倒看起来像“信号被翻转”其实是坐标映射错了。绘制前记得清屏或只清波形区域全屏清会导致明显闪烁我一般只清上一帧波形覆盖的矩形区域。3.4 幅值与频率的粗略测量Demo 阶段不必上 FFT用最朴素的方法就够交差幅值取一屏内的最大值减最小值换算成电压V adc * 3.3 / 4095频率则通过统计两次上升沿触发点之间的采样点数用f fs / 间隔点数估算。这两个值显示在屏幕角落答辩时能直接指着说“这里读出来是 1kHz、1.6V 峰峰值”。要注意频率测量只在信号周期明显大于采样间隔时可信接近奈奎斯特频率时读数会严重偏离这一点在报告里写清楚反而是加分项。4. 避坑与排查这类 Demo 最容易翻车的五个地方4.1 波形是一条直线或完全不动现象是屏幕上有网格但波形贴在某一条水平线上调时基也没反应。原因通常有两个ADC 根本没被触发或者 DMA 没搬数据。排查顺序是先看ADC1-DR在调试器里是否变化不变则查 TIM3 的CR2是否设了MMS若DR在变但缓冲区不变查 DMA 通道是否使能、CNDTR是否在递减。解决方法是把触发链路逐级点亮先手动软件触发一次 ADC 确认转换正常再开定时器触发最后接 DMA一级一级排比盯着整段代码猜快得多。4.2 波形左右乱跑、无法稳定现象是每次刷新波形起点都不一样像在“游走”。原因是没做触发或者触发阈值设在了噪声区。解决方法是启用find_trigger并把阈值设到信号幅度的中间偏上位置如果信号本身噪声大加一点迟滞——要求连续两个点都高于阈值才算触发能明显压住抖动。这一步做完波形“定住”的效果立竿见影是答辩时最直观的改进点。4.3 调高采样率后波形失真或幅度变小现象是时基往快档调波形幅度反而缩水、形状变尖。原因是 ADC 采样时间设得太短信号源内阻加采样电容还没建立到位就采样了。解决方法是把SMPR2的采样周期调长或者降低信号源阻抗。这里有个血泪经验示波器前端如果直接接信号源而没有缓冲运放高阻信号在快档下必然失真报告里如实写明“本 Demo 未加模拟前端快档精度受限”比硬说“全档位精确”更让人信服。4.4 屏幕刷新慢、波形闪烁严重现象是波形一帧一帧地跳肉眼可见闪烁。原因是每帧全屏清屏加逐点绘制SPI 屏带宽被吃满。解决方法是只清波形区域、用批量写像素代替单点写、把绘制放在 DMA 传输的空档期做。如果屏幕支持把波形画到一块内存显存再整块刷效果更好。这个优化不改变功能但直接决定演示时“像不像一台仪器”。4.5 缓冲区越界导致偶发死机现象是运行一段时间后程序跑飞或屏幕花屏。原因是触发点靠近缓冲区末尾时start x越界访问。解决方法是所有取点统一走% BUF_LEN回卷并且缓冲区长度取 2 的幂让编译器把取模优化成位与。这个坑很隐蔽因为越界读不一定立刻崩往往在演示到一半时才发作提前用取模封死最省心。5. 验收与答辩怎么证明这台简易示波器“真的能用”验收不要一上来就讲代码结构先让仪器自己说话。我一般准备三样东西一个已知频率和幅值的信号源函数发生器或另一块 MCU 的 PWM 加 RC 滤波、一张对照表、一段录屏。演示时先接 1kHz 方波调时基到能看见两三个周期指出屏幕上的幅值读数和频率读数再和信号源设定值对照误差在可接受范围内就算通过。接着换一个低频正弦展示触发稳定后波形不漂移。最后故意把时基调到最快档说明精度边界在哪里——主动交代局限比被问出来强。验收标准可以按下面这张表逐项打勾答辩时照着念都行验收项通过标准验证方法采样链路缓冲区数据随输入变化调试器观察 buf 内容触发稳定波形起点固定不左右游走连续观察 10 秒时基切换至少 3 档可切换且波形随之伸缩逐档演示幅值测量与信号源设定值误差在 10% 内对照读数频率测量中低频段误差在 10% 内对照读数长时间运行连续运行 10 分钟不死机不花屏计时观察答辩时最容易被追问的是“采样率和时基怎么对应”“触发是怎么实现的”“精度受什么限制”。前两个问题用第 2、3 章的公式和代码就能答第三个问题恰恰是展示工程判断力的地方把 ADC 采样时间、信号源阻抗、无模拟前端这三条讲清楚评委反而会觉得你懂边界。还有一个小技巧把find_trigger的阈值做成一个可调变量现场用按键改阈值演示“触发点从抖动到稳定”的过程比任何 PPT 都有说服力。从那以后我每次带这类采样加显示的 Demo都强制自己先把“定时器触发 ADC、DMA 搬运、软件触发定位”这三段分别用调试器点亮一遍再合起来跑因为合起来出问题时你根本分不清是哪一段在骗你。希望这套结构能帮到你照着搭一遍再按自己的屏幕和信号源改参数交一份能自圆其说的方案不难。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。