【自学嵌入式:51单片机】实现DS18B20温度报警器

说明

本文是自学江科大51单片机的学习笔记,我利用I2C通信与AT24C02进行通信,存储温度的最高阈值和最低阈值,利用单总线协议与温度传感器DS18B20进行通信,利用定时器扫描按键,利用LCD1602显示当前温度和温度的阈值,超出最高温度或者低于最低温度的阈值就会在屏幕上显示OV:H或者OV:L,为了能够不干扰单总线通信,I2C通信,我把之前做的单总线通信和I2C通信的代码加入了通信时临时关闭中断的功能,按键扫描不需要精准控制时间,所以通信时关闭中断也问题不大,这样不会造成屏幕闪烁,代码开源到:https://gitee.com/qin-ruiqian/ds18b20-temperature-alarm

电路

对应普中开发板的电路如下:
普中-2&普中-3&普中-4开发板原理图(1)_01(1)

代码

代码思路全在注释中,现展示如下:

main.c

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

float T,Tshow; //温度
char TLow, THigh; //报警的最低温度和最高温度
unsigned char KeyNum; // 按键键码值

void Delay(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
}

//初始化定时器0
void initTimer0()
{
	TMOD = 0x01;
	TH0 = 0xFC;  //11.0592Mhz,高8位
	TL0 = 0x66; //低8位,注意大小端模式别写反了
	EA = 1; //允许定时器中断
	ET0 = 1;
	TF0 = 0;		//清除TF0标志
	TR0 = 1; // 开启定时器0
}

//定时器0中断,只单纯中断按键,那么单总线通信这种微秒级别的会被打断
//此时要在单总线发送比特位的时候,加入中断的关与开
//发信息的时候,中断必须关闭
//同理I2C那里也是
//因为是按键扫描,按键扫描没有那么严格的时间要求
void Timer0_Rountine() interrupt 1
{
	static unsigned int T0Count1; //轮询检测按键值的变量
	TH0 = 0xFC;  //11.0592Mhz
	TL0 = 0x66;
	T0Count1++;
	if(T0Count1>=20)
	{
		T0Count1 = 0;
		Key_Loop(); //每次定时器检测按键的键码
		//中断函数里调用的函数在main中不能调用
	}
}

void main()
{
	LCD1602_Init();
	//上电还是加个提醒吧,提示加载中
	LCD1602_ShowString(0,0,"Now Loading");
	//定时器初始化
	initTimer0();
	//上电读取温度,延迟1s,否则读取默认值85
	DS18B20_ConvertT();
	Delay(1000);
	//上电的时候默认从EEPROM读取最高温度和最低温度
	THigh = readByteE2PROM(0);
	TLow = readByteE2PROM(1);
	//如果存的是非法数据,自动修正,并存进去
	if(THigh > 125 || TLow < -55 || THigh <= TLow)
	{
		THigh = 20;
		TLow = 15;
		writeByteE2PROM(0, THigh);
		Delay(5);
		writeByteE2PROM(0, TLow);
		Delay(5);
	}
	LCD1602_ShowString(0,0,"           ");
	LCD1602_ShowString(0,0,"T:");
	LCD1602_ShowString(1,0,"TH:");
	LCD1602_ShowString(1,8,"TL:");
	//按键按下之前提前显示一下
	LCD1602_ShowSignedNum(1, 3, THigh, 3);
	LCD1602_ShowSignedNum(1, 11, TLow, 3);
	while(1)
	{
		KeyNum = GetKey(); //用定时器扫描按键
		//温度读取及显示
		DS18B20_ConvertT();
		T=DS18B20_ReadT();
		Tshow = T;
		if(T<0)
		{
			LCD1602_ShowChar(0, 2, '-');
			Tshow = -Tshow;
		}
		else
		{
			LCD1602_ShowChar(0, 2, '+');
		}
		LCD1602_ShowNumWithLength(0,3, Tshow, 3);
		LCD1602_ShowChar(0,6, '.');
		//显示2位小数
		LCD1602_ShowNumWithLength(0,7, (unsigned int)(Tshow*100) % 100, 3);
		//阈值判断及显示
		//判断按键是否按下
		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;
				}
			}
			LCD1602_ShowSignedNum(1, 3, THigh, 3);
			LCD1602_ShowSignedNum(1, 11, TLow, 3);
			//把最高温度和最低温度写入AT24C02芯片中
			writeByteE2PROM(0, THigh); //向0号地址EEPROM写THigh
			Delay(5); //等5ms写入
			writeByteE2PROM(1, TLow); //向1号地址写TLow
			Delay(5); //等5ms写入
		}
		if(T>THigh)
		{
			LCD1602_ShowString(0, 12, "OV:H");
		}
		else if (T<TLow)
		{
			LCD1602_ShowString(0, 12, "OV:L");
		}
		else
		{
			LCD1602_ShowString(0, 12, "    ");
		}
	}
}

