江科大51单片机笔记【12】DS18B20温度传感&温度报警器(下)

写在前言

此为博主自学江科大51单片机(B站)的笔记,方便后续重温知识

在后面的章节中,为了防止篇幅过长和易于查找,我把一个小节分成两部分来发,上章节主要是关于本节课的硬件介绍、电路图、原理图等理论知识,主要是为下章节的代码部分打基础。

我的单片机是24年12月在tb普中买的,型号是STC89C52,在原视频中引脚或接口不对应的我都会改正,保证在我的机子上能运行才发上来的,还有一些文字部分是我的理解,并非照搬,所以可能有理解不到位的现象。

如有误或交流,敬请指点提问

一、温度读取

先创建新的工程导入必须的模块(LCD1602和Delay),然后测试一下

#include <REGX52.H>
#include " LCD1602.h"
#include " Delay.h"


void main()
{ 		
	LCD_Init();
	LCD_ShowString(1,1,"Temperature:");
	while(1)
	{

	
	}

}

代码总体思路:

创建一个1-Wire模块,里面写五个函数,初始化,发送一位,接收一位,发送一个字节,接收一个字节;创建一个DS18B20,里面写两个函数,开始温度转换和读取函数;最后把main直接调用DS18B20的函数

1.OneWire.c函数

(1)定义端口

这步看原理图

sbit OneWire_DQ=p3^7;

(2)初始化

先把总线置1,再把总线拉低(置0),然后延时个480us,但是我们之前写的延时函数不支持延时1us级别,我们需要去STC里直接生成一个延时500us的函数(安全起见)

然后再次释放(置1),然后延时70us,然后再定义一个变量AckBit,把当前的DQ(总线值)读出来,然后再延时500us(确保整个时间段走完)

最后记得return 变量AckBit

//OneWire.c

unsigned char OneWire_Init(void)
{
	unsigned char AckBit;
	unsigned char i;
	OneWire_DQ=1;
	OneWire_DQ=0;
	i = 227;while (--i);   //Delay500us
	OneWire_DQ=1;
	i = 29;while (--i);    //Delay70us
	AckBit=OneWire_DQ;
	i = 227;while (--i);   //Delay500us
	return AckBit;
}

然后我们测试一下

//main

unsigned char Ack;

void main()
{ 		
	LCD_Init();
	LCD_ShowString(1,1,"Temperature:");
	Ack=OneWire_Init();
	LCD_ShowNum(2,1,Ack,3);
	while(1)
	{
	}

}

可以看到当DS18B20(矩阵键盘旁边那个)插上时显示0,拔掉后复位显示1

(3)发送一位

首先先拉低,延时14us(延时函数本身就需要4us)判断一下DQ是0还是1,如果是0就是发送0,如果是1就是发送1;然后把总线等于参数bit,然后再延时50us,最后再把总线拉高

这样巧妙的省略了一下判断语句,当bit为0时,延时10us,然后赋值给总线,然后就持续50us,拉高结束;当bit为1时,延时10us,总线自动释放,还是为1,持续50us,结束

void OneWire_SendBit(unsigned char Bit)
{
    unsigned char i;
	OneWire_DQ=0;
	i = 3;while (--i);     //Delay10us	
	OneWire_DQ=Bit;
	i = 22;while (--i);    //Delay50us
	OneWire_DQ=1;
}

(4)接受一位

先拉低,然后延时5us释放掉,然后再延时5us再采样,然后再延时50us

unsigned char OneWire_ReceiveBit(void)
{
	unsigned char Bit;
	unsigned char i;
	OneWire_DQ=0;
	i = 1;while (--i);    //延时5us
	OneWire_DQ=1;
	i = 1;while (--i);    //延时5us
	Bit=OneWire_DQ;
	i = 20;while (--i);   //延时50us
	
	return Bit;
}

(5)发送一个字节和接受一个字节

我们只需要创建一个函数,然后循环

与是赋值,或是取值

void OneWire_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		 OneWire_SendBit(Byte&(0x01<<i);)
	}
}
	
unsigned char OneWire_ReceiveByte(void)
{
	unsigned char i;
	unsigned char Byte=0x00;
	for(i=0;i<8;i++)
	{
		 if(OneWire_ReceiveBit()){Byte|=(0x01<<i);)}
	}
}

上面两个函数八次就可以啦

最后在.h文件里声明这几个函数就可以啦

2.DS18B20.c函数

(1)转换温度

先定义函数宏名,然后按照图中函数写就行,调用前面写的发送字节函数

#define DS18B20_SKIP_ROM            0xCC
#define DS18B20_CONVERT_T          0x44 


void DS18B20_Convert(void)
{
	OneWire_Init();
	OneWire_SendBit(DS18B20_SKIP_ROM);
	OneWire_SendBit(DS18B20_CCONVERT_T );
}

(2)温度读取

这里定义的返回值是float类型,有正负的浮点数

依旧定义函数宏名

定义一个int类型的Temp变量,将TLSB和TMSB整合在一起存储起来(16位)

然后又因为最低位并不是2^0,而是2^4,因为他后面四位为了存小数,但是我们前面定义Temp是整形变量,所以我们强行转为int等于把原来的数值向左移四位,又知道二进制向左移一位跟乘2是一样的,所以我们要把int/16才是实际温度,但是还要除16.0浮点数才不会损失精度

float DS18B20_ReadT(void)
{
	unsigned char TLSB,TMSB;
	int Temp;
	float T;
	OneWire_Init();
	OneWire_SendBit(DS18B20_SKIP_ROM);		
	OneWire_SendBit(DS18B20_READ_SCRATCHPAD);
	TLSB=OneWire_ReceiveByte();
	TMSB=OneWire_ReceiveByte();
	Temp=TMSB<<8|TLSB;
	T=Temp/16.0;
	return T;
}

然后在.h文件里声明就写完了

3.主函数

因为之前写的LCD_ShowNum函数没有显示小数功能,所以要分开写,这样采用先乘10000(四位小数)然后再对10000取余就可以,然后再强制转换为unsigned long类型

当然也没有显示正负号,所以也要分开写,这里要进行判断

#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include " Delay.h"

float T;


void main()
{
	DS18B20_ConvertT();		//上电先转换一次温度,防止第一次读数据错误
	Delay(1000);	
	LCD_Init();
	LCD_ShowString(1,1,"Temperature:");

	while(1)
	{
		DS18B20_ConvertT();
		T=DS18B20_ReadT();
		if(T<0)
		{
			LCD_ShowChar(2,1,'-');
			T=-T;
		}
		else
		{
			LCD_ShowChar(2,1,'+');
		}
		LCD_ShowNum(2,2,T,3);
		LCD_ShowChar(2,5,'.');
		LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);
	}
}

OneWire.c

#include <REGX52.H>

sbit OneWire_DQ=P3^7;


unsigned char OneWire_Init(void)
{
	unsigned char AckBit;
	unsigned char i;
	OneWire_DQ=1;
	OneWire_DQ=0;
	i = 227;while (--i);   //Delay500us
	OneWire_DQ=1;
	i = 29;while (--i);    //Delay70us
	AckBit=OneWire_DQ;
	i = 227;while (--i);   //Delay500us
	return AckBit;
}

void OneWire_SendBit(unsigned char Bit)
{
	unsigned char i;
	OneWire_DQ=0;
	i = 3;while (--i);     //Delay10us	
	OneWire_DQ=Bit;
	i = 22;while (--i);    //Delay50us
	OneWire_DQ=1;
}

unsigned char OneWire_ReceiveBit(void)
{
	unsigned char Bit;
	unsigned char i;
	OneWire_DQ=0;
	i = 1;while (--i);    //延时5us
	OneWire_DQ=1;
	i = 1;while (--i);    //延时5us
	Bit=OneWire_DQ;
	i = 20;while (--i);   //延时50us
	
	return Bit;
}

void OneWire_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		 OneWire_SendBit(Byte&(0x01<<i));
	}
}
	
unsigned char OneWire_ReceiveByte(void)
{
	unsigned char i;
	unsigned char Byte=0x00;
	for(i=0;i<8;i++)
	{
		 if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
	}
	return Byte;
}

OneWire.h

#ifndef __ONEWIRE_H__
#define __ONEWIRE_H__

unsigned char OneWire_Init(void);
void OneWire_SendBit(unsigned char Bit);
unsigned char OneWire_ReceiveBit(void);
void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveByte(void);

#endif

DS18B20.c

#include <REGX52.H>
#include " OneWire.h"


#define DS18B20_SKIP_ROM			0xCC
#define DS18B20_CONVERT_T			0x44
#define DS18B20_READ_SCRATCHPAD 	0xBE




void DS18B20_ConvertT(void)
{
	OneWire_Init();
	OneWire_SendBit(DS18B20_SKIP_ROM);
	OneWire_SendBit(DS18B20_CONVERT_T);
}


float DS18B20_ReadT(void)
{
	unsigned char TLSB,TMSB;
	int Temp;
	float T;
	OneWire_Init();
	OneWire_SendBit(DS18B20_SKIP_ROM);		
	OneWire_SendBit(DS18B20_READ_SCRATCHPAD);
	TLSB=OneWire_ReceiveByte();
	TMSB=OneWire_ReceiveByte();
	Temp=TMSB<<8|TLSB;
	T=Temp/16.0;
	return T;
}

DS18B20.h

#ifndef __DS18B20_H__
#define __DS18B20_H__

void DS18B20_ConvertT(void);
float DS18B20_ReadT(void);

#endif

二、温度报警器

先新建一个工程,把需要的模块都导进来

#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "AT24C02.h"
#include "Key.h"

先把前面写的温度读取代码照搬下来

float T;

void main()
{
	
	LCD_Init();
	LCD_ShowString(1,1,"T:");
	while(1)
	{
 		DS18B20_ConvertT();
		T=DS18B20_ReadT();
		if(T<0)
		{
			LCD_ShowChar(1,3,'-');
			T=-T;
		}
		else
		{
			LCD_ShowChar(1,3,'+');
		}
		LCD_ShowNum(1,4,T,3);
		LCD_ShowChar(1,7,'.');
		LCD_ShowNum(1,8,(unsigned long)(T*100)%100,2);  //显示两位小数
	}
	
}

接下来是新的内容

然后把温度的上下值读取出来,定义两个变量char TLow,THigh

这里单独把代码拿出来是为了方便理解,并不是实际的代码就这样写

再次运用LCD函数在第二行把TH和TL显示出来

char 	TLow,THigh;	

    LCD_ShowString(2,1,"TH:");
	LCD_ShowString(2,9,"TL:");


		/*阈值判断及显示*/
		LCD_ShowSignedNum(2,4,THigh,3);
		LCD_ShowSignedNum(2,12,TLow,3);

接下来用按键实现这两个阈值的加减

这里嵌套的if语句可以实现,只有当按键按下才会刷新,不会一直让LCD在扫描占用资源

unsigned char KeyNum;

		Key_Loop();
		KeyNum=Key();

		if(KeyNum)
		{
			if(KeyNum==1)
			{
				THigh++;
			}
			if(KeyNum==2)
			{
				THigh--;
			}
			if(KeyNum==3)
			{
				TLow++;
			}
			if(KeyNum==4)
			{
				TLow--;
			}		
		}

因为我们的测温范围是-55℃到+125摄氏度,所以这里还要再嵌套一个判断语句反正溢出

并且TH一定是比TL要大的

		if(KeyNum)
		{
			if(KeyNum==1)
			{
				THigh++;
				if(THigh>125){THigh=125;}
			}
			if(KeyNum==2)
			{
				THigh--;
				if(THigh<=TLow){THigh++;}
			}
			if(KeyNum==3)
			{
				TLow++;
				if(TLow>=THigh){TLow--;}
			}
			if(KeyNum==4)
			{
				TLow--;
				if(TLow<-55){TLow=-55;}
			}		
		}

接下来判断实际温度T和阈值TH和TL之前的关系

	if(T>THign)
	{
		LCD_ShowString(1,13,"OV:H");
	}
	else if(T<TLow)
	{
		LCD_ShowString(1,13,"OV:L");
	}
	else
	{
		LCD_ShowString(1,13,"    ");
	}

这里还有一个问题,因为我们之前在温度读取的时候,定义当T为负数时直接让T=-T,这样会出错,我们直接再定义一个TShow,专门用于T的显示

float T,TShow;

			TShow=-T;

            TShow=T;

		LCD_ShowNum(1,4,TShow,3);

下面我们把这个值存储在AT24C02里面,并且第一次读取之前就显示出来

有因为AT24C02有可能第一次存储的是非法的数值,我们就进行判断,只有符合格式的才会读出来,不然就读取默认值

	THigh=AT24C02_ReadByte(0);
	TLow=AT24C02_ReadByte(1);
	if(THigh>125 || TLow<-55 || THigh<=TLow)
	{
		THigh=20;
		TLow=15;
	}

			AT24C02_WriteByte(0,THigh);
			Delay(5);
			AT24C02_WriteByte(1,TLow);
			Delay(5);

我们再在初始的时候就转换一下温度,延时一秒再开机,让他先转换好温度再显示出来

到这代码基本就写完了

接下来再优化一下代码

首先是定时器扫描按键,我们在这里当按键按下的时候温度显示会卡住,如果换成定时器扫描,就可以在按下的时候也能刷新

但是当我们直接把定时器加上后,会发现LCD一直一闪一闪的,这是因为我们的定时器的中断函数会打断我们的主函数,而主函数的DS18B20的底层OneWire里的延时函数是us级的,当中断函数打断后,这个延时就会特别久,就会出现问题,我们就需要在OneWire.c的函数里,当延时进行前,把EA=0(即禁止所有中断),延时结束后再EA=1(开启所有中断)

这个是单总线的一个鸡肋的地方

#include <REGX52.H>

//引脚定义
sbit OneWire_DQ=P3^7;

/**
  * @brief  单总线初始化
  * @param  无
  * @retval 从机响应位,0为响应,1为未响应
  */
unsigned char OneWire_Init(void)
{
	unsigned char i;
	unsigned char AckBit;
	EA=0;
	OneWire_DQ=1;
	OneWire_DQ=0;
	i = 247;while (--i);		//Delay 500us
	OneWire_DQ=1;
	i = 32;while (--i);			//Delay 70us
	AckBit=OneWire_DQ;
	i = 247;while (--i);		//Delay 500us
	EA=1;
	return AckBit;
}

/**
  * @brief  单总线发送一位
  * @param  Bit 要发送的位
  * @retval 无
  */
void OneWire_SendBit(unsigned char Bit)
{
	unsigned char i;
	EA=0;
	OneWire_DQ=0;
	i = 4;while (--i);			//Delay 10us
	OneWire_DQ=Bit;
	i = 24;while (--i);			//Delay 50us
	OneWire_DQ=1;
	EA=1;
}

/**
  * @brief  单总线接收一位
  * @param  无
  * @retval 读取的位
  */
unsigned char OneWire_ReceiveBit(void)
{
	unsigned char i;
	unsigned char Bit;
	EA=0;
	OneWire_DQ=0;
	i = 2;while (--i);			//Delay 5us
	OneWire_DQ=1;
	i = 2;while (--i);			//Delay 5us
	Bit=OneWire_DQ;
	i = 24;while (--i);			//Delay 50us
	EA=1;
	return Bit;
}

/**
  * @brief  单总线发送一个字节
  * @param  Byte 要发送的字节
  * @retval 无
  */
void OneWire_SendByte(unsigned char Byte)
{
	unsigned char i;
	for(i=0;i<8;i++)
	{
		OneWire_SendBit(Byte&(0x01<<i));
	}
}

/**
  * @brief  单总线接收一个字节
  * @param  无
  * @retval 接收的一个字节
  */
unsigned char OneWire_ReceiveByte(void)
{
	unsigned char i;
	unsigned char Byte=0x00;
	for(i=0;i<8;i++)
	{
		if(OneWire_ReceiveBit()){Byte|=(0x01<<i);}
	}
	return Byte;
}

