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

简介这份资源是面向嵌入式初学者与电子竞赛备赛者的STM32F103C8T6简易示波器完整工程Demo围绕ADC采样、定时器触发、串口通信与波形显示等核心环节展开可帮助读者理解从模拟信号采集到上位机绘制的完整链路适合具备一定C语言与单片机基础、希望动手实践信号采集项目的开发者。压缩包共298个文件约8.56MB以c与h源码、Keil工程文件uvprojx、uvoptx、编译中间文件o、d、crf及hex、axf固件为主另含少量bat脚本与htm说明工程结构完整、可直接导入IDE编译调试。目前已有2916人学习下载说明该案例在同类练手项目中认可度较高。资源涵盖ADC多通道配置、定时器触发采样、UART数据传输、滤波与峰值检测等知识点并附硬件电路与调试思路读者可据此复现波形采集流程、对照源码理解各外设协同方式并在此基础上扩展采样率与显示界面是学习STM32外设综合应用的实用参考。1. 需求先立住为什么用 STM32F103C8T6 做一台“够用”的简易示波器很多人第一次接触示波器是被实验室那台几万块的台式机劝退的——体积大、菜单深、探头一接就懵。但真正做嵌入式项目时你需要的往往不是 200MHz 带宽和 8 位垂直分辨率而是“这个引脚上到底有没有波形、频率大概多少、占空比对不对”。基于 STM32F103C8T6 的简易示波器 Demo解决的正是这个场景一块十几块钱的核心板加上少量外围做成一台能看 010kHz 左右信号、能测频率和峰峰值、能在小屏上画出波形的手持小工具。它适合正在做课程设计、毕业设计的学生也适合想给自家调试台添一台“随手抓波形”工具的从业者。整套方案的核心不在堆性能而在把采样、触发、显示、测量这条链路讲清楚、跑通、能验收。2. 总体方案设计采样链路、触发机制与显示刷新怎么分工2.1 从信号到像素一条必须想清楚的链路简易示波器的本质是一条数据流水线模拟信号进来经过调理和偏置送进 ADC 采样采样数据进缓冲区按触发条件找到波形起点再缩放映射到屏幕坐标最后刷到显示屏上。STM32F103C8T6 的资源很有限——72MHz 主频、64KB Flash、20KB SRAM、2 个 12 位 ADC、DMA 可用。这意味着你不能像台式示波器那样做深存储和复杂后处理必须把每一份 SRAM 都花在刀刃上。我一般会把整条链路拆成四个职责明确的模块输入调理负责把 -5V5V 的信号抬到 03.3V 的 ADC 输入范围采样控制负责用定时器触发 ADC、用 DMA 搬运保证采样率稳定触发与缓冲负责在环形缓冲里找上升沿或下降沿确定一帧波形的起点显示与测量负责把缓冲区数据映射成像素点同时算频率、峰峰值、平均值。四个模块之间只通过缓冲区指针和几个全局状态变量交互边界清楚调试时能单独把某一环拉出来看。这里有个反直觉的选型点很多人第一反应是用 ADC 连续转换模式加中断觉得“中断响应快”。但在 72MHz 的 F103 上ADC 采样率一旦上到几百 kHz中断频率就跟着上去了CPU 大量时间耗在进出中断上显示刷新直接卡死。正确做法是定时器触发 DMA 循环搬运CPU 只在半满/全满中断里处理数据这是这套方案能跑起来的前提。2.2 采样率与存储深度的取舍采样率决定了你能看多高频率的信号。按奈奎斯特定理要还原一个正弦波至少需要 2 倍采样率但实际显示要好看、测量要准通常取 10 倍以上。F103 的 ADC 在 12 位模式下配合合理的采样时间做到 1Msps 左右是可行的。假设我们目标看 10kHz 信号取 100ksps 采样率一屏显示 2 个周期那么一帧需要 20 个采样点——太少波形会很粗糙。所以实际会取更高采样率、更多点比如 200ksps、每帧 512 点这样一屏时间窗约 2.56ms能覆盖 10kHz 信号的 25 个周期左右显示密度足够。存储深度直接受 SRAM 限制。20KB SRAM 里显示缓冲、栈、全局变量都要占留给采样缓冲的通常 48KB。用 uint16_t 存 12 位 ADC 值8KB 就是 4096 个点做双缓冲一帧采集、一帧显示就是每缓冲 2048 点。这个深度对简易示波器够用但别指望做深存储和缩放查看那是另一套架构。参数取值说明主控STM32F103C8T672MHz64KB Flash20KB SRAMADC 分辨率12 位理论 4096 级实际有效位略低目标采样率100200ksps定时器触发DMA 搬运单帧点数5121024受 SRAM 与刷新率共同约束输入范围03.3V调理后原始信号需偏置与限幅显示128×64 OLED 或 240×320 TFT按刷新率与成本选测量项频率、峰峰值、平均值基于缓冲区统计2.3 触发机制没有触发波形就会“跑”新手最容易翻车的地方是波形在屏幕上左右乱窜根本定不住。原因是没有触发。示波器的触发本质是“从缓冲区的哪个位置开始画”。做法是在环形缓冲里搜索满足条件的点比如电压从低于中值跳到高于中值上升沿触发找到后把该点作为一帧的起点后续固定从这个偏移开始映射到屏幕。这样每次刷新画的是同一相位波形就稳了。触发电平通常设成可调默认取 ADC 量程中点。触发搜索要加超时如果一帧内找不到触发点就强制用缓冲区起点避免屏幕空白。这个细节在验收时经常被问到——“信号很小时为什么还能显示”答案就是自动触发兜底。/* 在环形缓冲区中搜索上升沿触发点返回帧起始索引 */ uint16_t find_trigger(uint16_t *buf, uint16_t len, uint16_t level) { for (uint16_t i 1; i len; i) { /* 前一采样点低于触发电平当前点高于等于判定为上升沿 */ if (buf[i - 1] level buf[i] level) { return i; } } return 0; /* 未找到触发点退回缓冲区起点保证有波形可显示 */ }这段代码是触发模块的核心参数level就是触发电平通常由按键调节或固定为 204812 位中点。len是一帧搜索长度一般取半个缓冲区留出余量避免越界。返回的索引直接作为显示映射的起点。注意这里用的是“前一点低于、当前点不低于”的判定而不是简单比较大小否则在噪声下会频繁误触发。3. 关键模块实现ADC 采样、波形映射与测量算法3.1 定时器触发 ADC DMA 的配置要点采样链路的稳定性全靠这套配置。思路是用一个定时器比如 TIM2产生固定频率的更新事件作为 ADC 的外部触发源ADC 配置为外部触发、连续转换关闭、DMA 循环模式DMA 把每次转换结果搬进缓冲区搬满半缓冲和全缓冲各产生一次中断。这样采样率由定时器频率精确决定CPU 不参与单点搬运。/* TIM2 配置为 ADC 触发源假设系统时钟 72MHz目标 200ksps */ void tim2_adc_trigger_init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); TIM_TimeBaseInitTypeDef tim; /* 72MHz / (0 1) / (359 1) 200kHz 更新频率 */ tim.TIM_Prescaler 0; tim.TIM_Period 359; tim.TIM_CounterMode TIM_CounterMode_Up; tim.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM2, tim); /* 更新事件作为 TRGO 输出供 ADC 外部触发使用 */ TIM_SelectOutputTrigger(TIM2, TIM_TRGO_Update); TIM_Cmd(TIM2, ENABLE); }参数说明TIM_Prescaler是预分频TIM_Period是自动重装值两者共同决定更新频率。72MHz 除以 360 得 200kHz正好对应 200ksps 采样率。改采样率就改这两个值但要注意 ADC 本身有最小转换时间限制12 位模式下采样时间加转换时间不能超过触发间隔否则会丢点。这是踩坑高发区后面避坑章节会细说。ADC 侧的关键配置是ADC_ExternalTrigConv_T2_TRGODMA 侧用DMA_Mode_Circular缓冲区大小设为 2 的幂次方便索引。半满中断里可以处理前半段数据做显示全满中断处理后半段形成流水避免等整帧采完才刷新。3.2 波形映射从 ADC 值到屏幕坐标映射看着简单做不好就出现波形上下跳动、幅度不准。核心公式是屏幕 Y 坐标 屏幕高度 - (ADC 值 - 偏移) × 垂直缩放系数。偏移用来把波形垂直居中缩放系数决定每格代表多少电压。因为屏幕分辨率有限比如 64 像素高12 位 ADC 的 4096 级必须压缩压缩比要可调否则小信号看起来就是一条直线。/* 将 ADC 采样值映射为屏幕 Y 坐标screen_h 为屏幕高度 */ uint8_t adc_to_pixel(uint16_t adc_val, uint8_t screen_h, uint16_t offset, uint8_t v_scale) { int32_t centered (int32_t)adc_val - (int32_t)offset; /* 垂直居中 */ int32_t scaled centered * v_scale / 16; /* v_scale 为缩放档位 */ int32_t y (screen_h / 2) - scaled; /* 屏幕坐标向下为正 */ if (y 0) y 0; /* 限幅防止越界写显存 */ if (y screen_h - 1) y screen_h - 1; return (uint8_t)y; }offset一般取 2048v_scale是档位系数16 表示 1:1 映射数值越大波形越“高”。限幅那两行不能省否则信号超范围时会写到显存外面轻则花屏重则 HardFault。显示时把相邻两个采样点的像素用直线连起来比只画点好看得多这也是简易示波器“看起来专业”的一个小技巧。3.3 频率与峰峰值测量别用浮点用计数测量算法要在有限算力下算准。频率测量有两种思路一是对缓冲区做零交叉计数统计单位时间内的过零次数二是用输入捕获测周期。简易 Demo 里前者更省事因为数据已经在缓冲区里了。做法是找上升沿触发点后统计一帧内过中值的次数除以时间窗得到频率。峰峰值就是缓冲区最大值减最小值再按分压比换算成电压。/* 基于缓冲区统计频率与峰峰值sample_rate 单位 Hz */ void measure_wave(uint16_t *buf, uint16_t len, uint32_t sample_rate, float *freq, float *vpp, float vref) { uint16_t vmax 0, vmin 4095; uint16_t cross 0; uint16_t mid 2048; for (uint16_t i 1; i len; i) { if (buf[i] vmax) vmax buf[i]; if (buf[i] vmin) vmin buf[i]; /* 统计上升过中点次数即周期数 */ if (buf[i - 1] mid buf[i] mid) cross; } float window_sec (float)len / (float)sample_rate; /* 时间窗长度 */ *freq (cross 0) ? (cross / window_sec) : 0.0f; *vpp (vmax - vmin) * vref / 4095.0f; /* 换算为电压峰峰值 */ }vref是 ADC 参考电压通常 3.3V。这里用了浮点F103 没有硬件 FPU浮点运算靠软件模拟所以这段测量不要放在高频中断里放在主循环或显示刷新前算一次即可。频率精度受采样率和窗口长度影响窗口越长越准但刷新越慢实际取 5121024 点是个平衡点。4. 避坑与排查这类 Demo 最容易翻车的五个地方4.1 波形左右乱跑触发像没生效现象是屏幕上有波形但一直横向滚动按键调触发也没用。原因多半是触发搜索的缓冲区和你实际显示的缓冲区不是同一份或者触发点算出来后没有真正用于映射起点。解决方法是把“找触发点”和“映射起点”绑在同一个函数里触发索引直接作为显示循环的起始偏移并在调试时把触发索引打印到串口确认它确实在变化。4.2 采样率一调高就丢点、波形失真现象是低频信号正常频率一上去波形就变形、幅度变小。原因是 ADC 的采样时间加转换时间超过了定时器触发间隔ADC 还没转换完下一次触发就来了结果被忽略。解决方法是查芯片手册里的 ADC 转换时间公式把采样周期调小或降低目标采样率保证“采样时间 12.5 个转换周期”小于触发间隔。这个参数在标准库的ADC_RegularChannelConfig里设置很多人直接抄例程不改就会踩这个坑。4.3 屏幕刷新慢波形一顿一顿现象是波形能显示但刷新率很低调参数时明显卡顿。原因通常是显示驱动用了软件模拟 SPI 且逐点写或者测量算法里塞了大量浮点运算在主循环。解决办法是显示改用硬件 SPI DMA 刷屏测量降频到每几帧算一次并且把浮点换算提前算成整数系数。血泪经验是先把刷新率单独测出来再往里加功能否则后面根本分不清是采样慢还是显示慢。4.4 输入信号稍大就花屏或死机现象是接上幅度稍大的信号后屏幕乱码甚至程序复位。原因是输入调理没做限幅信号超过 3.3V 直接灌进 ADC 引脚或者映射时坐标越界写坏了显存。解决方法是输入端加钳位二极管和分压电阻软件里对映射结果做上下限幅并且在 ADC 配置时确认引脚是模拟输入模式而不是浮空输入。4.5 频率测量值跳动大、不准现象是频率读数在小范围内频繁跳变。原因是过零计数对噪声敏感信号上一点毛刺就被多计一次。解决办法是在测量前对缓冲区做简单的滑动平均或中值滤波或者把过零判定改成带迟滞的比较高于中点加阈值才算上升低于中点减阈值才算下降。这个改动很小但读数稳定性提升明显答辩演示时特别管用。5. 验收与答辩怎么证明这台简易示波器“真的能用”验收这件事我一般建议按“可复现的测试用例”来准备而不是口头说“功能都实现了”。具体做法是准备一台信号发生器没有的话用另一块单片机输出 PWM 和正弦查表也行按固定顺序测四组直流电平、1kHz 方波、5kHz 正弦、10kHz 方波。每组记录三件事——屏幕波形是否稳定、频率读数误差、峰峰值误差。把结果填进一张表误差在 ±5% 以内就算合格超出就回到对应模块查。测试项输入信号观察点合格标准直流偏置1.65V 直流波形是否居中居中且不漂移低频方波1kHz03V边沿与占空比边沿清晰占空比误差 5%中频正弦5kHz1Vpp波形连续性与频率读数无明显断点频率误差 5%高频方波10kHz2Vpp是否丢点、幅度是否塌陷波形可辨峰峰值误差 10%答辩时最容易被追问的是“你的采样率到底是多少、怎么保证的”。这时候不要背理论直接打开定时器配置那几行指着预分频和重装值说“72MHz 除以 360 得 200kHzADC 触发间隔 5 微秒转换时间 4 微秒左右留了余量”。再被问“触发有什么用”就现场把触发关掉让波形跑起来再打开波形定住这个对比比任何解释都有说服力。还有一个加分技巧提前准备一段串口输出把每帧的频率、峰峰值、触发索引打出来。答辩现场屏幕小、光线差评委未必看得清波形细节但串口日志能证明你的数据链路是通的、测量是算出来的而不是“画上去的”。我吃过一次亏演示时屏幕反光波形几乎看不见全靠串口日志救场。从那以后我每次做这类带显示的 Demo都强制把关键测量值同步走一路串口算是给自己留的后悔药。希望这套结构和验收思路能帮到你照着改一改就能变成你自己的课程设计或毕设方案。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。