【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南

1)实验平台:正点原子STM32MP157开发板
2)购买链接:https://item.taobao.com/item.htm?&id=629270721801
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-318813-1-1.html
4)正点原子官方B站:https://space.bilibili.com/394620890
5)正点原子STM32MP157技术交流群:691905614
在这里插入图片描述

第二十六章 DS18B20数字温度传感器实验

本章,我们将介绍通过STM32MP157读取外部温度传感器的温度,来得到较为准确的环境温度。本章节我们先了解单总线技术,再了解温度传感器DS18B20,然后实现STM32MP157和DS18B20进行通信,把获取到的温度通过串口打印出来。
本章分为如下几个小节:
26.1、单总线和DS18B20简介;
26.2、硬件设计;
26.3、软件设计;
26.4、编译和测试;
26.1 单总线和DS18B20简介
26.1.1 单总线特点
单总线(1-Wire Bus),即只有一根线,是由美国DALLAS半导体公司推出的扩展总线技术,和SPI、I2C不同的是,单总线只有一根信号线(地线除外),整个通信过程的数据交换和控制都由这根线完成,可以说单总线既作为时钟线,又作为数据线,数据在单总线上传输是双向的。采用单总线的好处是:节省IO资源、硬件开销小、资源结构简单、易于控制、易于扩展以及后期维护、成本低等,单总线结构具有简洁且经济的特点,可使用户轻松地组建传感器网络,从而为测量系统的构建引入全新的概念。
挂在单总线上的器件我们称为单总线器件,目前,常见的单总线器件主要有:数字温度传感器(如DS18B20和DS1821)、门禁、身份识别器(如DS1990A)、A/D转换器(如DS2450)等。单总线的主要特点如下:
①、只有一根线;
②、传输速度一般是16.3Kbit/s,最大可达142Kbit/s;
③、只有一个单主机(可以是微控制器),可以是一个或多个从机,所挂的从机数量几乎不受限制;
④、总线空闲时为高电平,数据传输是双向的;
⑤、总线是分时工作的,同一个时刻只能和一个从机进行通信;
⑥、连接到单总线的每个器件必须具有漏极开路或三态输出;
⑦、总线上的信号,除了应答脉冲外,其它都是由主机发出同步信号,并且发出的所有命令和数据字节的低位在前。
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第1张图片

图26.1.1.1 单总线挂载多个设备
26.1.2 单总线硬件接口结构
如下是单总线的硬件接口示意图,设备(主机或者从机)通过漏极开路或者三态端口接到单总线上,当设备不发送数据时释放总线,以便总线给其它设备使用。由于是漏极开路,需要在电路中加一个上拉电阻,图中,单总线外接一个4.7KΩ的上拉电阻,所以,当单总线为空闲状态时,总线状态为高电平。不管什么原因,如果传输过程需要暂时挂起,且要求传输过程还能够继续的话,则总线必须处于空闲状态。位传输之间的恢复时间没有限制,只要总线在恢复期间处于空闲状态(高电平)。如果总线保持低电平超过480us,总线上的所有器件将复位。
我们来看看主机和从机发送数据的过程:
①、当主机通过Tx端发送逻辑1时,经过反相器处理,总线呈现逻辑0,逻辑0经过1-Wire端口的反相器传输给从机的Rx端后为1,则从机接收到逻辑1;
②、当主机通过Tx端发送逻辑0时,经过反相器处理,总线呈现逻辑1,逻辑1经过1-Wire端口的反相器传输给从机的Rx端后为0,则从机接收到逻辑0;
③、当从机通过Tx发送逻辑1时,Tx处的NMOS管导通,总线呈逻辑0,到了主机的Rx反相器后,主机得到逻辑1;
④、当从机通过Tx发送逻辑0时,Tx处的NMOS管截止,总线呈现逻辑1,到了主机的Rx反相器后,主机收到逻辑0。
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第2张图片

