目录

1.DS18B20介绍

2.引脚及应用电路

3.内部结构框图

4.存储器结构

5.单总线介绍

6.单总线电路规范

7.单总线时序结构

初识化(蓝色表示,S):

发送一位(绿色表示,s:(BIT(比特位))):

接受一位(紫色表示,R:(BIT(比特位))):

发送一个字节(S:(BYTE(字节)))/接受一个字节(R:BYTE(字节)):

8.DS18B20操作流程

ROM指令:

功能指令:

9.DS18B20数据帧

10.温度储存格式

11.ROM操作图

12.DS18B20读取温度代码

工程思路:

第一步:

第二步:

第三步:

第四步:

第五步:

第六步:

第七步:

第八步:

第九步:

最终代码:

模块:

DS18B20.c

DS18B20.h

OneWire.c

OneWire.h

main.c

13.温度报警器代码

第一步:

第二步:

第三步:

第四步:

第五步:

第六步:

第七步:

第八步:

第九步:

第十步:

最终代码:

模块:

Key.c

Key.h

OneWire.c

OneWire.h

main.c


1.DS18B20介绍

DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有功能强大、硬件简单、易扩展、抗干扰性强等特点

测温范围:-55°C 到 +125°C

通信接口:1-Wire(单总线)

其它特征:可形成总线结构、内置温度报警功能、可寄生供电

2.引脚及应用电路

3.内部结构框图

64-BIT ROM:作为器件地址,用于总线通信的寻址

SCRATCHPAD(暂存器):用于总线的数据交互

EEPROM:用于保存温度触发阈值和配置参数

操作流程:发送一条温度转换指令,温度值存放在TEMPERATURE SENSOR(这个硬件)然后再放再暂存器里面,我们再发送一条读暂存器的指令,根据我们的读时序就能把我们的暂存器读出来,然后这两个字节通过DQ,使用温度传感器,这样就有了温度

4.存储器结构

5.单总线介绍

单总线(1-Wire BUS)是由Dallas公司开发的一种通用数据总线 一根通信线:DQ

异步、半双工

单总线只需要一根通信线即可实现数据的双向传输,当采用寄生供电时,还可以省去设备的VDD线路,此时,供电加通信只需要DQ和GND两根线

6.单总线电路规范

设备的DQ均要配置成开漏输出模式

DQ添加一个上拉电阻,阻值一般为4.7KΩ左右

若此总线的从机采取寄生供电,则主机还应配一个强上拉输出电路

规范的前两条就是I2C的那个模式,就是为了多级通信。第三条是单总线的特色功能。本节我们使用的是图1的电路图

7.单总线时序结构

初识化(蓝色表示,S):

主机将总线拉低至少480us,然后释放总线,等待15~60us后,存在的从机会拉低总线60~240us以响应主机,之后从机将释放总线

这里的时间是绝对时间,圆弧线段表示弱上拉

发送一位(绿色表示,s:(BIT(比特位))):

主机将总线拉低60~120us,然后释放总线,表示发送0;主机将总线拉低1~15us,然后释放总线,表示发送1。从机将在总线拉低30us后(典型值)读取电平,整个时间片应大于60us

左图表示发送1,右图表示发送0;右图阴影表示可以在这个时间范围内释放总线

接受一位(紫色表示,R:(BIT(比特位))):

主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us的末尾),读取为低电平则为接收0,读取为高电平则为接收1 ,整个时间片应大于60us

阴影表示可以在这个时间范围内释放总线

虽然发送和接受很相似,但是时序结构决定了,主机先发送和才能接受,主机发送完后从机才能获取总线的控制权

发送一个字节(S:(BYTE(字节)))/接受一个字节(R:BYTE(字节)):

发送一个字节:连续调用8次发送一位的时序,依次发送一个字节的8位(低位在前)

接收一个字节:连续调用8次接收一位的时序,依次接收一个字节的8位(低位在前)

8.DS18B20操作流程

初始化:从机复位,主机判断从机是否响应

ROM操作:ROM指令+本指令需要的读写操作

功能操作:功能指令+本指令需要的读写操作

ROM指令:

SEARCH ROM [F0h](搜索 ROM,操作很复杂)

READ ROM [33h](读 ROM,调用了这个指令直接可以直接读出ROM)

MATCH ROM [55h](匹配 ROM,发送地址后通过这个指令与某一个设备匹配)

SKIP ROM [CCh](跳过 ROM,当只有一个从机时直接可以连接这台设备,有多台时不能用这个指令)