OneWire.h

#ifndef __ONEWIRE_H__
#define __ONEWIRE_H__

unsigned char OneWire_Init(void); // 初始化单总线通信
void OneWire_SendBit(bit Bit); //发送一位
bit OneWire_ReceiveBit(); //接收一位
void OneWire_SendByte(unsigned char Byte); //发送一个字节
unsigned char OneWire_ReceiveByte(void); //接收一个字节

#endif

OneWire.c

#include <REGX52.H>
#include <intrins.h> // 延时空语句

sbit OneWire_DQ = P3^7;

void Delay500us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 227;
	while (--i);
}

void Delay70us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 29;
	while (--i);
}

void Delay10us()		//@11.0592MHz
{
	unsigned char i;

	i = 2;
	while (--i);
}

void Delay50us()		//@11.0592MHz
{
	unsigned char i;

	_nop_();
	i = 20;
	while (--i);
}

void Delay5us()		//@11.0592MHz
{
}

unsigned char OneWire_Init(void)
{
	bit EA_State = EA; //获取当前中断状态
	bit AckBit = 0; //确认帧 
	EA = 0; //关闭中断
	OneWire_DQ = 1;
	OneWire_DQ = 0;
	Delay500us(); //根据单总线通信协议,主机拉低至少480微秒,这里用500微秒
	OneWire_DQ = 1;//然后再释放
	Delay70us(); //等待15-60微秒后,存在的从机会拉低总线60-240微秒以响应主机,所以要延迟60~(60+240)微秒
	//延迟70微秒,包括了等待,和从机响应主机的时间
	AckBit = OneWire_DQ;
	//要求主机将总线拉低至少480微秒,继续延迟500微秒
	Delay500us();
	EA = EA_State;
	return AckBit;
}

//发送一位
void OneWire_SendBit(bit Bit)
{
	bit EA_State = EA; //获取当前中断状态
	EA = 0; //关闭中断
	//主机将总线拉低60~120us,然后释放总线,表示发送0
	//主机将总线拉低1~15us,然后释放总线,表示发送1
	//从机将在总线拉低30us后(典型值)读取电平
	//整个时间片应大于60us
	OneWire_DQ = 0; //先拉低
	//在10us的时候检测一下,如果是0,那就一直是0
	//如果是1,那就拉高(释放总线)
	//直接写OneWire_DQ = Bit;
	//Bit是0就是0,Bit为1,就在1-15us(也就是我设定的10us)拉高为1
	//但是考虑到调用一个函数是4us
	//江科大版本在这里直接把函数体的内容拿过来
	//比如软件延时功能写出来的函数,延迟10us
	//是算上了调用函数的时间,实际函数体6us,调用函数6us
	//江科大这里是把函数体的内容拿过来
	//这样不能用生成的延迟10us的函数(因为函数体6us)
	//所以他在视频中用延迟14us的函数的函数体
	//但是我这里还是正常调用延迟10us函数,包括函数体和调用过程
	Delay10us();
	OneWire_DQ = Bit;
	//整个时间片应大于60us(至少60us)
	//所以再延迟个50us,这个时序就结束了(50+10=60)us
	Delay50us();
	//通信结束,弱上拉拉回去
	OneWire_DQ = 1;
	EA = EA_State;
}

//接收一位
bit OneWire_ReceiveBit()
{
	bit EA_State = EA; //获取当前中断状态
	//主机将总线拉低1~15us,然后释放总线
	//并在拉低后15us内读取总线电平(尽量贴近15us的末尾)
	//读取为低电平则为接收0
	//读取为高电平则为接收1
	//整个时间片应大于60us
	bit Bit;
	EA = 0; //关闭中断
	//先拉低
	OneWire_DQ = 0;
	Delay5us();
	//再释放
	OneWire_DQ = 1; //拉高就是释放总线,弱上拉,从机传来1就是1,从机传来是0瞬间拉低
	Delay5us();
	//15us内,目前是总共延迟10us内,读取总线电平
	Bit = OneWire_DQ;
	//时间片至少60us,之前有10us,还需要延迟50us
	Delay50us();
	//通信结束后,不用主机释放总线,从机释放总线
	EA = EA_State;
	return Bit;
}