图26.1.2.1 单总线的硬件接口示意图
26.1.3 DS18B20简介

  1. DS18B20简介
    在工农业以及日常生活中经常需要测试温度,传统的方式是使用热电偶或热电阻,测试的值需要经过A/D转换,将模拟量转换为数字量得到,采用这种方式进行设计,硬件结构较复杂,制作成本较高。DS18B20是由美国DALLAS半导体公司推出的一种“单总线”接口的温度传感器,因其突出的优点而广泛用于农业生产、工业制造、气象观测、仓库管理、科学研究、弹药库测温等众多领域,与传统的热敏电阻等测温元件相比,其主要特点有:
    ①、单总线接口方式,只需要一根线即可与微控制通信(地线除外),在使用中不需要任何外围元件,使用简单,节约硬件成本,且以单总线的数字方式传输,大大提高了抗干扰性;
    ②、最高12位分辨率,测试温度范围为-55~+125℃,精度为±0.5℃,测温范围广;
    ③、12位分辨率时,在750毫秒(最大)内可将12位温度转换为数字,转换速度快;
    ④、工作在3~5.5V的电压范围,适用电压宽;
    ⑤、全数字温度转换和输出,能直接读出被测温度,可实现9~12位的数字值读数方式(可编程设备温度读数);
    ⑥、体积小、多种封装形式,适应狭小空间测温,使系统设置灵活、方便;
    ⑦、设定分辨率以及用户设定的报警温度存储在EEPROM中,掉电后依然保存;
    ⑧、每个DS18B20都包含唯一的芯片序列号,因此同一条1-Wire总线上可以挂接多个DS18B20。
    如下图是不同封装的DS18B20,主要引脚有3个:
    【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第3张图片

表26.1.3.1 DS18B20引脚
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第4张图片

图26.1.3.1 DS18B20的封装和引脚排列
2. DS18B20内部结构
如下是DS18B20的内部结构图:
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第5张图片

图26.1.3.2 DS18B20内部结构图
上图中显示了DS18B20的主要组件,其温度检测和数字输出全部集中在一个芯片中,主要部分有:
(1)64位的ROM
ROM中的64位序列号是出厂前被标记好的,它可以看作是该DS18B20的地址序列码,每个DS18B20的64位序列号均不相同。64位ROM的排列是:低8位是产品家族码,接着48位是DS18B20的序列号,最高8位是前面56位的循环冗余校验码(CRC=X8+X5+X4+1)。总线主机可以从64位ROM的前56位计算出CRC值,并将其与DS18B20的8位CRC的值进行比较,以确定总线主机是否已正确接收ROM数据。
ROM作用是使每一个DS18B20都各不相同,这样就实现了一根总线上挂载多个DS18B20。
在这里插入图片描述

图26.1.3.3 64位序列号
(2)温度传感器
DS18B20的温度传感器部分用于完成温度测试,测试得到的值传输到高速缓存存储器中以供主机获取。DS18B20的分辨率可配置(9、10、11或12位),这相当于0.5℃、0.25℃、0.125℃或0.0625℃的温度分辨率,而12位读数为出厂默认状态。我们以12位为例进行介绍:数据以16位符号扩展二进制的补码形式存储在高速缓存存储器中,以0.0625℃/LSB的形式进行表达,MSB包含符号位“S”,该位用于表示温度是整数还是负数:
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第6张图片

图26.1.3.4 DS18B20温度格式(12位)
转化后得到的12位数据存储在两个字节的RAM中,以二进制表示,最高5位是符号位,表示负数还是正数:如果测试的温度大于0,这5位为0,这16位测试得到的值乘以0.0625就是温度值;如果温度小于0,这5位为1,这16位测试得到的值先取反再加1,最后再乘以0.0625就得到温度值。
例如,+125℃的数值为0X07D0H,计算方法:
0X07D0H二进制00000 0111 1101 0000十进制2000温度=0.06252000=125℃。
例如,-55℃的数值为FC90H,计算方法是:
FC90H二进制1111 1100 1001 0000取反后0000 0011 0110 1111加1后为 0000 0011 0111 0000十进制880温度=0.0625
880=-55℃。
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第7张图片

