ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于STM32的数据采集系统设计:从ADC采样到串口协议全解析

基于STM32的数据采集系统设计:从ADC采样到串口协议全解析 简介本资源是一套面向电子信息、物联网、自动化等专业本科生的高分单片机课程设计与毕业设计实践方案聚焦基于STM32的数据采集系统开发解决传感器信号采集、AD转换、实时显示与数据存储等典型嵌入式应用问题。压缩包共194个文件含43个头文件.h定义外设接口与结构体42个C源文件.c实现ADC采样、定时器控制、LCD显示及串口通信等核心功能辅以编译中间文件.o/.d/.crf、工程配置.uvproj/.uvopt、烧录镜像.hex及自动化脚本.bat完整覆盖Keil MDK开发全流程包体大小为4.71MB。已有133人下载学习资源源自答辩得分95分的校级优秀项目提供可直接运行的源码、详细教程文档及模块化工程结构便于理解底层驱动逻辑、快速复现系统功能或在此基础上扩展温湿度、压力等多通道采集需求。 每年到毕业季和课设提交期“基于STM32的数据采集系统”这类题目就会在各个技术社区刷屏。找我咨询这个题目的人特别多但大部分人的处境都差不多要么从网上下了一堆打包好的源码却跑不通要么跑通了但答辩时被老师问得哑口无言。这篇文章我想把我做一个完整数据采集系统的思路、器件选型逻辑、代码框架、调试踩坑过程以及最后怎么把项目资料整理得像模像样一次性讲清楚。适合正在做这个题目的在校学生也适合刚入门STM32想系统做一个小项目的开发者参考。1. 设计一个数据采集系统第一步不是写代码而是定需求1.1 从项目名称反推这个系统要干什么“STM32单片机数据采集系统”听起来很宽泛实际上不管题目怎么描述核心需求就那么几件事采集多路模拟信号经过MCU处理后把数据上传到上位机进行显示或存储。我在做这个项目时最早犯的错误就是一上来就翻数据手册、找例程结果半个月过去了还在看ADC的寄存器。后来冷静下来花了一天时间把需求拆清楚整个项目的开发效率反而高了很多。拆需求时我习惯问自己五个问题采集几路信号是电压、电流、温度还是其他物理量信号的幅度范围是多少是否需要信号调理电路要求的采样率是多少每秒采10次还是每秒采10万次这决定了用不用DMA、用不用定时器触发。数据传到哪PC上位机、手机APP还是云端供电方式是什么是USB供电还是独立电源供电这些问题看着很简单但直接把系统架构定死了。以我现在手上这套系统为例采集两路0-3.3V的模拟电压信号其中一路是温度传感器经调理后的电压加上一路通过SPI接口读取的数字温湿度传感器数据采样率定在每秒1000个点传输方式用串口连PC上位机。这套需求定位是入门级但功能完整的课设/毕设项目不追求极致性能但五脏俱全。1.2 器件选型主控、传感器、通信模块怎么搭配先把选型思路讲清楚这部分决定了项目后面做得顺不顺利。主控芯片我选的是STM32F103C8T6。这颗芯片几乎是国内开发者最熟悉的MCU价格便宜、资料多、教程多遇到问题随便一搜就有解决方案。虽然现在STM32G4、H7系列性能更强但对这个项目来说F103完全够用而且用最经典的芯片把系统做透反而比盲目上高性能芯片更能学到东西。传感器我的系统设计了两类输入信号。一路是电位器输出的0-3.3V模拟电压用来模拟真实的传感器输出信号方便演示和调试另一路是LM35温度传感器输出电压与温度呈线性关系10mV/℃0℃时输出0V。选择LM35而不用DS18B20就是为了同时覆盖模拟信号采集和数字信号采集两种形态。如果你要用真正的工业传感器比如PT100热电阻或4-20mA电流环变送器就需要在选型阶段额外考虑信号调理电路的设计这个我在后面硬件部分单独说。通信模块最稳妥的方案就是USB转串口也就是CH340或CP2102芯片的小板子直接连到STM32的USART1引脚。不推荐选用蓝牙或WiFi模块作为主通信方式因为无线传输的调试难度会成倍增加而且答辩现场很容易出现连接不稳定的尴尬情况。1.3 采样率与分辨率先算清楚再动手很多初学者不关心采样率觉得ADC配置好能出数就行。但数据采集系统的核心指标就是采样率和分辨率这两个参数决定了系统的应用边界。STM32F103的ADC是12位的也就是量程0-3.3V被分成4096份理论分辨率是3.3V除以4096约为0.8mV。这个分辨率对于大部分课设场景足够了。如果算上放大器噪声和参考电压波动实际有效位数大概在10-11位左右这个后面调试时候会验证到。采样率方面F103的ADC最高可以跑到1MHz采样时钟但注意这是指ADC转换本身实际系统采样率受限于信号调理电路的带宽、DMA传输速度以及MCU处理能力。我的系统定在每秒1000个点每个点同时采集两路ADC通道对F103来说非常轻松CPU占用率很低可以腾出时间处理通信和显示。这里我建议初学者设计时先算清楚这个账不要盲目追求高采样率否则后期会陷入各种瓶颈。注意如果要做FFT频谱分析或电机电流波形捕获这类应用采样率的要求会严格很多。这时建议直接用定时器触发ADC采样而不是用轮询或延时方式后者会产生严重的采样时间抖动导致频域计算结果失真。定时器触发这部分在软件章节详细展开。2. 硬件电路里最容易丢分的地方信号调理与电源设计2.1 模拟前端传感器信号进ADC之前的必经之路不少人做数据采集系统时直接把传感器输出线接到STM32的ADC引脚上这在小信号场景下是致命的。以LM35为例它在室温25℃时输出电压只有250mV而STM32的ADC量程是0-3.3V如果用满量程测量250mV只占ADC编码的不到1/13温控精度会非常有限而且信号极易被噪声淹没。因此我加了一级同相放大电路把LM35的输出放大10倍这样室温下信号变成2.5V左右能充分利用ADC的量程。运放选择了LM358虽然它带宽不高、噪声也不算最低但便宜、单电源供电、在低速采集场景下完全够用。需要注意的是同相放大电路的增益由电阻比值决定也就是1加上Rf除以Rin。我用了10kΩ和1kΩ的电阻组合增益为1加上10k除以1k等于11倍实际输出稍微比10倍高一点但正好留出了余量。LC或RC滤波电路也不能省。在每个ADC输入引脚前放一个100Ω电阻和100nF电容组成RC低通滤波器截止频率约为1除以2π乘以100乘以1e-7大约16kHz可以有效抑制高频干扰。这个设计看似简单但能明显降低ADC采样值的跳动幅度后面调试章节我会展示实测对比数据。2.2 电源与参考电压ADC跳动一半是供电惹的祸这个坑我踩得非常深。最初我直接用USB线给开发板供电ADC采集的数值跳动范围达到了十几个LSB也就是多达十几级的波动用信号发生器输入标准的1V电压读到的数值却一直在变化根本无法使用。排查了很久后才锁定问题根源——USB供电的5V经过LDO降到3.3V后纹波很大而这个3.3V直接作为ADC参考电压ADC基准本身不稳转换结果自然就跟着乱跳。解决办法将模拟电路和数字电路分开供电至少要在电路板上将模拟地AGND和数字地DGND单点连接避免数字开关噪声串入模拟回路。在STM32的VREF引脚F103C8T6没有独立VREF引脚它与VDDA在内部相连处加一个10uF钽电容和100nF陶瓷电容并联去耦这两个电容要尽量靠近MCU的电源引脚放置。如果追求更高精度可以外接一个REF3030或TL431基准电压源把参考电压做稳。虽然F103不能直接通过外部电压驱动内部ADC参考源但可以通过外部基准芯片接到VDDA上来间接改善。我在这套系统中经过上述处理后ADC跳动从十几个LSB降到了正负2个LSB以内效果非常显著。这部分经验值得所有做数据采集的人记下来。2.3 硬件连接检查清单画原理图和焊接前一定要检查这些点我每次做板子都会过一遍至少帮我避免一半以上的低级错误每个电源引脚旁都有100nF去耦电容引脚较远的再加一个10uF电容。复位引脚外部接10kΩ上拉电阻和100nF电容而不是悬空。BOOT0引脚通过10kΩ电阻下拉到地确保从主Flash启动。芯片的VDDA引脚必须接3.3V并加滤波电容而不是悬空。ADC输入信号电压不能超过VDDA否则内部保护二极管会导通轻则采集不准重则损坏引脚。SWD调试接口的SWDIO和SWCLK两个引脚不要复用做其他功能否则程序烧录到一半可能连不上调试器。3. STM32软件框架ADC多通道扫描与DMA的配合方式3.1 标准外设库、HAL库还是LL库软件部分第一个选择就是开发库。很多人纠结这个问题我的建议很简单如果老师没有特殊要求用HAL库STM32CubeMX初始化。理由有三生成代码快、配置外设时不容易漏掉时钟树设置、网上HAL库的教程数量已经超过了标准外设库。但HAL库也有让人头疼的地方比如初始化代码量大、回调机制不够直观、调试时看着层层封装有点懵。所以我自己的代码风格是CubeMX生成初始化代码然后在main函数或专门的任务函数里写业务逻辑核心的ADC采集和串口接收使用HAL库API但中断处理部分自己写逻辑。3.2 ADC多通道扫描DMA循环采样的配置思路先解释一下为什么必须要用DMA。如果不用DMA每采集一个ADC通道的数据CPU都要在ADC转换完成后去读数据寄存器这段时间里CPU不能做其他事。而使用DMA后ADC转换完成会自动触发DMA搬运把转换结果从ADC的数据寄存器搬到内存数组里全程不需要CPU干预。对于多路循环扫描来说DMA的循环模式还能自动循环填充缓冲区CPU只需要定期去读缓冲区的内容即可。在CubeMX中的关键配置如下ADC设为Scan Conversion Mode也就是扫描模式Enable。Continuous Conversion Mode设为Enable连续转换。Number Of Conversion配置为2因为我有两路模拟输入。在Rank下拉菜单中依次配置两个通道比如Rank 1选IN0Rank 2选IN1采样周期Sampling Time选择239.5 Cycles。为什么选这么长的采样周期因为输入信号源阻抗较大时ADC内部的采样电容需要足够时间充放电采样时间太短会导致精度下降。打开ADC的DMA请求Mode设为Circular循环模式Data Width设为Half Word。注意ADC1的数据寄存器是16位的而STM32的DMA传输宽度要匹配好否则数据会错乱。然后在代码中定义两个变量即可uint16_t adc_buffer[2] {0, 0}; HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buffer, 2);启动DMA后adc_buffer[0]永远是通道0的转换结果adc_buffer[1]永远是通道1的结果前提是配置DMA时数据长度要刚好等于通道数且ADC和DMA都工作正常。这里有个常见错误如果你的DMA配置成Normal模式一轮采集完成后DMA就停止了后续ADC数据不会自动搬运导致缓冲区长期停留在第一次采样的结果。我之前在这个问题上卡了整整一个下午。3.3 定时器触发采样为什么需要精确等间隔连续扫描DMA循环模式已经能自动采集数据了为什么还需要定时器触发这里面有一个关键概念叫等间隔采样。普通的连续转换模式ADC会等上一次转换结束后立刻开始下一次转换两次转换之间的时间间隔完全取决于硬件状态不是严格等间隔的。虽然大部分时候差别不大但如果你对采集的数据做FFT、滤波或者计算频率特征非等间隔采样会在频谱上引入不该有的噪声成分。定时器触发采样的原理很好理解定时器产生一个固定频率的更新事件这个事件直接通过硬件信号连接到ADC的触发输入端ADC在这个信号的上升沿开始一次转换于是每次采样的间隔就由定时器的精准时钟决定与软件执行时间完全无关。具体配置方法在CubeMX中选一个定时器比如TIM2配置为PWM Generation或Update Event模式频率设为1000Hz。然后在ADC配置里Trigger Source选择TIM2 Trigger Out。这样硬件会完成时间同步不需要在代码里去操作定时器。初始化时先启动定时器再启动ADC的DMA采集HAL_TIM_Base_Start(htim2); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buffer, 2);这里顺序很重要。必须先启动定时器否则ADC触发信号一直不会来DMA缓冲区就一直不会被更新。我调试的时候曾经把两行顺序写反结果采集到的数据永远是上一个周期的旧数据找了好久才意识到是触发时序的问题。4. 串口数据链路协议设计、空闲中断与环形缓冲4.1 数据帧格式怎么定采集到的数据最终要发给上位机显示或存储串口是最简单可靠的通道。但如果你直接把原始数据裸发出去接收端根本不知道哪两个字节是一帧遇到偶尔的字节丢失就可能整帧错乱。所以自定义一个帧协议是必须的。我的协议设计如下字段长度说明帧头2字节固定为0xAA 0x55用于识别帧起始命令字1字节0x01表示实时数据帧0x02表示历史数据查询长度1字节有效数据字节数数据N字节ADC通道值、传感器值等校验2字节CRC16对整个数据段做校验实际发送时我只发送帧头、命令字、长度、数据和校验。接收端收到0xAA 0x55后开始按帧格式解析读取长度字段等长度字段指定的字节数都收齐后计算CRC并与收到的校验值比对。比对通过就处理数据比对失败就丢弃这帧重新搜索帧头。CRC16的实现可以自己写查表法也可以直接用XMODEM的CRC16算法。我的建议是直接移植一个成熟的查表实现因为单字节计算法在中断里执行时间太长有可能影响下个字节的接收。4.2 HAL库串口空闲中断的用法传统接收串口数据的方式是一个字节一个字节地触发中断每个字节进一次中断CPU开销大不说处理帧还要自己判断数据什么时候结束。STM32的串口空闲中断可以很好地解决这个问题——它在一串连续数据结束后触发一次中断意味着你可以在空闲中断里把一整帧数据取走。在HAL库中可以使用HAL_UARTEx_ReceiveToIdle_DMA()函数启用空闲中断DMA接收模式。这个函数会在DMA接收期间检测总线空闲状态当发现空闲时调用空闲回调函数HAL_UARTEx_RxEventCallback。回调函数的参数里包含了本次实际接收到的数据长度因为DMA缓冲区的固定长度往往比分帧实际长度长而字段中的Size变量就是实际DMA搬运的字节数。我的配置是把DMA接收缓冲区设为256字节上位机每50ms发送一次查询请求每次收到约60个字节的一帧数据。当数据到达后串口空闲中断立刻触发回调函数我在回调里解析协议解析完一帧完整的命令后置一个标志位主循环检测到这个标志位后再去执行相应的动作。这样架构清晰中断里只做最快的事耗时操作全部放到主循环。4.3 环形缓冲区让接收和处理解耦如果你用过直接数组缓存串口数据的方式一定遇到过这样的情况数据来的速度比主循环处理速度快后到的数据把先到的数据覆盖掉了导致粘包、缺包。环形缓冲区就是解决这个问题的标准方法。简单说环形缓冲区就是一个带有读指针和写指针的数组。写指针由串口接收逻辑中断或DMA推进读指针由主循环的数据处理逻辑推进。当写指针追上读指针时说明缓冲区满当读指针追上写指针时说明缓冲区为空。关键在于读写指针的推进操作互不干扰接收方不会因为处理方慢而丢数据处理方也不会因为接收方快而读出旧数据。实际实现时要注意判断指针相等初始化时读写指针都指向0后续只有完全相等才表示空或满这个判断用减法比用相等更安全。我见过不少人在这个地方写错导致缓冲区明明没数据却一直在处理垃圾数据。5. 实测与排查ADC跳动、DMA错位、串口丢帧的完整处理过程5.1 第一个问题ADC采样值为什么一直在跳现象用电位器输入一个稳定的1.5V电压万用表测量纹丝不动但串口上报的ADC值却在1800到1950之间大幅波动。排查链路第一步确认信号源是否真的稳定。我用万用表测量了电位器输出端确认电压稳定在1.5V排除了外部信号源的问题。第二步检查ADC配置。采样时间从239.5Cycles改到最长问题依旧说明不是采样时间不足。第三步怀疑参考电压。用示波器测量VDDA引脚发现上面叠加了大约200mV的噪声纹波。问题找到了不是ADC本身坏了而是参考电压不干净。解决方案在VDDA与地之间加一个10uF电容和100nF陶瓷电容同时把模拟部分和数字部分在PCB上做好单点接地。处理后重新测试ADC值波动范围从±75降至±3。这对一个12位ADC来说已经是相当不错的结果了。这个问题的教训是数据采集系统里ADC只是一个转换器它不会平白无故准确需要硬件电路配合才能发挥性能。出了跳动问题先量参考电压这是最快的排查路径。5.2 第二个问题DMA缓冲区里的数据为什么会错位现象两路ADC通道输入不同的电压理论上缓冲区[0]对应通道0[1]对应通道1。但实际读取时发现两个通道的数据偶尔会发生交换表现为通道0的数据突然变成了通道1的值然后下一轮又恢复正常。排查链路一开始我怀疑是DMA配置有问题仔细检查后确认传输宽度、地址长度都对得上ADC通道数。后来想到可能是ADC的扫描顺序和DMA缓冲区索引之间的关系没搞明白当ADC完成通道0的转换后DMA搬运它的结果到adc_buffer[0]然后ADC开始通道1的转换转换完成后DMA搬运到adc_buffer[1]。这个流程本身没问题问题出在该读取缓冲区数据的时刻ADC可能刚好正在进行下一轮转换。如果我在ADC正在转换通道0的时候去读adc_buffer[0]读到的是上一轮数据如果这个时刻刚好在DMA搬运的间隙缓冲区中某些通道的数据就是旧值看起来就会错位。解决方案在主循环里读取数据时先暂停ADC的DMA传输HAL_ADC_DMAStop此时ADC仍在转换但不会有新数据写入缓冲区读取缓冲区内容后再重新启动DMA。或者更优雅的办法是开启DMA传输完成中断在每次整轮传输完成时拷贝缓冲区内容到安全区域。这里我推荐后者因为频繁停启DMA会丢失时间基准。5.3 第三个问题串口丢帧与中断优先级设置现象串口助手显示的数据帧偶尔会缺少末尾几个字节或者出现一帧数据被分割成两部分接收的情况。排查链路串口波特率115200每个字节传输时间约86.8微秒数据帧总共60字节总耗时约5.2毫秒。我怀疑某个中断处理时间过长导致串口接收数据期间没有及时取走DMA缓冲区的数据新来的数据把旧数据覆盖了。查看代码后发现我在串口中断回调里做了一件特别耗时的事把接收到的数据复制到一个较大数组里时使用了memcpy处理了整段缓冲区而且这段代码是在中断上下文内执行的。对于60字节的数据量memcpy本身不算慢但我在中断里还做了协议解析和命令执行这就大大超过了串口接收的时间窗口。修复方案中断回调函数里只做标志位置位和必要的数据搬移绝对不做协议解析。把协议解析放到主循环处理读取到标志位后再去解析数据。检查中断优先级将串口全局中断优先级设为当前最高优先级数字最小保证接收不被其他中断抢占。加上这几个修改后串口丢帧问题彻底消失。这里要特别强调中断服务函数应该越短越好这条规则几乎所有入门教程都说了但真正遇到问题的时候才会深刻理解。6. 项目资料整理与答辩展示源码之外要掌握的东西6.1 代码注释与文档决定项目能打多少分很多同学的技术水平不差但项目资料一塌糊涂代码没有注释文档没有目录老师拿到手里都不想翻。我的经验是评分的差距往往不在功能实现上而在文档规范和表达逻辑上。整理项目资料时至少包含这几个文件README.md概述整个项目包括系统截图、功能列表和环境依赖。让人一眼看出项目做的是什么。系统框图用Visio或draw.io画清楚传感器到MCU到上位机的数据流向附上硬件引脚连接说明。硬件设计文档原理图、PCB布局图、关键器件选型理由、信号调理电路的计算过程。软件设计文档模块划分、关键函数说明、数据帧协议定义、流程图。代码注释方面每个.c源文件顶部写清楚模块功能、作者、日期、修改记录。每个函数上方用两三行注释说明输入参数、返回值和功能。关键算法处至少写清思路不用堆砌注释但核心逻辑必须有解释。6.2 上位机演示与波形展示一个能呈现数据的上位机界面比十页文字说明都管用。最简单的方案是用Processing或Python的pyqtgraph库写一个串口画图程序实时读取串口数据并显示波形。这样答辩时评委能看到实时变化的曲线比空口说“我的采集系统工作正常”有说服力得多。如果时间和精力不允许也可以用串口助手把数据记录到CSV文件再导入Excel画图。虽然不如实时波形炫酷但至少能展示数据的完整性和准确性。我当时用Python写了一个40行的实时绘图程序效果非常好具体代码网上有大量现成例子稍微改改就能用。6.3 老师必问的几个问题务必提前准备答辩时老师一般不会问过于刁钻的问题但有几个方向是必问的你的ADC采样率为什么这么定——要能从理论依据出发说清这个采样率能满足奈奎斯特采样定理且高于信号最高频率的2倍最好留2-5倍余量。用了DMA有什么好处——要答出CPU解放、批量搬运、与ADC硬件协同等几个关键点。如果采集到的数据跳变很厉害有哪些原因——这个问题我在第5部分已经详细讲过了能回答出参考电压不稳、信号源阻抗过大、采样时间不足、数字噪声耦合这四点就能拿到高分。为什么选这个传感器它的输出范围是多少——要能快速报出传感器的量程、灵敏度、输出范围和供电电压。这些问题的共同点是考察你是否真的理解了系统设计过程而不是只会烧录代码。把本文前面几节的原理吃透答辩就不是问题。最后说一个我在整个项目中体会最深的事数据采集系统看起来只是“读几个ADC值再发给串口”但真正做下来硬件、软件、通信协议、调试工具、上位机、文档组织每个环节都不能掉链子。如果你正卡在某个环节不要急着怀疑代码先从硬件供电和信号质量查起再回到软件逻辑。硬件基础稳了软件才有意义。本文还有配套的精品资源点击获取
返回列表