ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

STM32电压电流采集与LCD波形显示实战详解

STM32电压电流采集与LCD波形显示实战详解 简介基于STM32的信号参数测量与波形显示系统源自2014年全国大学生电子设计竞赛D题面向电子工程与嵌入式开发初学者适合作为毕业设计、课程设计或综合实践的基础方案。方案以STM32微控制器为核心完成模拟信号高精度采样、电压电流实时测量并在液晶屏上呈现时域波形结合快速傅里叶算法生成频域谱线设计声反馈振荡检测与数字陷波滤波实现自适应抑制完整覆盖硬件架构、数据采集与频谱分析流程。资源包共308个文件以C语言源码和头文件为主辅以启动文件、工程配置与备份以及编译好的HEX固件压缩后仅1.01MB。文件结构清晰便于直接导入Keil工程进行学习与二次开发。当前已有73人学习使用内容包含完整项目源码、工程配置及算法实现可帮助读者快速理解STM32的ADC采集、LCD驱动、FFT频谱分析和数字滤波等关键技能是电子设计与竞赛准备中一份值得参考的实践资料。 做电压电流采集加波形显示听起来像是个毕业设计题或者实验室的小项目实际做下来你会发现真正花时间的不是接线和调库而是把采样链路里的精度问题、屏幕刷新逻辑和调试环境理顺。这套基于STM32的系统核心就干两件事用ADC把电压电流变成数字量再在LCD上把这些数字量画成波形。适合正在做课程设计的学生、刚入门嵌入式想做个完整小项目的开发者也适合工厂里想做简易功率监测但预算有限的一线工程师参考。先说结论这个项目的技术门槛其实不高STM32的ADC加上一块SPI接口的LCD屏就能跑起来但要做到波形不闪烁、数据不跳变、长时间运行稳定里面的细节非常多。我把我实际调试过程中的完整方案、参数计算和踩过的坑都整理在下面照着做能省你至少两三天时间。1. 项目整体思路与硬件选型1.1 标题里的“采集”到底在测什么电压电流采集通俗说就是用STM32内部自带的模数转换器ADC去读取外部模拟电压信号。电压采集相对直接用电阻分压把被测电压降到STM32能承受的0到3.3V范围就行电流采集则绕一道弯多数方案是先把电流转成电压再送进ADC。常见的做法有两种一是串联一个毫欧级采样电阻用运放把电阻两端的微小压差放大二是直接用霍尔电流传感器模块比如ACS712系列输出已经是一个与电流成正比的电压值。波形显示则是把连续采样到的数据点映射到LCD屏幕的坐标轴上让原本枯燥的数字变成肉眼可见的曲线。这要求采样率要够、屏幕刷新要快否则波形要么糊成一片要么像幻灯片一样卡顿。整个系统的核心链路就是信号输入 - 信号调理 - STM32 ADC采样 - 数据处理 - LCD绘制波形。1.2 电压采集分压电阻的数学账分压电路是电压采集的基础但很多初学者容易在这里犯迷糊。假设你要测0到24V的直流电压STM32的ADC输入范围是0到3.3V那就需要把24V按比例缩小到3.3V以内。这里有个经验值我习惯把最大测量电压对应的ADC输入电压控制在3.0V左右留出10%的余量防止过压烧引脚。计算过程并不复杂。已知最大输入电压Vin_max24V期望ADC引脚最大电压Vadc_max3.0V则分压比KVadc_max/Vin_max3.0/240.125。选择上分压电阻R1100kΩ下分压电阻R2R1×K/(1-K)100k×0.125/0.875≈14.3kΩ可以取标称值15kΩ。这时候实际分压比是15/(10015)0.1304最大输入电压对应ADC引脚电压是24×0.13043.13V仍在3.3V安全范围内。另一个容易忽略的问题是电阻阻值本身不能太大。STM32的ADC内部有采样电容外部信号源阻抗过高会导致采样电容充不满从而产生采样误差。100kΩ级别的分压电阻虽然功耗低但对ADC的驱动能力是个考验。我的做法是在分压网络和ADC引脚之间加一级电压跟随器用LM358或TL072这类通用运放做缓冲阻抗问题就彻底解决了。1.3 电流采集采样电阻加运放与ACS712模块的取舍电流采集的方案选择直接决定成本和精度。采样电阻加运放的方式成本低且带宽高适合做高频电流波形观察。比如测0到5A电流选用10mΩ的精密采样电阻满量程压降为5A×0.01Ω50mV这个信号太小必须放大。用轨到轨运放比如LMV358设置放大倍数为50倍输出就是0到2.5V刚好匹配ADC范围。ACS712模块则省去了放大电路的设计它的典型灵敏度是66mV/A以ACS712-05B为例5A满量程输出是0.33V实际上模块内部已经把静态输出电压偏置在VCC/2≈1.65V。它的优点是即插即用缺点是带宽只有80kHz测50Hz工频电流完全没有问题但如果你想觀察更高速的电流变化它就不太够用了。我在这个项目里最终选了采样电阻加运放的方案原因很简单一是精度可以自己通过校准电阻调整二是这个方案对理解模拟信号链路更有帮助。如果你手头有ACS712模块直接用也是可以的后续代码里只需要调整换算系数。2. STM32 ADC采样核心细节2.1 ADC通道配置与多通道切换STM32系列芯片的ADC资源差异较大以常见的STM32F103C8T6为例它有两个12位ADC共16个外部通道。对于电压和电流两路信号可以分别使用ADC1的通道0和通道1这样硬件上天然隔离互不干扰。配置时我用标准外设库代码如下ADC_InitTypeDef ADC_InitStructure; GPIO_InitTypeDef GPIO_InitStructure; // 使能时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE); RCC_ADCCLKConfig(RCC_PCLK2_Div6); // 72MHz / 6 12MHz ADC时钟 // 配置PA0和PA1为模拟输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, GPIO_InitStructure); // ADC1配置 ADC_InitStructure.ADC_Mode ADC_Mode_Independent; ADC_InitStructure.ADC_ScanConvMode ENABLE; // 扫描模式多通道必须开启 ADC_InitStructure.ADC_ContinuousConvMode DISABLE; ADC_InitStructure.ADC_ExternalTrigConv ADC_ExternalTrigConv_None; ADC_InitStructure.ADC_DataAlign ADC_DataAlign_Right; ADC_InitStructure.ADC_NbrOfChannel 2; ADC_Init(ADC1, ADC_InitStructure); // 通道顺序和采样时间 ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5); ADC_Cmd(ADC1, ENABLE);采样时间这里选用55.5个周期对应12MHz的ADC时钟大约是4.6微秒。如果只测低频信号其实20个周期左右就够了但采样时间长一些能有效降低噪声刚做出来的板子不建议一上来就用最快采样速度。2.2 采样时间与等效输入阻抗的“隐形坑”前面提到外部信号源阻抗的问题这里展开说。STM32的ADC核心是一个逐次逼近型转换器内部有一个约4pF的采样电容。每次转换前采样开关会闭合一段时间让电容充电到输入电压。如果外部源阻抗过高充电时间常数就大采样电容上的电压可能还没充到实际值就被断开了结果就是转换结果偏小。计算一下假设外部源阻抗R_source10kΩ采样电容C_sample4pF时间常数τ10k×4pF40ns。55.5个采样周期在12MHz下是4.625μs约等于115个时间常数电容基本能充满。但如果你直接把100kΩ分压电阻接到ADC引脚τ就变成400ns仍能充满可如果采样时间只设1.5个周期125ns就明显不够了。我的经验是分压电阻总阻值控制在20kΩ以内或者加一级运放跟随器配合至少20个周期的采样时间测出来的电压基本不会飘。此外在ADC引脚对地并联一个0.1μF的滤波电容能明显降低高频噪声对采样值的影响代价是信号响应变慢但对应50Hz工频信号完全无感。2.3 DMA与数据搬移为什么不直接读数据寄存器很多人图省事循环里直接调用ADC_GetConversionValue()读数据这在单通道低频采集时问题不大。但当采样率提高到每秒几千次CPU就被困在等待转换和搬数据上了根本腾不出手去刷新LCD。正确的做法是开启DMA让ADC转换完成后自动把结果搬进内存数组全程不需要CPU干预。DMA_InitTypeDef DMA_InitStructure; uint16_t adc_buf[2]; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); DMA_InitStructure.DMA_PeripheralBaseAddr (uint32_t)ADC1-DR; DMA_InitStructure.DMA_MemoryBaseAddr (uint32_t)adc_buf; DMA_InitStructure.DMA_DIR DMA_DIR_PeripheralSRC; DMA_InitStructure.DMA_BufferSize 2; DMA_InitStructure.DMA_PeripheralInc DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize DMA_PeripheralDataSize_HalfWord; DMA_InitStructure.DMA_MemoryDataSize DMA_MemoryDataSize_HalfWord; DMA_InitStructure.DMA_Mode DMA_Mode_Circular; DMA_InitStructure.DMA_Priority DMA_Priority_High; DMA_InitStructure.DMA_M2M DMA_M2M_Disable; DMA_Init(DMA1_Channel1, DMA_InitStructure); DMA_Cmd(DMA1_Channel1, ENABLE); ADC_DMACmd(ADC1, ENABLE);这里的DMA_Mode_Circular循环模式非常关键它会让DMA自动回绕持续不断地把最新采样值更新到adc_buf数组里。主循环里只需要定期读取adc_buf[0]和adc_buf[1]就能拿到最新的电压和电流数据CPU大部分时间都用在LCD绘制上。3. LCD波形显示的实现要点3.1 屏幕选型SPI屏还是并口屏LCD屏的选型直接影响刷新速度和接线复杂度。市面上常见的小尺寸屏有两种接口SPI串口屏和8080并口屏。SPI屏引脚少只需四根线SCL、SDA、CS、DC但刷新速度受限于SPI时钟通常2.4寸的SPI屏全屏刷新一帧要50毫秒以上。并口屏数据线16根加上控制线占用的IO很多但刷屏速度能快到20毫秒以内。对波形显示这种需要频繁局部刷新的场景SPI屏完全够用因为我并不需要全屏重绘只需要擦掉旧波形、画上新波形每次操作量很小。我用的是一块1.8寸的ST7735驱动屏幕SPI时钟设到36MHz实测局部刷新一块160×80区域的波形肉眼感知不到闪烁。3.2 波形图不是“画图”而是坐标变换把ADC采样值显示成波形本质上是一个坐标换算的过程。屏幕的Y轴对应电压值X轴对应时间或采样点序号。ADC是12位的取值范围0到4095换算成电压的公式是float voltage (float)adc_buf[0] * 3.3f / 4095.0f;但屏幕显示不能直接用这个电压值得把它映射到屏幕的Y坐标。比如LCD显示区域宽度是128像素高度是160像素我计划把波形区域设为高度120像素顶部留20像素显示文字信息。那么Y坐标就是uint16_t y WAVE_TOP (uint16_t)(voltage / VREF_MAX * WAVE_HEIGHT);这里VREF_MAX是你要显示的最大电压如果设定为24V那么24V对应Y轴的底部0V对应顶部。注意LCD左上角是原点Y轴向下增长所以电压越大Y坐标反而越大这一点容易搞反。我一开始就没加这个补偿波形是镜像的后来用软件取反才解决。3.3 波形滚动与刷新率优化波形滚动显示有几种策略整屏平移、环形缓冲区写点、或者只显示最新的一段采样数据。整屏平移在SPI屏上开销很大因为要频繁读取和重写显存。我用的是环形缓冲区加局部刷新思路是这样的ADC连续采样每采到一个点就存进一个长度为128的数组数组满后从头覆盖。在显示循环里每次只擦除上一帧波形所在的点再绘制当前帧的新点。// 伪代码简化显示逻辑 for (int x 0; x 128; x) { uint16_t y_old y_values[x]; // 旧坐标 uint16_t y_new get_adc_scaled(x); // 新坐标 draw_pixel(x, y_old, BG_COLOR); // 擦除旧点 draw_pixel(x, y_new, WAVE_COLOR); // 绘制新点 y_values[x] y_new; }这段逻辑在主循环里跑得非常快因为SPI屏支持单点写入每次操作不过是写两个字节的命令加两个字节的数据。再加上把采样率控制在每秒500点到1000点波形既平滑又不至于让CPU吃不消。这里有个优化技巧不要每采一个点就刷新一次屏幕而是攒够128个点、凑满一屏再刷。这样刷新率等于采样率除以128大约是4到8帧每秒肉眼看起来是流畅的实时波形。如果觉得卡就把采样点数降到64或者只显示波形区域的一半宽度。4. 开发环境与调试坑实录4.1 环境搭建从0到1的完整路径这套系统我用的是Keil MDK加STM32标准外设库开发虽然现在HAL库是主流但标准库资料多、代码直观用来学习ADC和DMA的工作流程更友好。新建工程时有一个关键点必须在Device选项里选择正确的芯片型号然后从标准库的库函数文件夹里把对应外设的源文件加进工程。很多人第一次建工程时报错找不到头文件多半是没在C/C选项卡的Include Paths里添加库头文件路径。还有一个平台通吃的坑Keil MDK 5默认是不兼容C51工程的如果你之前学过51单片机电脑上可能装了C51版本的Keil直接打开STM32工程会报一堆莫名其妙的错误。解决办法是安装Keil MDK后再装一个C51支持包或者在电脑上共存两个版本、分别建立快捷方式。4.2 ST-Link连接报错no stm32 target found这个问题大概能排进STM32初学者十大噩梦之首报错信息为Error: No STM32 target found如果你用ST-Link下载程序时遇到它先冷静排查确认ST-Link的SWDIO、SWCLK、GND三根线是否连对目标板是否供电正常。多数情况下是杜邦线接触不良或者目标板没上电。如果硬件没问题再看看Keil的Debug选项卡里是否选择了ST-Link Debugger以及Utilities选项卡的Flash Download设置是否正确。更隐蔽的情况是目标芯片的SWD引脚被代码复用成了普通GPIO。比如GPIOA的PA13和PA14默认是SWDIO和SWCLK如果初始化代码把它们重新配置为推挽输出芯片就没法和调试器通信了。解决办法是用ST-Link Utility的Connect Under Reset模式按住目标板复位键再连接通常能救回来。建议在项目早期就禁用JTAG只保留SWDGPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);4.3 虚拟串口叹号与延时函数卡死如果你打算用STM32的USB虚拟串口把采样数据实时传到电脑大概率会遇到设备管理器里出现黄叹号的问题。这不是硬件坏了而是驱动没安装或者固件里的USB描述符不匹配。下载ST官方STM32 Virtual COM Port驱动重新安装即可。要注意的是如果固件里定义的USB PID和驱动程序不匹配同样的驱动也装不上。还有那个经典问题延时函数delay卡死。用标准库的朋友如果直接复制网上的延时函数而不配置SysTick定时器程序就会死在Delay内部。我的习惯是优先使用DWTData Watchpoint and Trace模块做微秒级延时它不需要占用定时器外设。如果你的代码卡在某一行while循环先查中断优先级是否配置正确再查时钟初始化是否完整这两个原因占了九成。5. 常见问题与排查技巧速查现象可能原因排查思路ADC读数始终为0通道未使能或引脚配置错误检查GPIO模式是否为模拟输入ADC通道号是否对应采样值跳变严重电源纹波大或采样时间过短增加滤波电容增大ADC采样周期LCD白屏SPI引脚初始化错误或复位时序不对检查CS、DC、RST引脚电平状态确认屏幕供电电压波形显示方向反了坐标变换未取反检查Y轴计算电压越大Y值应越大下载程序时提示找不到目标SWD引脚被复用或接线不良尝试Connect Under Reset检查杜邦线再补一个我做这个项目时踩过的坑LCD极化和视角问题。屏幕的偏光方向决定了最佳可视角度如果画出来的波形颜色淡或者闪烁不一定是代码问题也有可能是屏幕的对比度寄存器配置不对。ST7735这类屏上电后需要发送一组初始化命令来设置偏压和显示方向直接用厂家给的参考代码最稳妥不要自己折腾显示方向的旋转参数。另外电源设计很重要。STM32和LCD共用一个USB供电时如果电流采集的是电机这种感性负载启动瞬间的电流尖峰会导致屏幕闪屏。我的做法是用一个独立的AMS1117-3.3稳压给STM32和LCD供电采样电路的参考地单独走线回电源地信号地和功率地单点连接波形瞬间干净了很多。想把这个项目继续扩展的话有两个方向可以考虑一是加一个ESP8266模块把采样数据通过MQTT上传到手机端做远程监控二是把数据存储到SD卡实现长时间记录后再离线分析。核心的ADC采集和LCD显示框架都可以复用改动的工程量不大。本文还有配套的精品资源点击获取
返回列表