表26.1.3.2 DS18B20温度数据表
(1)上电复位寄存器值为+85℃
DS18B20的测量结果将存储在高速缓存存储器中,可通过主机发出存储功能命令读取高速缓存存储器的内容,从而获得数值以便进行计算出温度值,关于存储功能命令我们后面会介绍到。
(3)高温和低温触发器(TH和TL)
温度警报触发器TH和TL分别由1字节EEPROM组成,如果未将警报搜索命令应用于DS18B20,则这些寄存器可用作通用用户存储器。可使用存储器功能命令完成TH,TL和配置字节的写入,通过高速缓存存储器对这些寄存器进行读写时,首先读取和写入的是所有数据的最低有效位。
(4)配置寄存器
配置寄存器在高速缓存存储器中占用一个字节,配置寄存器包含设备将温度到数字转换的分辨率信息,其中,读取低5位默认得到1,R0和R1位是分辨率设置位,出厂默认值为R0=1和R1=1(12位分辨率),最高位是测试模式位,用于设置DS18B20处于工作模式(0),还是测试模式(1),默认出厂的时候该位为0,表示工作模式,其它位无法更改,能改动的只有R0和R1位。
在这里插入图片描述

图26.1.3.5 配置寄存器
R0和R1位的配置如下:
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第8张图片

表26.1.3.3 配置寄存器的R1和R0位
(5)DS18B20的内存映射
DS18B20的内存映射如下,由高速缓存存储器和非易失性电可擦除EEPROM组成,后者存储高低温触发器TH和TL以及配置寄存器。高速缓存存储器为8个字节的内存(0~7),前2个字节分别包含所测温度信息的LSB和MSB,第三、第四和第五个字节分别是TH、TL和配置寄存器的易失性副本,每次上电复位时都会刷新,新的数值从EEPROM得到。第六、第七和第八字节用于内部计算或保留的字节。还有一个字节我们可以称为第9字节,它包含一个循环冗余校验(CRC)字节,它存储的是前八个字节的CRC值。
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第9张图片

图26.1.3.6 DS18B20的内存映射
当温度转换命令发出后,转换得到的温度值以二字节补码的形式存储在高速缓存存储器的第0和第1个字节中,主机可通过单总线接口读取这两个字节的数值,且读取的时候,低位在前,高位在后。读取得到的值用于计算温度值的方法在前面第(2)小节已经讲解。
下面,我们结合DS18B20来了解单总线时序,通过时序我们可以知道怎么去操作DS18B20。
26.1.4 DS18B20时序
根据DS18B20的通信协议,主机通过1-Wire端口与DS18B20通信,主机控制DS18B20完成温度转换必须经过以下步骤:初始化发送一条ROM指令发送RAM指令。我们逐个分析这些过程:

  1. 初始化
    单线总线上的所有任务都以初始化序列开始,初始化序列由总线主设备发送的复位脉冲和从设备DS18B20发送的存在脉冲组成,存在脉冲让总线上的主机知道DS18B20在总线上并且已经准备就绪。主机通过将总线拉低发出复位信号,要求拉低至少480µs,这样确保总线上的从机都复位,然后主机释放总线,因总线上拉4.7KΩ的电阻,所以总线变成高电平,DS18B20在检测到DQ引脚上的上升沿后,DS18B20等待15-60µs,然后DS18B20发送存在脉冲(60-240µs的低信号)来产生应答,主机接收过程至少480us,主机收到此脉冲后,表示复位成功,初始化过程完成。
    【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第10张图片

