STC8H开发(十一): GPIO单线驱动多个DS18B20数字温度计

发布者:innovator7最新更新时间:2022-06-07 来源: eefocus关键字:GPIO  DS18B20  数字温度计 手机看文章 扫描二维码
随时随地手机看文章

DS18B20

参数

单线总线结构, 允许一根总线上挂接多个 DS18B20 并分别通信

在普通温度下, 可以直接从数据口取电, 这时候只需要两根连线.

供电电压 [3.0V, 5.5V]

温度检测范围 [-55°C, +125°C]摄氏度, [-67°F, +257°F]华氏度

精确率: 在 [-10°C, +85°C] 为 ±0.5°C

Pin脚

一般见到的都是3pin的To-92封装, 和普通三极管一样, 使平面朝向自己, Pin脚朝下, 从左往右依次为: GND, DQ, VDD

内部存储结构

DS18B20内部有9字节的暂存器和3个字节的EEPROM存储, EEPROM可以擦写5万次以上. 结构如下

测温

DS18B20的核心功能就是数字化的温度读数, 可以设置为9, 10, 11, 12位分辨率, 缺省分辨率是12位. 各分辨率对应的读数, 温度分辨率分别是0.5, 0.25, 0.125, 0.0625摄氏度.


在执行温度转换命令Convert T0x44后, 温度会被转换并存储在一个2字节的内存单元, 然后通过读取命令Read Scratchpad0xBE读出.


转换时间

在温度转换命令Convert T0x44发起到采集完成需要的时间可能会长达750 ms. 实际使用中, 不同批次 DS18B20 的转换时间差异也很大, 有的在200-300 ms, 有的接近 800 ms. 貌似越是最近制造的时间越短(可能是工艺改进了?).


如果没有从VDD供电, DS18B20 的 DQ 必须在转换过程中保持高电平以提供能量, 因此在这种场景下, 采集的过程中不允许进行其他活动.


读数结构

这两个字节各个bit分别代表的数字含义如下, 高字节的高5位仅用于表示温度的正负, 正温度是0, 负温度是1, 后面11个bit表示的数字, 负值使用的是补码, 读数用 (0xFF - 读数)


正温度时, 将16位整数乘以对应的温度分辨率

负温度时, 将16位整数取反加1后, 乘以对应的温度分辨率

image.png

读数快查表

上电后的缺省值为0x0550, 对应85°C, 如果一直读出都是这个值, 需要检查接线

image.png

ROM读数

每个 DS18B20 包含一个唯一的只读的64bit编码, 其结构为


最初 8 bits 为固定的 0x28, 1-Wire family code

接下来的 48 bits 是唯一序列号

最后的 8 bits 是前面 56 bits 的 CRC 校验码.

这个 64-bit ROM 和 ROM 方法允许在单线(1-Wire)总线上运行多个 DS18B20, 使用单线总线需要使用下面的方法之一发起:


Read ROM,

Match ROM,

Search ROM,

Skip ROM, or

Alarm Search.

After a ROM function sequence has been successfully executed, the functions specific to the DS18B20 are accessible and the

bus master may then provide one of the six memory and control function commands.


CRC 计算

DS18B20 在读取8字节ROM和9字节暂存器时, 最后一个字节都是前面所有字节的CRC校验值. CRC值的比较与是否继续操作完全由总线控制端决定, DS18B20 内部仅计算CRC, 并不会对CRC不匹配的情况进行处理, 需要总线控制端主动判断.


计算CRC的等效多项式函数为(这是datasheet中的式子, 并非幂运算, 要结合后面的流程图理解)

image.png


1-Wire总线的CRC计算由移位寄存器异或门组成的多项式发生器来执行: 移位寄存器位初始化为0, 然后从第一个字节的最低位开始, 一次移入一位, 根据计算结果决定是否与第4, 第5位作异或, 然后CRC也往右移, 最后移位寄存器的值就是CRC.


使用C语言表示的8位CRC计算函数为


uint8_t DS18B20_Crc(uint8_t *addr, uint8_t len)

