目录
一、设计背景和意义
1.1设计背景
随着智能化、信息化技术的快速发展,物联网(IoT)、嵌入式系统和云计算等技术正不断渗透到人们的日常生活中,推动传统行业向智能化方向转型。鱼缸养殖作为家庭休闲娱乐和小型生态系统管理的一种形式,越来越受到城市居民的青睐。然而,传统鱼缸管理仍高度依赖人工,诸如水质变化监测、水温控制、定时投喂、充氧以及换水等环节均需要人为干预。这不仅大大增加了操作的复杂性和人力成本,同时也对养殖者的经验和时间管理能力提出较高要求,若未能及时维护,极易引发水质恶化、鱼类应激反应或死亡等问题。
1.2设计意义
本课题所研究的"智能鱼缸养殖系统",旨在通过现代嵌入式技术、传感器技术、无线通信与云平台服务相结合,实现鱼缸环境的智能监测与自动化管理。这种系统化、模块化的智能养殖解决方案,不仅可以降低用户管理难度、提高养殖成功率,还可以实现远程控制、历史数据分析和精准干预等功能,提升整个系统的可用性、可控性和用户体验。该课题是嵌入式系统设计、传感器数据处理、无线通信协议实现和物联网平台应用等多个技术领域的交叉点,具有较强的工程综合性和创新实践价值。通过该系统的构建与实现,能够深入探索STM32单片机在智能设备中的应用能力,研究如何实现高效的多传感器数据融合与控制策略制定,同时探索轻量级WiFi模块(如ESP8266)与云平台之间的数据交互方式,具有较强的研究推广意义。本课题涵盖嵌入式编程、电路设计、云平台对接、前后台联调、系统联动测试等多个技能模块,能够有效锻炼学生的软硬件协同开发能力、问题分析与解决能力、系统集成与调试能力,是高校实践教学的重要补充内容。同时,该项目的研究成果还可为智能农业、智能水产、远程生态监测等更广泛的智能控制应用场景提供参考与借鉴,具有较强的教学与科研拓展潜力。
二、实物效果展示
2.1实物图片

2.2实物演示视频
【开源】基于STM32的智能鱼缸养殖系统
三、硬件功能简介
3.1项目功能详解
- 传感器检测:检测水温、超声波水位、TDS水质、光照强度数据。
- 数据显示:0.96OLED屏幕显示全部的传感器数据以及传感器的阈值等数据。
- 执行机构:控制LED灯、步进电机(喂食)、加水、放水、加热、充氧等开关。
- 接入云平台:系统通过ESP8266 WIFI联网后,接入机智云平台。
- App远程查看:通过App远程监控全部传感器数据;App远程控制LED和步进电机。
- App远程控制:App控制LED灯、步进电机(喂食)、加水、放水、加热、充氧等开关。
- 阈值数据设定:系统通过按键设定阈值,也可以通过手机App远程设定。
- 定时投喂:可以谁设定时间,实现周期开启投喂开关。
- 定时充氧:设定充氧开关,实现周期性开启关闭充氧,
- 模式切换:可以通过按键或者手机App实现自动/手动模式的切换。
- 手动模式:通过手机App或按键控制LED灯、步进电机(喂食)、加水、放水、加热、充氧等开关。
- 自动模式:检测到的水位低于阈值,自动开启加水水泵;检测到的水质低于水质阈值自动开启排水和加水的水泵。检测到的水温低于水温阈值自动开启加片加热。根据设定的时间周期性开启投喂和充氧开关。
- 声光报警:当前的水温高于水温阈值会自动开启声光报警。
3.2元器件清单
- 主控STM32F103C8T6最小系统板
- 0.96OLED显示屏幕
- JDY31-SPP蓝牙模块
- 超声波水位传感器
- TDS电导率水质传感器
- DS18B20水温传感器
- 继电器
- 加热片
- 加水水泵
- 排水水泵
- 充氧泵
四、主框图与软件流程图
主框图

流程图

五、硬件PCB展示