图26.1.4.1初始化过程=复位脉冲+从机应答脉冲
2. ROM指令
复位成功后,一旦总线主机检测到从机存在,它可以发出5个ROM功能命令之一:1)读取ROM;2)匹配 ROM;3)搜索 ROM;4)跳过ROM或5)警告搜索。所有ROM功能命令都有8位长,这些命令的列表如下:
指令 指令代码 说明
读ROM 33h 读DS18B20中的64位ROM。仅当总线上有一个DS18B20时,才能使用此命令,如果总线上存在多个从机,则当所有从机同时尝试传输时,将发生数据冲突。
匹配ROM 55h 匹配ROM后面紧跟64位ROM序列,主机在总线上寻址特定的DS18B20,只有与64位ROM序列完全匹配的DS18B20才会响应,所有与64位ROM序列不匹配的从机将等待复位脉冲。此命令可用于总线上的单个或多个设备。
跳过ROM CCh 忽略64位ROM地址,直接向DS1820发送读取温度命令,这样可以节省时间。该方式适用于单个从机,当有多个从机时,总线上将发生数据冲突。
搜索ROM F0h 当系统最初启动时,总线主机可能不知道单线总线上的设备数量或它们的64位ROM代码,搜索ROM用于确定挂接在同一总线上DS18B20的个数和识别64位ROM地址,为操作各器件作好淮备。
告警搜索 ECh 执行后只有温度超过设定上限或下限值的从机才做出响应,DS18B20仅在上次温度测量时遇到报警情况时才会响应此命令(报警条件定义为温度高于TH或低于TL)。
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第11张图片

表26.1.4.1 操作ROM指令
3. RAM指令
成功执行ROM功能序列后,主机可以访问存储和控制功能,然后主机可以提供6个存储和控制功能命令中的任何一个:1)写缓存器;2)读缓存器;3)温度转换;4)复制缓存器;5)重调缓存器;6)读供电。首先读取和写入所有数据的最低有效位。
指令 指令代码 说明
写缓存器 4Eh 发出向内部RAM的第2、3和4字节写上、下限温度数据命令,紧跟该命令之后,是传送三字节的数据。
读缓存器 BEh 连续读内部RAM的9字节的内容。
温度转换 44h 启动DS18B20进行温度转换,会将EEPROM的温度值和配置寄存器的值传输给RAM(此操作也会在DS18B20上电后发生,即刷新操作,RAM才能获得有效数据)。12位转换时间最长为750ms,9位为93.75ms(详见表32.1.3.2)
复制缓存器 48h 将RAM的第2、3和4字节字节的内容复制到EEPROM中
重调缓存器 B8h 将存储在EEPROM中的值复制到RAM中的第2、3和4字节,此操作在DS18B20上电时也会发生。
读供电 B4h 读DS1820的供电模式:寄生供电时DS1820发送“0”,外接电源供电时DS1820发送“1”
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第12张图片

表26.1.4.2 操作RAM指令
主机和从机之间的数据交换和控制全部在单总线上完成,要想保证数据的完整性,单总线器件必须按照严格的协议来和主机进行通信,要求:除应答信号外,其它信号都由主机发出,并且发出的数据或者命令字节的低位在前,高位在后;主机和从机的数据传输通过时隙(其实也就是时序)来完成,完成1bit传输的时间叫做一个时隙,一个字节8bi就需要8个时隙,这里分为写时隙和读时隙,写时隙是主机把数据传送给从机,读时隙是从机把数据传给主机;写时隙和读时隙有有:写0时隙、写1时隙、读0时隙和读1时隙。
(1)写时隙
当主机将数据线从高电平拉到低电平时,写时隙被启动,所有写时序至少需要60us,两次独立的写时隙之间至少需要1us的恢复时间(图中的tREC)。从图中可以看出:主机要产生一个写0时间隙,就必须把数据线拉低并保持60 us;主机要产生一个写1时间隙,就必须把数据线拉低,在写时间隙开始后的15 us内允许数据线拉高。当数据线拉低后,在15 ~ 60 us的时间窗口内对数据线进行采样:如果数据线为低电平,就是写0,如果数据线为高电平,就是写1。
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第13张图片

图26.1.4.2 写时隙
(2)读时隙
当主机将数据线从逻辑高电平拉到逻辑低电平时,启动读取时隙。当主机把总线拉低时,数据线必须保持在低逻辑电平至少1µ后释放,必须在15 us内读取数据。所有读取时隙的持续时间必须至少为60µs。两次独立的读时隙之间至少需要1us的恢复时间(图中的tREC):
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第14张图片

