ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于STM32的简易数字示波器设计与实现:从硬件到代码的全流程解析

基于STM32的简易数字示波器设计与实现:从硬件到代码的全流程解析 这次我们来看一个非常硬核的嵌入式毕业设计项目基于STM32的简易数字示波器。这不是一个商业产品而是一个从零开始、包含硬件设计、固件开发和软件调试全过程的实践记录。对于电子、自动化、通信等相关专业的学生或者想深入理解ADC采样、信号处理和嵌入式系统开发的工程师来说这个项目极具参考价值。项目的核心在于“手搓”——从绘制硬件原理图、焊接PCB到编写STM32的ADC采样、数据处理和波形显示代码再到通过串口或屏幕将波形可视化整个过程完整地复现了一个简易示波器的核心功能。它不追求商业示波器的超高带宽和丰富功能而是聚焦于理解示波器的工作原理和STM32的实际应用。本文将带你完整复盘这个毕设项目。我们会重点拆解其硬件架构、核心代码逻辑、关键参数如采样率、电压量程的实现并提供一套可复现的验证流程。无论你是想借鉴其设计思路完成自己的课程设计还是希望深入学习STM32在数据采集领域的应用这篇文章都能提供直接的帮助。1. 核心能力速览在深入细节之前我们先通过一个表格快速了解这个自制示波器项目的关键规格和特点。这些参数是基于常见的STM32F1/F4系列和简易示波器设计推导的实际性能需以具体硬件和代码为准。能力项说明与典型值核心主控STM32F103C8T6 (蓝色战舰) / STM32F407VET6 等带ADC的型号模拟前端信号调理电路衰减、放大、电平偏移输入保护ADC采样内置12位ADC最高采样率约1MHz依赖主频和配置电压量程通常设计为±5V、±10V等通过硬件分压/放大切换带宽理论带宽受限于ADC采样率实际有效带宽通常为几十到几百KHz波形显示通过TFT液晶屏如ILI9341或PC上位机软件串口绘图触发功能支持边沿触发上升沿/下降沿软件实现测量功能可计算并显示频率、周期、峰峰值、平均值等基本参数代码架构基于HAL库或标准库模块化设计ADC、DMA、定时器、显示开发环境Keil MDK-ARM / STM32CubeIDE / VSCode PlatformIO适合场景教学演示、课程设计、毕业设计、低频信号观测、嵌入式学习2. 适用场景与使用边界这个自制STM32示波器项目最适合以下几类人群和场景高校学生特别是电子信息、自动化、通信工程等专业的学生用于完成课程设计、电子竞赛或毕业设计。它是一个将单片机原理、模拟电路、数字信号处理等多门课程知识融会贯通的绝佳实践。嵌入式初学者/爱好者希望超越点灯、按键等基础实验挑战一个综合性项目的开发者。通过它可以深入掌握STM32的ADC、DMA、定时器、中断等核心外设的协同工作。低频信号观测需求用于观测音频信号、传感器输出如温度、压力、PWM波形、简单的数字通信信号如UART、I2C的波形等低频或中低频信号。然而必须清楚它的局限性性能有限受限于STM32内置ADC的采样率和精度无法测量高频信号通常超过几百KHz就会严重失真。商业示波器轻松达到几十MHz甚至GHz级别。功能简易虽然实现了核心的波形显示和基本测量但缺乏商业示波器的高级功能如高级数学运算、深存储、协议解码、丰富的探头支持等。精度与稳定性受硬件设计尤其是前端调理电路和软件算法的影响测量精度和稳定性无法与专业仪器相比更适合定性观察和原理验证。安全警告切勿直接测量市电220V AC或其它高压危险信号自制设备的输入保护电路通常比较薄弱存在损坏设备甚至人身伤害的风险。只能用于测量安全的低压信号。3. 环境准备与前置条件要复现或借鉴这个项目你需要准备以下软硬件环境。这是一个通用清单具体型号可根据你的STM32开发板调整。硬件清单STM32核心板/开发板推荐使用STM32F103如“蓝色战舰”或STM32F4系列。确保板载ADC引脚已引出。TFT液晶显示屏用于本地显示波形如1.44寸或2.4寸的SPI接口屏幕驱动芯片ILI9341/ST7735等。信号调理板可选但推荐这是项目的关键硬件部分。你需要自己焊接或使用万用板搭建包含衰减网络用精密电阻分压扩展电压测量范围例如将±20V输入衰减到MCU可接受的±3.3V。电压跟随器使用运放如TL084, LM358提高输入阻抗减少对被测电路的影响。电平偏移电路因为STM32的ADC只能测量0-3.3V的正电压而信号常有正负。需要通过运放将信号整体抬升1.65VVref/2使-1.65V~1.65V的信号对应ADC的0~3.3V。输入保护至少包含钳位二极管和限流电阻防止过高电压损坏MCU。调试器ST-Link V2或J-Link用于程序下载和调试。信号源用于测试例如函数信号发生器、另一块单片机产生的PWM、或者简单的RC振荡电路。万用表、示波器商业用于校准和对比验证。软件环境集成开发环境IDEKeil MDK-ARM传统选择需安装对应器件包。STM32CubeIDEST官方免费IDE集成CubeMX配置工具推荐新手使用。VSCode PlatformIO轻量级、高可定制化选择。STM32CubeMX用于图形化配置引脚、时钟、外设ADC、DMA、定时器、SPI等并生成初始化代码。即使使用其他IDE也强烈建议用其进行基础配置。串口调试助手/上位机软件如果你计划通过串口将数据发送到PC显示需要如SecureCRT、Putty、或者自行编写的Python上位机。电路设计软件用于查看或修改原理图/PCB如立创EDA、Altium Designer、KiCad等。4. 硬件设计核心信号调理电路自制示波器的性能瓶颈和安全性很大程度上取决于模拟前端电路。这里拆解一个典型的双运放调理电路设计思路。设计目标将输入信号例如±10V安全地、线性地转换到STM32 ADC的输入范围0~3.3V。电路结构两级运放第一级衰减与跟随输入通过一个高压电容如104耦合隔离直流分量如果测量交流信号。衰减网络电阻分压器。例如要将±10V衰减到±1V分压比约为10:1。电压跟随器衰减后的信号接入一个运放构成的电压跟随器利用其高输入阻抗、低输出阻抗的特性缓冲信号避免后续电路影响衰减精度。第二级电平偏移与放大加法器电路这是一个核心电路。利用运放的加法器功能将第一级输出的信号包含正负与一个固定的参考电压Vref/2 1.65V相加。计算V_out -Rf/R1 * V_signal - Rf/R2 * V_ref。通过精心选择电阻比值使得当V_signal -1V时V_out 3.3V当V_signal 1V时V_out 0V当V_signal 0V时V_out 1.65V。这样就完成了双极性到单极性的转换并且信号幅度被适配到ADC全量程。保护电路在ADC输入引脚前串联一个小的限流电阻如100Ω。并联一对钳位二极管如1N4148到GND和3.3V确保输入电压不会超过-0.7V~3.9V。原理图片段示意概念性输入 | [隔直电容] | [衰减电阻网络] --- 运放1 (电压跟随器) --- 运放2 (加法器: 信号 1.65V参考) --- ADC引脚 | | [保护二极管] [限流电阻] | | GND/3.3V GND注意实际电阻值需要根据所选量程和运放供电电压精确计算。运放应选择轨到轨输入输出的型号并使用±5V或单5V供电需产生虚地。5. 软件架构与核心代码实现软件部分是STM32的“灵魂”。整个程序围绕实时采集、处理、显示三个环节展开必须高效利用DMA和中断。5.1 系统工作流程初始化配置系统时钟、ADC、DMA、定时器、SPI用于屏幕、USART用于串口输出。触发等待ADC持续采样但数据不处理。软件检测采样值等待满足触发条件如上升沿超过某个阈值。数据采集触发后启动一次固定长度的连续采集例如1024个点。使用定时器触发ADCADC通过DMA将数据自动搬运到内存中的数组全程无需CPU干预。数据处理采集完成后DMA传输完成中断触发。在中断服务程序或主循环中对原始ADC值进行软件校准、电压换算、触发点对齐等处理。波形显示/上传将处理后的数据点转换为屏幕坐标通过SPI驱动TFT屏绘制波形或通过串口按特定格式发送给PC上位机。5.2 关键代码模块拆解1. ADC与DMA配置使用STM32CubeMX生成后修改// ADC1 初始化片段使用DMA定时器2触发 ADC_HandleTypeDef hadc1; DMA_HandleTypeDef hdma_adc1; void MX_ADC1_Init(void) { hadc1.Instance ADC1; hadc1.Init.ScanConvMode ADC_SCAN_DISABLE; // 单通道 hadc1.Init.ContinuousConvMode DISABLE; // 禁用连续转换由定时器触发 hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.ExternalTrigConv ADC_EXTERNALTRIGCONV_T2_TRGO; // 定时器2触发 hadc1.Init.DataAlign ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion 1; HAL_ADC_Init(hadc1); // 配置ADC通道 ADC_ChannelConfTypeDef sConfig {0}; sConfig.Channel ADC_CHANNEL_0; // 对应PA0 sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_3CYCLES; // 采样时间影响转换速度 HAL_ADC_ConfigChannel(hadc1, sConfig); } // DMA配置CubeMX通常可图形化配置 // 需要将DMA模式设为循环模式或正常模式内存地址递增外设地址固定。2. 定时器配置用于精确控制采样率采样率 定时器更新频率。例如要实现100Ksps每秒10万个点定时器频率需配置为100kHz。// 定时器2初始化用于触发ADC TIM_HandleTypeDef htim2; void MX_TIM2_Init(void) { TIM_ClockConfigTypeDef sClockSourceConfig {0}; TIM_MasterConfigTypeDef sMasterConfig {0}; htim2.Instance TIM2; htim2.Init.Prescaler 84 - 1; // 假设系统时钟84MHz预分频后为1MHz htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 10 - 1; // 自动重装载值决定更新频率1MHz / 10 100kHz htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; HAL_TIM_Base_Init(htim2); sClockSourceConfig.ClockSource TIM_CLOCKSOURCE_INTERNAL; HAL_TIM_ConfigClockSource(htim2, sClockSourceConfig); sMasterConfig.MasterOutputTrigger TIM_TRGO_UPDATE; // 更新事件触发TRGO sMasterConfig.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(htim2, sMasterConfig); }3. 数据采集与触发逻辑#define SAMPLE_BUFFER_SIZE 1024 uint16_t adc_buffer[SAMPLE_BUFFER_SIZE]; // DMA目标缓冲区 volatile uint8_t data_ready 0; // 数据就绪标志 // 启动一次采集 void Start_Sampling(void) { data_ready 0; // 启动DMAADC定时器 HAL_ADC_Start_DMA(hadc1, (uint32_t*)adc_buffer, SAMPLE_BUFFER_SIZE); HAL_TIM_Base_Start(htim2); // 定时器开始ADC开始被触发转换 } // DMA传输完成中断回调函数 void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { HAL_TIM_Base_Stop(htim2); // 停止定时器停止触发ADC HAL_ADC_Stop_DMA(hadc); // 停止ADC和DMA data_ready 1; // 设置标志通知主循环处理数据 } // 简单的软件边沿触发检测在ADC中断或循环中调用 uint8_t Check_Trigger(uint16_t current_sample, uint16_t prev_sample, uint8_t edge) { // edge: 0-上升沿1-下降沿 if(edge 0) { return (prev_sample TRIGGER_LEVEL) (current_sample TRIGGER_LEVEL); } else { return (prev_sample TRIGGER_LEVEL) (current_sample TRIGGER_LEVEL); } } // 当检测到触发时调用 Start_Sampling();4. 数据处理与电压换算// 将ADC原始值转换为实际电压值单位伏特 // V_ref: ADC参考电压通常是3.3V // offset: 零点偏移对应的ADC值输入0V时的ADC读数 // scale: 电压缩放系数由前端调理电路决定 float ADC_To_Voltage(uint16_t adc_value, float V_ref, uint16_t offset, float scale) { // 假设ADC是12位满量程值4095对应V_ref float voltage ((int32_t)adc_value - offset) * (V_ref / 4095.0f); return voltage * scale; // 乘以硬件衰减/放大系数 } // 寻找波形周期和频率简易过零检测法 float Calculate_Frequency(float* voltage_buffer, uint32_t size, float sample_rate) { uint32_t zero_cross_count 0; int8_t last_sign (voltage_buffer[0] 0) ? 1 : -1; for(uint32_t i1; isize; i) { int8_t current_sign (voltage_buffer[i] 0) ? 1 : -1; if(current_sign ! last_sign) { zero_cross_count; last_sign current_sign; } } // 每两个过零点为一个周期 float period_samples (size * 2.0f) / zero_cross_count; return sample_rate / period_samples; }6. 功能测试与效果验证流程完成硬件焊接和软件编写后需要系统性地测试示波器的各项功能。以下是一个从简单到复杂的验证流程。6.1 基础硬件与ADC测试目的确保信号调理电路和ADC基本工作正常。输入接地GND将探头输入端短路到地。运行程序观察屏幕或上位机显示的波形是否是一条在0V附近的直线。读取ADC原始值这个值就是你的“零点偏移”offset记录下来用于软件校准。输入固定直流电压使用可调电源或稳压芯片输入一个已知的稳定直流电压例如1.0V。测量实际输入电压并记录示波器显示的电压值。计算误差调整软件中的scale系数。输入方波信号低频使用另一块单片机或信号发生器产生一个100Hz、0-3.3V的方波。观察波形是否显示为清晰的方波上升/下降沿是否可见。调整显示的时间轴通过改变采样率和显示点数实现使屏幕上能显示1-2个完整周期。6.2 触发功能测试目的验证边沿触发是否稳定波形是否能够稳定显示。输入一个频率为1KHz的正弦波。在软件中设置触发模式为“上升沿”触发电平设置为波形中间值例如1.65V。观察波形。每次触发后波形起点是否都在上升沿穿过触发电平的位置多次触发波形是否重叠良好稳定改为“下降沿”触发重复测试。调整触发电平将触发电平设置到波形幅度之外例如高于波峰或低于波谷观察波形是否停止刷新等待触发状态。这证明触发逻辑有效。6.3 测量功能测试目的验证软件算法计算的参数是否准确。频率/周期测量输入一个已知频率如1.000KHz的正弦波或方波。使用示波器的自动测量功能或读取代码计算出的频率值与信号源标称值对比。计算相对误差。峰峰值Vpp测量输入一个已知幅度的信号如2.0Vpp正弦波。对比示波器测量的Vpp值与万用表测量值或信号源设定值。平均值/有效值RMS对于正弦波其RMS值应为Vpp/(2√2)。对比测量值。6.4 带宽与采样率极限测试目的探索自制示波器的性能边界。逐步增加信号频率从1KHz开始逐步增加正弦波信号的频率10KHz, 50KHz, 100KHz, 200KHz...。观察波形失真注意观察波形幅度是否开始下降带宽不足以及波形形状是否从正弦波变得“棱角分明”采样率不足导致的混叠失真。使用奈奎斯特定律理论上要无失真还原信号采样率至少是信号最高频率的2倍。如果你的采样率是100Ksps那么能观测到的信号最高频率理论上不超过50KHz。实际由于前端电路限制有效带宽会更低。记录-3dB点当信号幅度下降到低频时的0.707倍时对应的频率即为系统的-3dB带宽。这是评估你硬件设计尤其是运放和PCB布局的关键指标。7. 通过串口与PC通信上位机方案如果屏幕显示受限或者需要更复杂的分析可以将数据发送到PC用Python等工具绘制波形。STM32端代码发送一帧数据// 假设使用USART1 void Send_Waveform_Data(float* voltage_data, uint32_t size) { char tx_buffer[64]; // 可以发送简单的格式如每行一个数据点 for(uint32_t i0; isize; i) { int len snprintf(tx_buffer, sizeof(tx_buffer), %.3f\n, voltage_data[i]); HAL_UART_Transmit(huart1, (uint8_t*)tx_buffer, len, 1000); } // 发送帧结束标识 HAL_UART_Transmit(huart1, (uint8_t*)END\n, 4, 1000); }PC端Python上位机示例使用matplotlibimport serial import matplotlib.pyplot as plt import matplotlib.animation as animation from collections import deque # 配置串口 ser serial.Serial(COM3, 115200, timeout1) # 端口和波特率根据实际情况修改 data_buffer deque(maxlen1024) # 环形缓冲区 def read_from_serial(): while ser.in_waiting: line ser.readline().decode(utf-8, errorsignore).strip() if line END: # 一帧数据接收完毕可以触发绘图更新 pass elif line: try: value float(line) data_buffer.append(value) except ValueError: pass fig, ax plt.subplots() line, ax.plot([], []) def update(frame): read_from_serial() if data_buffer: line.set_data(range(len(data_buffer)), list(data_buffer)) ax.relim() ax.autoscale_view() return line, ani animation.FuncAnimation(fig, update, interval50, blitTrue) plt.show()8. 常见问题与排查方法在制作和调试过程中你几乎一定会遇到以下问题。这里提供排查思路。问题现象可能原因排查方式解决方案屏幕无显示或花屏1. SPI引脚连接错误或接触不良。2. 屏幕初始化序列或驱动代码有误。3. 电源电压不足。1. 用逻辑分析仪或示波器检查SPI的CLK、MOSI信号。2. 核对屏幕型号与驱动代码中的初始化命令。3. 测量屏幕供电电压通常是3.3V或5V。1. 重新检查接线确保CS、DC、RESET等控制线正确。2. 查找官方驱动示例替换初始化代码。3. 确保电源能提供足够电流。ADC采样值不变或全为01. ADC引脚配置错误模拟输入模式。2. 信号调理电路无输出或运放未工作。3. DMA配置错误数据未搬运到内存。4. 定时器未正确触发ADC。1. 用CubeMX检查引脚模式。2. 用万用表测量运放输出端电压。3. 在DMA完成中断里设置断点看是否进入。4. 用示波器看定时器TRGO输出引脚是否有脉冲。1. 将引脚重配置为模拟输入。2. 检查运放供电、虚地电压1.65V是否正常。3. 检查DMA通道、内存地址、数据宽度配置。4. 检查定时器时钟、预分频、重装载值配置。波形严重失真或噪声大1. 采样率过低发生混叠。2. 信号调理电路带宽不足高频衰减。3. PCB布局不佳引入数字噪声。4. 电源纹波大。1. 输入一个低频正弦波看是否正常。2. 测量调理电路在不同频率下的增益。3. 用示波器直接测量ADC输入引脚上的信号。4. 测量电源轨上的噪声。1. 提高采样率降低定时器周期值。2. 选择更高带宽的运放优化反馈回路。3. 模拟部分与数字部分特别是MCU、晶振分开布局加粗地线。4. 增加电源滤波电容使用LDO稳压。触发不稳定波形抖动1. 触发电平设置不当。2. 信号本身有噪声在触发电平附近抖动。3. 触发判断逻辑有bug如未做去抖。1. 观察信号和触发电平的位置。2. 在触发判断函数中加入迟滞比较Hysteresis。1. 将触发电平设置在信号变化陡峭的区域。2. 修改触发判断(prev level-hyst) (curr level-hyst)用于上升沿。测量参数频率、电压误差大1. 软件换算公式错误。2. 硬件调理电路电阻精度不够。3. ADC参考电压不准内部Vref。4. 未进行软件校准零点、增益。1. 用已知精度的信号源和万用表做基准对比。2. 测量并记录输入多个已知电压时的ADC值做线性拟合。1. 核对电压换算代码。2. 使用1%精度的电阻。3. 使用外部精密基准电压源给ADC做参考。4. 在代码中增加两点校准功能V_real k * ADC_raw b通过实测两点解出k和b。串口上位机收不到数据或数据乱码1. 串口波特率、数据位、停止位不匹配。2. 发送数据过快缓冲区溢出。3. 文本格式解析错误。1. 用串口调试助手先收发简单字符测试。2. 在STM32端发送后加延时或使用DMA发送。3. 检查上位机代码的编码和解码方式。1. 确保STM32与PC软件串口参数完全一致。2. 优化发送逻辑或使用流控。3. 发送时加入帧头、帧尾、校验和增强协议鲁棒性。9. 项目优化与扩展方向当基础功能实现后可以考虑以下方向进行优化和扩展这能让你的毕设更具深度和亮点。多量程自动切换通过模拟开关如CD4051切换前端衰减网络的不同电阻配合软件实现电压量程如1V/div, 2V/div, 5V/div的自动或手动切换。时基扫描速度调整动态改变定时器的频率采样率和显示缓冲区对应的实际时间宽度实现类似商业示波器的“秒/格”调整功能。双通道显示使用STM32的多个ADC单元或交替采样配合两个独立的前端调理电路实现双通道输入和显示可以观察两个信号的相位关系。波形存储与回放利用STM32的内部Flash或外接SD卡将采集到的波形数据保存下来之后可以回放分析。FFT频谱分析在STM32上实现简单的FFT算法或利用DSP库将时域波形转换为频域频谱并显示用于分析信号的频率成分。更好的显示界面在TFT屏上绘制网格、刻度、测量参数文字实现更专业的UI。甚至可以实现触摸屏控制。使用更高性能的MCU升级到STM32H7系列或带有高速ADC的型号可以大幅提升采样率和有效带宽。这个基于STM32的简易示波器项目其价值远不止于得到一个能用的测量工具。它贯穿了硬件设计、信号处理、嵌入式编程和系统调试的完整流程每一个问题的排查和解决都是宝贵的经验。从调通第一个ADC采样值到稳定显示一个清晰的波形再到实现触发和测量这个过程会让你对“示波器”这个黑盒子的内部原理有透彻的理解。建议你在动手时做好详细的实验记录包括每一次电路修改、每一版代码的变更和对应的测试结果这不仅是完成毕设的要求更是未来解决更复杂工程问题的良好习惯。
返回列表