{

    uint8_t crc = 0, inbyte, i, mix;


    while (len--)

    {

    // inbyte 存储当前参与计算的新字节

        inbyte = *addr++;


        for (i = 8; i; i--) 

        {

        // 将新字节与CRC从低位到高位, 依次做异或运算, 每次运算完CRC右移一位

        // 如果运算结果值为1, 则将CRC与 1000 1100 作异或

        // 第3,4位代表流程图中的异或运算, 第7位其实就是运算结果移入的1

            mix = (crc ^ inbyte) & 0x01;

            crc >>= 1;

            if (mix) 

            {

                crc ^= 0x8C;

            }

            inbyte >>= 1;

        }

    }

    return crc;

}


ROM Search 搜索算法

当单线总线上挂接了多个DS18B20时, 总线控制端需要通过 ROM Search 命令来判断总线上存在的设备以及获取他们的8字节唯一ROM.


ROM搜索算法是重复进行一个简单的三步操作: 读取一位, 读取这位的补码, 写入这一位的目标值.


总线控制端在8字节ROM的每一位上执行这个三步操作后, 就能知道一个 DS18B20 的 8字节 ROM 值, 如果总线上有多个 DS18B20, 则需要重复多次.


搜索示例

下面的例子假设总线上有4个设备, 对应的ROM值分别为


ROM1 00110101…

ROM2 10101010…

ROM3 11110101…

ROM4 00010001…

搜索过程如下


单线总线控制端(以下简称总控)执行 RESET, 所有的 DS18B20设备(以下简称设备)响应这个RESET

总控执行 Search ROM 命令

总控读取1个bit. 这时每个设备都会将自己的ROM的第一个bit放到总线上, ROM1 和 ROM4 会对总线写0(拉低总线), 而 ROM2 和 ROM3 则会对总线写1, 允许总线保持高电平. 这时候总控读取的是0(低电平). 然后总控继续读下一个bit, 每个设备会将第一个bit的补码放到总线上, 这时候 ROM1 和 ROM4 写1, 而 ROM2 和 ROM3 写0, 因此总控依然读到一个0, 这时候总控会知道存在多个设备, 并且它们的ROM在这一位上的值不同. 从每次的两步读取中观察到的值分别有以下的含义

00 一定有多个设备, 且在这一位上值不同

01 所有设备(不一定有多个), ROM在这一位上的值是0

10 所有设备(不一定有多个), ROM在这一位上的值是1

11 总线上没有设备

总控写入一个bit, 比如写入0, 表示在后面的搜索中屏蔽 ROM2 和 ROM3, 仅留下 ROM1 和 ROM4

总控再执行两次读操作, 读到的值为0,1, 这表示总线上所有设备在这一位上的值都是0

总控写入一个bit, 因为值是确定的, 这次写入的是0

总控再执行两次读操作, 读到的值为0,0, 这表示总线上还有多个设备, 在这一位上的值不同

总控写入一个bit, 这次写入0, 这将屏蔽 ROM1, 仅留下 ROM4

总控重复进行三步操作, 读出 ROM4 剩余的位, 完成第一次搜索

总控再次重复之前的搜索直到第7位

总控写入一个bit, 这次写入1, 将屏蔽 ROM4, 仅保留 ROM1

总控通过重复三步操作, 读出 ROM1 剩余的位

总控再次重复之前的搜索直到第3位

总控写入一个bit, 这次写入1, 将屏蔽 ROM1 和 ROM4 仅保留 ROM2 和 ROM3

重复之前的逻辑, 当所有00读数都被处理, 说明设备的ROM已经全部被读取.

总控通过单线总线读取所有设备, 每个设备需要的时间为960 µs + (8 + 3 x 64) 61 µs = 13.16 ms, 识别速度为每秒钟75个设备.


代码逻辑

整体的逻辑是按一个固定的方向(先0后1)深度优先遍历一个二叉树.


数据结构


预设一个8字节数组 Buff 用于记录路径(即ROM的读数)

预设一个8字节数组 Stack, 用于记录每一位的值是否确定, 如果确定就是1, 未确定就是0.

预设一个整数变量 Fork_Point 用于记录每一轮搜索中得到的最深分叉点的位置, 下一次到这一位就用1进行分叉.

每一轮的遍历逻辑


从低位开始, 每一位进行两次读, 得到这一位的值和补码

对前面的结果进行判断

如果为11, 说明没有设备, 直接退出

如果为01, 说明这一位都是0, 写入 Buff, 同时将 Stack 这一位设成 1 (已确认)