图26.1.4.3 读时隙
26.2 硬件设计

  1. 例程功能
    把DS18B20传感器插在预留的接口上,采用DS18B20默认的12位分辨率测试当前的环境温度,并将温度通过串口打印出来。实验中通过观察LED0闪烁提示程序在运行。预留的接口在开发板底板的位置如下:
    【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第15张图片

图26.2.1 开发板硬件示意图
将DS18B20插到开发板的预留接口的时候,位置一定要插对,不能将DS18B20的GND插在VCC上,否则会烧坏DS18B20甚至开发板,使用过程中不要用手触摸DS18B20露出来的金属部分。如下图,正面对着DS18B20平面的那一侧,管脚向下,从左到右依次为GND、DQ、VDD,上图中的开发板预留接口的4个引脚从左到右依次为VCC、DQ、GND、GND,所以DS18B20这样插入开发板:
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第16张图片

图26.2.2硬件连接示意图
2. 硬件资源
在这里插入图片描述

表26.2.1硬件资源
3. 原理图
如下图,DS18B20接在PF2上,我们通过该IO口模拟单总线的时序来控制DS18B20:
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第17张图片

图26.2.3原理图部分
26.3 软件设计
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\ 19 DS18B20。
26.3.1 新建和配置工程
新建工程DS18B20,然后配置LED0和UART4,再配置PF2为开漏输出、上拉、高速模式:
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第18张图片

图26.3.1.1 配置GPIO
本节实验会用到微秒延时函数,需要将第二十三章实验的delay.h和delay.c文件。将上一章节实验的BSP文件夹拷贝到M4工程的Core/Src下,然后在BSP文件夹下新建ds18b20.c文件,在BSP/Include下新建ds18b20.h文件,最后的部分工程如下:
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第19张图片

图26.3.2 工程部分目录
26.3.2 添加用户代码
关于LED0和UART4相关的代码请参考前面实验章节部分,下面我们直接添加DS18B20相关的代码。DS18B20驱动代码在ds18b20.c和ds18b20.h文件中,下面我们开始添加用户代码:

  1. ds18b20.h文件代码
    ds18b20.h的文件代码如下,主要是引脚的定义、IO操作函数定义和函数的声明:
#ifndef __DS18B20_H
#define __DS18B20_H
#include"gpio.h"
/* DS18B20引脚 定义 */
#define DS18B20_DQ_GPIO_PORT      GPIOF
#define DS18B20_DQ_GPIO_PIN       GPIO_PIN_2
/* PF口时钟使能 */
#define DS18B20_DQ_GPIO_CLK_ENABLE()  do{ __HAL_RCC_GPIOF_CLK_ENABLE(); }while(0)   
/* IO操作函数 */
#define DS18B20_DQ_OUT(x)   do{ x ? \ 	
HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN, GPIO_PIN_SET):\
HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN, GPIO_PIN_RESET);\
							}while(0)        /* 数据端口输出 */
/* 数据端口输入 */
#define DS18B20_DQ_IN  HAL_GPIO_ReadPin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN)     

uint8_t ds18b20_init(void);         			/* 初始化DS18B20 */
uint8_t ds18b20_check(void);        			/* 检测是否存在DS18B20 */
short ds18b20_get_temperature(void);		/* 获取温度 */

#endif
DS18B20_DQ_IN用于读取PF2引脚(总线)的电平,DS18B20_DQ_OUT通过参数X设置引脚PF2的电平,当X为0时,设置PF2输出0,当X为1时,设置PF2输出1。
  1. ds18b20.c文件代码
    ds18b20.c文件代码中有几个函数,分别是:初始化DS18B20的IO口、复位DS18B20、等待DS18B20的回应、从DS18B20读取一个位、从DS18B20读取一个字节、写一个字节到DS18B20、开始温度转换以及从DS18B20得到温度值。我们分别进行讲解:
    (1)复位DS18B20
/**
 * @brief       复位DS18B20
 * @param       data: 要写入的数据
 * @retval      无
 */
