STM32简易数字存储示波器:从采样链路到触发与波形绘制
简介面向全国大学生电子设计竞赛选手及指导教师这是一份围绕B题“简易数字存储示波器”的赛题培训资料适用于电子类竞赛备赛、课程设计与综合实践。文档从任务说明、基本要求、发挥部分到评分标准逐项展开重点解析单次触发存储显示、输入阻抗、垂直/水平分辨率、三档扫描速度、垂直灵敏度、内触发方式以及连续触发存储、双踪示波、水平移动扩展、0.01V/div高灵敏度提升等关键设计指标并附上测试过程不得操作普通示波器的注意事项帮助读者建立完整的设计与验证思路。资源包仅含1个doc文件大小约57KB内容精炼、结构清晰既可对照任务书逐项核对设计要点也可作为撰写设计报告和答辩准备的参考资料。目前已有134人学习适合需要系统理解赛题要求、完善方案论证的参赛者。1. 电赛B题的简易数字存储示波器先想清楚三条链路再动手全国大学生电子设计竞赛里简易数字存储示波器是出镜率很高的B题方向。这题最反常的一点是真正难住队伍的从来不是把波形画到屏幕上而是让波形“站得住”。指标只要落在频率、幅度、触发这几个词上背后全是信号链路的功夫。有人第一天就点亮了TFT第三天发现采样率上不去第四天发现触发沿在屏幕上乱跳最后交上去的东西更像电压表加一块屏。这道题的本质是让参赛队在有限时间里搭一台能用的测量仪器信号调理、模数转换、数据缓冲、波形重建每一环都能卡住人。本文按备赛节奏先把采集原理讲透再给出STM32上可复现的采集与波形绘制代码然后处理时基和垂直档位的调参问题最后用方波做整机自检。2. 数字存储示波器的采集原理实时采样、等效时间采样与触发预算2.1 实时采样与等效时间采样的适用边界STM32F103的内部ADC在72MHz主频下常规转换包含采样保持时间大约能做到1Msps量级。按一个周期采20个点估算实时方式能比较保真地显示50kHz以下的信号再往上边沿细节就开始丢失。这组数字直接决定系统架构如果被测信号是单次的比如开关机浪涌或某个一次性脉冲只能靠实时采样内部ADC速度不够就得上外部并行ADC如果题目明确写“被测信号为周期信号”那就应该走等效时间采样。等效时间采样靠触发来攒时间。每次触发沿到来时采一个点采完把采样时刻相对触发沿往后挪一小段等下一次触发再采下一个点循环几百次就把一个周期波形按更细的时间步进重建出来。它绕开了ADC速度的物理上限但前提是信号严格周期而且触发系统要非常稳定。这也解释了为什么题目里提到周期信号时触发电路不是加分项而是基础架构。预触发功能依赖存储架构。常见做法是用SRAM做环形缓冲区采集期间持续写入触发发生后把显示起点定在触发点之前的若干点就能看到触发前的波形。存储深度决定预触发能看多长4096点对大多数题目场景够用不够就外扩SRAM。提示区分实时采样和等效时间采样只看一点——信号是否重复出现。单次信号没有第二个边沿可以参考等效采样完全不适用只能堆实时采样率。2.2 前端调理衰减、放大与电平搬移ADC的输入范围是0到3.3V被测信号可能是正负几十伏前端调理必须解决三件事输入阻抗、幅度变换、电平搬移。输入级用1MΩ电阻落地配合探头的高阻输入让示波器对被测电路的影响尽量小运放输入端再并两个二极管到电源轨做钳位保护防止误接高压烧掉ADC。幅度变换分衰减和放大两级。衰减网络用电阻分压常见做法是1倍、5倍、10倍、50倍四档用波段开关或模拟开关切换后级同相放大器提供2、4、20倍几档增益两两组合得到垂直灵敏度档位。运放选型看两个参数单位增益带宽要高出信号最高频率一个数量级压摆率要足够否则方波边沿会被明显拉缓。电平搬移是把双极性信号垫到ADC量程中点最常用的方法是在放大级后加加法器把信号叠到1.65V直流偏置上。下表是一组按满屏八格设计的档位组合衰减比与后级增益相乘后ADC输入幅度都落在±1.6V左右给ADC留了余量。垂直档位满屏输入衰减比后级增益ADC输入幅度20mV/div±80mV1:1×20±1.6V100mV/div±400mV1:1×4±1.6V500mV/div±2V5:1×4±1.6V2V/div±8V10:1×2±1.6V10V/div±40V50:1×2±1.6V这组参数确定后程序里只需维护一份“每格对应多少ADC码”的常量表垂直标尺就和硬件统一了。衰减电阻要选1%精度放大倍数由负反馈电阻比例决定反馈电阻的温漂会影响长时间稳定性。2.3 触发电平的产生与比较器电路硬件触发是等效时间采样的前提。常规做法是从前级放大器输出引一路信号进比较器与触发电平比较比较器输出接MCU的外部中断或定时器捕获。触发电平的来源有两种SPI接口DAC芯片或者定时器PWM加RC低通滤波。竞赛经费有限时后者更常见一个定时器通道加两级RC就能输出稳定的直流电平。// 触发电平发生TIM2_CH1 输出100kHz PWM经两阶RC低通后接比较器参考端 // duty 取值 0~1000对应输出电压 0~3.3V // 例如 duty500触发电平约1.65V void TRIG_SetLevel(uint16_t duty) { if (duty 1000) { duty 1000; } TIM_SetCompare1(TIM2, duty); // ARR999时占空比 duty/1000 }这段代码里定时器ARR设为999比较寄存器写入值就是千分比分辨率0.1%。RC低通的截止频率要远低于PWM频率两级各取100Hz左右纹波能压到毫伏级。比较器必须加正反馈回差回差太小噪声会让输出在阈值附近反复翻转回差太大小信号触发不起来。经验值是回差取信号幅度的1%到3%然后据此计算正反馈电阻分压比。3. STM32的DMA采样与波形存储跑通核心采集代码3.1 定时器触发ADC与DMA循环搬运采集部分需要三个外设配合定时器提供采样时钟ADC在每个时钟边沿转换一次DMA把转换结果搬进内存CPU全程不参与数据传输。采样率由定时器的预分频值和重装值决定以72MHz主频为例预分频设为71得到1MHz计数频率重装值设为9采样率就是100kHz。#define SAMP_BUF_LEN 4096 uint16_t s_scopeBuf[SAMP_BUF_LEN]; // DMA缓冲区循环写入 void SCOPE_ADC_Init(void) { TIM_TimeBaseInitTypeDef tim; ADC_InitTypeDef adc; DMA_InitTypeDef dma; GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟 72MHz/6 12MHz // PA1 配置为模拟输入接前端调理电路输出 gpio.GPIO_Pin GPIO_Pin_1; gpio.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, gpio); // TIM3更新事件经TRGO触发ADC采样率 1MHz / (TIM_Period 1) tim.TIM_Prescaler 71; // 72MHz/72 1MHz tim.TIM_Period 9; // 100kHz采样率 tim.TIM_ClockDivision TIM_CKD_DIV1; tim.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM3, tim); TIM_SelectOutputTrigger(TIM3, TIM_TRGOSource_Update); adc.ADC_Mode ADC_Mode_Independent; adc.ADC_ScanConvMode DISABLE; adc.ADC_ContinuousConvMode DISABLE; // 由定时器外部触发 adc.ADC_ExternalTrigConv ADC_ExternalTrigConv_T3_TRGO; adc.ADC_DataAlign ADC_DataAlign_Right; adc.ADC_NbrOfChannel 1; ADC_Init(ADC1, adc); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_1Cycles5); dma.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; dma.DMA_MemoryBaseAddr (uint32_t)s_scopeBuf; dma.DMA_DIR DMA_DIR_PeripheralSRC; dma.DMA_BufferSize SAMP_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.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, dma); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE); ADC_Cmd(ADC1, ENABLE); ADC_ResetCalibration(ADC1); while (ADC_GetResetCalibrationStatus(ADC1)); ADC_StartCalibration(ADC1); while (ADC_GetCalibrationStatus(ADC1)); ADC_ExternalTrigConvCmd(ADC1, ENABLE); TIM_Cmd(TIM3, ENABLE); }代码要点有三处。第一ADC工作在非连续转换模式每次转换由TIM3的TRGO触发定时器更新事件的频率就是采样率时间间隔由硬件保证比软件延时精确得多。第二DMA用环形模式缓冲区写满后自动回到起点继续写任何时候缓冲区里都是最近4096个采样点这就是预触发数据的物理基础。第三ADC_SampleTime_1Cycles5是F1系列最快采样时间加上12.5个转换周期总耗时约1.17μs上限约850kSPS代码里默认100kHz只是起点改TIM_Period即可换挡。目标采样率TIM_Period写入值适用时基说明1MHz020μs/div接近ADC上限需留余量500kHz150μs/div中等偏快档100kHz9100μs~1ms/div代码默认值20kHz4910ms/div慢扫档配合时基选择时有一个原则一屏10格每格至少要显示20个点所以采样率下限等于每格点数除以每格时间。这个关系下一章还要展开。3.2 在缓冲区里定位触发点DMA环形缓冲区是持续覆盖的显示前需要找到触发点把显示窗口的起点定在触发点之前若干个点这样画出来的波形才能稳定不跳动。下面这个函数在缓冲区里从最新写入位置向后搜索第一个穿过触发电平的采样点。#define PRE_TRIG_PTS 800 // 预触发点数显示起点从这里开始 // buf: DMA缓冲区 writeIdx: 最新写入下标 level: 触发电平(ADC码) edge: 0上升/1下降 int16_t SCOPE_FindTrigger(uint16_t *buf, uint16_t writeIdx, uint16_t level, uint8_t edge) { uint16_t hyst 8; // 回差8个LSB按3.3V/4096折算约6mV for (uint16_t i 1; i SAMP_BUF_LEN; i) { uint16_t prev (writeIdx i - 1) % SAMP_BUF_LEN; uint16_t cur (writeIdx i) % SAMP_BUF_LEN; if (edge 0) { if (buf[prev] level buf[cur] level hyst) return (int16_t)cur; // 上升沿穿过电平 } else { if (buf[prev] level buf[cur] level - hyst) return (int16_t)cur; // 下降沿穿过电平 } } return -1; // 整圈没找到需要调整触发电平 }这里用取模运算模拟环形索引从最新写入点往前找保证找到的是时间上最近的触发事件。回差与比较器一样重要搜索时要求当前值比电平至少高出hyst避免幅度在电平附近抖动时反复触发。找不到触发点时返回-1界面上应提示“触发电平超出范围”而不是画一条乱线。3.3 波形绘制与双缓冲刷新画波形的核心是把ADC码映射到屏幕坐标。以300×240的LCD为例横向10格每格30像素纵向8格垂直方向把0~4095映射到240行。若当前垂直档位是1V/div那么每格对应ADC码为4096/8512屏幕上一格正好是1V。// 从显示起点画pts个点每列一个像素 void SCOPE_DrawWaveform(uint16_t *buf, uint16_t startIdx, uint16_t pts) { float k 240.0f / 4096.0f; // ADC码到屏幕行的比例 for (uint16_t x 0; x pts x 300; x) { uint16_t val buf[(startIdx x) % SAMP_BUF_LEN]; uint16_t y (uint16_t)(val * k); if (y 240) y 239; // 防止越界 LCD_DrawPoint((uint16_t)x, 239 - y); // 屏幕顶部为0翻转y轴 } }绘制前务必把数据从DMA缓冲区复制到一块快照数组否则DMA持续写入会让同一帧数据首尾来自不同时刻波形出现撕裂。常见做法是开两块数组DMA先填A填满后切换填B同时CPU绘制A下次DMA回切到ACPU绘制B这就是乒乓缓冲。刷新频率按每秒10帧设置即可太快LCD控制器带宽吃紧反而挤占DMA的总线时间。4. 把时基和垂直档位做准参数映射与三个常见坑4.1 采样率与时基的映射关系时基档位、采样率、每格点数三者必须同步。设每格时间为T_div每格想要显示N个像素点那么采样率fs N / T_div。LCD宽度300像素每格30像素如果要求每格60到100个点采样率就是60到100除以T_div。点太少波形毛糙点太多低于ADC能力就只能切等效采样。下表以每格200个点为例列出常用时基对应的采样率以及一屏实际观察时长。时基档位每格时间采样率(200点/格)一屏时长20μs/div20μs10MHz200μs100μs/div100μs2MHz1ms200μs/div200μs1MHz2ms1ms/div1ms200kHz10ms10ms/div10ms20kHz100ms可以看到20μs/div和100μs/div要求的采样率超过了内部ADC能力这时候要切换到等效时间采样模式每触发一次采一个点。实现上只需在定时器中断里计数主循环查当前时基索引所需采样率超过850kSPS就置ET标志。注意档位切换时要先停ADC改完定时器重装值再启动避免切换过程中采出间隔不均的数据。4.2 垂直灵敏度的两点校准衰减电阻和反馈电阻都有误差运放还有输入失调直接按理论倍数算电压必定偏。所以装机后要做校准输入短路读一次AD值记offset输入一个已知直流电压例如用1%精度的分压电阻从5V基准得到2.000V再读一次AD值两点确定一条直线。// 两点校准把ADC码线性映射到实际电压 // v1/v2: 两次输入的已知电压(V)adc1/adc2: 对应ADC均值 void SCOPE_Calibrate(float v1, float v2, float adc1, float adc2, float *k, float *offset) { *k (adc2 - adc1) / (v2 - v1); // 每伏对应的ADC码 *offset adc1 - (*k) * v1; // 零偏对应的ADC码 }校准后显示电压按 voltage (adc_code - offset) / k 换算。每个垂直档位都要各自校一遍因为每档衰减比不同但校准时输入幅度不能超过该档满屏范围。校准数据用EEPROM或Flash保存开机时读出掉电不丢。两类错误很常见一是校准时输入电压精度不够直接带偏斜率二是只校了增益没校offset导致方波正负半周不对称。4.3 三个高频故障混叠、触发抖动和DMA总线争用混叠。采样率低于信号频率的2倍时屏幕上会出现一个频率不对的低频假波形而且换慢时基档位后假波形的周期跟着变。判别方法就是改档位看波形周期变不变变了说明在混叠。对策是实时采样模式只接50kHz以下信号更高频率切等效采样两档之间留有判断余量。触发抖动。等效采样对触发抖动极其敏感触发时刻抖一点重建波形就在水平方向多出几十纳秒的随机位移。抖动来源通常是比较器回差不足、触发电平落在信号边沿的非陡峭段、地线噪声。对策是加大回差到信号幅度的1%~3%触发电平尽量设在边沿中点比较器电源加去耦电容。DMA总线争用。LCD刷新和DMA搬运共用AHB总线绘图占用总线时采样数据到达内存的时间间隔会不均匀波形水平方向产生毛刺和压缩。对策是LCD刷新放在两帧采样的间隙做用乒乓缓冲把采集和显示解耦或者把DMA优先级提到Highest。观察毛刺是否随时间分布均匀就能判断是总线问题还是信号本身的问题。5. 用信号源方波自检测带宽、扫速和触发抖动5.1 用上升时间估算整机带宽方波边沿陡是检验带宽的现成工具。信号源输出1kHz、5Vpp的方波示波器设置为1V/div、100μs/div触发电平调到1.5V。读取波形从10%幅值上升到90%幅值的时间tr用经验公式估算带宽BW ≈ 0.35 / tr。实测上升时间估算整机带宽判断700ns约500kHz仅适合低频训练题350ns约1MHz达到基础要求175ns约2MHz前端和ADC都没拖后腿如果测出的上升时间明显慢于预期优先检查前级运放的增益带宽积和压摆率其次是ADC采样保持电路前的RC低通。测上升时间时要保证采样率足够否则测出来的是采样间隔而不是信号边沿这个数字没有意义。5.2 扫速、垂直刻度和触发稳定性的交叉验证扫速和时基是否一致用方波周期来验证。1kHz方波周期1ms时基设在1ms/div、一屏10格时应当刚好显示一个完整周期如果屏幕上周期跨了1.2格说明定时器分频和档位表对不上优先核对TIM_Prescaler和TIM_Period的实际写入值。垂直刻度用幅度验证5Vpp方波在1V/div档位上应当占据5格读到4.8格或5.2格时回到第4章的校准函数重新算k和offset。触发稳定性看上升沿的水平位置。把方波降到2kHz时基放到50μs/div用水平光标在上升沿位置来回测记录最大偏移。偏移超过半格说明抖动偏大。调节顺序是先动比较器回差再调触发电平最后检查触发信号的RC滤波不要一上来就改采样率那是另一个问题。每次改完运放反馈电阻、换过PCB走线或换了MCU引脚后把上述四项按顺序重跑一遍把这四个读数记在一张表里和上次对比哪一项变化异常就顺着对应环节去查这是整机调试里性价比最高的动作。本文还有配套的精品资源点击获取