首页 测试文本

清翔零基础教你学51单片机_个人学习笔记(14)_数字温度传感器DS18B20(理论+实践)

说明

本人使用的是清翔的51单片机开发板,如果型号相同最方便,但是如果型号不同也可以参考,因为芯片都是一样的,只是外设不同而已,使用时只需要对照自己的开发板原理图稍微修改下引脚即可。

|

本次笔记对应视频教程的第40,,41集数字温度传感器DS18B20(理论+实践)

如果笔记之中有任何错误,请在评论区指出,谢谢

一、DS18B20

1.1 总览

注意中文数据手册有些许错误,看的时候需要留心。

引脚说明

1.2 两种供电方式

DS18B20可以不接VCC而从数据引脚吸取电源,这样最少只需要数据脚和接地两根线就行了。

寄生供电

外部电源供电

1.3 温度寄存器的格式

S为0表示正的温度,S为1表示负的温度

注:上电默认温度85℃

1.4 内部存储器结构

 设置完数据之后可以再发送拷贝命令把数据复制到EEPROM中,上电后会自动把EEPROM的数据复制到TH,TL,和配置寄存器中,实现掉电不丢失数据。

配置报警

位0和位1是测得的温度,位2位3是用户设置的高低温度报警数据,位4是配置寄存器

配置精度

存储器位4是配置精度的

1.5 访问DS18B20的顺序

通过单线总线端口访问 DS18B20 的协议如下:

  1. 步骤1. 初始化
  2. 步骤2. ROM 操作指令必须先发出一条ROM指令表明了顺序很重要
  3. 步骤3. DS18B20 功能指令

每一次 DS18B20 的操作都必须满足以上步骤,若是缺少步骤或是顺序混乱,器件将不会返回值。

1.6  ROM指令

Search ROM [F0h] ( 搜索 ROM 指令)

READ ROM [33h] ( 读取 ROM 指令 )

MATH ROM [55h] ( 匹配 ROM 指令 )

SKIP ROM [CCh] ( 忽略 ROM 指令)

ALARM SEARCH [ECH] 报警 搜索指令

1.7 功能指令

1.8 功能指令总结表

清翔PPT总结ROM指令和功能指令

二、 单总线信号

2.1 初始化时序

 主机先拉低总线480us以上,构成复位脉冲,然后释放总线,DS18B20会在探测到上升沿后,等待15到60us,再拉低总线60到240us构成存在脉冲。

2.2 写时序

要产生写时序,先把总线拉低至少1us,如果写0,总线必须拉低60到120us,然后释放总线,如果写1,在写时序之后15us内把总线拉高,总时长要大于60us。注意两次写周期之间至少要间隔1us

2.3 读时序

 要产生读时序,必须把总线拉低至少1us,然后释放总线,在读信号开始后15us内总线控制器采样总线数据,读一位数据至少保持在60us以上。两次读周期之间至少间隔1us

2.4 操作举例

三、 开发板原理图上的DS18B20

由于开发板上只挂载了1个DS18B20,所以可以直接发送忽略ROM指令,不需要发送64位地址,节省时间。

四、编程

4.1 创建工程

复制上一份工程文件夹,修改名称为“14.数字温度传感器DS18B20”,进入项目文件夹,打开工程文件,删除main.c函数的内容。

4.2 main.c

void main()
{
    int TEM;
    uchar TEML;
    uchar TEMH;
    while(1)
    {
        DSinit();               // 初始化DS18B20
        DSWriteByte(0xCC);      // 发送忽略ROM指令
        DSWriteByte(0x44);      // 发送温度转换功能指令
        // 上面3个步骤已经完成,再次操作需要再次进行3步
        DSinit();               // 初始化DS18B20
        DSWriteByte(0xCC);      // 发送忽略ROM指令
        DSWriteByte(0xBE);      // 读取暂存器功能指令
        TEML = DSReadByte();    // 读取1个字节(温度低位)
        TEMH = DSReadByte();    // 读取1个字节(温度高位)
        DSinit();               // 停止读取数据
        TEM = TEMH;
        TEM <<= 8;
        TEM |= TEML;
        TEM = TEM * 0.0625 * 10 + 0.5;  // 扩大十倍并四舍五入
        SEG_DIS3(TEM);
        SEG_DIS(2, 22);
    }
}

/**********************************************************************************/
/*************************  DS18B20  *********************************************/
/**********************************************************************************/

// DS18B20初始化
bit DSinit()
{
    bit i;
    DS = 1;
    _nop_();
    DS = 0;
    delay_us(73);  // 11.95 + 6.5*x = 480,得x ≈ 73
    DS = 1;        // 释放总线
    delay_us(8);   // 等待60us, 11.95 + 6.5*x = 60,得x=8
    i = DS;
    delay_us(29);  // 等待DS发送完存在信号,200us, 11.95+6.5*x=200,得x=29
    DS = 1;
    return (i);
}

// DS18B20写时序,写一个字节
void DSWriteByte(uchar DAT)
{
    uchar i;
    DS = 1;
    for (i = 0; i < 8; i++)
    {
        DS = 0;
        _nop_();
        DS = DAT & 0x01;
        DAT >>= 1;
        delay_us(11);  // 写0和写1都要求时长超过60us,11.95+6.5*x=80,->x=11
        DS = 1;        // 释放总线,等待下一次传输
        _nop_();
    }
    DS = 1;
}

// DS18B20读一个字节
uchar DSReadByte()
{
    uchar i;
    uchar j;
    uchar dat;
    DS = 1;
    for (i = 0; i < 8; i++)
    {
        DS = 0;
        _nop_();       // 产生读时序
        DS = 1;        // 释放总线
        delay5us();    // 延时一会儿
        j = DS;
        delay_us(10);  // 延时等待这次数据DS18B20发送完全
        DS = 1;
        _nop_();
        dat = (j << 7) | (dat >> 1);  // 接收到的数据是从低位到高位,顺序调换一下
    }
    return (dat);
}

void delay_us(uchar us)
{
    while(us--);  // 进入需要11.95us,执行一次us--需要6.5us
}

delay_us的时间可以用debug功能查看 。本代码并未考虑温度为负的情况。根据温度寄存器的格式,高字节前5位都是符号位,所以可以判断高字节前5位的任何1位是否为1,为1则表示温度为负。再处理一下数码管显示部分,就可以实现负温度显示了。

注意负数是以补码的形式存储在内存中的,所以还要处理一下,还原温度值。关于负数如何以补码的形式存储,请自行查阅资料。为了验证自己的负数代码是否正确,可以注释掉从DS18B20读取数据的代码,然后自己手动给对应的变量赋值,编译下载看数码管是显示正常。

对于显示位数,也可以通过处理显示函数来实现,温度整数部分最大是3位,小数部分最大是4位,可以先判断温度是否为负,来决定是否显示负号,再判断百位,十位,个位是不是0,如果是0则不显示,位置可以用一个变量来判断,某个位有数字显示(非0),就显示完之后让这个变量自加,如果某个位是0,就不运行这个位的代码,从而能实现自动向左靠齐。个位判断完后,在个位显示代码里面要加上小数点,然后再处理剩下的小数部分。小数部分最多4位,如果想实现不显示小数最后的0,可以判断最后1位是否为0,为0则不显示,如果最后一位为0,再判断倒数第2位是否为0,如此连续判断4位小数,就可以实现去掉小数末尾的0

在显示全部整数和小数中,就没必要进行四舍五入了,直接显示原转换精度,把转换后的TEM乘以0.0625再乘以10000,这样就能去掉全部的小数,把小数转换成了一个整数,再送入数码管显示处理。

注意,由于DS18B20测温范围最大125℃,日常测温在10到40度之间,但是第二位的小数点是手动打上去的,实际变量的值要乘以10倍,也就是100到400,超过了uchar类型的范围,因此我修改了一下SEG_DIS3为

void SEG_DIS3(uint i)
{
    SEG_DIS(1, i / 100);
    SEG_DIS(2, i % 100 / 10);
    SEG_DIS(3, i % 10);
}

只需要修改一下参数由uchar编程uint就行了。

如果想要修改成其他精度,可以参考

  • 1.5 访问DS18B20的顺序
  • 1.6  ROM指令
  • 1.7 功能指令

这三个小节的内容。

DSinit();               // 初始化DS18B20
DSWriteByte(0xcc);      // 发送跳跃ROM指令
DSWriteByte(0x4e);      // 写暂存器指令
DSWriteByte(0x7f);
DSWriteByte(0xf7);
DSWriteByte(0x1f);      // 配置工作在9位模式下
DSinit();               // 初始化DS18B20
DSWriteByte(0xcc);      // 发送跳跃ROM指令
DSWriteByte(0x48);      // 把温度上下限和精度设置存储到EEPROM中,下次上电自动读取

这些内容放在while循环的前面就行了,因为这些内容只需要执行一次,不需要一直在while循环里面重复执行,而且EEPROM有写入次数限制

4.3 main.h

写入函数声明和变量定义

/**************  DS18B20函数  *************************/
bit DSinit();                // DS18B20初始化
void DSWriteByte(uchar DAT); // DS18B20写时序,写一个字节
uchar DSReadByte();          // DS18B20读一个字节

sbit DS = P2^2;

4.4 修改后的代码

void SEG_DIS(uchar position, uchar number)
{
    //P0 = 0xFF;
    DU = 0;
    P0 = we[position - 1];
    WE = 1;
    WE = 0;

    //P0 = 0x00;
    P0 = du[number];
    DU = 1;
    DU = 0;
}

bit s;  // 标志温度是否为负的变量

void main()
{
    // 这些内容只需要执行一次即可,执行完可以注释掉
    unsigned long TEM;
    uchar TEML;
    uchar TEMH;
    uchar i;
//    
//    DSinit();       // 初始化DS18B20
//    DSWriteByte(0xCC);  // 发送忽略ROM指令
//    DSWriteByte(0x4e);  // 写暂存器指令
//    DSWriteByte(0x1e);   // 设置报警温度上限:30
//    DSWriteByte(0x1d);   // 设置报警温度下限:29
//    DSWriteByte(0x7f);  // 设置12位精度
//    
//    DSinit();       // 初始化DS18B20
//    DSWriteByte(0xCC);  // 发送忽略ROM指令
//    DSWriteByte(0x48);  // 拷贝暂存器数据到EEPROM实现掉电不丢失

    // 循环部分
    while(1)
    {
        DSinit();               // 初始化DS18B20
        DSWriteByte(0xCC);      // 发送忽略ROM指令
        DSWriteByte(0x44);      // 发送温度转换功能指令
        // 上面3个步骤已经完成,再次操作需要再次进行3步
        DSinit();               // 初始化DS18B20
        DSWriteByte(0xCC);      // 发送忽略ROM指令
        DSWriteByte(0xBE);      // 读取暂存器功能指令
        TEML = DSReadByte();    // 读取1个字节(温度低位)
        TEMH = DSReadByte();    // 读取1个字节(温度高位)
        DSinit();               // 停止读取数据
        TEM = TEMH;
        TEM <<= 8;
        TEM |= TEML;
        if (TEMH & 0x80)    // 符号位为1,说明温度小于0,需要根据补码求原码,显示负号
        {
            TEM = ~TEM + 1;
            s = 1;
        }
        else
            s = 0;  // 温度为正数,不显示负号,标志位为0
        TEM = TEM * 0.0625 * 10000;    // 扩大10000倍,4位小数全部变成整数
        i = 0;
        // 符号位
        if (s) SEG_DIS(++i, 20);
        // 百位
        if (TEM < 1000000);
        else SEG_DIS(++i, TEM / 1000000 % 10);
        // 十位
        if (TEM < 100000);
        else SEG_DIS(++i, TEM / 100000 % 10);
        // 个位
        SEG_DIS(++i, TEM / 10000 % 10);
        delay(1);       // 延时是因为个位显示比较暗
        SEG_DIS(i, 22);
        // 第一位小数
        SEG_DIS(++i, TEM / 1000 % 10);
        // 倒数第3位小数
        if (TEM % 1000) SEG_DIS(++i, TEM / 100 % 10);
        // 倒数第2位小数
        if (TEM % 100) SEG_DIS(++i, TEM / 10 % 10);
        // 最后1位小数
        if (TEM % 10) SEG_DIS(++i, TEM % 10);
    }
}

/**********************************************************************************/
/*************************  DS18B20  *********************************************/
/**********************************************************************************/

// DS18B20初始化
bit DSinit()
{
    bit i;
    DS = 1;
    _nop_();
    DS = 0;
    delay_us(73);  // 11.95 + 6.5*x = 480,得x ≈ 73
    DS = 1;        // 释放总线
    delay_us(8);   // 等待60us, 11.95 + 6.5*x = 60,得x=8
    i = DS;
    delay_us(29);  // 等待DS发送完存在信号,200us, 11.95+6.5*x=200,得x=29
    DS = 1;
    return (i);
}

// DS18B20写时序,写一个字节
void DSWriteByte(uchar DAT)
{
    uchar i;
    DS = 1;
    for (i = 0; i < 8; i++)
    {
        DS = 0;
        _nop_();
        DS = DAT & 0x01;
        DAT >>= 1;
        delay_us(11);  // 写0和写1都要求时长超过60us,11.95+6.5*x=80,->x=11
        DS = 1;        // 释放总线,等待下一次传输
        _nop_();
    }
    DS = 1;
}

// DS18B20读一个字节
uchar DSReadByte()
{
    uchar i;
    uchar j;
    uchar dat;
    DS = 1;
    for (i = 0; i < 8; i++)
    {
        DS = 0;
        _nop_();       // 产生读时序
        DS = 1;        // 释放总线
        delay5us();    // 延时一会儿
        j = DS;
        delay_us(10);  // 延时等待这次数据DS18B20发送完全
        DS = 1;
        _nop_();
        dat = (j << 7) | (dat >> 1);
    }
    return (dat);     // 接收到的数据是从低位到高位,顺序调换一下
}

4.5 现象

位数会随温度变化而变化,要测试负数,可以自己赋值s=1

本次笔记对应视频教程的第40,,41集 数字温度传感器DS18B20(理论+实践),到此结束。

|

下次笔记将对应视频教程的第42,43集 红外通讯(理论+实践)

|

如果笔记之中有任何错误,请在评论区指出,谢谢

posted @ 2025-11-08 15:24  BO_S  阅读(132)  评论(0)    收藏  举报
页脚 测试文本