static void ds18b20_reset(void)
{
    DS18B20_DQ_OUT(0);  	/* 主机拉低DQ,复位 */
    delay_us(750);      	/* 主机拉低750us */
    DS18B20_DQ_OUT(1);  	/* DQ=1, 主机释放复位 */
    delay_us(15);       	/* 延迟15US */
}
经过前面时序的分析,复位DS18B20函数很好理解,先将DQ拉低750us(在480us~960us时间内),然后再拉高DQ,即释放DQ,然后再延时15us等待DS18B20应答。

(2)等待DS18B20的回应

/**
 * @brief       等待DS18B20的回应
 * @param       无
 * @retval      0, DS18B20正常
 *              1, DS18B20异常/不存在
 */
uint8_t ds18b20_check(void)
{
    uint8_t retry = 0;
    uint8_t rval = 0;
    /* 读取DQ值,等待DQ变低, 等待200us */
    while (DS18B20_DQ_IN && retry < 200)
    {
        retry++;
        delay_us(1);
    }
    /* 当等待时间大于200us时,DS18B20异常 */
    if (retry >= 200) 
    {
        rval = 1;
    }
    /* 当等待时间小于200us时,DS18B20正常 */     
    else                                                                                                            
    {
        retry = 0;
        /* 等待DQ变高, 等待240us */
        while (!DS18B20_DQ_IN && retry < 240)   
        {
            retry++;
            delay_us(1);
        }
        /* 超过240us,则认为DS18B20异常 */
        if (retry >= 240) rval = 1;                         
    }
    return rval;
}

该函数的实现也是依据时序图进行逻辑判断,例如当主机发送了复位信号之后,按照时序图,DS18B20应拉低数据线60~240us,整个过程中主机接收最小时间为480us,所以我们就依据这两个硬性条件进行判断:首先需要设置一个时限等待DS18B20响应,时间为200us,DS18B20通过将总线拉低来应答;然后,将总线拉高,模拟DS18B20释放总线,并且延时240us,因为主机的整个接收过程至少为480us(200+240=480),当满足这两个条件后即判断DS18B20成功响应。
(3)从DS18B20读取一个位

/**
 * @brief       从DS18B20读取一个位
 * @param       无
 * @retval      读取到的位值: 0 / 1
 */
static uint8_t ds18b20_read_bit(void)
{
    uint8_t data = 0;   	/* 读到的值默认为0 */
    DS18B20_DQ_OUT(0);  	/* 主机将总线DQ拉低 */
    delay_us(2);        	/* 延时2us */
    DS18B20_DQ_OUT(1);  	/* 主机将总线DQ拉高,释放总线 */
    delay_us(12);       	/* 延时12us,等主机读取 */

    if (DS18B20_DQ_IN)  	/* 读到的值为1 */
    {
        data = 1;
    }
    delay_us(50);        /* 延时50us,因为整个读的时间至少为60us */
    return data;
}
从DS18B20读一个字节比较简单,按照时序图进行,主机将总线拉低2us后再释放总线,然后延时12us,因为主机要在15us内读取,这里就设置为12us,然后在总线拉高的状态再延时50us,因为主机读取的过程至少要60us(2+12+50>60),所以这里设置50us。

(4)从DS18B20读取一个字节
从DS18B20读取一个字节比较简单,每次读一位,分别读取8次即可:

/**
 * @brief       从DS18B20读取一个字节
 * @param       无
 * @retval      读到的数据
 */
static uint8_t ds18b20_read_byte(void)
{
    uint8_t i, b, data = 0;
    for (i = 0; i < 8; i++)     	/* 一个字节8位,分8次 */
    {
        b = ds18b20_read_bit(); 	/* DS18B20先输出低位数据 ,高位数据后输出 */    
        data |= b << i;         	/* 填充data的每一位 */ 
    }
    return data;
}

(5)写一个字节到DS18B20
根据读写时序,编写如下的程序:

/**
 * @brief       写一个字节到DS18B20
 * @param       data: 要写入的字节
 * @retval      无
 */
