基于51单片机的智能鱼缸设计

基于51单片机的智能鱼缸设计利用AT89C51作为系统主控,LCD1602作为系统主控,DS18B20作为本次系统的温度传感器,实时检测鱼缸温度信息,并通过该信息调整加热器件,实现鱼缸的恒温设计,同时还有定时加水功能,为了提高设计的灵活性,使用按键进行模式切换、位置切换、减、加等功能。

一、硬件设计

1、继电器设计

本次设计利用继电器加NPN三极管进行组合,实现控制,由于加热和水阀一般输入大功能器件,通过NPN三极管的特性,导通电平为高电平,由于Proteus中并没有专门的加热和水阀器件,这里使用led进行代替。

2、DS1302

DS1302的电路设计主要包括DS1302芯片和32.768khz的晶振。DS1302使用SPI的通信方式。

3、外部中断扩展

本次设计使用二极管扩展外部中断,因为本次设计对于按键响应具有实时性的要求。

二、软件设计

1、DS1302驱动

cpp 复制代码
#include <REGX52.H>

sbit DS1302_CE=P1^0;
sbit DS1302_SCLK=P1^1;
sbit DS1302_IO=P1^2;

//Write:
#define DS1302_SECOND  0x80
#define DS1302_MINUTE  0x82
#define DS1302_HOUR    0x84
#define DS1302_DATA    0x86
#define DS1302_MONTH   0x88
#define DS1302_DAY     0x8a
#define DS1302_YEAR    0x8c
#define DS1302_WP      0x8e

char DS1302_Time[]={22,1,24,20,01,40,1}; 
void DS1302_Init()
{
	DS1302_CE=0;
	DS1302_SCLK=0;
}	
void DS1302_WriteByte(unsigned char Command,Data)
{
	unsigned char i;
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	for(i=0;i<8;i++)
	{
		DS1302_IO=Data&(0x01<<i);
		DS1302_SCLK=1;
		DS1302_SCLK=0;
	}
	DS1302_CE=0;	
}	
unsigned char DS1302_ReadByte(unsigned char Command)
{
	unsigned char i,Data=0x00;
	Command|=0x01;
	DS1302_CE=1;
	for(i=0;i<8;i++)
	{
		DS1302_IO=Command&(0x01<<i);
		DS1302_SCLK=0;
		DS1302_SCLK=1;
	}
	for(i=0;i<8;i++)
	{
		DS1302_SCLK=1;
		DS1302_SCLK=0;
		if(DS1302_IO){Data|=(0x01<<i);}
	 }
	DS1302_IO=0;
	DS1302_CE=0;

	return Data;
}	
void DS1302_SetTime()
{
	DS1302_WriteByte(DS1302_WP,0x00);
	DS1302_WriteByte(DS1302_YEAR,DS1302_Time[0]/10*16+DS1302_Time[0]%10);
	DS1302_WriteByte(DS1302_MONTH,DS1302_Time[1]/10*16+DS1302_Time[1]%10);
	DS1302_WriteByte(DS1302_DATA,DS1302_Time[2]/10*16+DS1302_Time[2]%10);
	DS1302_WriteByte(DS1302_HOUR,DS1302_Time[3]/10*16+DS1302_Time[3]%10);
	DS1302_WriteByte(DS1302_MINUTE,DS1302_Time[4]/10*16+DS1302_Time[4]%10);
	DS1302_WriteByte(DS1302_SECOND,DS1302_Time[5]/10*16+DS1302_Time[5]%10);
	DS1302_WriteByte(DS1302_DAY,DS1302_Time[6]/10*16+DS1302_Time[6]%10);
	DS1302_WriteByte(DS1302_WP,0x80);
}	

void DS1302_ReadTime()
{
	unsigned char temp;
	temp=DS1302_ReadByte(DS1302_YEAR);
	DS1302_Time[0]=temp/16*10+temp%16;
	temp=DS1302_ReadByte(DS1302_MONTH);
	DS1302_Time[1]=temp/16*10+temp%16;
	temp=DS1302_ReadByte(DS1302_DATA);
	DS1302_Time[2]=temp/16*10+temp%16;
	temp=DS1302_ReadByte(DS1302_HOUR);
	DS1302_Time[3]=temp/16*10+temp%16;
	temp=DS1302_ReadByte(DS1302_MINUTE);
	DS1302_Time[4]=temp/16*10+temp%16;
	temp=DS1302_ReadByte(DS1302_SECOND);
	DS1302_Time[5]=temp/16*10+temp%16;
	temp=DS1302_ReadByte(DS1302_DAY);
	DS1302_Time[6]=temp/16*10+temp%16;
}	

2、LCD驱动

cpp 复制代码
#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^0;
sbit LCD_RW=P2^1;
sbit LCD_EN=P2^2;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

