☰
基于STM32的本地版智能鱼缸设计:从硬件选型到状态机控制实战
2026/11/1 22:24:54 网站建设 项目流程

简介:基于STM32的智能鱼缸项目资料包,面向嵌入式初学者与DIY爱好者,解决居家养鱼过程中水温检测、水质检测、自动/手动换水、氛围灯变换、自动/手动喂食等实际控制需求。包内共176个文件,以STM32的C源码、H头文件、Keil工程文件为核心,辅以设计文档PDF/Word、实物接线图JPG、烧录工具EXE及部分调试中间文件,压缩包整体约63.54MB,目录结构清晰,可按模块快速查阅。目前已有5569人学习/下载,可用于毕业设计、课程实践或产品原型开发。资料覆盖硬件选型、传感器数据采集、步进电机投食、RGB灯控制、电动马达换水到系统联调完整链路,并附有实物演示视频与配套工具,方便快速复现和二次开发。 养了半年鱼,死了一缸又一缸,最后我实在绷不住了,决定自己做一个基于STM32的智能鱼缸,而且明确要求“本地版”。为什么是本地版?因为我不想再被手机App绑定,家里Wi-Fi一断,鱼缸控制也跟着断,这事太蠢了。一个鱼缸而已,它的控制逻辑本来就该在本地闭环完成,STM32完全扛得住,没必要把数据绕一圈云端再回来。

这个项目适合谁?很简单:正在学STM32、想做课程设计或毕业设计的同学,以及真的花钱买了鱼却总养不活的懒人。它既能锻炼外设驱动、状态机设计、传感器采集、执行器控制这些基础能力,又能让你实实在在得到一个能自动喂食、自动调温、自动开关灯的“不用伺候的鱼缸”。如果你之前只做过点灯、跑马灯这类板级例程,那这个项目就是一次典型的“把点灯升级成一个完整系统”的实战机会。

1. “本地版”到底意味着什么:不联网也能全自动

先掰扯清楚“本地版”这三个字。市面上大量智能鱼缸方案都愿意往“云版”靠,加一块ESP8266或者ESP32,搞个MQTT、配个App,看起来高级。但代价是系统复杂度暴涨:要处理网络重连、云平台鉴权、协议解析,而且一旦断网,鱼缸就变成“智障鱼缸”——连定时喂食都做不到。

本地版的思路完全不同。所有传感器数据在STM32内部完成采集和处理,所有控制决策也由STM32自己做出,水泵、气泵、加热棒、喂食器、补光灯全部由板载逻辑直接驱动。你不需要App,不需要路由器,甚至不需要手机。鱼缸就是一个独立运行的嵌入式设备,上电就干活,断电再上电照样恢复工作。

我当时定的功能清单是这样的:

  • 实时水温检测,低于阈值自动开启加热棒,高于阈值自动关闭,采用滞回控制避免继电器频繁动作;
  • 定时/手动双模式喂食,用舵机旋转出料仓,一次投喂量可调;
  • 水位监测,水位过低时禁止水泵工作,防止水泵空转损坏;
  • 光照控制,每天按时间段自动开关LED补光灯;
  • OLED屏幕显示实时温度和运行状态,按键手动切换参数。

这套功能拆开看都不难,难的是把它们组合在一起还能稳定跑几个月不出幺蛾子。这正好就是“本地版”的意义:所有逻辑都在自己手里,出了问题可以排查,可以改进,而不是对着一个云平台日志发呆。

提示:如果你是毕业设计选题,我强烈建议在论文标题里加上“本地版”这个限定词。它一方面缩小了系统边界,另一方面也体现了设计的独立性,比满大街的“基于STM32的物联网智能鱼缸”更容易做出深度。

2. 硬件选型与电路设计:别把钱花在花里胡哨的地方

智能鱼缸的硬件方案其实非常成熟,难点全在组合和取舍。

2.1 主控选型:为什么用STM32F103C8T6

这个项目用STM32F103C8T6就完全够了,不需要上F407、H743。原因很简单:鱼缸控制涉及到的外设无外乎ADC、GPIO、定时器、I2C/SPI、UART、PWM,这些C8T6全都有,64KB Flash跑裸机程序绰绰有余,72MHz的主频处理温度采样和控制逻辑也毫无压力。

如果手头实在没有C8T6,用F103ZET6、F407VET6也完全没问题,代码层面几乎不用改多少,无非是个别引脚重映射而已。但注意,开发板最好选带板载ST-Link接口的最小系统板,调试烧录体验会好很多。我自己用的是一块普中或者正点原子那种通用型最小系统板,加一个ST-Link V2下载器,Keil MDK环境,整条链路稳定可靠。

2.2 传感器与执行器选型对比

这是整个项目中真正需要花心思的地方。我对比过不少方案,表格里是最终结论:

功能方案选择替代方案选择理由
水温检测DS18B20防水探头NTC热敏电阻、DHT22DS18B20单总线协议简单,防水探头直接扔水里,精度±0.5°C,读取稳定
水位检测不锈钢电极 + 电压比较器超声波测距、浮球开关简单可靠成本低,但要注意电极表面电解问题,建议用低压交流或降低采样频率
光照检测光敏电阻(光敏电阻模块)BH1750数字光照传感器光敏电阻模块带比较器和电位器,输出数字高低电平,代码最简单
水泵5V/12V小型潜水泵直流泵 + L298N小缸用5V泵配NMOS开关足够,功耗低不漏电
加热棒220V加热棒 + 继电器12V加热膜(养小鱼缸用)大功率就必须走继电器隔离,务必注意安全
喂食器SG90舵机 + 3D打印/塑料瓶出料仓步进电机舵机体积小、控制简单、扭矩够用

2.3 电源与IO分配

电源部分我最开始走了弯路,直接用USB线给5V水泵和STM32板共用一个5V电源,结果水泵一启动,单片机就重启。原因很简单,水泵启动瞬间电流冲击大,5V电压被拉低,STM32掉电复位。后来我把电源分成两路:12V/2A适配器作为系统总电源,降压到5V给水泵和舵机,再通过AMS1117-3.3降到3.3V给STM32和传感器。一个电源模块分离功率地和逻辑地,问题就消失了。

IO分配建议如下,这个分配表是我实际验证过的,继电器、蜂鸣器、信号线之间没有冲突:

外设引脚说明
DS18B20PA1单总线,接4.7k上拉电阻到3.3V
水位电极PA2ADC采样输入,通过分压电路接入
光敏模块PA3数字输入,模块上自带电位器调阈值
OLED I2CPB6/PB7SCL/SDA,标准I2C
舵机PA8定时器1_CH1输出PWM,频率50Hz
加热继电器PB0高电平触发继电器模块
水泵继电器PB1高电平触发继电器模块
LED补光灯PB10高电平开启,P-MOS或者继电器均可
按键1PA11进入设置菜单/确认
按键2PA12参数调整/退出
蜂鸣器PB3报警输出,接三极管驱动

注意:继电器模块要买带光耦隔离的低电平或高电平触发版本,并且线圈两端并联一个1N4007二极管吸收反向电动势,否则继电器吸合瞬间的电磁干扰可能直接让STM32的ADC采样数据飘掉。这个坑我后面还会细说。

3. 软件架构与核心控制逻辑:从裸机循环到状态机

系统功能不复杂,软件架构也别搞复杂。裸机大循环加定时器中断就够了,不需要上FreeRTOS。原因很实在:鱼缸控制是慢速过程,温度变化以分钟计,水位变化以秒计,舵机动作偶尔发生,多任务实时性要求极低。引入实时操作系统反而增加优先级配置、信号量交互的麻烦,调试成本翻倍。

3.1 主循环与定时器的分工

主循环里做这么几件事:读取按键状态、更新OLED显示、状态机的运行。而所有需要精确计时的任务都用定时器中断完成。

我用了TIM2作为1ms时基,定义了一个软件计数器数组tick[N],每个任务模块各自递减计数。比如水位检测每500ms执行一次,温度采集每2秒执行一次,OLED刷新每200ms一次。这样一来,主循环永远不会被传感器延时卡住,按键响应也快。

温度采集要重点说一下。DS18B20是单总线协议,时序要求比较严格,如果直接在主循环里读温度,传感器转换期间(通常需要750ms)整个循环就卡住了。我的做法是在定时器中断里只触发温度转换,设置一个标志位,主循环检测到转换完成后再去读取。或者更省事一点,直接把读DS18B20的函数放在主循环,但关闭定时器中断防止时序被破坏,读完再恢复。这两种方式我都试过,推荐第一种,代码更优雅。

3.2 状态机:把手动和自动模式织在一起

整个控制逻辑我用一个简单的状态机组织起来:

  • 状态A:自动运行模式,系统根据传感器数据自动控制各执行器;
  • 状态B:参数设置模式,用户可调整目标温度、喂食时间、光照时段等;
  • 状态C:手动强制模式,临时手动开关水泵、加热棒,方便换水冲洗鱼缸。

按键1用于在自动模式和设置模式之间切换,按键2在参数项之间移动和调节数值。状态切换的核心代码如下(浓缩版):