static void ds18b20_write_byte(uint8_t data)
{
    uint8_t j;
    for (j = 1; j <= 8; j++)
    {
        /* 写 1 操作*/
        if (data & 0x01)
        {
            DS18B20_DQ_OUT(0);  /* 主机拉低DQ */
            delay_us(2);         /* 拉低2 us的时间 */
            DS18B20_DQ_OUT(1);  /* DQ=1, 主机释放DQ */
            delay_us(60);        /* 延时60us */
        }
        /*  写 0 操作*/
        else
        {
            DS18B20_DQ_OUT(0);  /* 主机拉低DQ */
            delay_us(60);        /* 拉低60 us的时间 */
            DS18B20_DQ_OUT(1);  /* DQ=1, 主机释放DQ */
            delay_us(2);         /* 延时2 us */
        }
        data >>= 1;              /* 右移,获取高一位数据 */
    }
}

(6)开始温度转换
温度转换的过程是先复位DS18B20,等待DS18B20应答,主机收到应答后,在只有一个从机时,可跳过ROM直接执行RAM指令进行温度转换,代码如下:

/**
 * @brief       开始温度转换
 * @param       无
 * @retval      无
 */
static void ds18b20_start(void)
{
    ds18b20_reset();            /* 复位DS18B20 */
    ds18b20_check();            /* 等待DS18B20的回应 */
    ds18b20_write_byte(0xcc);   /*  跳过ROM */
    ds18b20_write_byte(0x44);   /*  开始温度转换 */
}

(7)从DS18B20获取温度值

1   /**
2    * @brief       从DS18B20得到温度值(精度:0.1C)
3    * @param       无
4    * @retval      温度值 (-550~1250)
5    *   @note      返回的温度值放大了10倍.
6    *              实际使用的时候,要除以10才是实际温度.
7    */
8   short ds18b20_get_temperature(void)
9   {
10      uint8_t flag = 1;           	/* 默认温度为正数 */
11      uint8_t TL, TH;
12      short temp;
13      
14      ds18b20_start();            	/*  开始温度转换 */
15      ds18b20_reset();            	/* 复位DS18B20 */
16      ds18b20_check();            	/* 等待DS18B20应答 */
17      ds18b20_write_byte(0xcc);   /*  跳过ROM */
18      ds18b20_write_byte(0xbe);   /*  读缓存器 */
19      TL = ds18b20_read_byte();   /*  获取温度低位值 LSB */
20      TH = ds18b20_read_byte();   /*  获取温度高位值 MSB */
21          
22      if (TH > 7)                 	/* 判断温度正负 */
23      {
24          TH = ~TH;
25          TL = ~TL;
26          TL+=1;
27          flag = 0;               	/* 温度为负 */
28      }
29      
30      temp = TH;                  	/* 获得高八位 */
31      temp <<= 8;
32      temp += TL;                 	/* 获得底八位 */
33      temp =(double)temp * 0.625; /* 转换 */
34
35      if (flag == 0)              	/* 如果温度为负数 */
36      {
37          temp = -temp;          	 /* 将温度转换成负温度 */
38      }
39      
40      return temp;
41  }
第10行,设置标志位flag,如果flag为1,表示温度为整数,如果flag为0,表示温度为负数(即零下温度);
第14~18行,温度转换的步骤是:先开始温度转换,此时EEROM里得到最新的温度值,且值从EEROM传输给RAM,然后复位DS18B20并等待应答,因为只有一个DS18B20,所以可以直接跳过ROM,直接向DS1820发送读取温度命令去读取温度值,这些值是从RAM中得到的。
第19~20行,读取到的温度值,高8位保存在MSB中,低8位保存在LSB中。
第22~28行,判断温度值是正数还是负数,第32.1.3小节有介绍过,如果高5位是0,温度为正值;如果高5位为1,温度为负值;所以当为负数时,高5位的值至少大于7(二进制是0000 011)。
第30~33行,分别获取高8位和低8位的值,两个值组合得到用于计算温度的值。在前面第32.1.3小节我们有讲解温度的计算方法:默认采用12位分辨率时,如果测试的温度大于0,这5位为0,这16位测试得到的值乘以0.0625就是温度值;如果温度小于0,这5位为1,这16位测试得到的值先取反再加1,最后再乘以0.0625就得到温度值。
第35~38行,如果温度值是负数,将温度转换成负值。

