免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

基于STM32F103的模拟示波器设计与实现:从ADC采样到LCD波形显示

基于STM32F103的模拟示波器设计与实现:从ADC采样到LCD波形显示 简介基于正点原子战舰版与TFTLCD3.5英寸屏实现的模拟波形示波器工程面向嵌入式初学者和电子爱好者用于学习STM32外设操作、模拟信号采集与实时波形显示。压缩包共171个文件约4.81MB核心包含大量.c/.h源码如lcd.c、stm32f10x_adc.c、stm32f10x_tim.c、Keil工程构建文件uvprojx/uvoptx以及编译生成的hex、axf、o、d、crf等文件便于直接打开工程查看、编译或烧录验证。资源围绕战舰版板载ADC、定时器与LCD驱动展开覆盖波形采集、信号处理、按键交互、菜单显示等关键环节同时附带Administrator配置、keilkilll清理脚本和.map/.htm等辅助文件目录结构清晰。目前已有1352人学习浏览非常适合希望在真实开发板上动手实践示波器原理、深入理解嵌入式驱动与数字信号处理流程的开发者参考使用。 做嵌入式开发这几年我有一句很深的体会学单片机和学车很像倒车入库练得再熟练也不敢说自己真会开车。真正的门槛在于把几个独立的基础模块串成一个完整系统。断断续续折腾了一段时间我基于正点原子战舰版开发板做了一个模拟波形示波器从ADC采集、DMA传输、定时器触发到LCD波形绘制把STM32F103ZET6这颗芯片的核心外设串了个遍。这个项目非常适合刚学完基础外设、想进入“裸机综合项目”阶段的嵌入式爱好者也适合正在准备毕设或者电赛作品的同学参考。所谓模拟波形示波器本质上就是用STM32以固定频率对输入模拟信号进行采样然后把采样点按时间顺序绘制到LCD屏幕上配合网格、触发、时基调节这些功能模拟出传统示波器的显示效果。硬件成本极低一块战舰版就能跑通全流程。接下来我按项目从设计到调试的完整链路把思路、原理、代码框架和踩坑经验都梳理一遍。1. 项目整体设计与思路拆解1.1 为什么选正点原子战舰版做示波器选战舰版而不是Mini板或者F4系列关键原因是“资源刚好够用资料足够齐全”。战舰版的核心是STM32F103ZET6主频72MHz512KB Flash64KB SRAM内置3个ADC、2个DAC、8个定时器板载2.8寸TFTLCD和多个按键。用这块板子做示波器所有硬件模块板载就有不需要额外搭复杂的外围电路。很多人一开始觉得F103最高1M采样率太低做不了“正经示波器”。这话对也不对。1M采样率面对音频信号、传感器输出、PWM波、通信波形这些常见调试场景完全够用。一个项目最重要的是先跑起来再谈性能。战舰版的资料开放程度也高例程覆盖了ADC、DMA、定时器、LCD这些关键模块移植和查资料的成本很低。1.2 整机架构定时器触发ADCDMA搬运数据CPU只管画图示波器的数据链路看起来很直接但实现方式决定了稳定性。轮询方式采集ADC最蠢CPU会被采样占满波形容易丢点中断方式稍微好点但频繁进中断会影响显示刷新。我最终采用的是典型的“定时器触发ADC DMA自动搬运 双缓冲切换”方案。STM32的ADC支持外部触发定时器产生更新事件后会自动启动ADC转换转换完成后由DMA将结果搬到内存数组。这个过程中CPU基本不参与实现真正的“采样与计算并行”。我用TIM2做主触发定时器ADC1的通道对应引脚接输入信号DMA1通道1搬运数据全程不占用CPU时间主循环只负责把最新一帧数据画到LCD上。层级关系可以简化为物理层输入信号经引脚进入ADC板载按键作为交互入口LCD显示波形驱动层定时器配置、ADC配置、DMA配置、LCD驱动应用层数据采集管理、触发检测、波形绘制、时基和幅值调节1.3 功能规划先做减法再做加法我对第一版的需求做了一个严格的功能减法先不追求多通道、不追求FFT、不追求波形存储只做四件事——实时显示波形、自动量程到3.3V内、上升沿触发、按键调节时基。把这条最小路径跑通之后再逐步加触发调节、频率显示、多通道切换。减法思维很重要。见过太多人一开始就想做“万能示波器”结果卡在DMA中断里半个月出不来。嵌入式项目的核心法则是“功能越少越容易做稳”先把采集-显示这条主干打通后续所有扩展都是锦上添花。2. 核心细节解析与实操要点2.1 ADC配置分辨率、采样时间和参考电压STM32F103的ADC是12位逐次逼近型量程默认0V到3.3V参考电压就是VREF引脚上的电压。战舰版直接接在3.3V电源上所以输入信号必须限制在0~3.3V之间超出会削波甚至损坏引脚。这对示波器来说是个大限制我第一版就直接标称“0~3.3V量程”后续可以加电阻分压或运放调理电路扩展。采样时间也直接影响等效输入阻抗和采样率。ADC完成一次转换的时间 采样时间 12.5个ADC时钟周期。我把ADC时钟设为12MHz采样时间选1.5周期单次转换约1.17us配合定时器触发可以达到约850kHz的采样率。想提高采样率就得加采样保持电路但在板上练习阶段1M以内的采样率足够暴露和解决绝大多数问题。关键配置代码如下ADC_InitTypeDef ADC_InitStructure; RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADC时钟 72MHz / 6 12MHz ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode DISABLE; ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_T2_CC2; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 1; ADC_Init(ADC1, ADC_InitStructure);注意ADC_ExternalTrigConv_T2_CC2这个配置意思是ADC由TIM2的捕获/比较通道2事件触发这一点是实现“固定频率采样”的核心。2.2 定时器触发如何决定一屏多少个点示波器显示的横轴是时间采样点必须等间隔。用延时轮询ADC做不到严格的等间隔而定时器触发可以做到微秒级的稳定间隔。我选了TIM2作为触发源通过调节定时器分频和自动重装值来快速改变采样率对应示波器上的“时基SEC/DIV”功能。一屏显示多少点需要事先规划。战舰版LCD分辨率是240x320我按横向320点显示波形采样缓冲区定为1024个点显示时每3个采样点取1个等效刷新一屏需要约341个原始采样点。1024深度的缓冲还留出了软件触发的“预触发”空间可以显示触发点之前的波形。时基调节的本质就是改变定时器的触发频率。比如采样率设为100kHz时1024个点表示约10.24ms的数据显示区横轴320分10格每格约3.2ms即3.2ms/div。采样率降到10kHz每格变成32ms/div就能观察更慢的信号。2.3 DMA与双缓冲不让采样阻塞显示DMA搬运数据最容易踩的坑是“边搬边读”。如果CPU在DMA写入数组的同时去刷新LCD会读到一半旧数据一半新数据的“撕裂波形”。我的处理方式是开2KB的双缓冲DMA半传输中断和全传输中断交替切换读/写缓冲区保证CPU永远只读“写满的那一半”。双缓冲的关键配置是DMA的半传输中断标志位。开启DMA传输后第一次完成前512次转换触发半传输中断CPU处理缓冲A继续完成剩余512次转换触发全传输中断CPU处理缓冲BDMA自动循环交替触发这样采样持续不间断CPU有整整512点的时间窗口去刷新LCD时间上非常充裕。2.4 波形显示网格、基线、实时刷新LCD画波形不能直接把点连成线就完事那样满屏都是杂散点。我的绘制流程分三层第一层静态网格。横向10格纵向8格用画线的细线绘制模拟示波器屏幕的网格刻度第二层波形曲线。每两个相邻采样点之间做线性插值计算对应的LCD坐标然后画线第三层文字信息。在屏幕顶部显示当前采样率、触发电平、电压范围等参数有个细节显示刷新不要清屏清屏整个LCD区域会导致严重闪烁。我采用“局部清除”策略先以背景色重新绘制上一帧波形所在位置的直线用线段覆盖再绘制新波形这样人眼几乎看不出闪烁。2.5 触发功能找到波形的起点模拟示波器如果没有触发波形会在屏幕上左右乱跑很难观察稳定波形。实现触发的方式有两种硬件触发和软件触发。我在第一版用的是软件触发因为实现简单且不增加硬件成本。软件触发的思路是在采集到的数据中找到第一个“从低于触发电平变为高于触发电平”的点作为波形的显示起点然后从这个起点开始显示固定长度的数据。这样每次刷新时波形起点都会对齐在信号的同一个相位看起来就是稳定的。当然软件触发有局限触发位置只能落在已采样的数据区间里但1024点缓冲给了足够的搜索空间。3. 实操过程与核心环节实现3.1 整体代码框架与文件规划项目不是从零裸写的我基于正点原子的标准例程做了裁剪。main.c 负责初始化外设和进入主循环adc.c 负责ADC和DMA配置timer.c 负责触发定时器lcd.c 延续正点原子的显示屏驱动scope.c 是核心逻辑触发检测和波形绘制。main函数主循环是一个典型的状态机int main(void) { delay_init(); LCD_Init(); KEY_Init(); ADC_DMA_Init(); TIM_Trigger_Init(100000); // 初始采样率100kHz Scope_Init(); while (1) { if (dma_half_done_flag || dma_full_done_flag) { dma_half_done_flag 0; dma_full_done_flag 0; trigger_process(); // 找触发点 draw_waveform(); // 画波形 scan_key_adjust(); // 按键调时基 } } }3.2 信号源没有外部信号发生器怎么测做示波器第一件要解决的事是“拿什么信号测试”。如果你手头有信号发生器当然最好但没有也不要紧。STM32F103内置DAC可以用另一路定时器触发DAC输出正弦波或三角波把输出引脚直接连到ADC输入引脚就构成了一个完整的自测回环。我用TIM4的更新事件触发DAC查一个256点的正弦表DMA循环搬运来产生约1kHz的正弦波。把PA4DAC_OUT1用杜邦线连到PA1ADC1通道1马上就能在LCD上看到稳定正弦波。这套自自测方案在我调试时帮了大忙后面排查问题也全靠它。3.3 采样率参数的计算时基调节显示的时间范围是通过分频和重装值算出来的。假设定时器输入时钟为72MHz预分频Prescaler设置为71则计数频率为1MHz。如果自动重装值ARR设为99则触发频率为1MHz/10010kHz每1024个点采集102.4ms的数据每格显示10.24ms。实时调节时基的做法是在按键响应中直接修改ARR并重新初始化定时器然后用预分频值保持计数频率不变。注意重新配置定时器时先停止TIM_Cmd防止配置过程中出现误触发。3.4 绘制代码的坐标变换细节波形数据到屏幕坐标的变换是图形显示的核心。ADC值是12位的范围0~4095对应0~3.3V。LCD坐标Y轴是0~320屏幕顶部Y值小电压高的点反而要在屏幕上方这个方向关系很容易搞反。我的变换公式很简单uint16_t x (uint16_t)(index * WAVE_DISPLAY_WIDTH / WAVE_SAMPLES); uint16_t y (uint16_t)(WAVE_DISPLAY_TOP (4095 - adc_value) * WAVE_DISPLAY_HEIGHT / 4096);其中WAVE_DISPLAY_WIDTH是320WAVE_DISPLAY_HEIGHT是240。先算X再算Y然后相邻两点之间调用画线函数波形的连续性和平滑度才有保障。4. 常见问题与排查技巧实录4.1 波形剧烈闪烁甚至花屏这是第一次跑通后遇到最头疼的问题。原因是每帧都全屏清除再重绘LCD总线程负担太重。换局部清除之后基本解决。另一个隐藏原因是DMA还在搬运数据时LCD读取的数据总线被占用适当提高LCD的CS片选切换优先级可以缓解。这里有一个判断技巧花屏出现频率和采样率是否相关如果采样率调高时花屏更严重说明是数据总线冲突如果固定每秒闪一下更像是DMA中断和显示刷新抢CPU时间。4.2 波形顶部和底部被削平很大概率不是程序bug是输入信号超出ADC量程。STM32的ADC输入引脚不支持负电压如果信号有交流成分ADC测不到负半周显示出来就是顶部削平的失真波形。我加了简单的外部偏置电路用两个电阻把输入信号叠加到1.65V中点电压让交流信号在0~3.3V内完整显示。这是第一版里最值得做的硬件改动之一。4.3 波形不稳定、左右漂移触发功能没生效的时候波形就会左右乱跑。排查时先在LCD左上角打印当前触发点位置如果每次采集触发点index都不同说明阈值判断条件有问题。常见错误是大于小于号搞反了或者没考虑信号在阈值附近反复穿越的情况。我加了一个简单条件必须连续两个点都高于触发电平才确认触发防止噪声导致误触发。这个“去抖”思路和按键消抖一脉相承效果立竿见影。4.4 按键调时基时波形短暂中断早期版本修改定时器参数后直接从头开始采样导致波形在一两帧内出现空白。解决办法是保留上一次采样的波形数据在时基切换后的第一个刷新周期只显示新的网格和旧数据等新数据填充后再切换显示。用户体验上几乎察觉不到切换过程。5. 踩坑后的几点扩展建议项目跑到稳定状态之后我开始考虑加功能。最容易见效的三个方向第一个是增加触发电平调节旋钮用ADC通道2采集一个电位器电压作为动态触发电平比固定电平实用得多。第二个是增加频率自动测量用定时器输入捕获功能测量信号的周期换算成频率显示在LCD上。这也是示波器的一个标志性功能。第三个是增加PC通信把采样数据通过串口发到电脑用Python的matplotlib实时绘图。这样可以绕开LCD分辨率的限制观察更精细的波形细节。战舰板的串口和USB转串口都是现成的接线方便。如果想把采样率再往上提可以考虑两个方向一是优化ADC采样时间到最小同时降低LCD刷新密度二是参考正点原子F407系列的做法把数据搬运和显示彻底解耦用更快的FMC总线刷屏。我个人的体会是这类综合项目真正的难点不在某个具体外设怎么配置而在于把外设之间的时序关系理清楚。定时器发出触发脉冲的频率是多少DMA搬运16位数据需要多少总线周期LCD从一个点画到另一个点要多久这些时间参数环环相扣理解得越透调起bug来就越快。最后再分享一个小技巧调试示波器项目的时候不要一上来就接真实信号先用DAC输出固定频率的正弦波自测。这个回环链路清楚、信号稳定排除了外部干扰之后再用外部信号源或者传感器信号做进一步验证。从自测到实测误差来源一步一步收敛问题定位就变得非常快。本文还有配套的精品资源点击获取
返回列表