typedef enum { AUTO_MODE, SET_TEMP_MODE, SET_FEED_TIME_MODE, MANUAL_MODE } SysState; SysState state = AUTO_MODE; void StateMachine_Run(void) { switch (state) { case AUTO_MODE: AutoControl_Run(); if (key1_short_press()) state = SET_TEMP_MODE; if (key1_long_press()) state = MANUAL_MODE; break; case SET_TEMP_MODE: if (key2_short_press()) SetTargetTemp(GetTargetTemp() + 0.5); // 上调0.5度 if (key1_short_press()) state = SET_FEED_TIME_MODE; break; // 其他状态类似 } }

按键消抖不能偷懒,我采用的是“消抖标记 + 定时器扫描”的方式:定时器每10ms扫描一次按键,连续3次电平一致才认为有效,按下和弹开都做过滤。这个处理看似简单,但实际使用中能省掉大量玄学问题——鱼缸附近有水汽,湿度大,按键引脚的ADC和GPIO误触发概率比干燥环境高得多。

3.3 温度滞回控制与投喂逻辑

加热控制直接靠“低于阈值就开、高于阈值就关”会有个大问题:继电器会频繁吸合,机械寿命迅速下降,而且水温和设定值之间会产生振荡。我用了标准的滞回控制:

#define HYSTERESIS_VALUE 0.8 // 回差0.8度 if (temp <= targetTemp - HYSTERESIS_VALUE) Heater_On(); else if (temp >= targetTemp + HYSTERESIS_VALUE) Heater_Off();

这样加热器启动和停止之间有一个死区,水温会在24.6°C到25.4°C之间自然波动,继电器一个小时内只动作几次,而不是每分钟都在咔哒咔哒响。

投喂逻辑我一开始想得太复杂,研究过不同时间点喂不同量、周末跳过喂食等等。后来发现实用场景里根本不需要,一个最基本的“每天固定投喂1-2次,每次舵机旋转180度”就够用了。用STM32内部的RTC模块做时间基准,如果板子没有外接电池,断电后时间会丢失,这时候就退化成“上电后以当前累计时间为基准的相对时间”模式。如果想省心,加一个DS3231 RTC模块,断电时间也不丢,价格也就几块钱,我是强烈建议加的。

3.4 水位联锁保护

水泵空转是鱼缸损坏的头号杀手。我的逻辑是:水位电极检测到低水位信号之后,不论自动还是手动模式,都强制关闭水泵,同时蜂鸣器报警。只有水位恢复正常并且确认解除报警之后,水泵才允许重新启动。

这个逻辑排布在状态机的最外层,相当于一个安全联锁。代码上我单独写成了SafetyCheck()函数,放在主循环最前面。别小看这个函数,鱼缸漏水、水泵坏掉、水位传感器误报等各种异常场景,全靠它兜底。嵌入式开发里,功能逻辑可以出问题,安全逻辑必须可靠,这是我很坚持的一点。

4. 踩坑记录与排查经验:这些问题实测必现

这个项目前后调试了一个多月,记录下来的坑不少。挑几个最典型、也最可能复现的写出来,按频率从高到低排。

4.1 继电器一吸合,STM32就重启

现象:打开加热继电器或者水泵继电器的瞬间,OLED闪一下,系统重启。

排查过程:刚开始以为是代码问题,加了一堆延时、关了中断,仍然会重启。后来用示波器看3.3V供电,发现继电器吸合瞬间有一个明显的电压跌落尖峰到2.5V以下,直接触发STM32的掉电复位。

解决方法:给继电器模块单独供电,地线单点汇接;继电器线圈两端并联续流二极管1N4007;在STM32的3.3V引脚旁加100uF电解电容和0.1uF瓷片电容;水泵两端并联一个104瓷片电容吸收火花干扰。经过这几步处理后,基准电压再也没跌破过3.2V。

注意:很多人买继电器模块时只关注触发电压高电平低电平,忽略了继电器吸合时产生的巨大电动势噪声。凡是电感负载,必须加续流二极管,这个习惯一定要养成。

4.2 DS18B20隔一会儿就读取失败

现象:温度显示偶尔变成85°C或者空白,程序卡在读温度函数里。

原因分析:85°C是DS18B20上电时的默认温度值,出现这个值说明传感器复位了。最常见的原因是单总线没有接上拉电阻,或者上拉电阻过大。数据线过长且与电源线走在一起,也会干扰时序,导致CRC校验不过。

解决做法:DS18B20数据线接4.7k电阻拉到3.3V,线长控制在30cm以内,并且不要和继电器输出线绑在一起。如果必须走长线,数据线用双绞线并在传感器端多加一个104电容。另外代码里读温度一定要做CRC校验,失败就丢弃本次数据,继续用上一次的有效值,不要因为一次读取失败就卡死主循环。

4.3 水位检测电极被电解腐蚀

现象:水位电极用了两周,表面出现一层黑绿色物质,检测失灵。

原因:不锈钢电极通直流电放在水里,本质上就是一个电化学腐蚀装置。

解决:我尝试过几种方案,最有效的是降低电极两端电压差和采样频率。具体做法是用一个PWM引脚输出1kHz方波,经过RC滤波后变成交流小信号,但代码比较复杂。更简单的替代方案是直接用双探针检测模块,或者干脆用常开型浮球开关。浮球开关没有电解问题,安装也简单,就是体积略大。考虑到鱼缸美观和使用难度,我个人更推荐浮球开关作为水位检测,除非你真的很享受焊电极的乐趣。

4.4 光敏模块在夜间误触发

现象:半夜LED灯突然亮起来,早上又自己灭了,像是闹鬼。

原因:光敏模块的电位器阈值调得临界,周围有一点杂散光或者阴影变化就会翻转。

解决:将阈值固定后,在软件里加入一段时间延时确认,也就是光敏信号连续保持30秒才认为光照状态真的改变了。这种“去抖延时”的思路在处理所有传感器瞬时变化时都好用,鱼缸里水流波动、水面反光、水草晃动都会造成干扰,没有延时确认机制,必出乱子。

我整理了一个常见问题速查表,照着排查能省去大量时间:

问题现象可能原因解决方案
系统上电后不运行电源功率不足、BOOT0不为0检查适配器电流,C8T6最小系统BOOT0接地
OLED显示花屏I2C地址错误、接线松动、电平不匹配确认地址0x3C或0x3D,上拉电阻4.7k
舵机抖动PWM周期不是50Hz、电压不稳TIM配置周期20ms,占空比2.5%-12.5%,供电与逻辑隔离
定时喂食时间不准RTC没有后备电池扩展DS3231模块,断电时间不丢失
ADC采集数值跳动大参考电压不稳、采样引脚悬空加滤波电容,采用DMA多通道连续采集+滑动均值滤波
下载程序失败驱动异常、供电不足、调试口被禁用选“SWD”模式,按住复位键再点下载,必要时全片擦除

5. 成本核算与项目结构:这个方案到底值不值

老规矩,列一组成本数据。2025年的元器件价格波动不大,给一个参考区间:

器件型号/规格参考价格(元)
STM32F103C8T6最小系统板非官方,带ST-Link接口15-25
ST-Link V2下载器山寨版即可10-15
DS18B20防水探头1米线3-5
5V小型潜水泵3-5W8-15
220V加热棒50W小型15-25
继电器模块光耦隔离高电平触发5-10
SG90舵机180度5-8
OLED显示屏0.96寸I2C8-12
光敏电阻模块带比较器2-4
浮球开关常开型2-5
电源适配器12V/2A10-15
各类线材/杜邦线/洞洞板-10-20
合计-约100-150

比起买一个市售智能鱼缸(动辄四五百块),这个方案便宜了一大截,更重要的是过程里学到的东西远不是几百块能衡量的。

工程组织上,我建议用HAL库而不是标准库,原因只有一个:HAL库的代码可读性和移植性比标准库好太多,尤其是你要做毕业设计或者后续想在此基础上扩展蓝牙、Wi-Fi、触摸屏,HAL库环境下找例程和参考方案更容易。工程目录大概长这样:

SmartFishTank/ ├── Core/ │ ├── Inc/ │ └── Src/ ├── Drivers/ │ ├── STM32F1xx_HAL_Driver/ │ └── BSP/ │ ├── ds18b20.c │ ├── oled.c │ ├── servo.c │ ├── relay.c │ ├── key.c │ ├── pump.c │ ├── water_level.c │ └── buzzer.c ├── App/ │ ├── control.c │ ├── state_machine.c │ └── data_store.c └── MDK-ARM/

BSP层只负责具体外设的驱动,App层只负责业务逻辑,比如温度滞回控制、喂食调度、状态机迁移。这样分层的最大好处是:你想换传感器型号,只改BSP层对应文件;想改控制策略,只改App层,其他代码不用动。虽然项目不大,但这个习惯从初期就建立起来,后面的扩展才不会被屎山代码拖垮。

6. 从本地版起步,后续升级路径

最后聊一点项目之外的东西。很多人做完本地版之后,第一反应是“要不要加个ESP8266搞远程控制”。我的建议是:先把手上的本地版跑稳一周,记录运行数据,确认没问题了再考虑扩展。因为远程控制的坑并不在于ESP8266本身,而在于你要重新设计整套通信协议、心跳检测、断线重连、异常上报,这些加起来的工作量不亚于重新写一遍主控逻辑。

本地版是一切的基础。传感器数据采集准不准、控制策略稳不稳、异常处理全不全,这些基础功能做扎实之后,你把数据通过串口发给ESP8266或者ESP32模块,做成MQTT上报,只是多一层封装而已。

以我的实际体会来说,这个项目的最大收获不是鱼缸本身,而是让我真正理解了嵌入式系统设计的边界——哪些逻辑该放在本地控制器,哪些沟通才需要依赖网络。做技术开发,克制比堆功能更难,也更重要。鱼缸如此,很多项目也都一样。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询