ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于STM32的智能鱼缸系统设计:从传感器到自动控制的嵌入式实战

2026/8/31 14:44:33 拓冰建站 浏览量
基于STM32的智能鱼缸系统设计:从传感器到自动控制的嵌入式实战 简介本资源是一套完整的STM32嵌入式毕业设计项目面向电子信息、自动化及物联网方向的本科生与嵌入式初学者解决智能水产养殖系统中环境监测、自动调控与远程交互等典型工程问题。压缩包共98个文件含41个.h头文件定义传感器驱动与系统接口、38个.c源文件实现温度/TDS/水位采集、OLED显示、继电器逻辑控制、舵机喂食计时及ESP8266联网通信等核心功能另有电路图PDF、元器件清单Excel、Keil工程文件uvprojx、Hex可执行文件及实物接线参考图等结构清晰模块划分明确便于理解与二次开发。资源包仅604KB轻量实用已获82人学习下载。读者可直接获取从硬件选型STM32F103C8T6DS18B20TDS超声波ESP8266、原理图设计、全功能源码到手机APP联动的完整技术链涵盖阈值按键调节、蜂鸣报警、多路继电器协同换水、LED照明控制等真实场景逻辑是嵌入式课程设计与毕设落地的高复用性参考方案。1. 项目概述与设计思路1.1 这个智能鱼缸到底在做什么去年帮一位学弟做毕业设计题目就是STM32智能鱼缸。他最初的想法特别朴素——鱼缸里养了几条金鱼每次出门几天就担心没人喂食、水温突变把鱼搞死。后来我们把整个需求拆开发现这其实是一个典型的嵌入式综合应用项目包含了传感器采集、电机驱动、人机交互、定时控制等多个嵌入式必考点特别适合作为毕设或者竞赛练手项目。这个项目的核心功能大致可以归成四类环境监测实时采集水温、水位、光照强度可选等参数并在显示屏上呈现。自动控制温度低了自动加热水位低了自动补水饲料定时定量投放。交互配置通过按键或手机蓝牙/WiFi视扩展需求设定目标温度、喂食时间。告警保护水温越界、水位过低时发出声光报警防止设备空转烧毁。你看到的那个压缩包里装的正是这套系统的完整源码、电路图含原理图和PCB、元器件清单。我依自己的实际开发经验把从选型、画板、写代码到调试的全过程梳理一遍尤其重点讲那些学校里PPT上不怎么会说、但是实际动手时最容易踩坑的细节。1.2 为什么选STM32而不是51单片机先说结论这个项目选STM32F103C8T6不是因为它“高级”而是因为它刚好卡在“性能足够、学习成本不高、资料极多”的平衡点上。如果拿51单片机来做也能实现基本功能但有几个绕不过去的坎。首先是ADC精度51系列大多没有内置多通道ADC外接ADC芯片会增加电路复杂度和成本其次是定时器资源自动喂食的步进电机控制、水泵的PWM调速、DS18B20的单总线时序、按键扫描消抖这些全要占用定时器或外部中断资源51在这种多任务场景下要来回切换写起来非常痛苦第三是掉电存储喂食时间表和目标温度这类参数断电后要保住需要外挂EEPROM虽说不难但又是一堆额外工作。STM32F103C8T6这颗芯片72MHz主频64KB Flash20KB RAM自带3个USART、2个I2C、2个SPI、3个ADC共10个通道、4个16位定时器关键是它还内置了闪存模拟EEPROM的功能意味着参数保存这个需求可以直接用Flash的某几页去做不用专门挂芯片。这样一个项目最复杂的几块需求——ADC采集、PWM调速、定时控制、掉电保存——全部被这颗芯片的原生外设覆盖了软件写的顺畅硬件也能简洁不少。这里补充一个经验之谈毕设答辩时老师基本都会问“为什么选这款主控”。标准回答思路应该是从外设资源、开发成本、资料生态三个维度对比51和STM32再结合本项目需要什么外设来佐证。千万别只说一句“性能更强”那样显得像抄的。2. 硬件电路设计与元器件选型2.1 电路模块划分完整的智能鱼缸电路按功能拆开看大致是这几个模块主控最小系统STM32F103C8T6 晶振 复位电路 去耦电容 BOOT跳线。电源系统外部DC电源12V/2A适配器经降压芯片到5V和3.3V两级电压。温度采集DS18B20防水探头单总线协议。水位检测电极式或浮球式开关接到GPIO读取高低电平。加热控制220V加热棒或12V加热膜根据实际场景选择通过继电器通断。水泵控制12V直流潜水泵用MOS管做PWM调速或直接继电器通断。喂食机构步进电机或舵机带动储料仓旋转定时投放饲料。显示交互0.96寸OLEDI2C或TFT液晶屏 矩阵按键。告警模块有源蜂鸣器 LED指示灯。这套电路的难点不在单个模块而在模块之间的协调——电压域隔离、信号线长度、大电流负载对弱电信号的干扰。毕设层面不需要做军用级抗干扰但至少要做到继电器或电机动作瞬间OLED屏幕不乱闪、DS18B20读数不跳变。2.2 元器件清单与选型避坑压缩包里的元器件清单表我自己整理的时候是按模块分组的。这里挑几个最容易出问题的元器件讲讲选型逻辑和避坑要点。DC-DC降压芯片的选择电源是整个系统里“炸机”概率最高的部分。12V转5V建议用MP1584或LM2596模块而不是用7805线性稳压。原因很简单水泵和继电器这类负载在启动瞬间电流很大线性稳压的压差热量会非常可观12V降到5V1A电流就是7W热量散热片都压不住。所以12V转5V这一段用开关电源方案5V转3.3V这一段负载电流小主要是主控和外设用AMS1117-3.3线性稳压就够便宜、外围简单、纹波也能接受。选DC-DC模块时有一个容易忽略的点电感的额定电流。MP1584模块如果买的是便宜货电感规格经常缩水水泵启动瞬间电流一冲就容易饱和轻则输出电压跌落重则烧芯片。稳妥做法是选电感的饱和电流大于2A的版本。继电器 vs MOS管加热棒控制必须用继电器做物理隔离因为加热棒是220V交流负载。这里有两个要点第一继电器必须选带续流二极管的或者自己并在线圈两端并联一个1N4148/1N4007否则继电器释放瞬间的反电动势会打坏STM32的IO口第二STM32的GPIO驱动能力不够直接推继电器需要加一颗三极管如S8050来扩流或者用ULN2003这种达林顿阵列。水泵控制则建议用MOS管做PWM调速而不是继电器。继电器通断水泵每一次切换都是机械触点打火寿命和噪声都受影响。用一颗AO3400这种N沟道MOS管控制信号接GPIO注意共地水泵负极接MOS管D极S极接地G极通过10K电阻下拉。这样就能用PWM平滑调整水泵流量噪音小、寿命长还能实现水流大小的渐变。温度传感器选防水还是非防水DS18B18有两种封装一种是TO-92直插一种是带不锈钢探头的防水型。鱼缸项目必须用防水探头别想着用环氧树脂自己封实测下来自己封的探头热响应慢、防水可靠性差用久了还会渗水导致读数异常。防水探头要注意供电电压它本身支持3.0V-5.5V但不建议用5V供电然后引脚直接连STM32的PA口最好统一用3.3V供电数据线直接接GPIO即可省掉电平转换的烦恼。另外DS18B20的数据线需要接一个4.7K上拉电阻到VCC这个很多人会忘结果就是读不到温度或者偶发读错数据。2.3 原理图绘制注意事项原理图我用的是立创EDA嘉立创EDA理由是上手快、元件库全、能直接联动PCB打样对学生党友好。画图时几个注意点去耦电容的位置每个芯片电源引脚旁边加0.1uF陶瓷电容并且要尽可能靠近电源引脚放置。STM32的电源设计参考手册里要求VDD引脚加4.7uF0.1uF组合实际项目里至少也要每个电源域有一组。晶振电路8MHz晶振的两个负载电容选20pF左右走线要短。如果PCB布局紧张优先保证晶振靠近主控芯片不要在晶振下方走其他信号线。按键处理所有按键输入 GPIO 必须加上拉或下拉电阻并且软件消抖。有些同学觉得内部上拉就够了实际上在鱼缸这种湿度大的环境按键引脚的抗干扰能力要求更高建议外部加10K电阻。接口预留务必把SWD下载口PA13/PA14引出来哪怕用杜邦线飞线也要引。这个接口是DEBUG续命口没有它程序下载三次失败你就要拆芯片了。关于PCB布局我没法在博文里展开所有细节但有一个经验供参考电源模块、继电器、水泵驱动这类功率部分和主控、传感器这类弱电部分在PCB上要分区布局中间用地线或者大面积铺铜隔开。否则继电器吸合瞬间的电磁干扰可能让DS18B20的读数跳好几度排查起来极其头疼。3. 软件架构与代码实现3.1 程序整体框架软件部分的代码结构如果用一句话概括就是一个“超级循环 定时中断 状态机”的组合。这种结构虽然在嵌入式课程里已经被讲烂了但在这种中小型项目里它确实是最务实、最容易调试的选择。先说主循环main函数int main(void) { SystemInit(); delay_init(72); // 延时初始化 uart_init(115200); // 串口调试 DS18B20_Init(); // 温度传感器 OLED_Init(); // 显示屏 KEY_Init(); // 按键 Motor_Init(); // 水泵 Heater_Init(); // 加热 Feeder_Init(); // 喂食器 RTC_Init(); // 内部RTC uint32_t last_1s 0; uint32_t last_200ms 0; while (1) { // 每1秒刷新一次温度和水位信息 if (get_tick() - last_1s 1000) { last_1s get_tick(); temperature DS18B20_GetTemper(); water_level GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0); Check_Alarm(); // 超温/低水位告警 Display_Refresh(); // 刷新OLED } // 每200ms扫描一次按键 if (get_tick() - last_200ms 200) { last_200ms get_tick(); Key_Scan(); } // 喂食时间到就喂食 if (RTC_GetCounter() feed_time) { Feeder_Run(); } } }这里的延时和定时全都基于SysTick维护一个毫秒计数get_tick函数返回系统运行毫秒数避免在while循环里使用阻塞式delay让程序在等待某些事件时还能响应按键和告警。关于这个结构有两个值得说透的点。第一为什么不用RTOS实时操作系统。很多学生在毕设里喜欢上FreeRTOS觉得显得“高端”。但其实这个项目的任务并发度根本不高一个1秒周期的采集显示任务一个200ms周期的按键扫描一个按需触发的喂食动作。裸机超级循环完全能搞定反而更直观代码容易审查出bug好定位。如果答辩时老师问“为什么不用RTOS”老实的回答是任务调度周期和资源占用都是可控的裸机方案足够满足要求还可以减少系统复杂度和潜在的调度问题。这个回答是加分项因为它体现的是“选型考虑过选型有依据”而不是盲目追新。第二为什么用状态机思想管理告警逻辑。鱼缸的告警不是简单的“温度大于30度就报警”而是有回差控制的。比如目标温度26度加热到26.5度关掉加热低于25.5度重新开启中间这1度回差防止继电器频繁开关继电器机械寿命一般10万次频繁通断很快报废。这种带回差、带滞回的控制逻辑用状态机表达最清晰。3.2 温度采集与滤波DS18B20的驱动代码网上多如牛毛但不少代码有隐藏问题。这里我把我实测稳定的做法说一下尤其说三个常见的坑。坑一单总线时序必须关中断DS18B20是单总线协议时序要求比较高尤其读操作时每个时隙的采样点非常关键。如果在执行DS18B20读时序时来了一个中断哪怕中断只占几十微秒也可能错过采样窗口导致读到错误数据。所以驱动里经常要在读写函数里加“临界区保护”——进入时光标当前中断状态然后关闭中断读写完成后再恢复uint8_t DS18B20_ReadByte(void) { uint8_t data 0; __disable_irq(); // 关中断 for (int i 0; i 8; i) { data 1; DS18B20_DQ_OUT(0); // 拉低 delay_us(2); DS18B20_DQ_OUT(1); // 释放总线 delay_us(2); if (DS18B20_DQ_IN()) { data | 0x80; } delay_us(60); } __enable_irq(); // 恢复中断 return data; }注意不是简单的“关闭中断”就完了要在上下文中保存PRIMASK寄存器状态因为如果你的系统里还有别的关键代码比如喂食电机的脉冲输出开着中断这里不能粗暴地全部关掉要存旧值再恢复。坑二温度滤波别只取一次读数DS18B20的换算结果是12位精度按理说分辨率是0.0625°C但实际使用里读数在小数点后几位会轻微跳动而且偶尔会有明显的毛刺尤其是刚上电、水泵启动这些时刻。我的做法是连续采样5次去掉最大值和最小值剩下的3次取平均。这个滤波算法虽然朴素但在鱼缸这种温度变化缓慢的场景里效果非常好。float GetFilteredTemperature(void) { float samples[5]; for (int i 0; i 5; i) { samples[i] DS18B20_GetTemper(); delay_ms(10); // 相邻采样间隔大于时序转换时间 } // 选择排序去掉最大最小值 // ... 省略排序代码 return (samples[1] samples[2] samples[3]) / 3.0f; }坑三负温度处理如果鱼缸冬天气温很低水温可能到10度以下但一般不会到0度。不过DS18B20的负温度处理逻辑还是要写对否则在环境温度测试时冬天房间不开暖气可能出现读数为0或乱码。DS18B20的温度寄存器是16位带符号数要正确解析符号位if (temp 0x8000) { temp ~temp 1; // 取补码 negative 1; } temperature temp * 0.0625;3.3 自动喂食与电机控制喂食机构是这个项目里最“机构”味的部分也是实物演示时老师们最感兴趣的部分。它的本质是一个步进电机转N步带动机器内部一个带隔板的储料筒旋转隔板转一圈就把一小格饲料倒出去。步进电机我实测用28BYJ-48四相五线12V减速比1/64步距角5.625°/64便宜、稳定、力矩够用。虽然这个电机效率不高、转速慢但在喂食这种低速场景完全够用。ULN2003驱动板驱动它时控制逻辑其实就是四相八拍const uint8_t step_table[8] { 0x08, 0x0C, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09 }; void Stepper_Rotate(int steps, int delay_us) { int index 0; if (steps 0) { for (int i 0; i steps; i) { GPIO_Write(GPIOB, (GPIO_ReadOutputData(GPIOB) 0xFFF0) | step_table[index]); index (index 1) % 8; delay_us(delay_us); } } else { // 反向循环index递减 } }这里重点是喂食量校准。不同饲料的颗粒大小和密度差别很大如果直接固定“转两圈”有可能转出来一堆饲料把鱼撑死或者咔哒半天就出来三粒。我的调试经验是先在鱼缸外做空转实验把储料仓装满饲料记录半圈、一圈、一圈半、两圈各出来多少克饲料再根据鱼的数量和目标投喂量换算步数。这个标定数据建议做成一个可配参数存在Flash里而不是写死在代码里方便后续调整。还有一个实操细节28BYJ-48的减速比是1/64实际转一圈需要2048个步进脉冲8拍模式下。如果是转半圈就是1024步。按延时250us一拍计算转整圈大约需要0.5秒。这个速度人眼看着“咔咔咔”地转实测不会把鱼吓跑但如果你把喂食桶直接放在水面上方饲料掉落的“啪嗒”声倒是有可能引起鱼短暂的应激。建议出料口导向管倾斜一定角度让饲料沿管壁滑落而不是直接砸入水面。3.4 显示与按键交互显示我用的是0.96寸OLEDSSD1306驱动I2C接口。这个屏的优点功耗低、体积小、代码库成熟、接线只要4根线VCC、GND、SCL、SDA。缺点不能显示中文除非你刷字库内存又不够所以界面设计上我全用的英文和数字。OLED的刷新策略值得留意。这个屏是自发光每个像素都有控制开关管整屏全刷的电流会比较大刷新太快还会闪烁。我实测大概200ms500ms刷新一帧比较合适。不需要每帧都全屏清空再重画可以只更新数值变化的区域比如温度那行数字变了就只重画那个区域这样既省电又不会闪。按键部分最基本的3个键就够了模式键扫过参数项、加键、减键。实际上因为要配置的参数有目标温度、喂食间隔、喂食量我建议再加一个确认键做成“模式循环 调整 确认保存”的交互。这个交互逻辑也符合很多小家电的使用习惯作答辩时可以说“设计时参考了市面智能设备的通用交互模式降低用户学习成本”。按键消抖不能只靠延时。延时消抖会阻塞主循环导致OLED闪烁、或者温度采样被延迟。正确做法是用20ms定时中断扫描按键检测到电平变化后记录状态连续两次扫描都处于同一状态才视为有效按键动作。这种“非阻塞消抖”在主循环里更友好。3.5 掉电保存把参数写进Flash用户设置的目标温度、喂食时间表一旦断电不能丢所以需要一个掉电保存机制。STM32F103没有EEPROM但可以把地址空间里的Flash页当作EEPROM用。官方库有现成的Flash擦写函数但要注意两个问题Flash擦除是按页进行的F103C8每页1KB写之前必须擦除不能只写不改。Flash写入寿命大约是1万次擦写如果每改一次参数就擦写一次频繁修改会很快耗尽寿命。解决方式设置参数时先在内存里改只在用户按下“确认保存”后才写入Flash每次写入前检查一下有没有必要比如数值没变化就不写。每次开机时从Flash读取配置读取失败Flash全0xFF就用默认参数。这样既保证数据不丢又不会因为频繁擦写导致Flash提前报废。4. 电路图与PCB绘制实操4.1 从原理图到PCB的关键步骤压缩包里的电路图文件是立创EDA格式的。如果打算自己重新画一遍来练手我建议按这个顺序走先画电源部分输入电源座 → 保险丝 → DC-DC模块 → 5V电源轨 → AMS1117 → 3.3V电源轨。把电源搞定后续所有器件才能有“依据”。再画主控部分STM32最小系统包括晶振、复位、BOOT、去耦电容、SWD调试口。添加外设模块DS18B20接口3pin端子、水位传感器接口、继电器控制电路、MOS管驱动电路、OLED I2C接口、按键、蜂鸣器。整理连接关系用网络标签连接各模块不要在原理图上拉满交叉线可读性极差后期检查也痛苦。转为PCB布局优先把电源、继电器和主控分开然后按信号流向排放外围接口最后布线。PCB布线有一条很重要的规则电源线宽要足够。12V进线、5V电源轨这类线路至少1mm宽1盎司铜厚下大约能承受2A电流而3.3V逻辑信号线0.3mm就够。如果不加宽水泵启动时大电流会让电源走线上的压降明显导致3.3V供电跌到3.0V以下STM32直接复位。4.2 打样与焊接的实战建议用立创EDA画完可以直接在嘉立创下单打样10cm×10cm以内的小板子经常有优惠几块钱就能做5片适合学生党。GB文件下单时选沉金工艺如果预算允许对焊接更友好焊盘不易氧化。焊接顺序建议从矮到高先焊STM32主控TQFP480.5mm间距没有热风枪就用拖焊法加助焊剂用烙铁拖一圈。再焊电阻电容、晶振、按键这些小件。最后焊排针、接线端子、继电器这类大件。每焊完一个模块用万用表测一下电源和地之间有没有短路再继续焊下一个。焊完别急着上电。先用万用表电阻档测电源轨对地电阻确认没有短路。然后空载上电测5V和3.3V是否正常用手摸一下AMS1117、DC-DC芯片有没有异常发烫。确认正常后再插STM32用ST-Link连SWD口尝试连接芯片。如果连不上优先检查BOOT0是否接低正常运行时必须接地、SWDIO/SWCLK是否接反、芯片供电是否正常、复位脚是否被拉低。5. 常见问题与调试技巧实录5.1 典型问题速查表现象可能原因排查与解决ST-Link连不上芯片SWD引脚没引出/接反、电路无供电、BOOT0悬空或接高量供电、查BOOT0、确认SWD接线、必要时用USB转TTL串口ISP方式烧录温度读数一直为0或85DS18B20数据线没加上拉电阻、接线松、供电异常检查4.7K上拉、重新插拔探头、用串口打印原始ROM码确认设备存在温度读数比实际高好几度探头靠近加热棒/水泵、线缆受继电器干扰、滤波不足探测器远离热源、调整布局、加粗地线、程序里增加滤波继电器吸合瞬间OLED闪烁电源功率不足、纹波大、电源线太细换更大功率适配器、电源线加粗、继电器电源和控制电源分开走水泵噪音大或流量忽大忽小PWM频率不合适、MOS管驱动电压不足、供电不稳提高PWM频率到20kHz以上、确认G极在PWM高电平时能完全导通按键偶尔失灵没有消抖、按键接线下垂未接上拉软件消抖、外部加上拉电阻、按键另一端确认接GND掉电后参数丢失Flash地址冲突、写操作后没擦除、参数存储区被代码覆盖检查链接脚本/配置、确认Flash地址在应用代码区之后、加校验和喂食时电机卡住减速齿轮打滑、储料筒内饲料结块、力矩不足清理储料筒、校准步数、降低喂食频率、改用更大减速比电机5.2 调试利器串口日志我强烈建议在代码里留一个串口调试输出。USART1输出调试日志用USB转TTL转接器接到电脑上在串口助手工具里看运行状态。这样温度变化、按键事件、告警触发、喂食执行全都有据可查不用靠猜。实际调试时我常打的日志格式是[1200ms] TEMP26.31 HeaterON AlarmNONE [1400ms] KEYOK pressed, target temp set to 25.5 [3000ms] FEEDER run 1024 steps, feed1.2g这个习惯救了我很多次。比如继电器不动作一眼就能从日志里看到是不是程序压根没进判断分支水温读数异常也能看到是不是采集端问题。调试完毕设后串口日志不用删留着反而能当“运行状态说明”写进设计文档里。5.3 三个容易忽视的坑第一个坑是浮空输入的问题。水位传感器输出如果是高阻状态比如探头没有接触到水的瞬间GPIO配置成浮空输入会读到随机电平导致水位误判。正确做法是把GPIO配置成上拉输入逻辑改成“读低电平表示水位正常”也就是探头接触到水会把电平拉低。这样即使探针断开读到的也是高电平程序会判定为“缺水告警”而不是误报正常。第二个坑是继电器线圈续流二极管方向。续流二极管要反并联在继电器线圈两端即二极管阴极接电源正、阳极接驱动管集电极。如果焊反了轻则继电器不动作重则上电瞬间直接把三极管烧穿。这个真的见过不少次焊接的时候留意一下丝印方向。第三个坑是Flash擦写导致程序卡死。Flash操作期间CPU会暂停执行如果此时恰好来了中断尤其是一些需要实时响应的中断可能造成不可预知的行为。所以在执行Flash擦写时同样要加临界区保护并且擦写完成后要重新初始化RTC相关功能如果RTC使用了同一个Flash接口的LSE时钟操作后可能受影响。6. 写在最后一点实操体会这个项目做下来最大的感受是嵌入式项目里硬件和软件是互相约束的不能先画板子再写程序也不能先写程序再画板子。我在第一步就定了物料型号然后把每个外设的时序都过了一遍确认STM32F103C8T6的资源完全够用才回头画的原理图。这样代码库和外设驱动、电路图都能对上后期联调基本一遍过。如果你是自己复现这个项目有几点再强调一下。第一第一块板子别追求一次成功焊接要有耐心上电前用万用表多测几遍。第二程序模块化、加串口日志调试会比闷头写快十倍。第三这个项目的扩展空间非常大——往上加WiFi模块就能做成手机App远程监控的版本加TDS水质传感器就能增加水质告警功能加光敏传感器就能实现灯光自动控制。毕业设计做完基础功能拿到学位没有太大问题如果还想更完整往这些方向扩展就行。最后再说一个实际的小技巧。答辩现场的实物演示环节最好准备一个便携充电宝或移动电源给鱼缸供电以免现场找不到插座、或者教室里的插座接触不良导致临时掉电。演示前提前半小时调试好设备把鱼缸里的水放到合适水位确保温控、喂食、告警功能都能完整演示出来。这些小细节看似不起眼但现场演示顺利与否往往就取决于这些准备是否充分。本文还有配套的精品资源点击获取