如果为10, 说明这一位都是1, 写入 Buff, 同时将 Stack 这一位设成 1 (已确认)

如果为00, 说明这一位产生了分叉, 需要继续判断

对分叉的判断

如果当前位置比已知的分叉点更浅, 说明还没到该分叉的位置, 继续设置成 Buff 中上一次使用的值, Stack不变

如果当前位置等于分叉点, 说明已经到了上次定好的分叉位置, 上次用0, 这次就用1进行分叉, 这次分叉完, 这一位就确认了, 将 Stack 这一位设成 1 (已确认)

如果当前位置比已知的分叉点位置还要深, 说明发现了新的分叉点(例如用1分叉后, 进入了新的子树), 更新分叉点位置, 将 Stack 这一位设成 0 (未确认), 用默认的0继续往下走

在这轮遍历结束后, Buff 就得到了一个新的地址

在 Stack 上找到分叉点的位置, 将分叉点设置到最浅的一个0(未确定)的位置. 例如这次正好在分叉点使用1分叉, 当前点确认了, 而之后又全是确认的情况, 需要将分叉点往上移.

结束条件: 和深度遍历一样, 每一轮遍历后分叉点可能会上下变化, 当分叉点的位置为0时, 说明遍历结束


C语言代码实现

uint8_t DS18B20_Detect(uint8_t *buff, uint8_t *stack, uint8_t split_point)

{

    uint8_t len = 64, pos = 0;

    /* Start from deepest point */

    split_point = (split_point == 0x00)? 0xFF : split_point;

    /* Reset line */

    DS18B20_Reset();

    /* Start searching */

    DS18B20_WriteByte(ONEWIRE_CMD_SEARCHROM);


    while (len--)

    {

        // Read the value and its complement value of this bit

        __BIT pb = DS18B20_ReadBit();

        __BIT cb = DS18B20_ReadBit();

        if (pb && cb) // no device

        {

            return 0;

        }

        else if (pb) // bit = 1

        {

            *(buff + pos / 8) |= 0x01 << (pos % 8);

            DS18B20_WriteBit(SET);

            // confirm: set this bit to 1

            *(stack + pos / 8) |= 0x01 << (pos % 8);

        }

        else if (cb) // bit = 0

        {

            *(buff + pos / 8) &= ~(0x01 << (pos % 8));

            DS18B20_WriteBit(RESET);

            // confirm: set this bit to 1

            *(stack + pos / 8) |= 0x01 << (pos % 8);

        }

        else // bit can be 0 or 1, possible split point

        {

            if (split_point == 0xFF || pos > split_point)

            {

                // new split point, try 0

                *(buff + pos / 8) &= ~(0x01 << (pos % 8));

                DS18B20_WriteBit(RESET);

                // unconfirm: set this bit to 0

                *(stack + pos / 8) &= ~(0x01 << (pos % 8));

                // record this new split point

                split_point = pos;

            }

            else if (pos == split_point)

            {

                // reach split point, try 1

                *(buff + pos / 8) |= 0x01 << (pos % 8);

                DS18B20_WriteBit(SET);

                // confirm: set this bit to 1

                *(stack + pos / 8) |= 0x01 << (pos % 8);

            }

            else // middle point, use existing bit

            {

                DS18B20_WriteBit(*(buff + pos / 8) >> (pos % 8) & 0x01);

            }

        }

        pos++;

    }

    // Relocate split point, move it to the last *unconfirmed* bit of stack

    while (split_point > 0 && *(stack + split_point / 8) >> (split_point % 8) & 0x01 == 0x01) split_point--;

    return split_point;

}


调用方法


sp = 0;

do

{

    // ROM search and store ROM bytes to addr

    sp = DS18B20_Detect(addr, Search_Stack, sp);

    // Print the new split point and address

    UART1_TxHex(sp);

    UART1_TxChar(' ');

    PrintArray(addr, 0, 8);

    UART1_TxString("rn");

} while (sp);


寄生供电模式 Parasite Power Mode

DS18B20一个很有意思的特性, 就是支持寄生供电模式. 在寄生供电模式下, DS18B20不需要接VDD, 只需要GND和DQ两根线, DS18B20会从DQ通信中"窃取"一部分电量, 而这部分电量足以支撑它完成一次温度测量. 在实际应用中这能带来显而易见的好处: DS18B20经常用于长距离的温度监控, 可能会长达十几米, 如果只需要两根接线, 可以用最普通的双绞线连接.