//发送一个字节
void OneWire_SendByte(unsigned char Byte)
{
	bit EA_State = EA; //获取当前中断状态
	unsigned char i;
	EA = 0; //关闭中断
	for(i = 0; i <8 ;i++)
	{
		OneWire_SendBit(Byte & (0x01<<i)); //依次左移取位,从最低位开始取
	}
	EA = EA_State;
}

//接收一个字节
unsigned char OneWire_ReceiveByte(void)
{
	bit EA_State = EA; //获取当前中断状态
	unsigned char Byte = 0x00;
	unsigned char i;
	EA = 0; //关闭中断
	for(i = 0; i <8 ;i++)
	{
		//如果传过来1位1,就和0x00做或运算,把1写入每一位
		if(OneWire_ReceiveBit())
		{
			Byte |= (0x01 << i);
		}
	}
	EA = EA_State;
	return Byte;
}

LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__
#define uint8 unsigned char
#define uint16 unsigned int

void LCD1602_WriteCommand(uint8 command); //写指令到LCD1602
void LCD1602_WriteData(uint8 in_data); //LCD1602写数据
void LCD1602_Init(); //LCD1602初始化
void LCD1602_WritePos(uint8 Line,uint8 Column); //设置光标位置
void LCD1602_ShowChar(uint8 Line,uint8 Column,char Char); //显示字符
void LCD1602_ShowString(uint8 Line,uint8 Column,char* String); //显示字符串
void LCD1602_ShowNum(uint8 Line, uint8 Column, uint8 number); //显示数字
void LCD1602_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); //显示16进制数
void LCD1602_ShowNumWithLength(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length); //在LCD1602指定位置开始显示所给数字
void LCD1602_ClearPos(uint8 Line, uint8 Column); //清空指定位置
void LCD1602_ClearPosLength(uint8 Line, uint8 Column, uint8 Length); //从指定位置清空这一行一定长度的屏幕
void LCD1602_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length); //指定长度显示有符号数

#endif

LCD1602.c

#include <REGX52.H>

void LCD1602_Delay(unsigned int xms)		//@11.0592MHz
{
	unsigned char i, j;
	while(xms--)
	{
		i = 2;
		j = 199;
		do
		{
			while (--j);
		} while (--i);
	}
}


//写指令到LCD1602
void LCD1602_WriteCommand(unsigned char command)
{
	P2_6 = 0; // RS端低电平,指令模式
	P2_5 = 0; // WR端低电平,写模式
	P0 = command; // 传输对应指令到端口
	P2_7 = 1; // 使能端高电平,LCD屏幕开始工作
	LCD1602_Delay(1); // 延迟1ms,让指令送进去
	P2_7 = 0; // 使能端低电平,LCD不接收任何信号
	LCD1602_Delay(1); // 延迟1ms,让LCD稳定进入不接收信号状态
}

//LCD1602写数据
void LCD1602_WriteData(unsigned char in_data)
{
	P2_6 = 1; // RS端高电平,数据模式
	P2_5 = 0; // WR端低电平,写模式
	P0 = in_data; // 传输对应数据到端口
	P2_7 = 1; // 使能端高电平,LCD屏幕开始工作
	LCD1602_Delay(1); // 延迟1ms,让指令送进去
	P2_7 = 0; // 使能端低电平,LCD不接收任何信号
	LCD1602_Delay(1); // 延迟1ms,让LCD稳定进入不接收信号状态
}

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

// 1xxx xxxx设定要存入数据的地址,其中xxx xxxx是地址

//设置光标位置
void LCD1602_WritePos(unsigned char Line,unsigned char Column)
{
	//第一行地址从00H到27H
	//若要将光标设置在第一行第一列,则发送0x80(1000 0000)指令;
	//其中最高位1代表设置光标位置,剩下的七个0代表实际地址
	if(Line==0)
	{
		//0x80
		LCD1602_WriteCommand(0x80|Column); //用或,把实际地址放在后7位
	}
	else if(Line==1)	//第二行地址从40H到67H
	{
		LCD1602_WriteCommand(0x80|(Column+0x40));
	}
}

//显示字符
void LCD1602_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD1602_WritePos(Line, Column);
	LCD1602_WriteData(Char);
}

//显示字符串
void LCD1602_ShowString(unsigned char Line,unsigned char Column,char* String)
{
	unsigned char i = 0; //循环用变量
	LCD1602_WritePos(Line, Column);
	for(; String[i] != '\0'; i++)
	{
		LCD1602_WriteData(String[i]);
	}
}

//显示数字
void LCD1602_ShowNum(unsigned char Line, unsigned char Column, unsigned char number)
{
	//8位无符号整数,最大255,考虑百位
	//如果百位不为0,先写百位
	unsigned char hundred = number / 100 % 10;
	unsigned char ten = number / 10 % 10;
	unsigned char one = number % 10;
	LCD1602_WritePos(Line, Column);
	//从'0'代表的ASCII码开始的,所以要加0,才能找到对应数字的ASCII码
	if(hundred != 0)
	{
		LCD1602_WriteData(hundred+'0');
	}
	if(!(ten == 0 && hundred == 0))
	{
		LCD1602_WriteData(ten+'0');
	}
	LCD1602_WriteData(one+'0');
}

//取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;
}

//显示16进制数
void LCD1602_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
    unsigned char i;
    unsigned char SingleNumber;
    LCD1602_WritePos(Line,Column);
    for(i=Length;i>0;i--)
    {
        SingleNumber = Number/LCD_Pow(16,i-1)%16; //从高位到低位,取出每一个十六进制数字
        if(SingleNumber < 10)
            LCD1602_WriteData('0'+SingleNumber); 
        else
            LCD1602_WriteData('A'+SingleNumber-10); //SingleNumber已含有10+x
    }
}

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

//清空指定位置
void LCD1602_ClearPos(unsigned char Line, unsigned char Column)
{
	LCD1602_WritePos(Line, Column);
	LCD1602_WriteData(0x20);
}

//从指定位置清空这一行一定长度的屏幕
void LCD1602_ClearPosLength(unsigned char Line, unsigned char Column, unsigned char Length)
{
	unsigned char i = 0;
	if((Column + Length) > 16)
	{
		Length = 16 - Column;
	}
	for(; i<Length; i++)
	{
		LCD1602_ClearPos(Line, Column + i);
	}
}

//指定长度显示有符号数
void LCD1602_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD1602_WritePos(Line,Column);
	if(Number>=0)
	{
		LCD1602_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD1602_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD1602_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

key.h

#ifndef __KEY_H__
#define __KEY_H__

unsigned char Key(); //获取按键的键码
void Key_Loop(void); //中断时候获取按键的循环
unsigned char GetKey(); ////获取按键,每次按下后,按键键码值清零

#endif

key.c

#include <REGX52.H>

unsigned Key_KeyNumber;

void Delay20ms()		//@11.0592MHz
{
	unsigned char i, j;

	i = 36;
	j = 217;
	do
	{
		while (--j);
	} while (--i);
}

//直接获取按键状态
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;
}

//消抖获取按键状态
unsigned char Key()
{
	unsigned char KeyNumber = 0;
	if(P3_1==0){Delay20ms();while(P3_1==0);Delay20ms();KeyNumber=1;}
	if(P3_0==0){Delay20ms();while(P3_0==0);Delay20ms();KeyNumber=2;}
	if(P3_2==0){Delay20ms();while(P3_2==0);Delay20ms();KeyNumber=3;}
	if(P3_3==0){Delay20ms();while(P3_3==0);Delay20ms();KeyNumber=4;}
	return KeyNumber;
}

//获取按键,每次按下后,按键键码值清零
unsigned char GetKey()
{
	unsigned char Temp = 0;
	Temp = Key_KeyNumber;
	Key_KeyNumber = 0;
	return Temp;
}

//用定时器扫描按键
void Key_Loop(void)
{
	static unsigned char NowState, LastState; //当前按键状态,上一个按键状态
	LastState = NowState;
	NowState = Key_GetState();
	//上一个状态按键1按下,现在没有按键按下,此时属于刚按下1后松手状态
	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;
	}
}

I2C.h

#ifndef __I2C_H__
#define __I2C_H__

void delayI2C(); //I2C专用延时程序
void initI2C(); //初始化I2C通信
void closeI2C(); //关闭I2C通信
void writeByteI2C(unsigned char I2C_Byte); //写入一个字节
unsigned char readByteI2C(); //读一个字节
bit readAckI2C(); //读取I2C总线应答信号
void writeAckI2C(bit Ack_bit); //发送应答信号

#endif

I2C.c

#include <REGX52.H>
#include <intrins.h> // 延时空语句
sbit I2C_SCL = P2^1;
sbit I2C_SDA = P2^0;

//I2C通信专用延时程序
void delayI2C()
{
	_nop_();
	_nop_();
	_nop_();
	_nop_();
}

//初始化I2C通信
void initI2C()
{
	bit EA_State = EA; //获取当前中断状态
	EA = 0; //关闭中断,防止通信被中断打断
	//先都拉高成高电平
	I2C_SCL = 1;
	I2C_SDA = 1;
	//下降沿
	delayI2C();
	I2C_SDA = 0;
	delayI2C();
	I2C_SCL = 0;
	EA = EA_State; //结束后恢复原来的状态
}

//关闭I2C通信
void closeI2C()
{
	bit EA_State = EA; //获取当前中断状态
	EA = 0; //关闭中断,防止通信被中断打断
	//先都拉低
	I2C_SCL = 0;
	I2C_SDA = 0;
	delayI2C();
	I2C_SCL = 1; //先拉高I2C_SCL
	delayI2C();
	I2C_SDA = 1; //I2C_SCL高电平期间,再拉高I2C_SDA,这样就产生了一个上升沿
	delayI2C();
	EA = EA_State; //结束后恢复原来的状态
}

//I2C写入一个字节,I2C_Byte是待传送的数据
void writeByteI2C(unsigned char I2C_Byte)
{
	bit EA_State = EA; //获取当前中断状态
	unsigned char i = 0;
	EA = 0; //关闭中断,防止通信被中断打断
	for(i = 0; i<8; i++)
	{
		//I2C_Byte & 0x80(1000 0000)
		//获取最高位,看是1还是0
		if(I2C_Byte & 0x80)
		{
			I2C_SDA = 1;
		}
		else
		{
			I2C_SDA = 0;
		}
		delayI2C();
		I2C_SCL = 1; //拉高电平,发送数据
		delayI2C();
		I2C_SCL = 0; //拉低,发送完成
		I2C_Byte <<= 1; //左移,循环读取每一位,让每一位都成为首位
	}
	EA = EA_State; //结束后恢复原来的状态
}

// I2C读一个字节
unsigned char readByteI2C()
{
	bit EA_State = EA; //获取当前中断状态
	unsigned char i, Byte = 0x00;
	EA = 0; //关闭中断,防止通信被中断打断
	for(i = 0; i<8; i++)
	{
		I2C_SDA = 1; //拉高I2C_SDA,释放I2C总线
		//读总线之前一定要将I2C_SDA拉高
		I2C_SCL = 1; //然后将I2C_SCL拉高,准备接收I2C_SDA状态
		delayI2C();
		if(I2C_SDA)
		{
			Byte |= (0x80>>i); //依次循环右移,得到一个字节(0x00分别或每一位得到,如果是低电平,不需要操作,该位就是0)
		}
		I2C_SCL = 0;
	}
	EA = EA_State; //结束后恢复原来的状态
	return Byte;
}

//读取I2C总线应答信号
bit readAckI2C()
{
	bit EA_State = EA; //获取当前中断状态
	bit Ack_bit; //应答信号
	EA = 0; //关闭中断,防止通信被中断打断
	I2C_SDA = 1; //拉高I2C_SDA,释放I2C
	delayI2C();
	I2C_SCL = 1; // 把I2C_SCL拉高,第9个时钟信号
	delayI2C();
	Ack_bit = I2C_SDA; //读取应答信号
	delayI2C();
	I2C_SCL = 0; //拉低I2C_SCL,结束接收应答
	EA = EA_State; //结束后恢复原来的状态
	return Ack_bit;
}

