简介:面向全国大学生电子设计竞赛选手及指导教师,这是一份围绕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 | ±80mV | 1:1 | ×20 | ±1.6V |
| 100mV/div | ±400mV | 1:1 | ×4 | ±1.6V |
| 500mV/div | ±2V | 5:1 | ×4 | ±1.6V |
| 2V/div | ±8V | 10:1 | ×2 | ±1.6V |
| 10V/div | ±40V | 50: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 // 例如 duty=500,触发电平约1.65V void TRIG_SetLevel(uint16_t duty) { if (duty > 1000) { duty = 1000; } TIM_SetCompare1(TIM2, duty); // ARR=999时,占空比 = 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写入值 | 适用时基 | 说明 |
|---|---|---|---|
| 1MHz | 0 | 20μs/div | 接近ADC上限,需留余量 |
| 500kHz | 1 | 50μs/div | 中等偏快档 |
| 100kHz | 9 | 100μs~1ms/div | 代码默认值 |
| 20kHz | 49 | 10ms/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/8=512,屏幕上一格正好是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回切到A,CPU绘制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/div | 20μs | 10MHz | 200μs |
| 100μs/div | 100μs | 2MHz | 1ms |
| 200μs/div | 200μs | 1MHz | 2ms |
| 1ms/div | 1ms | 200kHz | 10ms |
| 10ms/div | 10ms | 20kHz | 100ms |
可以看到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引脚后,把上述四项按顺序重跑一遍,把这四个读数记在一张表里和上次对比,哪一项变化异常,就顺着对应环节去查,这是整机调试里性价比最高的动作。
本文还有配套的精品资源,点击获取