寄生供电接线

这个模式下, 与普通供电方式接线的变化是


DS18B20与上位机只连接DQ和GND

VDD 短接到 GND, 一起接地

上位机连接 DQ 的这个Pin需要用5K电阻上拉到VCC, 如果接线的距离较远或连接的DS18B20较多, 电阻的阻值可能还要降一些, 以保证有足够的电流提供给DS18B20. 对于 STC8H 因为可以通过寄存器设置上拉, 这个电阻可以省略.

强烈建议使用5V电压供电. 实际测试使用3.3V供电时, 会出现测温读数不更新的情况

寄生供电模式的代码

寄生供电模式下通信的方式并没有变化, 细节上需要一个小调整: 发起温度转换后, 不要进行while查询完成情况, 否则会使DS18B20电量不足而导致测温不成功, 读数一直是85度. 比较稳妥的方式是等待一个合理的时间(例如1秒), 然后直接读出温度值.


寄生模式的问题和解决

实际使用中容易看到DS18B20的个体差异, 有些对电压的适应很好, 有些比较挑剔例如电压稍低就不工作, 而有些在寄生模式下完全不工作, 全部输出低电平. 解决方法有以下几种


使用5V供电. 如果使用3.3V供电, 不工作的概率很大.

如果能挑的话, 尽量使用同一批次电压适应性好的产品, 这样正常接线即可不费事

仿照寄生供电原理, 改造成双线制的正常供电模式. DS18B20在正常供电模式下还是很稳定的, 可以仿照寄生供电的原理通过外置电容手工实现寄生供电

正常供电模式改造成寄生供电模式

存在一些在正常供电模式工作正常, 但是寄生模式完全无法工作的DS18B20, 可能是次品或者生产时偷工减料.


寄生供电的原理是在DQ内部通过一个二极管连接了电容, 当DQ高电平时对电容充电, 当DQ低电平时阻止电容放电, 而电容接在了内部的VCC上. 了解这个原理就可以从外部电路上对这些次品 DS18B20 进行改造, 使其也能通过双线连接加入到寄生供电网络


材料

1个容量大于100nF的电容, 可以用最普通的104瓷片电容

1个二极管. 1N5819之类的肖特基最好, 普通的1N4007也可以

接线

电容跨接到 DS18B20 的 GND 和 VDD, DQ接二极管正极, VDD接二极管负极

这两个元件必须靠近 DS18B20 部署, 如果上位机上连接了多个DS18B20, 并且相距较远, 需要在DS18B20的那一端部署这两个元件

上位机只连接 DS18B20 的 DQ 和 GND

使用5V供电

[1] [2]
关键字:GPIO  DS18B20  数字温度计 引用地址:STC8H开发(十一): GPIO单线驱动多个DS18B20数字温度计

上一篇:关于单片机编译器中对函数中局部变量的处理和PC不同
下一篇:STC8H开发(十): SPI驱动Nokia5110 LCD(PCD8544)

推荐阅读最新更新时间:2024-11-13 11:27

