演示视频:
温度数据储存报警器
源代码
如上图将17个文放在Keli5 中即可,然后烧录在单片机中就行了
烧录软件用的是STC-ISP,不知道怎么安装的可以去看江科大的视频:
【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】https://www.bilibili.com/video/BV1Mb411e7re?p=2&vd_source=ada7b122ae16cc583b4add52ad89fd5e
源代码:
头文件要记得宏定义和重定义,避免重复调用:
#ifndef _Timer0_h_//名字根据文件名定义即可
#define _Timer0_h_
//声明函数……
#endif
main.c
#include <STC89C5xRC.H>
#include "LCD1602.h"
#include "DS18B20.h"
#include "Delay.h"
#include "AT24C02.h"
#include "Key.h"
#include "Buzzer.h"
float T,TShow;//浮点数温度,温度显示
char TLow,THigh;//温度上限,温度下限
unsigned char KeyNum;//获取按键
void main(){
DS18B20_ConvertT();//上电先转换一次温度,防止第一次读数据错误
Delay(1000);//延时1秒启动
THigh=AT24C02_ReadByte(0);//在0字节地址读取温度上限;
TLow=AT24C02_ReadByte(1);//在1字节地址读取温度下限;
if(THigh>125 || TLow<-55 || THigh<=TLow){//初值判断
THigh=20;
TLow=15;
}
LCD_Init();//LCD1602初始化
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);
while(1){//无限循环
//温度读取与显示
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);//显示温度小数部分
//临界判断及显示
KeyNum=Key();//按键获取
if(KeyNum){//如果检测到按键执行下列代码
if(KeyNum==1){//如果按键值为1
THigh++;//上限加一
if(THigh>125){THigh=125;}//临界判断
}
if(KeyNum==2){//如果按键值为2
THigh--;//上限减一
if(THigh<=TLow){THigh++;}
}
if(KeyNum==3){//如果按键值为3
TLow++;//下限加一
if(TLow>=THigh){TLow--;}
}
if(KeyNum==4){//如果按键值为4
TLow--;//下限减一
if(TLow<-55){TLow=-55;}
}
LCD_ShowSignedNum(2,4,THigh,3);//LCD显示最高上限
LCD_ShowSignedNum(2,12,TLow,3);//LCD显示最低下限
AT24C02_WriteByte(0,THigh);//温度上限存在0字节地址;
Delay(5);//写入周期
AT24C02_WriteByte(1,TLow);//温度下限存在1字节地址;
Delay(5);//写入周期
}
if(T>THigh){//如果当前温度大于上限
LCD_ShowString(1,13,"OV:H");//LCD显示OV:H
Buzzer_Time(500);//蜂鸣器报警
}
else if(T<TLow){//如果当前温度小于下限
LCD_ShowString(1,13,"OV:L");//LCD显示OV:L
Buzzer_Time(500);//蜂鸣器报警
}
else{
LCD_ShowString(1,13," ");//清空状态显示
}
}
}
LCD1602.c
#include <STC89C5xRC.H>
//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0
//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay() //@11.0592MHz
{
unsigned char i, j;
i = 11;
j = 190;
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);//光标复位,清屏
}
/**
* @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');
}
}
LCD1602.h
#ifndef __LCD1602_H__
#define __LCD1602_H__
//用户调用函数:
void LCD_Init();//初始化
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);//显示单个字符
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);//显示字符串
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//显示数字
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);//显示带符号数字
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//显示十进制数字
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);//显示二进制数字
#endif
AT24C02.c
//AT24C02.c
#include <STC89C5xRC.H>
#include "I2C.h"
#define AT24C02_ADDRESS 0xA0
/**
* @brief AT24C02写入一个字节
* @param WordAddress 要写入字节的地址
* @param Data 要写入的数据
* @retval 无
*/
void AT24C02_WriteByte(unsigned char WordAddress,Data){
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_SendByte(Data);
I2C_ReceiveAck();
I2C_Shop();
}
/**
* @brief AT24C02读取一个字节
* @param WordAddress 要读出字节的地址
* @retval 读出的数据
*/
unsigned char AT24C02_ReadByte(unsigned char WordAddress){
unsigned char Data;
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS);
I2C_ReceiveAck();
I2C_SendByte(WordAddress);
I2C_ReceiveAck();
I2C_Start();
I2C_SendByte(AT24C02_ADDRESS|0x01);
I2C_ReceiveAck();
Data=I2C_ReceiveByte();
I2C_SendAck(1);
I2C_Shop();
return Data;
}
AT24C02.h
//AT24C02.h
#ifndef __AT24C02_H__
#define __AT24C02_H__
void AT24C02_WriteByte(unsigned char WordAddress,Data);
unsigned char AT24C02_ReadByte(unsigned char WordAddress);
#endif
Key.c
#include <STC89C5xRC.H>
#include "Delay.h"
unsigned char Key()//获取独立按键
{
unsigned char KeyNumber=0;
//进行判断是否摁下按键和防抖操作
if(P31==0){Delay(20);while(P31==0);Delay(20);KeyNumber=1;}
if(P30==0){Delay(20);while(P30==0);Delay(20);KeyNumber=2;}
if(P32==0){Delay(20);while(P32==0);Delay(20);KeyNumber=3;}
if(P33==0){Delay(20);while(P33==0);Delay(20);KeyNumber=4;}
return KeyNumber;
}
Key.h
#ifndef _Key_h_
#define _Key_h_
unsigned char Key();
#endif
I2C.c
//I2C.c
#include <STC89C5xRC.H>
sbit I2C_SCL=P2^1;
sbit I2C_SDA=P2^0;
//I2C开始
void I2C_Start(){
I2C_SDA=1;
I2C_SCL=1;
I2C_SDA=0;
I2C_SCL=0;
}
//I2C停止
void I2C_Shop(){
I2C_SDA=0;
I2C_SCL=1;
I2C_SDA=1;
}
//I2C发送一个字节
void I2C_SendByte(unsigned char Byte){
unsigned char i;
for(i=0;i<8;i++){
I2C_SDA=Byte&(0x80>>i);
I2C_SCL=1;
I2C_SCL=0;
}
}
//I2C接收一个字节
unsigned char I2C_ReceiveByte(){
unsigned char i,Byte=0x00;
I2C_SDA=1;
for(i=0;i<8;i++){
I2C_SCL=1;
if(I2C_SDA){Byte|=(0x80>>i);}
I2C_SCL=0;
}
return Byte;
}
//I2C发送应答
void I2C_SendAck(unsigned char AckBit){
I2C_SDA=AckBit;
I2C_SCL=1;
I2C_SCL=0;
}
//I2C接收应答
unsigned char I2C_ReceiveAck(){
unsigned char AckBit;
I2C_SDA=1;
I2C_SCL=1;
AckBit=I2C_SDA;
I2C_SCL=0;
return AckBit;
}
I2C.h
//I2C.h
#ifndef __I2C_H__
#define __I2C_H__
unsigned char I2C_ReceiveAck();
void I2C_SendAck(unsigned char AckBit);
unsigned char I2C_ReceiveByte();
void I2C_SendByte(unsigned char Byte);
void I2C_Shop();
void I2C_Start();
#endif
Delay.c
//Delay.c
#include <STC89C5xRC.H>
#include <INTRINS.H>
//延时函数
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char i, j;
while(xms){
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
Delay.h
//Delay.h
#ifndef __Delay_H__
#define __Delay_H__
//延时函数头文件
void Delay(unsigned int xms);
#endif
DS18B20.c
//DS18B20.c
#include <STC89C5xRC.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(){
OneWire_Init();//单总线初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//单总线发送1个字节(跳过ROM指令)
OneWire_SendByte(DS18B20_CONVERT_T);//单总线发送1个字节(转换温度指令)
}
/**
* @brief DS18B20读取温度
* @param 无
* @retval 温度数值
*/
float DS18B20_ReadT(){
unsigned char TLSB,TMSB;//温度格式
int Temp;//临时变量
float T;//浮点数温度
OneWire_Init();//单总线初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//单总线发送1个字节(跳过ROM指令)
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//单总线发送1个字节(转换温度指令)
TLSB=OneWire_ReceiveByte();//低8位
TMSB=OneWire_ReceiveByte();//高8位
Temp=(TMSB<<8)|TLSB;//合并温度16位
T=Temp/16.0;//正确转换温度(浮点数)
return T;//返回温度值
}
DS18B20.h
//DS18B20.h
#ifndef __DS18B20_H__
#define __DS18B20_H__
float DS18B20_ReadT();
void DS18B20_ConvertT();
#endif
OneWire.c
//OneWire.c
#include <STC89C5xRC.H>
//引脚定义
sbit OneWire_DQ=P3^7;
/**
* @brief 单总线初始化
* @param 无
* @retval 从机响应位,0为响应,1为未响应
*/
unsigned char OneWire_Init(){
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(){
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(){
unsigned char i;
unsigned char Byte=0x00;
for(i=0;i<8;i++){
if(OneWire_ReceiveBit()){(Byte|=0x01<<i);}
}
return Byte;
}
OneWire.h
//OneWire.h
#ifndef __OneWire_H__
#define __OneWire_H__
unsigned char OneWire_Init();
unsigned char OneWire_ReceiveByte();
void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveBit();
void OneWire_SendBit(unsigned char Bit);
#endif
Buzzer.c
//Buzzer.c
#include <STC89C5xRC.H>
#include <INTRINS.H>
#include "Delay.h"
//蜂鸣器端口
sbit Buzzer=P2^5;
//蜂鸣器私有延时函数,延时500us
void Buzzer_Delay500us() //@11.0592MHz
{
unsigned char i;
_nop_();
i = 227;
while (--i);
}
//蜂鸣器发声,ms是发声时长,单位毫秒
void Buzzer_Time(unsigned int ms){
unsigned int i;
for(i=0;i<ms*2;i++){
Buzzer=!Buzzer;//蜂鸣器发声
Buzzer_Delay500us();
}
}
Buzzer.h
//Buzzer.h
#ifndef __Buzzer_H__
#define __Buzzer_H__
void Buzzer_Time(unsigned int ms);
#endif
代码解析与教程:
Dealy模块
- 包含源代码与头文件,不需要知道怎么实现的会用即可,后续使用,直接将头文件和源代码拿过来用即可;
xms是定义的毫秒,1000毫秒就是1秒;模版生成的是1毫秒的,因此xms等于1000
LCD1602模块
- 包含源代码与头文件,不需要知道怎么实现的会用即可,后续使用,直接将头文件和源代码拿过来用即可;使用格式:(参考江科大的视频素材)
- LCD1602相关重要知识:
- LCD1602有两上下两行显示屏,每行各有16个小显示屏,如上图中的LCD_ShowString(1,3,"Hello"),第一个参数是第一行还是第二行,第2个参数是对应第几行的第几个小显示屏,最后一个是输出的东西,同理,到LCD_ShowNum(1,9,123,3)里,前三个和前面一样,最后一个参数是显示的位数,不够就在前面补0,例如输入1,参数为4,显示就是0001,输入23,参数为3,显示就是023
- 上图是LCD1602的开发原理模块图,由图可知,P10-P17是控制显示屏的重点;像矩阵那样,S1,由P17和P13控制,同理,其他按键一样,由于开发板限制,我们一般使用列遍历来控制按键,例如:S1,S5,S9,S13他们四个共用P13,所以将其为1列,将P13设定为1,P17设定为0,S1就会亮,其他按键同理。
Key模块
- 包含源代码与头文件,不需要知道怎么实现的会用即可,后续使用,直接将头文件和源代码拿过来用即可;
序号1是按键的防抖操作,不需要理解,有按键的地方直接用即可
序号2是独立按键控制变量。
KeyNumber就是返回值,按键K1就返回1,其他同理
I2C总线模块
- 包含源代码与头文件,需要知道怎么实现的,会用,后续使用,直接将头文件和源代码拿过来用即可;因为这部分比较难,轻度认识理解就行,越深越难
- 在此之前,我们要认识一下存储器:
- 本篇博客,以及博主的板子型号(STC89c52rc),用的储存器就是E2PROM储存器,简单介绍一下,RAM就是可读写储存器,SRAM是静态的,容量较大,成本高,类似于我们手机内存,DRAM是动态的,容量较小,成本较低,类似于我们手机的运行内存;ROM按常理来说是不可写的,只能读,但是他比RAM不易丢失,有断电保护,断电后数据不易丢失,因此非常受大家的喜欢,所以在后续的更新中,好多ROM也可以读写了,例如E2PROM。
- 了解上面之后,I2C总线可以理解成一个传输协议,或者传输方式,规则;作用是将一个机器(主机)的数据传输到另一个机器(从机);来看E2PROM原理图
- 可以看到SCL,SDA是I2C接口,A0,A1,A2是I2C地址,通过这些东西,将主机(单片机)数据传递到从机(E2PROM);下面来看传输是怎么实现的
- I2C总线传输时有两大部分,时序结构和数据帧,时序结构就是SCL,SDA这种东西的先后顺序或者说他俩的一些重要关系
- 时序结构有6个小模块,看图:
这是开始和结束两部分,中间框起来的部分是数据确认阶段,高电平是1(高一点的部分),低电平是0(低一点的部分);图中可以看到在数据确认阶段SCL始终为1,SDA也会变化,因此有:
//定义SCL,SDA的引脚 sbit I2C_SCL=P2^1; sbit I2C_SDA=P2^0; //I2C开始 void I2C_Start(){ I2C_SDA=1; I2C_SCL=1; I2C_SDA=0; I2C_SCL=0; } //I2C停止 void I2C_Shop(){ //因为开始后SCL=0了,所有这里不需要重复定义了 I2C_SDA=0; I2C_SCL=1; I2C_SDA=1; }
接下来是发送数据,发送一个字节(8个bit),在数据确认期间,SCL先0后1再0,SDA是数据,有可能是1,有可能是0,也会变化,即可发送一个bit,然后循环8次就是一个字节:
//I2C发送一个字节 void I2C_SendByte(unsigned char Byte){ unsigned char i; for(i=0;i<8;i++){ I2C_SDA=Byte&(0x80>>i); I2C_SCL=1; I2C_SCL=0; } }
发送字节是参数Byte,i是循环次数,也是右移几个单位,重点来了:注意看图中的先后顺序,SDA先变化,数据是高位在前,0x80是1000 0000,Byte&(有0即0)0x80就是保留Byte的第一位(最高位),其他全变0;然后变化SCL0,1,0;循环八次
接下来是接收数据,接收一个字节(8个bit),在数据确认期间,SCL先0后1再0,SDA是数据,有可能是1,有可能是0,也会变化,即可发送一个bit,然后循环8次就是一个字节:
//I2C接收一个字节 unsigned char I2C_ReceiveByte(){ unsigned char i,Byte=0x00; I2C_SDA=1; for(i=0;i<8;i++){ if(I2C_SDA){Byte|=(0x80>>i);} I2C_SCL=1; I2C_SCL=0; } return Byte; }
接收字节是无参数,返回值是Data,i是循环次数,也是右移几个单位,重点来了:注意,主机接收之前需要释放SDA(使SDA=1);注意看图中的先后顺序,SDA先变化,数据是高位在前,0x80是1000 0000,Byte&(有0即0)0x80就是保留Byte的第一位(最高位),其他全变0;然后变化SCL0,1,0;循环八次
![]()
接下来是发送应答和就收应答,作用就是在发送和接收时进行一个反馈,告诉你发送或者接收成功。
发送应答参数AckBit是应答数据,0就是表示应答,1是不应答; 注意看图中的先后顺序,SDA先变化,将应答数据赋值给SDA;然后变化SCL0,1,0;//I2C发送应答 void I2C_SendAck(unsigned char AckBit){ I2C_SDA=AckBit; I2C_SCL=1; I2C_SCL=0; } //I2C接收应答 unsigned char I2C_ReceiveAck(){ unsigned char AckBit; I2C_SDA=1; AckBit=I2C_SDA; I2C_SCL=1; I2C_SCL=0; return AckBit; }
接收应答无参数,返回值是AckBit是应答数据,0就是表示应答,1是不应答; 注意,主机接收之前需要释放SDA(使SDA=1);注意看图中的先后顺序,SDA先变化将SDA赋值给AckBit;然后变化SCL0,1,0;
- 了解时序结构之后,来看数据帧,也就是数据是怎么在I2C中传输的:
发送一帧数据是有I2C时序结构的6个小模块组成的,图中都标注了;需要注意的是要告诉I2C总线向谁发送,也就是要先发送从机地址,从机地址最后一个是告诉你是读还是写;
接收一帧数据是有I2C时序结构的6个小模块组成的,图中都标注了;需要注意的是要告诉I2C总线向谁接收,也就是要先发送从机地址,从机地址最后一个是告诉你是读还是写;注意最后一个发送应答是1,表示非应答
复合格式数据是有I2C时序结构的6个小模块组成的,图中都标注了;需要注意的是要告诉I2C总线向谁发送并接收,也就是要先发送从机地址,从机地址最后一个是告诉你是读还是写;注意最后一个发送应答是1,表示非应答
AT24C02模块 /E2PROM模块
- 包含源代码与头文件,需要知道怎么实现的,会用,后续使用,直接将头文件和源代码拿过来用即可;因为这部分比较难,轻度认识理解就行,越深越难
- AT24C02是E2PROM模块中的一小块,可以理解上述中所提到的从机,主机就是单片机,因此从机地址就是AT24C02地址;
字节写,随机读数据帧是有I2C时序结构的6个小模块组成的,图中不再标注了;需要注意的是要告诉I2C总线读还是写,也就是要发送从机地址SLAVE ADDRESS,从机地址最后一个是告诉你是读还是写;还有一个就是发送存储字节地址WORD ADDRESS;注意最后一个发送应答是1,表示非应答
//AT24C02.c #include <STC89C5xRC.H> #include "I2C.h" #define AT24C02_ADDRESS 0xA0 /** * @brief AT24C02写入一个字节 * @param WordAddress 要写入字节的地址 * @param Data 要写入的数据 * @retval 无 */ void AT24C02_WriteByte(unsigned char WordAddress,Data){ I2C_Start(); I2C_SendByte(AT24C02_ADDRESS); I2C_ReceiveAck(); I2C_SendByte(WordAddress); I2C_ReceiveAck(); I2C_SendByte(Data); I2C_ReceiveAck(); I2C_Shop(); } /** * @brief AT24C02读取一个字节 * @param WordAddress 要读出字节的地址 * @retval 读出的数据 */ unsigned char AT24C02_ReadByte(unsigned char WordAddress){ unsigned char Data; I2C_Start(); I2C_SendByte(AT24C02_ADDRESS); I2C_ReceiveAck(); I2C_SendByte(WordAddress); I2C_ReceiveAck(); I2C_Start(); I2C_SendByte(AT24C02_ADDRESS|0x01); I2C_ReceiveAck(); Data=I2C_ReceiveByte(); I2C_SendAck(1); I2C_Shop(); return Data; }
按照图中的I2C总线时序结构,依次调用函数,即可实现
注意写周期是5ms,意味着每次写完后要延时5ms;
单总线模块
- 包含源代码与头文件,需要知道怎么实现的,会用,后续使用,直接将头文件和源代码拿过来用即可;因为这部分比较难,轻度认识理解就行,越深越难
- 在此之前,我们要认识一下单总线:
- 单总线和上节课将的I2C总线有相似之处:
(51单片机)LCD显示数据存储(DS1302时钟模块教学)(LCD1602教程)(独立按键教程)(延时函数教程)(I2C总线认识)(AT24C02认识)-CSDN博客
- 了解之后,di总线可以理解成一个传输协议,或者传输方式,规则;作用是将一个机器(主机)的数据传输到另一个机器(从机);来看DS18B20原理图
由于可以看到I/O口是P37,因此单总线引脚就是P37
//引脚定义 sbit OneWire_DQ=P3^7;
- 通过这个东西,将主机(单片机)数据传递到从机(DS18B20);下面来看传输是怎么实现的
- 单总线传输时有时序结构,时序结构通过一条DQ线来控制,因此叫单总线
- 因为单总线只有一条线,需要通过不同的延时来实现数据传输,因此需要用STC-ISP的软件延时功能,生成延时:
- 时序结构有5个小模块,看图:
这是初始化部分,中间那条线就是DQ通讯线,高电平是1(高一点的部分),低电平是0(低一点的部分);图中可以看到DQ从1变0,然后480us后,释放总线变1,然后15-60us后再变0,60-240us后变1;因此有:
/** * @brief 单总线初始化 * @param 无 * @retval 从机响应位,0为响应,1为未响应 */ unsigned char OneWire_Init(){ 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; }
接下来是发送数据,发送一个bit,DQ先1后0,在1-15us内,让DS18B20数据感应,然后总时间大于60us释放总线变1,即可发送一个bit:
/** * @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; }
发送bit是参数Bit,i是延时用的,重点来了:注意看图中的时间先后;
接下来是接收bit,接收一个bit,DQ先1后0,在1-15us内变1,要在接近15us的时候让DS18B20数据感应,然后总时间大于60us,即可接收一个bit:
/** * @brief 单总线接收一位 * @param 无 * @retval 读取的位 */ unsigned char OneWire_ReceiveBit(){ 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; }
接收bit是无参数,返回值是bit,i是延时用的,重点来了:注意看图中的先后顺序,而且要在15us附近数据感应;
![]()
接下来是发送一个字节和接收一个字节。
/** * @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(){ unsigned char i; unsigned char Byte=0x00; for(i=0;i<8;i++){ if(OneWire_ReceiveBit()){(Byte|=0x01<<i);} } return Byte; }
低位在前,因此左移,&(有0即0)的操作是获取当前位,|(有1即1)的操作当OneWire_ReceiveBit不为0时,将当前位赋值给Byte
DS18B20模块
- 包含源代码与头文件,需要知道怎么实现的,会用,后续使用,直接将头文件和源代码拿过来用即可;因为这部分比较难,轻度认识理解就行,越深越难
- DS18B20是温度传感器,可以理解上述中所提到的从机,主机就是单片机
DS18B20有字节地址操作,作用是进行一定的操作,ROM和功能操作:
- 了解之后,来看数据帧,也就是数据是怎么在单总线中传输的:
首先可以看到温度变换的操作,先初始化,然后发送跳过ROM的字节地址进行跳过ROM,然后发送温度变换的字节地址,进行温度转换
/** * @brief DS18B20开始温度变换 * @param 无 * @retval 无 */ void DS18B20_ConvertT(){ OneWire_Init();//单总线初始化 OneWire_SendByte(DS18B20_SKIP_ROM);//单总线发送1个字节(跳过ROM指令) OneWire_SendByte(DS18B20_CONVERT_T);//单总线发送1个字节(转换温度指令) }
首先可以看到温度读取的操作,先初始化,然后发送跳过ROM的字节地址进行跳过ROM,然后发送温度读取的字节地址,进行读暂存器和连续的读操作
/** * @brief DS18B20读取温度 * @param 无 * @retval 温度数值 */ float DS18B20_ReadT(){ unsigned char TLSB,TMSB;//温度格式 int Temp;//临时变量 float T;//浮点数温度 OneWire_Init();//单总线初始化 OneWire_SendByte(DS18B20_SKIP_ROM);//单总线发送1个字节(跳过ROM指令) OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//单总线发送1个字节(转换温度指令) TLSB=OneWire_ReceiveByte();//低8位 TMSB=OneWire_ReceiveByte();//高8位 Temp=(TMSB<<8)|TLSB;//合并温度16位 T=Temp/16.0;//正确转换温度(浮点数) return T;//返回温度值 }
从上图可以看出,TLSB和TMSB温度格式,是8位的,MS的前5为都是符号位。将它俩合到一块就是温度。
Buzzer模块
- 先看原理图:
由BEEP控制
再看五线四项步进电机图
- 图中可以知道BEEP是OUT5,也就是IN5,由P25控制,因此:
//蜂鸣器端口 sbit Buzzer=P2^5;
定义好端口后,定义蜂鸣器的延时函数,蜂鸣器发声函数:
//蜂鸣器私有延时函数,延时500us void Buzzer_Delay500us() //@11.0592MHz { unsigned char i; _nop_(); i = 227; while (--i); } //蜂鸣器发声,ms是发声时长,单位毫秒 void Buzzer_Time(unsigned int ms){ unsigned int i; for(i=0;i<ms*2;i++){ Buzzer=!Buzzer;//蜂鸣器发声 Buzzer_Delay500us(); } }
让它500us的频率进行,ms是你控制的发声时长;
main模块
- 注释写的很清楚,这里不做解释了
#include <STC89C5xRC.H> #include "LCD1602.h" #include "DS18B20.h" #include "Delay.h" #include "AT24C02.h" #include "Key.h" #include "Buzzer.h" float T,TShow;//浮点数温度,温度显示 char TLow,THigh;//温度上限,温度下限 unsigned char KeyNum;//获取按键 void main(){ DS18B20_ConvertT();//上电先转换一次温度,防止第一次读数据错误 Delay(1000);//延时1秒启动 THigh=AT24C02_ReadByte(0);//在0字节地址读取温度上限; TLow=AT24C02_ReadByte(1);//在1字节地址读取温度下限; if(THigh>125 || TLow<-55 || THigh<=TLow){//初值判断 THigh=20; TLow=15; } LCD_Init();//LCD1602初始化 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); while(1){//无限循环 //温度读取与显示 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);//显示温度小数部分 //临界判断及显示 KeyNum=Key();//按键获取 if(KeyNum){//如果检测到按键执行下列代码 if(KeyNum==1){//如果按键值为1 THigh++;//上限加一 if(THigh>125){THigh=125;}//临界判断 } if(KeyNum==2){//如果按键值为2 THigh--;//上限减一 if(THigh<=TLow){THigh++;} } if(KeyNum==3){//如果按键值为3 TLow++;//下限加一 if(TLow>=THigh){TLow--;} } if(KeyNum==4){//如果按键值为4 TLow--;//下限减一 if(TLow<-55){TLow=-55;} } LCD_ShowSignedNum(2,4,THigh,3);//LCD显示最高上限 LCD_ShowSignedNum(2,12,TLow,3);//LCD显示最低下限 AT24C02_WriteByte(0,THigh);//温度上限存在0字节地址; Delay(5);//写入周期 AT24C02_WriteByte(1,TLow);//温度下限存在1字节地址; Delay(5);//写入周期 } if(T>THigh){//如果当前温度大于上限 LCD_ShowString(1,13,"OV:H");//LCD显示OV:H Buzzer_Time(500);//蜂鸣器报警 } else if(T<TLow){//如果当前温度小于下限 LCD_ShowString(1,13,"OV:L");//LCD显示OV:L Buzzer_Time(500);//蜂鸣器报警 } else{ LCD_ShowString(1,13," ");//清空状态显示 } } }
注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!!