ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于STM32的简易示波器设计与实现:从硬件到软件的完整实践

基于STM32的简易示波器设计与实现:从硬件到软件的完整实践 这次我们来看一个基于STM32的简易示波器项目。这不是一个商业产品而是一个完整的毕业设计实践从硬件选型、原理图绘制、PCB设计到嵌入式软件编写最终实现一个能测量和显示波形的设备。对于电子、自动化或嵌入式方向的学生和爱好者来说自己动手“搓”一个示波器是深入理解模拟信号采集、数字信号处理和嵌入式系统开发的绝佳途径。项目的核心在于将模拟世界的连续信号通过STM32的ADC模数转换器转换为数字量再经过一系列处理最终在屏幕上还原出波形。整个过程涉及硬件电路设计、ADC采样、数据处理、显示驱动等多个环节。本文将带你完整复盘这个项目的关键点并提供可参考的硬件原理图和核心代码让你不仅能看懂更能动手复现或改进。如果你正为嵌入式课程设计、电子竞赛或毕业设计寻找一个有挑战性又有趣的项目或者单纯想深入理解示波器的工作原理和STM32的高级应用那么这篇文章值得你仔细阅读。我们将重点关注硬件电路如何搭建、代码如何组织、关键参数如何调试以及最终能达到怎样的测量效果。1. 核心能力速览在深入细节之前我们先通过一个表格快速了解这个自制STM32示波器的核心规格和特点。这有助于你判断它是否符合你的需求或学习目标。能力项说明核心主控STM32F103C8T6或其他STM32F1系列具备ADC和足够速度模拟带宽理论最高约100kHz受限于ADC采样率和前端电路垂直分辨率取决于ADC位数通常为12位采样率最高可达1MHz依赖STM32的ADC配置和DMA显示方式TFT液晶屏如ILI9341驱动或OLED屏输入耦合直流耦合可通过电路设计增加交流耦合选项输入电压范围通常为0-3.3V需通过前端衰减/放大电路扩展触发功能支持边沿触发上升沿/下降沿软件实现波形测量可计算并显示频率、周期、峰值、平均值等代码架构基于HAL库或标准库模块化设计硬件扩展性留有按键、编码器接口用于控制可扩展SD卡存储适合场景学习、课程设计、毕业设计、低频信号观测、原理验证重要提示这是一个教学演示性质的简易示波器。其性能如带宽、精度无法与商用示波器相比主要价值在于理解原理和实现过程。2. 适用场景与使用边界在开始动手之前明确这个项目的适用场景和局限性至关重要。这个项目适合谁嵌入式/电子信息工程专业学生作为综合性的课程设计或毕业设计项目能串联单片机、模拟电路、数字信号处理等多门课程知识。电子爱好者与创客希望深入理解信号采集与显示原理并享受从零搭建一个测量工具的成就感。STM32学习者已经掌握GPIO、定时器、中断等基础想挑战ADC、DMA、LCD驱动等综合应用。它能解决什么问题原理学习直观展示ADC采样、波形重构、触发等示波器核心概念。低频信号观测可用于观察单片机产生的PWM波、音频信号、传感器输出等低频百kHz以下信号。系统调试辅助在开发其他STM32项目时可以临时用它来观测某个引脚上的信号变化。它不适合什么场景高精度测量其ADC精度、前端电路线性度、时基稳定性均无法满足精密测量要求。高频信号测量受限于STM32主频和ADC性能无法准确捕获MHz以上的信号。高压或危险信号测量自制电路缺乏必要的隔离和保护严禁直接测量市电或高压电路以防损坏设备或危及人身安全。商业或工业用途其稳定性和可靠性未经严格认证仅限学习和实验。安全与合规边界输入信号电压限制务必确保输入信号电压在硬件电路设计的安全范围内通常经过分压后不超过3.3V。测量未知信号时建议先使用万用表确认电压。接地与共地问题测量电路信号时注意共地问题避免形成地环路引入干扰或造成短路。静电防护在焊接和调试过程中注意静电防护避免损坏敏感的CMOS器件。3. 环境准备与前置条件要成功复现或借鉴这个项目你需要准备以下软硬件环境。硬件清单主控核心STM32F103C8T6最小系统板即常见的“蓝色小板”成本低廉且资源足够。显示模块一块SPI接口的TFT液晶屏如1.44寸或2.4寸的ILI9341驱动屏幕。OLED屏如SSD1306也可行但显示波形细节能力较弱。前端信号调理电路这是关键。你需要搭建一个电路板包含以下部分衰减网络用于测量高于3.3V的信号例如用1:10的分压电阻。电压抬升电路因为STM32的ADC只能测量0-3.3V的正电压而信号常有负电压部分。通常使用运放如LM358搭建一个加法器将信号整体抬升1.65VVref/2使-1.65V~1.65V的信号对应ADC的0~3.3V输入。低通滤波简单的RC滤波用于抗混叠滤除高于采样频率一半的信号成分。输入接口BNC接头或简单的探针接口。控制部件几个按键或一个旋转编码器用于调节时基、电压档位、触发模式等。电源5V USB供电经LDO如AMS1117-3.3转换为3.3V为系统供电。焊接与调试工具电烙铁、万用表、示波器 ironically你可以先用别人的示波器来调试你自己的示波器。软件与环境集成开发环境IDEKeil MDK-ARM 或 STM32CubeIDE。前者在国内更普及后者是ST官方免费工具且集成CubeMX。STM32CubeMX强烈推荐使用。用于图形化配置引脚、时钟、ADC、DMA、SPI等外设生成初始化代码能极大减少底层配置工作量。串口调试助手如XCOM、SSCOM用于调试阶段打印数据。电路设计软件立创EDA、Altium Designer或KiCad用于绘制原理图和PCB。4. 硬件电路设计详解硬件是基础一个稳定可靠的前端电路决定了示波器的性能下限。4.1 核心思想信号调理STM32的ADC输入范围是0到Vref通常接3.3V。为了测量交流信号包含负电压我们需要一个“电平移位”电路将信号整体向上平移。例如要测量±1V的信号我们将其加上1.65V的偏置变成0.65V~2.65V的信号这样就能被ADC正确采集。采集到的数字值在软件中再减去这个偏置即可还原原始波形。4.2 原理图关键部分以下是核心调理电路的简化示意图和分析输入 | ├───[R1]───┐ | | [C1] [R2] (分压与隔直可选) | | └───┬───────┘ | └───[运放同相输入端] | [偏置电压1.65V]──[R3]──┐ | | GND [运放输出]───→ ADC输入引脚 | | ... [反馈网络]这是一个概念图实际运放电路需连接电源、配置增益等具体模块说明输入保护与衰减在输入端串联一个电阻如1kΩ并配合TVS二极管可以起到一定的限流和过压保护作用。如果需要测量更高电压则需要精密电阻分压网络如9:1衰减。交流耦合可选通过一个电容C1串联输入可以隔离信号的直流分量只测量交流部分。这通常通过一个开关或跳线来选择。电平移位与缓冲这是核心。使用一个单电源运放如LMV358构成同相加法器电路。一路输入是待测信号另一路输入是由电阻分压产生的1.65V基准电压。运放的作用是提供高输入阻抗减少对被测电路的影响和低输出阻抗驱动ADC。低通滤波在运放输出端加入一个RC低通滤波器其截止频率应略高于你期望测量的最高信号频率以抑制高频噪声和防止混叠。PCB设计注意事项模拟地与数字地建议使用单点连接。将ADC、运放、基准电压源的地归为“模拟地”STM32数字部分的地归为“数字地”在电源入口处通过一个0欧电阻或磁珠连接。电源去耦在运放和STM32的电源引脚附近务必放置一个0.1uF的陶瓷电容和一个10uF的钽电容以滤除高频和低频噪声。信号走线模拟信号走线尽量短远离数字信号线如时钟、SPI线避免平行走线。5. 软件架构与代码解析软件部分负责驱动ADC采样、处理数据、控制显示和用户交互。代码应采用模块化设计。5.1 工程结构一个清晰的工程目录有助于管理/Project ├── /Core │ ├── Src │ │ ├── main.c │ │ ├── adc.c │ │ ├── dma.c │ │ ├── lcd.c │ │ ├── trigger.c │ │ └── ui.c │ └── Inc 对应的头文件 ├── /Drivers STM32 HAL库文件 ├── /Hardware 硬件驱动如按键、编码器 └── /Utilities 通用工具函数5.2 核心模块代码示例1. ADC与DMA配置使用CubeMX生成后补充ADC配置为连续扫描模式使用DMA将转换结果自动搬运到内存中的缓冲区。这是实现高速采样的关键。// 在 main.c 或 adc.c 中 #define ADC_BUFFER_SIZE 1024 // 采样缓冲区大小 uint16_t adc_buffer[ADC_BUFFER_SIZE]; // DMA目标缓冲区 // 启动ADC和DMA的代码片段通常在初始化函数中 HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buffer, ADC_BUFFER_SIZE);这段代码启动ADC1并让DMA自动将转换结果填充到adc_buffer数组中。采样率由ADC的时钟分频和采样周期决定可以在CubeMX中配置。2. 触发处理触发功能决定波形何时开始显示是示波器稳定显示波形的关键。一个简单的软件边沿触发实现如下// trigger.c typedef enum { TRIG_MODE_AUTO, TRIG_MODE_NORMAL, TRIG_MODE_SINGLE } TrigMode_t; typedef enum { TRIG_EDGE_RISING, TRIG_EDGE_FALLING } TrigEdge_t; uint8_t Trigger_Check(uint16_t *buffer, uint32_t size, uint16_t trig_level, TrigEdge_t edge) { static uint8_t last_state 0; uint8_t current_state; for (uint32_t i 1; i size; i) { current_state (buffer[i] trig_level) ? 1 : 0; if (edge TRIG_EDGE_RISING) { if (last_state 0 current_state 1) { // 找到上升沿返回触发位置 return i; } } else { if (last_state 1 current_state 0) { // 找到下降沿返回触发位置 return i; } } last_state current_state; } return 0; // 未找到触发点 }该函数遍历采样缓冲区寻找超过预设触发电平trig_level的边沿。找到后返回触发点在缓冲区中的位置后续显示波形时就从这里开始。3. 波形显示与UI在LCD上绘制波形本质是将ADC缓冲区中的数据点用线段连接起来。// lcd.c 或 ui.c void Draw_Waveform(uint16_t *data, uint32_t data_size, uint16_t trig_pos) { uint16_t prev_x, prev_y, curr_x, curr_y; uint16_t screen_height LCD_GetHeight(); uint16_t screen_width LCD_GetWidth(); uint16_t voltage_range 4096; // 12位ADC满量程 float volts_per_div 0.5; // 每格电压可调 float time_per_div 0.001; // 每格时间可调 (1ms/div) // 清屏或清空波形区域 LCD_Clear(BLACK); // 绘制网格 Draw_Grid(); // 计算缩放和偏移 uint16_t vertical_scale (screen_height / 8) / (volts_per_div * (voltage_range / 3.3)); uint16_t horizontal_scale (screen_width / 10) / (time_per_div * (data_size / get_sample_rate())); uint16_t vertical_offset screen_height / 2; // 零点在屏幕中央 // 从触发点开始绘制 uint32_t start_index trig_pos; prev_x 0; prev_y vertical_offset - ((int16_t)data[start_index] - 2048) * vertical_scale / voltage_range; // 假设2048对应0V for (uint32_t i 1; i screen_width (start_index i) data_size; i) { curr_x i; uint32_t data_index start_index i; // 将ADC值转换为屏幕Y坐标注意减去直流偏置2048 curr_y vertical_offset - ((int16_t)data[data_index] - 2048) * vertical_scale / voltage_range; // 在两点之间画线 LCD_DrawLine(prev_x, prev_y, curr_x, curr_y, GREEN); prev_x curr_x; prev_y curr_y; } }这段代码是高度简化的示例。实际项目中你需要管理时基每格时间、电压档位每格电压、波形在屏幕上的起始位置等。Draw_Grid()函数用于绘制背景网格。4. 测量与计算在稳定显示波形后可以计算一些基本参数// measurement.c float Calculate_Frequency(uint16_t *data, uint32_t size, float sample_rate) { uint32_t zero_crossings 0; uint8_t last_state (data[0] 2048) ? 1 : 0; // 2048为中间值 for (uint32_t i 1; i size; i) { uint8_t current_state (data[i] 2048) ? 1 : 0; if (last_state 0 current_state 1) { zero_crossings; } last_state current_state; } if (zero_crossings 2) return 0.0; // 频率 (过零点次数 / 2) / (数据点数 / 采样率) return (zero_crossings / 2.0) / (size / sample_rate); } void Calculate_Vpp(uint16_t *data, uint32_t size, uint16_t *min, uint16_t *max) { *min 0xFFFF; *max 0; for (uint32_t i 0; i size; i) { if (data[i] *min) *min data[i]; if (data[i] *max) *max data[i]; } }6. 系统集成与调试流程将硬件和软件组合起来并按步骤调试是成功的关键。6.1 上电前检查目视检查检查PCB有无短路、断路、虚焊特别是电源和地之间。电源测试不插主控先上电用万用表测量3.3V、1.65V基准电压等关键点是否正常。6.2 分模块调试显示模块先编写一个简单的LCD测试程序画点、线、矩形、显示文字确保屏幕驱动正常。ADC静态测试将ADC输入引脚通过一个电位器接到3.3V和GND之间编写代码读取ADC值并通过串口打印。旋转电位器观察打印值是否在0-4095间线性变化。前端电路测试使用信号发生器或另一块单片机产生一个已知的简单信号如1kHz正弦波用商用示波器测量前端电路输出端即STM32 ADC引脚处的波形确保电平移位和幅度符合预期。ADC动态采样测试将测试信号接入用DMA连续采样并通过串口将大量采样数据发送到电脑用PythonMatplotlib或串口绘图工具绘制波形验证采样是否正常。6.3 系统联调基础波形显示将采样数据直接映射到屏幕先不加入触发。你应该能看到一个滚动的波形。实现触发加入触发功能使波形稳定。添加控制通过按键或编码器实现时基秒/格和电压档位伏/格的调节。这需要动态调整波形绘制的缩放系数。添加测量在波形稳定显示后实现频率、峰峰值等参数的自动测量与屏幕显示。7. 性能优化与提升方向基础功能实现后可以考虑以下优化提高采样率使用STM32的ADC双模式或交替模式。优化时钟树在允许范围内提高ADC时钟。减少ADC采样周期。使用定时器触发ADC实现精确的等间隔采样。改善显示效果实现波形余辉模拟示波器效果。添加插值算法如正弦插值使波形更平滑。实现双通道显示需要两个ADC或一个ADC多路复用。增强测量功能上升时间、占空比测量。FFT频谱分析需要移植DSP库。波形存储与回放需要外接SD卡。改进用户体验设计更友好的菜单系统。添加自动设置AutoSet功能。支持光标测量。8. 常见问题与排查方法在制作过程中你可能会遇到以下问题问题现象可能原因排查方式解决方案屏幕无显示电源接反/未接、SPI线接错、复位或背光控制错误、初始化序列不对1. 检查电源电压。2. 用逻辑分析仪或示波器抓取SPI时序。3. 核对LCD驱动芯片型号及初始化代码。1. 确保接线正确。2. 查阅LCD数据手册逐条核对初始化命令。3. 降低SPI时钟频率测试。ADC采样值不变或全为0ADC引脚配置错误、参考电压未接、DMA未正确配置、采样时间太短1. 用万用表测量ADC引脚实际电压。2. 检查CubeMX中ADC通道和DMA配置。3. 改用轮询模式读取ADC排除DMA问题。1. 检查硬件连接和CubeMX配置。2. 增加ADC采样周期。3. 确保HAL_ADC_Start_DMA在初始化后被调用。波形严重失真前端运放电路工作点不对、电源噪声大、输入信号超出范围、低通滤波截止频率过低1. 用已知好的示波器测量前端电路各点波形。2. 测量运放电源引脚上的纹波。3. 输入一个直流电压看ADC读数是否线性。1. 调整运放偏置电路电阻。2. 加强电源滤波使用线性稳压电源。3. 确保输入信号在调理电路允许范围内。触发功能不稳定触发电平设置不当、信号噪声大、触发判断算法有缺陷1. 通过串口打印触发函数找到的触发点位置。2. 观察信号在触发电平附近的噪声。1. 实现触发滞后Hysteresis避免噪声引起误触发。2. 对信号进行软件滤波后再进行触发判断。测量频率误差大采样率不准确、过零点检测受噪声影响、信号非周期性1. 校准系统时钟HSE。2. 用标准信号源如函数发生器输入固定频率信号测试。3. 尝试更稳健的频率算法如过零周期测量。1. 使用更高精度的外部晶振。2. 在频率计算前对数据进行平滑滤波。3. 实现多周期平均测量。系统运行一段时间后死机堆栈溢出、中断冲突、DMA传输完成中断未处理、硬件不稳定1. 检查.map文件看堆栈设置是否足够。2. 检查中断优先级。3. 在DMA传输完成中断和半传输中断中添加标志位和超时判断。1. 增大堆栈大小。2. 合理分配中断优先级避免嵌套过深。3. 添加看门狗IWDG防止程序跑飞。9. 项目总结与资源获取通过这个“手搓”示波器的项目你能够将STM32的ADC、DMA、定时器、SPI等外设融会贯通并深入理解信号调理、采样定理、触发显示等模拟与数字混合系统的核心概念。虽然最终成品的性能有限但其学习价值远超一个简单的开发板实验。最值得尝试的点完整的系统思维从需求分析、硬件选型、电路设计、PCB绘制、软件架构到调试优化体验一个完整电子产品的开发流程。软硬件协同调试学会使用万用表、示波器、逻辑分析仪等工具定位问题理解软件逻辑如何受硬件制约。解决真实问题在调试过程中遇到的噪声、失真、不稳定等问题都是理论学习中难以触及的实战经验。最先应该验证的功能建议按照“LCD显示 - ADC静态读取 - 前端电路验证 - DMA连续采样 - 基础绘图 - 触发稳定 - 参数测量 - 交互控制”的顺序逐步推进。每完成一步都进行充分测试。最容易踩的坑前端电路设计电平移位电路计算错误导致信号超出ADC输入范围。电源噪声开关电源的噪声会耦合到模拟部分严重影响小信号测量。地线处理模拟地和数字地混乱引入干扰。软件时序ADC采样、DMA传输、数据处理、LCD刷新之间的时序冲突导致显示卡顿或数据错乱。资源获取与下一步原理图与代码由于篇幅限制完整的工程文件无法在文中展示。你可以在开源硬件平台如GitHub、Gitee上搜索“STM32 Oscilloscope”或“STM32示波器”能找到许多优秀的开源参考项目。深入学习在完成基本功能后可以挑战更高阶的功能如FFT频谱显示、自动测量、二次开发接口通过串口发送波形数据到PC等。工程化改进考虑设计一个美观的外壳将PCB、屏幕、按键集成在一起制作成一个真正的便携设备。动手实践是学习嵌入式系统最好的方式。这个项目就像一把钥匙帮你打开通往更复杂嵌入式系统设计的大门。建议收藏本文在实践的不同阶段回来查阅对应的章节希望能为你排忧解难。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进