void LCD_Clear()
{
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

3、主函数设计

cpp 复制代码
#include <REGX52.H>
#include "LCD1602.H"
#include "DS18B20.H"
#include "delay.h"
#include "DS1302.h"

typedef unsigned char u8;
typedef unsigned short u16;

sbit warm = P1^3;
sbit water = P1^4;

float T;
int Tem_Set=30;
u8 mode=0;
u8 keyNum=0;
u8 change=0;
u8 select=0;
u8 T_flag=0;
u8 cnt=0;

char hour=16, min=7;

//外部中断0配置
void EXTI0_Init()
{
	IT0 = 1; // 设置INT0为下降沿触发
    EX0 = 1; // 使能外部中断0
    EA = 1;  // 开启全局中断	
}

// 中断服务程序
void externalInt0_ISR() interrupt 0 
{
	//模式切换
	if(P2_4 == 0){
		mode++;
		mode%=3;
		change=1;
	}
	
	//时钟位切换
	if(P2_5 == 0){
		if(mode == 2){
			select++;
			select %= 2;
		}			
	}
	if(P2_6 == 0){
		if(mode == 1){
			Tem_Set--;
			if(Tem_Set < 0)Tem_Set=0;
		}		
		if(mode == 2){
			if(select==0){
				min--;
				if(min < 0)min=0;
			}
			if(select==1){
				hour--;
				if(hour < 0)hour=0;				
			}
		}	
	}
	if(P2_7 == 0){
		if(mode == 1){
			Tem_Set++;
		}		
		if(mode == 2){
			if(select==0){
				min++;
				min%=60;
			}
			if(select==1){
				hour++;
				hour%=60;		
			}		
		}		
	}
}

void main()
{
	//关闭加热、水阀
	warm=0;
	water=0;
	//初始转换一次温度
    DS18B20_ConvertT();
	delay(1000);
	//lCD初始化
	LCD_Init();
	//DS1302初始化
	DS1302_Init();
	//外部中断0配置
	EXTI0_Init();
	while(1)
	{		
		//运行模式
		if(mode == 0){
			DS18B20_ConvertT();
			T = DS18B20_ReadT();
			
			DS1302_ReadTime();
			LCD_ShowNum(1,6,T, 2);			

			LCD_ShowString(1,1,"Tem:");
			LCD_ShowNum(2,1,DS1302_Time[3], 2);
			LCD_ShowString(2,3,":");
			LCD_ShowNum(2,4,DS1302_Time[4], 2);
			LCD_ShowString(2,6,":");
			LCD_ShowNum(2,7,DS1302_Time[5], 2);	

			LCD_ShowNum(1,12,hour, 2);
			LCD_ShowString(1,14,":");
			LCD_ShowNum(1,15,min, 2);
		}
		//设置恒温温度
		else if(mode == 1){
			LCD_ShowString(1,1,"Tem_Set:");
			LCD_ShowNum(1,10,Tem_Set, 2);
		}
		//设置定时开水时间
		else if(mode == 2){
			LCD_ShowString(1,1,"Time_Set:");
			if(select == 0){
				if(T_flag){
					LCD_ShowNum(2,4,min, 2);
				}
				else{
					LCD_ShowString(2,4,"  ");
				}
				LCD_ShowNum(2,1,hour, 2);
			}
			else if(select == 1){
				if(T_flag){
					LCD_ShowNum(2,1,hour, 2);
				}
				else{
					LCD_ShowString(2,1,"  ");
				}
				LCD_ShowNum(2,4,min, 2);				
			}
			LCD_ShowString(2,3,":");
		}
		
		//温度低于设定值,继电器开始加热
		if(T < Tem_Set){
			warm = 1;
		}
		else{
			warm = 0;
		}
		
		//定时开水阀
		if(DS1302_Time[3] == hour && DS1302_Time[4] == min){
			water = 1;
		}
		else{
			water = 0;
		}
		
		
		//计自加,修改标志位
		if(++cnt >= 10){
			T_flag = !T_flag;
			cnt = 0;
		}
		
		if(change == 1){
			LCD_Clear();
			change=0;	
		}
		
		delay(10);
	}
}

三、功能演示

1、初始状态

初始状态下LCD1602显示温度、时间设定值、当前时间。

2、自动功能演示

温度小于30摄氏度,系统自动开启加热功能。

当时间达到设定时间的时候,水阀会自动打开,进行加水。

3、按键调节

按下模式切换按键会循环切换温度设定值、时间值,按下加减可以对设定值进行加减。

四、项目总结

本次设计利用STM32作为系统主控,利用DS1302提供时间、DS18B20实时检测环境温湿度,实现了通过温度进行恒温设计,定时打开水阀,通过按键调节设置温度、设定时间等。

相关推荐
blessing。。2 分钟前
I2C学习
linux·单片机·嵌入式硬件·嵌入式
嵌新程1 小时前
day03(单片机高级)RTOS
stm32·单片机·嵌入式硬件·freertos·rtos·u575
Lin2012301 小时前
STM32 Keil5 attribute 关键字的用法
stm32·单片机·嵌入式硬件
电工小王(全国可飞)2 小时前
STM32 RAM在Memory Map中被分为3个区域
stm32·单片机·嵌入式硬件
maxiumII2 小时前
Diving into the STM32 HAL-----DAC笔记
笔记·stm32·嵌入式硬件
美式小田5 小时前
单片机学习笔记 9. 8×8LED点阵屏
笔记·单片机·嵌入式硬件·学习
兰_博5 小时前
51单片机-独立按键与数码管联动
单片机·嵌入式硬件·51单片机
时光の尘5 小时前
C语言菜鸟入门·关键字·float以及double的用法
运维·服务器·c语言·开发语言·stm32·单片机·c
-一杯为品-6 小时前
【51单片机】程序实验5&6.独立按键-矩阵按键
c语言·笔记·学习·51单片机·硬件工程
嵌入式大圣7 小时前
单片机结合OpenCV
单片机·嵌入式硬件·opencv