//main.c

#include <REGX52.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "AT24C02.h"
#include "Key.h"
#include " Timer0 .h"

float T,TShow;
char 	TLow,THigh;
unsigned char KeyNum;

void main()
{
	DS18B20_ConvertT();
	Delay(1000);
	THigh=AT24C02_ReadByte(0);
	TLow=AT24C02_ReadByte(1);
	if(THigh>125 || TLow<-55 || THigh<=TLow)
	{
		THigh=20;
		TLow=15;
	}
	LCD_Init();
	LCD_ShowString(1,1,"T:");
	LCD_ShowString(2,1,"TH:");
	LCD_ShowString(2,9,"TL:");
	
	Timer0_Init();
	
	while(1)
	{
		Key_Loop();
		KeyNum=Key();
		
		/*温度读取及显示*/
 		DS18B20_ConvertT();
		T=DS18B20_ReadT();
		if(T<0)
		{
			LCD_ShowChar(1,3,'-');
			TShow=-T;
		}
		else
		{
			LCD_ShowChar(1,3,'+');
			TShow=T;
		}
		LCD_ShowNum(1,4,TShow,3);
		LCD_ShowChar(1,7,'.');
		LCD_ShowNum(1,8,(unsigned long)(T*100)%100,2);  //显示两位小数
		
		/*阈值判断及显示*/
		if(KeyNum)
		{
			if(KeyNum==1)
			{
				THigh++;
				if(THigh>125){THigh=125;}
			}
			if(KeyNum==2)
			{
				THigh--;
				if(THigh<=TLow){THigh++;}
			}
			if(KeyNum==3)
			{
				TLow++;
				if(TLow>=THigh){TLow--;}
			}
			if(KeyNum==4)
			{
				TLow--;
				if(TLow<-55){TLow=-55;}
			}		
			LCD_ShowSignedNum(2,4,THigh,3);
			LCD_ShowSignedNum(2,12,TLow,3);		
			AT24C02_WriteByte(0,THigh);
			Delay(5);
			AT24C02_WriteByte(1,TLow);
			Delay(5);
			
		}
		if(T>THigh)
		{
			LCD_ShowString(1,13,"OV:H");
		}
		else if(T<TLow)
		{
			LCD_ShowString(1,13,"OV:L");
		}
		else
		{
			LCD_ShowString(1,13,"    ");
		}	
	
	}

}

void Timer0_Routine() interrupt 1
{
	static unsigned int T0Count;
	TL0 = 0x18;		//设置定时初值
	TH0 = 0xFC;		//设置定时初值
	T0Count++;
	if(T0Count>=20)
	{
		T0Count=0;
		Key_Loop();
	}
}

下面是完整代码

相关推荐
LuckyLay4 小时前
Golang学习笔记_49——解释器模式
笔记·学习·设计模式·golang·解释器模式
C_VuI4 小时前
STM32初始安装
stm32·单片机·嵌入式硬件
爱写代码的雨一颗4 小时前
STM32-SPI通信协议
stm32·单片机·嵌入式硬件
weixin_502539855 小时前
rust学习笔记13-18. 四数之和
笔记·学习·rust
宇希啊5 小时前
2025/03/06(嵌入式学习开始第二天)
嵌入式硬件·学习
触角010100017 小时前
STM32步进电机驱动全解析(上) | 零基础入门STM32第五十七步
驱动开发·stm32·单片机·嵌入式硬件·物联网
郭涤生7 小时前
在线程间共享数据_第三章_《C++并发编程》笔记
c++·笔记·算法
与光同尘 大道至简7 小时前
万字技术指南STM32F103C8T6 + ESP8266-01 连接 OneNet 平台 MQTT/HTTP
stm32·单片机·嵌入式硬件·物联网·http·docker·信息与通信
-一杯为品-8 小时前
【51单片机】程序实验14.I2C-EEPROM
单片机·嵌入式硬件·51单片机
执念斩长河9 小时前
Go泛型学习笔记
笔记·学习·golang