六、软件程序设计
cpp
#include "stm32f10x.h"
#include "sys.h"
#include "led.h"
#include "bmp.h"
#include "oled.h"
#include "adc.h"
#include "ds18b20.h"
#include "timer.h"
#include "delay.h"
#include "usart.h"
#include "usart2.h"
#include "cJSON.h"
#include "sr04.h"
int LED_STATUS = 0;
int FAN_STATUS = 0;
char WIFIName[] = "ESP8266-WIFI";
char WIFIpwd[] = "12345678";
int temp_up = 35,temp_down = 15; // 温度上下限
int tds_up = 90,tds_down = 0; // 水质上下限
int level_up = 5,level_down = 60; // 水位上下限
int maxHighLevel = 5; // 换水最高水位
int feedTime = 30; // 喂食时间
int cacheFeedTime = 30; // 缓存喂食时间
int sendDataTime = 3; // 数据发送时间
int cacheSendTime = 3; // 缓存数据发送时间
#define LED PAout(2) // LED
#define BUZZ PAout(4) // 蜂鸣器
#define JD1 PAout(8) // 继电器1-抽水
#define JD2 PAout(5) // 继电器2-放水
#define JD3 PAout(6) // 继电器3-加热
#define JD4 PAout(7) // 继电器4-降温
#define KEY_EDIT PBin(13) // 设置按钮
#define KEY_NEXT PBin(14) // 切换按钮
#define KEY_SWIT PBin(15) // 下一页按钮
#define KEY_ADD PAin(11) // 加一按钮
#define KEY_DEC PBin(12) // 减一按钮
#define TEST PCout(13)
#define SG90_CLOSE 175 // 舵机关闭
#define SG90_OPEN 185 // 舵机打开
// 上位机数据处理
int connectFlag = 0; // 连接成功标志位
int handleFlag = 0; // 判断上位机发送的数据需要处理哪一个
int sendFlag = 0; // 定时器时间到了该位置一表示发送数据
int initFlag = 0; // ESP8266初始化成功标志位
int paramFlag = 1; // 是否开启参数检查
int changeWaterFlag = 0; // 换水标志位
int feedFlag = 0; // 是否喂食
int feedEndFlag = 0; // 喂食结束
unsigned char temp = 0,level = 0; // 温度,湿度
void paramCheck( void ); // 检查参数是否超过
void handleData( void ); // 上位机数据处理
void co2Process(void); // CO2数据处理
void DisplayUI( void ); // 固定页面UI渲染
void paramEdit( void ); // 阈值参数设置
void editUiDisplay( int pageIndex ); // 设置页面UI初始化
void runAlter(int cursor,int count); // 执行参数修改
extern char *USARTx_RX_BUF; // 串口数据缓存
extern float TDS_value;
int main(void)
{
int time;
delay_init();
LED_Init();
OLED_Init();
Adc_Init();
DS18B20_Init(); // 温度初始化
HC_SR04Config(); // 超声波初始化
uart_init(115200);
timeInit(4999,7199); // 72M 0.1ms 500ms 串口数据初始化、
timeSendInit(9999,7199); // 72m 0.1ms 1s 发送数据定时
timePwmInit(199,7199); // PWM-> CH3->PA2 CH4->PA3 (调脉宽)TIM_SetCompare4(TIM2,100);
TIM_SetCompare4(TIM2,SG90_CLOSE); // 关闭喂食
OLED_ShowChLength(38,16,47,3); // 显示启动中
ESP8266Init(WIFIName,WIFIpwd);
OLED_Clear();
while(1){
DisplayUI();
if( time++ > 5 ){
time = 0;
temp = getTemperture(); // 获取温度
delay_ms(1000);
delay_ms(1000);
temp = getTemperture(); // 获取温度
TDS_Value_Conversion(); // 获取TDS水质值
}
level = Getlength(); // 获取水位
OLED_ShowNum(45,0,temp,2,16,1);
OLED_ShowNum(45,16,level,3,16,1);
OLED_ShowNum(45,32,TDS_value,3,16,1);
OLED_ShowNum(76,48,cacheFeedTime,3,16,1);
OLED_Refresh();
// 开始喂食
if( feedFlag ){
TIM_SetCompare4(TIM2,SG90_OPEN);
// 等待喂食结束
if( feedEndFlag ){
feedFlag = 0;
feedEndFlag = 0;
feedTime = cacheFeedTime;
TIM_SetCompare4(TIM2,SG90_CLOSE);
}
}
// 进入设置页面
if( !KEY_EDIT ){
while( !KEY_EDIT ); // 防抖
paramEdit(); // 进入参数设置
// 刷新参数提示按钮(每次进入设置以后出来这个按钮没有,需要更新一下)
if( !paramFlag ) OLED_ShowChLength(105,2,62,1);
else OLED_ShowString(105,2," ",16,1);
}
// 切换参数提醒设置
if( !KEY_NEXT ){
while( !KEY_NEXT );
paramFlag = !paramFlag;
if( !paramFlag ){ // 关闭提示
JD2 = 0;JD3 = 0;JD4 = 0;JD1 = 0;BUZZ = 0;
OLED_ShowChLength(105,2,62,1);
}else{
OLED_ShowString(105,2," ",16,1);
}
OLED_Refresh();
}
// 上位机更改数据
if( handleFlag ) handleData();
// 3s上传一次数据
if( sendFlag && 1){
ESP8266Pub(temp,level,TDS_value);
sendFlag = 0; // 重置标志位
sendDataTime = cacheSendTime; // 刷新发送时间
}
// 检查参数是否超出范围(开启了参数检查)
if( paramFlag ) paramCheck();
delay_ms(100);
}
}
// 绘制固定不变的UI画面
void DisplayUI( void ){
// 显示汉字
OLED_ShowChLength(0,0,11,2);
OLED_ShowChLength(0,16,87,2);
OLED_ShowChLength(0,32,67,2);
OLED_ShowChLength(0,48,75,4);
// 汉字冒号
OLED_ShowString(33,0,":",16,1);
OLED_ShowString(33,16,":",16,1);
OLED_ShowString(33,32,":",16,1);
OLED_ShowString(65,48,":",16,1);
// 单位
OLED_ShowChLength(65,1,21,1);
OLED_ShowString(74,16,"CM",16,1);
OLED_ShowString(74,32,"PPM",16,1);
OLED_ShowString(105,48,"S",16,1);
}
// 处理阈值参数
void handleData( void ){
cJSON *data = NULL;
cJSON *upData = NULL;
cJSON *downData = NULL;
data = cJSON_Parse( (const char *)USARTx_RX_BUF );
switch( handleFlag ){
case 1:
LED = 1;
break;
case 2:
LED = 0;
break;
case 3:
break;
case 4:
break;
case 5:
upData = cJSON_GetObjectItem(data,"temp_up");
downData = cJSON_GetObjectItem(data,"temp_down");
temp_up = upData->valueint;
temp_down = downData->valueint;
break;
case 6:
upData = cJSON_GetObjectItem(data,"level_up");
downData = cJSON_GetObjectItem(data,"level_down");
level_up = upData->valueint;
level_down = downData->valueint;
break;
case 7:
upData = cJSON_GetObjectItem(data,"tds_up");
downData = cJSON_GetObjectItem(data,"tds_down");
tds_up = upData->valueint;
tds_down = downData->valueint;
break;
}
// USART_SendString(USART2,(char *)USARTx_RX_BUF);
clearCache();
cJSON_Delete(data);
handleFlag = 0;
USART_RX_STA = 0;
}
void paramEdit( void ){
int pageIndex = 0,cursor = 0,alterTip = 0,beforeCursor = 0;
// 初始化UI
OLED_Clear();
OLED_ShowString(112,0,"<",16,1);
while( 1 ){
editUiDisplay(pageIndex); // UI显示
if( !KEY_NEXT ){
while( !KEY_NEXT );
beforeCursor = (cursor % 4) * 16; // 拿到之前的修改显示位置
cursor++;
cursor = (cursor % 4) + 4 * pageIndex; // 得到第几个数据
alterTip = (cursor % 4) * 16; // 提示按钮的显示行
OLED_ShowString(112,beforeCursor," ",16,1); // 显示提示<
OLED_ShowString(112,alterTip,"<",16,1); // 显示提示<
}
if( !KEY_SWIT ){ // 按下页面切换按钮
while(!KEY_SWIT);
pageIndex = (++pageIndex)%2; // 切换页面
// pageIndex = !pageIndex; // 切换页面
cursor = 4 * pageIndex; // 拿到参数的游标
OLED_Clear();
OLED_ShowString(112,0,"<",16,1);// 重新显示提示按钮
OLED_Refresh();
}
if( !KEY_ADD ){
while(!KEY_ADD);
runAlter(cursor,1);
}
if( !KEY_DEC ){
while(!KEY_DEC);
runAlter(cursor,-1);
}
if( !KEY_EDIT ){ // 按下设置按钮推出当前页面
while( !KEY_EDIT );
OLED_Clear();
break;
}
if( handleFlag ) handleData();
OLED_Refresh();
}
}
// 执行修改param1执行类型 游标
void runAlter(int cursor,int count){
int countResult;
switch( cursor ){
case 0:
countResult = temp_up + count;
temp_up = countResult>0?countResult:0;
break;
case 1:
countResult = temp_down + count;
temp_down = countResult>0 ? countResult : 0;
break;
case 2:
countResult = level_up + count;
level_up = countResult>0 ? countResult : 0;
break;
case 3:
countResult = level_down + (count * 5);
level_down = countResult>0 ? countResult : 0;
break;
case 4:
countResult = tds_up + (count * 10);
tds_up = countResult>0 ? countResult : 0;
break;
case 5:
countResult = tds_down + (count * 10);
tds_down = countResult>0 ? countResult : 0;
break;
case 6:
countResult = cacheFeedTime + (count * 5);
cacheFeedTime = countResult>0 ? countResult : 0;
break;
case 7:
break;
}
}
// 设置初始化UI
void editUiDisplay( int pageIndex ){
// 如果是第一页显示第一页
if( pageIndex == 0){
// 显示汉字
OLED_ShowChLength(0,0,23,4);
OLED_ShowChLength(0,16,27,4);
OLED_ShowChLength(0,32,83,4);
OLED_ShowChLength(0,48,87,4);
// 显示冒号
OLED_ShowString(65,0,":",16,1);
OLED_ShowString(65,16,":",16,1);
OLED_ShowString(65,32,":",16,1);
OLED_ShowString(65,48,":",16,1);
// 显示测量值
OLED_ShowNum(76,0,temp_up,2,16,1);
OLED_ShowNum(76,16,temp_down,2,16,1);
OLED_ShowNum(76,32,level_up,2,16,1);
OLED_ShowNum(76,48,level_down,2,16,1);
return;
}
if( pageIndex == 1 ){
// 显示第二页
OLED_ShowChLength(0,0,67,4);
OLED_ShowChLength(0,16,71,4);
OLED_ShowChLength(0,32,75,4);
OLED_ShowChLength(0,48,79,4);
// 显示冒号
OLED_ShowString(65,0,":",16,1);
OLED_ShowString(65,16,":",16,1);
OLED_ShowString(65,32,":",16,1);
OLED_ShowString(65,48,":",16,1);
// 显示测量值
OLED_ShowNum(76,0,tds_up,4,16,1);
OLED_ShowNum(76,16,tds_down,4,16,1);
OLED_ShowNum(76,32,cacheFeedTime,4,16,1);
OLED_ShowNum(76,48,00,4,16,1);
}
OLED_Refresh();
}
// 检查个参数是否超出范围
void paramCheck( void ){
int buzzFlag; // 保存蜂鸣器的状态
buzzFlag = 0;
if( !changeWaterFlag ){
// 1.检查水位是否超过范围
if( level && level <= level_up ){
JD2 = 1; // 继电器1工作(放水)
JD1 = 0; // 继电器1停止(停止抽水)
// buzzFlag = 1;
}else if( level && level >= level_down ){
JD2 = 0; // 继电器1停止(停止放水)
JD1 = 1; // 继电器1工作(抽水)
// buzzFlag = 1;
}else{
JD2 = 0; // 继电器1停止(停止放水)
JD1 = 0; // 继电器1停止(停止抽水)
}
}
// 2.检查温度是否超过范围
if( temp && temp >= temp_up){
JD4 = 1; // 继电器3工作(散热)
buzzFlag = 1;
}else if( temp && temp <= temp_down ){
JD3 = 1; // 继电器1工作(加热)
buzzFlag = 1;
}else{
JD3 = 0; // 继电器2停止(停止加热)
JD4 = 0; // 继电器3停止(停止散热)
}
// 3.检查水质是否超过范围
if( TDS_value && TDS_value >= tds_up ){
if( changeWaterFlag == 0) {changeWaterFlag = 1; JD2 = 0;JD1 = 0;}
}
// 水质超过阈值开始放水
if( changeWaterFlag == 1 ){
JD2 = 1; //开始放水
if( level >= level_down ){
changeWaterFlag = 2;
}
}else if( changeWaterFlag == 2 ){
JD2 = 0; // 停止放水
JD1 = 1; // 开始抽水
if( level && (level <= maxHighLevel) ){
JD1 = 0; // 停止抽水
changeWaterFlag = 0;
}
}
// 控制蜂鸣器
if( buzzFlag ){
BUZZ = 1;
}else{
BUZZ = 0;
}
}
七、项目资料包内容
