Protues 基于51单片机的DS18B20温度采集:键盘输入温度上下限_超出设定温度范围报警_8数码管显示

功能实现:
1、设计单片机与16按键的矩阵式键盘接口以及8数码管的接口电路,测试显示和按键功能。
2、设计单片机与DS18B20的接口电路,实现数字温度信息的采集,然后编程处理采集到的数据,得到温度值。
3、将采集的温度信息的显示在数码管上。
4、设计声光报警电路,设置温度的上下限值,实现报警功能
5,将温度上下限显示在数码管上。
6,温度超过温度上限时,步进电机驱动风扇降温。
欢迎超链接过来的读者,功能演示为GIF图片,鼠标请滑慢一点

protues仿真图
在这里插入图片描述

功能展示

温度的实时获取
在这里插入图片描述
温度的设置与查看
在这里插入图片描述
报警效果
在这里插入图片描述
代码较长分开说明:

一、 定义与函数声明

#include <reg52.h>
#include <intrins.h> // 因为此文件中用到了延时函数_nop_()(延时一个机器周期),所以要包含_nop_()的头文件、
#define u16 unsigned int
#define u8 unsigned char
char temperatureH = 100, temperatureL = -10; //最高温,最低温  char范围-128~127
// 键盘定义
u8 line, row, Sort = 0, kvalue = 0, kscan, kscanA = 0, kscanB = 0;
u8 keyRowLine();
void getKeyVaule();
void setH();
void setL();
void showH();
void showL();
void dealKey();
// 测温定义
sbit SPEAK = P3 ^ 1;
sbit DQ = P3 ^ 7;
u8 flag = 0, EX = 0, setting = 0, led_active = 0;
u16 n = 0;
u8 dat[10] = {0, 0, 0, 12, 5, 6, 7, 8, 9};
u8 dispcode[] =
    {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
     0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71,
     0x40, 0x76, 0xb8, 0xb7, 0xbe}; //'-'  'H'   'L'  'n'  'u'
// *****************函数声明*******************
void delay5(u8);
u8 initialize();
void write1Byte(u8 come);
u8 read1Byte();
u16 getTemperature();
void Temp_treatment();
void LED_4();
void alarm();

唯一要注意的是延时函数_nop_()为延时一个机器周期的作用

、代码逻辑概括 主函数

// *****************主函数**********************
void main()
{
    u8 i;
    delay5(1000);
    while (1)
    {
        getKeyVaule();    //键盘函数
        dealKey();        //处理按键
        Temp_treatment(); //温度函数
        for (i = 0; i < 30; i++)
        {
            LED_4(); //显示LED
            delay5(1000);
            if (EX)
            {
                alarm(); //报警
                delay5(1000);
            }
        }
    }
}

for (i = 0; i < 30; i++) 语句是为了在一次while (1)循环中LED显示次数增多,增强LED显示稳定性。
EX代表温度超过范围,会使蜂鸣器报警。

、精确延时5us 延时函数

// *****************延时函数********************
void delay5(u8 n)
{
    do
    { //搭配11.059MHZ 精确延时5us
        _nop_();
        _nop_();
        _nop_();
        n--;
    } while (n);
}

、51单片机想向18B20打招呼 初始化函数

// *****************初始化函数******************
u8 initialize()
{
    u8 Prence;
    DQ = 0;      //拉低总线
    delay5(120); //600us
    DQ = 1;      //总线控制给18b20
    delay5(16);  //80us
    if (DQ)
    {
        Prence = 0;
    }
    else
    {
        Prence = 1;
    }
    delay5(80); //400us
    return Prence;
}

初始化时序图
在这里插入图片描述
黑色实线: 51控制总线 灰色实线: 18B20控制总线
Prence为设定的标志位,防止无人应答进入死循环。

、51单片机向18B20发指令 写函数

// *****************写函数**********************
void write1Byte(u8 come)
{
    u8 i;
    for (i = 0; i < 8; i++)
    {
        DQ = 0;           //51控制总线
        DQ = come & 0x01; //低位开始写,1us
        delay5(12);       //60us
        DQ = 1;           //18b20控制总线
        come = come >> 1;
        delay5(5);
    }
}

写时序图
在这里插入图片描述
无论读写,都是从低字节,低位开始的。

、51单片机从18B20读取温度 读函数

// *****************读函数**********************
u8 read1Byte()
{
    u8 i, value = 0;
    for (i = 0; i < 8; i++)
    {
        DQ = 0;             //51控制总线
        delay5(1);          //5us
        DQ = 1;             //释放总线
        value = value >> 1; //从低位开始
        _nop_();            //一个机器周期的时间
        if (DQ)
        {
            value = value | 0x80;
        }
        delay5(11);
    }
    return value;
}

读时序图
在这里插入图片描述

if (DQ)
        {
            value = value | 0x80;
        }

整个函数中这一句话是最难理解的,指当18B20传来的数为1时,执行语句,为0时不执行,毕竟51和18B20只有DQ连着,所以18B20传什么,DQ就是什么。直接举个栗子。

// DS18B20   1110 0011   0X80=1000 0000
// 51  i=0  1111 0001   i=1
// 51  1111 1000  i=2
// 51  0111 1100  i=3
// 51  0011 1110  i=4
// 51  0001 1111  i=5
// 51  1000 1111  i=6
// 51  1100 0111  i=7
// 51  1110 0011  i=8

经过一个读函数,51成功读到了18B20的一个字节。

、准备工作完成开始 测温

// *****************测温函数******************
u16 getTemperature()
{
    // 启动温度转换
    u8 low, high, Temp;
    initialize();
    delay5(10);
    write1Byte(0xcc);//单片工作
    write1Byte(0x44);//启动温度转换
    initialize();
    write1Byte(0xcc);
    write1Byte(0xbe);//读取
    low = read1Byte();
    high = read1Byte();
    Temp = (high << 4) | (low >> 4);
    if (Temp > 127)
    { //温度为负值,将补码转换成原码
        Temp = ~Temp + 1;
        flag = 1;
    }
    else
    {
        flag = 0;
    }
    return (Temp);
}

解释一下下面语句如何获得温度的整数值

    Temp = (high << 4) | (low >> 4);
//和
if (Temp > 127)
    { //温度为负值,将补码转换成原码
        Temp = ~Temp + 1;
        flag = 1;
    }

首先,温度的数据放在DS18B20的前2个字节
在这里插入图片描述
所以,low位温度值低位,high为温度值高位。
这2个字节各个位的含义:
在这里插入图片描述

    // 2字节 16位  从低位开始
    // 1~4位 温度的小数部分
    // 5~11位  温度的整数部分
    // 12~16位  符号位
    Temp = (high << 4) | (low >> 4);
    //0111 0000  |   0000 1101= 0111 1101
    // 即十进制的125
    //如果Temp为正数,Temp最大为 0111 1111 即127。补码就是原码
    //Temp > 127,则温度为负数,符号位为1,将补码转换成原码

求小数部分,这个实验中没用到,拓展一下

    //** 小数部分**xiaoshu1表示小数点后1位//
    xiaoshu = (low & 0x0f);
    xiaoshu1 = xiaoshu * 625 / 1000;
    xiaoshu2 = (xiaoshu * 625 / 100) % 10;
    xiaoshu3 = (xiaoshu * 625 / 10) % 10;
    xiaoshu4 = (xiaoshu * 625) % 10;

、得到温度之后,根据温度处理各个标准位

温度处理

// *****************温度处理函数****************
void Temp_treatment()
{
    int Temp;
    Temp = getTemperature();
    if (flag)
    { //负数
        dat[4] = 16;
        Temp = 0 - Temp;
        if ((Temp) > (temperatureH))
        {
            dat[7] = 15;
            EX = 1; //报警暂停
        }
        else if ((Temp) < (temperatureL))
        {
            dat[7] = 10;
            EX = 1; //报警暂停
        }
        else
        {
            dat[7] = 12;
            EX = 0; //报警暂停
        }
        dat[5] = 16 - (((Temp % 100) / 10) & 0x0f);
        if (dat[5] == 16)
        {
            dat[5] = 0;
        }
        dat[6] = 16 - ((Temp % 10) & 0x0f);
        if (dat[6] == 16)
        {
            dat[6] = 0;
        }
    }
    else
    { //正数
        dat[4] = (Temp / 100) & 0x0f;
        if ((Temp) > (temperatureH))
        {
            dat[7] = 15;
            EX = 1; //报警开始
        }
        else if ((Temp) < (temperatureL))
        {
            dat[7] = 10;
            EX = 1; //报警开始
        }
        else
        {
            dat[7] = 12;
            EX = 0; //报警暂停
        }
        dat[5] = ((Temp % 100) / 10) & 0x0f;
        dat[6] = (Temp % 10) & 0x0f;
    }
}

temperatureH temperatureL 表示温度上限和下限,初始默认值为:100℃和-10℃。
这个函数实现的功能是:

  1. 温度超过指定范围,蜂鸣器报警。
  2. 高于温度上限,最后一位数码管显示F,低于温度下限,最后一位数码管显示A,正常情况下显示C。
    在这里插入图片描述

、LED显示与报警

解释略

// *****************LED显示函数*****************
void LED_4()
{
    P1 = P1 | 0XFF; //消隐
    P0 = dispcode[dat[led_active]];
    P1 = ~(1 << led_active);
    led_active++;
    if (led_active >= 8)
    {
        led_active = 0;
    }
}
// *****************警报函数********************
void alarm()
{
    n++;
    if (n < 150)
    // 0~0.5s  2KHz
    {
        SPEAK = ~SPEAK;
    }
    if ((n >= 150) && (n < 300))
    // 0.5s~1s 1KHz
    {
        if (n % 2)
        // 2KHz频率减半
        {
            SPEAK = ~SPEAK;
        }
    }
    if (n == 300)
    // 完成一个周期
    {
        n = 0;
    }
}

、键盘函数

行列转换法
// *****************键盘函数********************
u8 keyRowLine()
{
    u8 before;
    P2 = 0x0f;
    line = P2;
    if (line != 0x0f)
    {
        /* 判断行状态 */
        delay5(1000);
        line = P2;
        if (line != 0x0f)
        {
            /* 有按键按下 */
            line = line & 0x0f;
            P2 = 0xf0;
            // delay1ms(1); //等待变化时间
            row = P2;
            row = row & 0xf0;
            kscan = line | row;
            before = P2;
            while (P2 == before)
            {
                /* code */
            }
            return kscan;
        }
    }
    return 0xff;
}

下面这个语句有利有弊,可以防止按一次键盘,产生多次按下的效果,但是按键按下时,LED会黑一下。自己权衡吧。

            before = P2;
            while (P2 == before)
            {
                /* code */
            }
根据按键调整标志位

在这里插入图片描述

// 获得键编码,调整标志位
void getKeyVaule()
{
    u8 Kscan;
    Kscan = keyRowLine();
    switch (Kscan)
    {
    case 0xee:
        setting = 1; //显示最低温
        break;
    case 0xde:
        setting = 2; //显示最高温
        break;
    case 0xbe:
        setting = 3; //设置最低温
        break;
    case 0x7e:
        setting = 4; //设置最高温
        break;
    case 0xed:       //归零
        dat[0] = 0;
        dat[1] = 0;
        dat[2] = 0;
        dat[3] = 12;
        Sort = 0;
        break;
    case 0xdd:       //输入负号
        dat[0] = 16;
        Sort = 1;
        break;
    case 0xbd:
        dat[Sort] = 0;
        Sort++;
        break;
    case 0x7d:
        dat[Sort] = 1;
        Sort++;
        break;
    case 0xeb:
        dat[Sort] = 2;
        Sort++;
        break;
    case 0xdb:
        dat[Sort] = 3;
        Sort++;
        break;
    case 0xbb:
        dat[Sort] = 4;
        Sort++;
        break;
    case 0x7b:
        dat[Sort] = 5;
        Sort++;
        break;
    case 0xe7:
        dat[Sort] = 6;
        Sort++;
        break;
    case 0xd7:
        dat[Sort] = 7;
        Sort++;
        break;
    case 0xb7:
        dat[Sort] = 8;
        Sort++;
        break;
    case 0x77:
        dat[Sort] = 9;
        Sort++;
        break;
    default:
        break;
    }
    if (Sort >= 3)
    {
        Sort = 0;
    }
}

Sort取值0~2,使输入在数码管1 ~3 位循环

按键响应函数
// *****************按键响应函数********************
void dealKey()
{
    if (setting == 1)
    {
        showL();//显示温度下限
    }
    else if (setting == 2)
    {
        showH();//显示温度上限
    }
    else if (setting == 3)
    {
        setL();//设定温度下限
    }
    else if (setting == 4)
    {
        setH();//设定温度上限
    }
}

设定温度上下限函数差不多,这里就说说设定温度上限函数

void setH()
{
    temperatureH = 0;
    temperatureH = 10 * dat[1] + dat[2];
    if (dat[0] == 16)
    {
        temperatureH = 0 - temperatureH;
    }
    else
    {
        temperatureH = 100 * dat[0] + temperatureH;
    }
    Sort = 0;//*
    setting = 0;//*
    dat[3] = 17;
}

首先判断最高位是不是负号,然后将数码管的值赋给全局变量temperatureH即可。

    Sort = 0;//*
    setting = 0;//*

这两句话非常重要,setting也是全局变量,如果不置成0,按键响应函数将不断重复上一次执行的功能。Sort = 0;是为了保证,按键响应后,键盘输入从数码管第一位开始。

如果设置温度上限成功,数码管第4位(输入状态)会显示‘H’
在这里插入图片描述

//************显示温度下限***************
void showL()
{
    char LowTemperature = temperatureL;//*
    if (LowTemperature < 0)
    {
        LowTemperature = 0 - LowTemperature;
        dat[0] = 16;
        dat[1] = LowTemperature / 10;
        dat[2] = LowTemperature % 10;
    }
    else
    {
        dat[0] = LowTemperature / 100;
        dat[1] = (LowTemperature / 10) % 10;
        dat[2] = LowTemperature % 10;
    }
    Sort = 0;
    setting = 0;
    dat[3] = 20;
}

本函数需要注意的语句

 char LowTemperature = temperatureL;//*

本函数首先会让负数转为正数,temperatureL是全局变量,如果直接用temperatureL进行showL()操作,可能改变设置好的温度下限。
因此,采用局部变量LowTemperature对temperatureL进行替换,局部变量的改变不会影响全局变量。

最后显示成功如图(U的上面开口,下面封闭,用来表示下限,同理‘n’表示上限。
在这里插入图片描述

暂时就这样吧!


版权声明:本文为weixin_45942372原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。