基于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实时检测环境温湿度,实现了通过温度进行恒温设计,定时打开水阀,通过按键调节设置温度、设定时间等。