(8)初始化DS18B20的IO口
最后不要忘了要初始化总线,即DS18B20的IO口(PF2),初始化过程:开启PF2时钟、配置IO口为开漏输出、上拉、高速模式:

/**
 * @brief       初始化DS18B20的IO口 DQ 同时检测DS18B20的存在
 * @param       无
 * @retval      0, 正常
 *              1, 不存在/不正常
 */
uint8_t ds18b20_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    
    DS18B20_DQ_GPIO_CLK_ENABLE();                           	/* 开启DQ引脚时钟 */

    gpio_init_struct.Pin = DS18B20_DQ_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;            	/* 开漏输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    	/* 上拉 */
gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;     /* 高速 */
/* 初始化DS18B20_DQ引脚 */
    HAL_GPIO_Init(DS18B20_DQ_GPIO_PORT, &gpio_init_struct); 
    /* DS18B20_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 	也可以读取外部信号的高低电平 */

    ds18b20_reset();        	/* 复位DS18B20 */
    return ds18b20_check(); /* 返回DS18B20应答结果,这里可以判断DS18B20是否存在 */
}
最后两行代码,先复位DS18B20后再等待其应答,主要是判断DS18B20是否存在,存在的话,我们才可以进行后续的读写操作。
  1. main.c文件代码
    main.c文件部分代码如下,我们在标红的字体之间手动添加代码:
1   #include "main.h"
2   #include "usart.h"
3   #include "gpio.h"
4   /* USER CODE BEGIN Includes */
5   #include "./BSP/Include/led.h"
6   #include "./BSP/Include/delay.h"
7   #include "./BSP/Include/ds18b20.h"
8   /* USER CODE END Includes */
9 
10  void SystemClock_Config(void);
11
12  int main(void)
13  {
14    HAL_Init();                       	/* 初始化HAL库 */
15    if(IS_ENGINEERING_BOOT_MODE())
16    {
17      SystemClock_Config();            /* 系统时钟初始化 */ 
18    }
19    MX_GPIO_Init();                     /* GPIO初始化 */
20    MX_UART4_Init();                    /* UART4初始化 */
21    /* USER CODE BEGIN 2 */
22    HAL_UART_Receive_IT(&huart4,&RxBuffer,1); /* 以中断方式接收函数 */
23    led_init();                                     /* 初始化LED  */
24    delay_init(209);                               /* 延时初始化延时函数 */
25    uint8_t t = 0;
26    short temperature;
27    /* USER CODE END 2 */
28    while (1)
29    {
30      /* USER CODE BEGIN 3 */
31        while (ds18b20_init())                     /* DS18B20初始化 */
32        {
33          printf("DS18B20 Error!\r\n");
34          delay_ms(1000);
35        }
36        while (1)
37         {
38          t++;
39          if (t == 20)
40          {
41              t = 0;
42              temperature = ds18b20_get_temperature(); /* 获取温度值*/
43              /* 计算温度值,注意最后要除以10(因前面程序乘以0.625)*/
44              printf("DS18B20 Temp=%.1f\r\n", temperature / 10.0f);   
45              LED0_TOGGLE();                                 /* LED0闪烁 */
46          }
47              delay_ms(10);                                  /* 延时10ms */
48        }
49    }
50    /* USER CODE END 3 */
51  }
第31~35行,初始化DS18B20;
第42行,获取温度值;
第44行,打印温度值,注意要再除以10才得到真正的温度值。

26.4 编译和测试
开发板上电,进入仿真运行后结果如下,可以看到一开始打印85.0,这是因为上电后,复位寄存器值默认为85℃。然后,串口打印此时测试的环境温度,且LED0在闪烁。
【正点原子MP157连载】第二十六章 DS18B20数字温度传感器实验-摘自【正点原子】STM32MP1 M4裸机CubeIDE开发指南_第20张图片

图26.4.1 运行结果

你可能感兴趣的:(正点原子,stm32,单片机,arm)