ALARM SEARCH [ECh](报警搜索,那台设备在报警的时候可以直接搜索到,操作很复杂)

功能指令:

CONVERT T [44h](温度变换,要实现温控时这个指令要用到这个)

WRITE SCRATCHPAD [4Eh](写暂存器,使用了这个指令会把字节写入存储器结构中的Byte2、Byte3、Byte4这三个字节)

READ SCRATCHPAD [BEh](读暂存器,使用这个指令后会把存储器结构中从Byte 0到Byte 8全部读出来)

COPY SCRATCHPAD [48h](复制暂存器,使用了这个指令从机会把写入存储器结构中的Byte2、Byte3、Byte4这三个字节复制到EEPROM里面)

RECALL E2 [B8h](返回E2,与复制暂存器功能相反,把EEPROM里面的字节返回对应的字节内)

READ POWER SUPPLY [B4h](读取设备的供电模式)

9.DS18B20数据帧

温度变换:初始化→跳过ROM →开始温度变换

温度读取:初始化→跳过ROM →读暂存器→连续的读操作

10.温度储存格式

MS BYTE前5位是符号位,1为负数,0为正数(在前);LS BYTE后4位是小数位,如果BIT3是1 存的是0.5度、BIT2为1 存的是0.25度、BIT1是1 存的是0.125度、BIT0是1 存的是0.0625度;从BIT4~BIT10存的是实际数据的二进制补码的形式存储。对于这里的二进制补码,可以去看小编的原码反码补码的博客。

11.ROM操作图

12.DS18B20读取温度代码

工程思路:

先写One Wire模块里面写初识化、发送一位、接受一位、发送一个字节、接受一个字节这五个程序;再写DS18B20模块,里面有开始温度变化和读取温度变化这两个程序;这两个模块共同组成了DS18B20和单总线的操作;主函数里面调用DS18B20模块的开始温度变化和读取温度变化函数

第一步:

把延时、LCD液晶屏复制粘贴到本工程

第二步:

定义端口、再初始化(要拉低,所有先拉高)

第三步:

在One Wire模块里面初始化

第四步:

在One Wire模块,发送一位看下面字,i要定义一下,我们要在STC的软件延时器Delay

第五步:

在One Wire模块,发送接收一位,也要在STC的软件延时器Delay

第六步:

在One Wire模块,发送一个字节和接收一个字节及声明

第七步:

温度变换。在DS18B20模块,根据温度变换时序图写三个函数转换温度,发送一个指令0xcc

第八步:

读取温度。在DS18B20模块,用补码表示正负,所有用int接收,26行除以16.0右移的是4位

第九步:

主函数调用。转换后加个延时头引用,转换完成再读;数据处理挺麻烦的,主要是小数部分,多想想,原理不难,主要是取模(%)的使用,还有C语言的强制转换操作符和乘号(*)的使用

最终代码:

模块:

DS18B20.c

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

//DS18B20指令
#define DS18B20_SKIP_ROM			0xCC
#define DS18B20_CONVERT_T			0x44
#define DS18B20_READ_SCRATCHPAD 	0xBE

/**
  * @brief  DS18B20开始温度变换
  * @param  无
  * @retval 无
  */
void DS18B20_ConvertT(void)
{
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_CONVERT_T);
}

/**
  * @brief  DS18B20读取温度
  * @param  无
  * @retval 温度数值
  */
float DS18B20_ReadT(void)
{
	unsigned char TLSB,TMSB;
	int Temp;
	float T;
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(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

OneWire.c

#include <REGX52.H>

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

/**
  * @brief  单总线初始化
  * @param  无
  * @retval 从机响应位,0为响应,1为未响应
  */
unsigned char OneWire_Init(void)
{
	unsigned char i;
	unsigned char AckBit;
	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
	return AckBit;
}

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

/**
  * @brief  单总线接收一位
  * @param  无
  * @retval 读取的位
  */
unsigned char OneWire_ReceiveBit(void)
{
	unsigned char i;
	unsigned char Bit;
	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
	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;
}

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

main.c

#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)				//如果温度小于0
		{
			LCD_ShowChar(2,1,'-');	//显示负号
			T=-T;			//将温度变为正数
		}
		else				//如果温度大于等于0
		{
			LCD_ShowChar(2,1,'+');	//显示正号
		}
		LCD_ShowNum(2,2,T,3);		//显示温度整数部分
		LCD_ShowChar(2,5,'.');		//显示小数点
		LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,4);//显示温度小数部分
	}
}

13.温度报警器代码

第一步:

LCD1602模块、Delay模块、按键模块、I2C模块、AT24C02模块、One Wire模块、DS18B20模块复制粘贴到这个工程

第二步:

先把主函数写成DS18B20读取温度代码的形式

第三步:

在主函数。把温度的上线值和下限值定义出来

第四步:

在主函数。定义按键和按键判断、超出判断

第五步:

在主函数,按键函数的下面,越界判断

第六步:

在主函数。因为T=-T和T>THigh,所以T < TLow始终是正数,为了避免,我们要定义一个新的变量

第七步:

在主函数。写入操作和默认值设入

第八步:

把原来的按键模块移出去,导入定时器扫描按键的按键模块导入本工程

第九步:

在主函数中。复制粘贴中断函数,然后中断函数调用按键驱动函数

第十步:

One Wire模块。因为主函数的定时器中断函数,会打断主循环的进程。因为时序很严格,几us,中断完了,都过去了。所以我们要把One Wire模块所有的延迟,在中断函数开始时给屏蔽,结束了才打开。这样会对定时器有很大的影响,秒表这些功能无法实现,但对于这个功能没影响

最终代码:

模块:

Key.c

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

unsigned char Key_KeyNumber;

/**
  * @brief  获取按键键码
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key(void)
{
	unsigned char Temp=0;
	Temp=Key_KeyNumber;
	Key_KeyNumber=0;
	return Temp;
}

/**
  * @brief  获取当前按键的状态,无消抖及松手检测
  * @param  无
  * @retval 按下按键的键码,范围:0,1~4,0表示无按键按下
  */
unsigned char Key_GetState()
{
	unsigned char KeyNumber=0;
	
	if(P3_1==0){KeyNumber=1;}
	if(P3_0==0){KeyNumber=2;}
	if(P3_2==0){KeyNumber=3;}
	if(P3_3==0){KeyNumber=4;}
	
	return KeyNumber;
}

/**
  * @brief  按键驱动函数,在中断中调用
  * @param  无
  * @retval 无
  */
void Key_Loop(void)
{
	static unsigned char NowState,LastState;
	LastState=NowState;				//按键状态更新
	NowState=Key_GetState();		//获取当前按键状态
	//如果上个时间点按键按下,这个时间点未按下,则是松手瞬间,以此避免消抖和松手检测
	if(LastState==1 && NowState==0)
	{
		Key_KeyNumber=1;
	}
	if(LastState==2 && NowState==0)
	{
		Key_KeyNumber=2;
	}
	if(LastState==3 && NowState==0)
	{
		Key_KeyNumber=3;
	}
	if(LastState==4 && NowState==0)
	{
		Key_KeyNumber=4;
	}
}

Key.h

#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key(void);
void Key_Loop(void);

#endif

OneWire.c

#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;
}

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

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:");
	LCD_ShowSignedNum(2,4,THigh,3);
	LCD_ShowSignedNum(2,12,TLow,3);
	Timer0_Init();
	
	while(1)
	{
		KeyNum=Key();
		
		/*温度读取及显示*/
		DS18B20_ConvertT();	//转换温度
		T=DS18B20_ReadT();	//读取温度
		if(T<0)				//如果温度小于0
		{
			LCD_ShowChar(1,3,'-');	//显示负号
			TShow=-T;		//将温度变为正数
		}
		else				//如果温度大于等于0
		{
			LCD_ShowChar(1,3,'+');	//显示正号
			TShow=T;
		}
		LCD_ShowNum(1,4,TShow,3);		//显示温度整数部分
		LCD_ShowChar(1,7,'.');		//显示小数点
		LCD_ShowNum(1,8,(unsigned long)(TShow*100)%100,2);//显示温度小数部分
		
		/*阈值判断及显示*/
		if(KeyNum)
		{
			if(KeyNum==1)	//K1按键,THigh自增
			{
				THigh++;
				if(THigh>125){THigh=125;}
			}
			if(KeyNum==2)	//K2按键,THigh自减
			{
				THigh--;
				if(THigh<=TLow){THigh++;}
			}
			if(KeyNum==3)	//K3按键,TLow自增
			{
				TLow++;
				if(TLow>=THigh){TLow--;}
			}
			if(KeyNum==4)	//K4按键,TLow自减
			{
				TLow--;
				if(TLow<-55){TLow=-55;}
			}
			LCD_ShowSignedNum(2,4,THigh,3);	//显示阈值数据
			LCD_ShowSignedNum(2,12,TLow,3);
			AT24C02_WriteByte(0,THigh);		//写入到At24C02中保存
			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();	//每20ms调用一次按键驱动函数
	}
}
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