//I2C发送应答位
void writeAckI2C(bit Ack_bit)
{
	bit EA_State = EA; //获取当前中断状态
	EA = 0; //关闭中断,防止通信被中断打断
	//根据应答状态变量来确定I2C_SDA是1还是0
	if(Ack_bit)
	{
		I2C_SDA = 1;
	}
	else
	{
		I2C_SDA = 0;
	}
	//第9个时钟周期
	//拉高I2C_SCL,再拉低I2C_SCL,完成一次发送应答信号
	I2C_SCL = 1; 
	I2C_SCL = 0;
	EA = EA_State; //结束后恢复原来的状态
}


DS18B20.h

#ifndef __DS18B20_H__
#define __DS18B20_H__

void DS18B20_ConvertT(void); // 转换温度
float DS18B20_ReadT(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)
{
	bit EA_State = EA; //获取当前中断状态
	EA = 0; //关闭中断,防止通信被中断打断
	OneWire_Init(); //不做确认帧处理
	OneWire_SendByte(DS18B20_SKIP_ROM); //发送SKIP ROM,因为就一个设备,简单测试一下
	OneWire_SendByte(DS18B20_CONVERT_T);
	EA = EA_State;
}

//读取温度,4位小数
//51单片机对浮点数运算特别慢
//但是此处只是读取温度,这点时间还是耗得起
float DS18B20_ReadT(void)
{
	bit EA_State = EA; //获取当前中断状态
	unsigned char TLSB, TMSB;
	int Temp; //有符号16位int做中间变量
	float T; //最终的温度
	EA = 0; //关闭中断,防止通信被中断打断
	OneWire_Init();
	OneWire_SendByte(DS18B20_SKIP_ROM);
	OneWire_SendByte(DS18B20_READ_SCRATCHPAD);
	//发送完这个指令,就交给从机
	//开始接收从机发来的指令
	//传温度的数据,两个字节,16位
	TLSB = OneWire_ReceiveByte(); //Temperature LSB
	TMSB = OneWire_ReceiveByte(); //Temperature MSB
	Temp = (TMSB<<8)|TLSB; //强制转换为有符号的16位int,把TMSB左移8位到高位,然后TLSB和低8位0做或运算,放到低位
	//除16,向右移4位,见本文的温度存储格式章节
	T = Temp / 16.0;
	EA = EA_State;
	return T;
}

AT24C92.h

#ifndef __AT24C02_H__
#define __AT24C02_H__

void writeByteE2PROM(unsigned char addr, unsigned char dat);
unsigned char readByteE2PROM(unsigned char addr);

#endif

AT24C02.c

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

//向EEPROM中写入一个字节,addr是地址
void writeByteE2PROM(unsigned char addr, unsigned char dat)
{
	bit EA_State = EA; //获取当前中断状态
	EA = 0; //关闭中断,防止通信被中断打断
	//启动信号
	initI2C();
	//写入设备地址和写数据位
	writeByteI2C(0x50<<1); //寻址器件,后续为写操作,左移1位,右侧低位多出来的0是读写位的写入0
	//接收应答位
	readAckI2C();
	//写入寄存器地址
	writeByteI2C(addr);
	//接收应答位
	readAckI2C();
	//写入数据
	writeByteI2C(dat);
	//接收应答位
	readAckI2C();
	//停止信号
	closeI2C();
	EA = EA_State;
}

//读取EEPROM中的一个字节,addr是地址
unsigned char readByteE2PROM(unsigned char addr)
{
	bit EA_State = EA; //获取当前中断状态
	unsigned char dat = 0;
	EA = 0; //关闭中断,防止通信被中断打断
	//启动信号
	initI2C();
	//写入设备地址和写数据位
	writeByteI2C(0x50<<1);
	//接收应答位
	readAckI2C();
	//写入寄存器地址
	writeByteI2C(addr);
	//接收应答位
	readAckI2C();
	//这里不要有停止信号,看上面的示意图
	//closeI2C();
	//启动信号
	initI2C();
	//写入设备地址和写数据位(最低位为1,也就是读写位为1,读数据)
	writeByteI2C((0x50<<1)|0x01);
	//接收应答位
	readAckI2C();
	dat = readByteI2C(); //读取一个字节数据
	//发送应答位
	writeAckI2C(1);//这里应答位要发送1
	//停止信号
	closeI2C();
	///////
	EA = EA_State;
	return dat;
}
posted @ 2025-08-03 18:17  秦瑞迁  阅读(287)  评论(0)    收藏  举报