【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项目功能详解

  1. 传感器检测:检测水温、超声波水位、TDS水质、光照强度数据。
  2. 数据显示:0.96OLED屏幕显示全部的传感器数据以及传感器的阈值等数据。
  3. 执行机构:控制LED灯、步进电机(喂食)、加水、放水、加热、充氧等开关。
  4. 接入云平台:系统通过ESP8266 WIFI联网后,接入机智云平台。
  5. App远程查看:通过App远程监控全部传感器数据;App远程控制LED和步进电机。
  6. App远程控制:App控制LED灯、步进电机(喂食)、加水、放水、加热、充氧等开关。
  7. 阈值数据设定:系统通过按键设定阈值,也可以通过手机App远程设定。
  8. 定时投喂:可以谁设定时间,实现周期开启投喂开关。
  9. 定时充氧:设定充氧开关,实现周期性开启关闭充氧,
  10. 模式切换:可以通过按键或者手机App实现自动/手动模式的切换。
  11. 手动模式:通过手机App或按键控制LED灯、步进电机(喂食)、加水、放水、加热、充氧等开关。
  12. 自动模式:检测到的水位低于阈值,自动开启加水水泵;检测到的水质低于水质阈值自动开启排水和加水的水泵。检测到的水温低于水温阈值自动开启加片加热。根据设定的时间周期性开启投喂和充氧开关。
  13. 声光报警:当前的水温高于水温阈值会自动开启声光报警。

3.2元器件清单

  1. 主控STM32F103C8T6最小系统板
  2. 0.96OLED显示屏幕
  3. JDY31-SPP蓝牙模块
  4. 超声波水位传感器
  5. TDS电导率水质传感器
  6. DS18B20水温传感器
  7. 继电器
  8. 加热片
  9. 加水水泵
  10. 排水水泵
  11. 充氧泵

四、主框图与软件流程图

主框图

流程图

五、硬件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;
	}
}

七、项目资料包内容

资料获取:查看主页介绍"充哥单片机设计"

相关推荐
Hello_Embed4 小时前
STM32 智能垃圾桶项目笔记(四):PWM 回顾与舵机(SG90)控制实现
笔记·stm32·单片机·学习·嵌入式软件
零一iTEM4 小时前
NS4168输出音频通过ESP32C3测试
c++·单片机·嵌入式硬件·mcu·音视频·智能家居
时空自由民.4 小时前
GD32 I2C外设详介绍
单片机
Feng_Ye76 小时前
嵌入式开发笔记(1)
linux·c语言·嵌入式硬件·系统命令
小冷Hello7 小时前
【stm32】CAN分析仪+TJA1050+单片机通信不上,波特率等等都没问题,usb扩展坞的供电问题,绝了
stm32·单片机·嵌入式硬件
RFID舜识物联网9 小时前
NFC技术如何破解电子制造领域的效率瓶颈与追溯难题
大数据·人工智能·嵌入式硬件·物联网·安全·制造
一枝小雨9 小时前
Bootloader核心原理与简单实现:从零写一个bootloader
stm32·单片机·mcu·嵌入式·bootloader·从零写bootloader
充哥单片机设计16 小时前
【STM32项目开源】基于STM32的智能路灯控制系统
stm32·单片机·嵌入式硬件
啃硬骨头19 小时前
MC33PT2000控制详解七:软件代码设计1-图形化设置
单片机·嵌入式硬件