【STM32项目开源】基于STM32的智能鱼缸养殖系统
目录
一、设计背景和意义
1.1设计背景
1.2设计意义
二、实物效果展示
2.1实物图片
2.2实物演示视频
三、硬件功能简介
3.1项目功能详解
3.2元器件清单
四、主框图与软件流程图
五、硬件PCB展示
六、软件程序设计
七、项目资料包内容
资料获取:查看主页介绍“充哥单片机设计”
一、设计背景和意义
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展示
六、软件程序设计
#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;// 初始化UIOLED_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;}
}