基于NRF24L01的DS18B20温度无线传输单片机源码
单片机型号为stc12c5a60s2 温度传感器为DS18B20 无线传输为NRF24L01 温度显示LCD1602 单片机源程序如下: #include STC12C5A60S2.H #include stdio.h #include DELAY.h #include NRF24L01.h #include LCD1602.h #include DS18B20.h void main(void) { int temp,intt,dect; unsigned char temp_buf ={0}; EA=1; LCD_Init(); //LCD160
[单片机]
STM32的GPIO使用的函数剖析
该文是自己学习了一段STM32后所写,是对STM32使用固件库编程最简单的一段程序,是对固件库函数的一部分进行解析。如有错误之处请指正,不胜感激。 一、 GPIO_Init函数解析 1 1、参数GPIO_TypeDef 1 2、参数GPIO_InitStruct 2 3、函数代码详解 4 4、备注 6 一、GPIO_Init函数解析 首先来看一下GPIO_Init函数的原型void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)。这个函数的实现是在Stm32f10x_gpio.c文件中,若要使用该函数在相应的应用程序的前
[单片机]
【ARM】gpio·arm体系结构之gpio
GPIO Gerneral-Purpose IO ports,即通用IO口。 在嵌入式系统中常常有数量众多,但是却比较简单的外部设备/电路。 对这些设备/电路,有的需要CPU为之提供控制手段,有的则需要被CPU用做输入信号。 许多这样的设备/电路只要求一位,即只要有开/关两种状态就够了,比如控制某个LED灯亮与灭;或者通过获取某个管脚的电平属性来达到判断外围设备的状态。 对这些设备/电路的控制,使用传统的串行口或并行口都不合适,所以在微控制器芯片上一般都会提供一个“通用可编程IO接口”,即GPIO。 接口至少有两个寄存器,即“通用IO控制寄存器”与“通用IO数据寄存器”。 数据寄存器的各位都直接引致芯片外部,
[单片机]
GPIOLED配置、key、中断NVIC配置
1 #include stm32f10x.h 2 #include stm32f10x_gpio.h 3 4 //内核,(NVIC) 5 #include misc.h 6 7 //点亮红色灯 PB5 8 //step1:使能 9 Rcc_APB2PeriPhClockCmd( 10 Rcc_APB2PeriPh_GPIOB,ENABLE); 11 //step2:定义GPIO初始化结构体变量 12 GPIO_InitTypeDef a; 13 a.GPIO_Speed = GPIO_Speed_50MHz; 14 a.GPIO_Pin = GPIO_Pin_5; 15 //推挽输出 16 a.GPIO_Mode
[单片机]
GD32 MCU如何将烧录口配置为GPIO使用?
如果大家在进行GD32 MCU开发时发现GPIO引脚使用不足,可以尝试将烧录口配置为GPIO使用,这样就可以多出几个引脚使用,但使用的时候如何配置以及有哪些注意事项,本视频将会为大家进行解答。 GD32 MCU存在两种GPIO备用功能的配置,一种是采用成组重映射REMAP的模式,比如GD32F10X/20x/30x/e10x/E50X等系列,一种是采用AFIO模式,比如GD32F1X0/3X0/4XX/E230等。 成组reamp重映射的模式需要将一组的GPIO进行重映射,如下图GD32F30X IIC0的重映射配置,PB6和PB8需要组合使用,PB7和PB9需要组合使用,而PB6和PB9就不可以组合使用。 AFIO
[单片机]
GD32 MCU如何将烧录口配置为<font color='red'>GPIO</font>使用?
嵌入式-stm32学习:按键检测
bsp_key.h #ifndef __KEY_H #define __KEY_H #include stm32f4xx.h //引脚定义 /*******************************************************/ #define KEY1_PIN GPIO_Pin_0 //GPIO引脚号 #define KEY1_GPIO_PORT GPIOA //GPIO端口A #define KEY1_GPIO_CLK RCC_AHB1Periph_GPIOA //GPIO端口时钟 #define KEY2_
[单片机]
基于DS18B20 的远程粮仓温控系统
简介:本文介绍一种以单线数字温度传感器DS18B20为温度敏感元件的粮仓温控系统,系统以微型计算机为上位机, 89C51单片机为检测分机,DS18B20数字温度传感器直接与分机连接,分机与测温主机通过RS-485总线网进行通信,系统所有操作通过菜单命令完成。 1 引言 粮食温度检测是储备库中防止粮食霉烂、保质存放的重要环节。对于一个农业大国来讲,粮食生产、需求与储备量都很大。大量粮食在储备的过程中常因粮食湿度过大而升温发热,导致粮食大量腐烂变质,给国家带来巨大损失。所以粮仓监控系统中温度测量是整个系统的主要功能之一。 本文介绍一种以单线数字温度传感器DS18B20为温度敏感元件的粮仓温控系统,系统以微型计算机为上位机, 8
[单片机]
基于<font color='red'>DS18B20</font> 的远程粮仓温控系统
读取温度传感器DS18B20的实例
配置IO引脚 #define DS18B20 BIT4 //配置IO引脚 #define DS18B20_HIGH P2OUT |= BIT4 #define DS18B20_LOW P2OUT &= ~BIT4 精确延时宏代码 #define CPU_CLOCK 8000000UL //MCLK设为8MHz #define delay_us(us) __delay_cycles(CPU_CLOCK/1000000*(us)) #define delay_ms(ms) __delay_cycles(CPU_CLOCK/1000*(ms)) 求
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved