22. DS18B20数字温度传感器¶
DS18B20是美国DALLAS半导体公司生产的单总线数字温度传感器,其可直接将温度转化成数字信号输出,具有体积小、低功耗、抗干扰能力强、精度高等优点。DS18B20数字温度传感器接线方便,封装后可应用于各种场所测温,如电缆沟测温、机房测温、农业大棚测温、弹药库测温等各种非极限温度场所。适用于各种狭 小空间设备数字测温和控制领域。
本章节将带领大家学习DS18B20的工作方式,以及如何用单总线去控制其进行温度测量并显示出来。
22.1. 理论学习¶
22.1.1. ds18b20概述¶
DS18B20数字温度传感器提供9-Bit至12-Bit(可配置)温度读数和一个用户可编程的非易失性且具有高温和低温触发报警的报警功能。DS18B20采用1-Wire通信即仅采用一根数据线与微控制器进行通信。该传感器的温度检测范围为-55℃至+125℃,在范围-10℃至+85℃之间具有±0.5℃的精 度。此外读取、写入和执行温度转换的电源可以从数据线本身获得,而不需要外部电源,当然使用外部电源供电也行。
每个DS18B20都有一个独一无二的64位序列号,所以可以在一根总线上连接多个DS18B20设备。因此在一个分布式的大环境里用一个微控制器控制多个DS18B20是非常简单的。这些特征使得其在HVAC环境控制,在建筑、设备及机械的温度监控系统,以及温度过程控制系统中有着很大的优势。
22.1.2. DS18B20特性¶
独特的1-Wire接口只需要一个端口引脚用于通信。
多路采集能力使得分布式温度采集应用更加简单。
无需外围器件。
可以采用数据线供电,供电范围为3.0V至5.5V。
温度可测量范围为:-55℃到+125℃(-67\(℉\)至+125\(℉\))。
内部温度采集精度可以由用户自定义为9-Bit至12-Bit。
12Bit的温度采集精度转换时间最大为750ms。
用户可自定义非易失性的温度报警设置。
报警搜索命令识别并寻址温度超出编程限值的设备(温度报警条件)。
应用于温度控制、工业系统、民用产品、温度传感器或者任何温度检测系统中。
22.1.3. ds18b20结构¶
图 28‑1 DS18B20实物图
图 28‑2 DS18B20内部结构图
如图 28‑1和图 28‑2分别为DS18B20的实物图与其内部结构图。图 28‑2中内部的64位ROM存储着其独一无二的序列号。高速缓存器包含了存储有数字温度结果的2个字节宽度的温度寄存器。另外,高速缓存器还提供了一个字节的高温(TH)和一个字节的低温(TL)温度报警寄存器和一个字节的配置寄存器。 配置寄存器允许用户自定义温度转换精度(9位,10位,11位,12位)。高温和低温温度报警器寄存是非易失性的(EEPROM),其可以在断电的情况下保存。下面是高速缓存器的内部数据结构图,如图 28‑3所示。
图 28‑3 高速缓存器内部数据结构图
DS18B20使用的是1-Wire总线协议,该总线协议仅需一个控制信号进行通信。在该总线系统中,微控制器(主设备)通过设备的64位序列号来识别该总线上的设备,因为每个设备的序列号都是不同的。DS18B20可以无需外部电源供电,当数据线DQ为高时由数据线为设备供电。当总线拉高时给内部电容(Cpp)充电 ,当总线拉低时由该电容给设备供电。这种由总线给设备供电的方式称为“寄生电源”。此外,DS18B20也可以由外部电源通过VDD供电。本次实验是使用外部电源进行供电的,所以寄生电源供电模式的信号线控制就不进行详细讲解,有兴趣的读者可以通过DS18B20的数据手册进行了解。
22.1.4. 温度测量¶
DS18B20的核心功能是直接温度—数字测量。其温度转换可由用户自定义为9、10、11、12位,精度分别为0.5℃、0.25℃、0.125℃、0.0625℃分辨率,若不设置则默认为12位的转换精度。若要测量温度,主设备必须向DS18B20发送温度转换命令[44h]才能开始温度转换。温度转换后,转换的 温度值将会保存在高速缓存器的温度寄存器中。只有通过读高速缓存器命令[BEh]才能将数据读出,数据通过1-Wire总线传输,传输顺序为低位到高位依次传输。温度数据中包含“符号”(S)位,表示温度的正负。
DS18B20的温度输出数据是在摄氏度下校准的;若是在华氏度下应用的话,可以用查表法或者常规的数据换算。温度以一个16位标志扩展二进制补码的形式存储在温度寄存器中(详见图 28‑4)。符号标志位(S)温度的正负极性:若S=0,则为正数;若S=1,则为负数。如果DS18B20被定义为12位的转换精度,温度寄存器中所有位都将包含有效数据。若定义为11位转换精度,则bit 0(最低位)为未定义的。若定义为10位转换精度,则bit 0和bit 1为未定义的。若定义9位转换精度,则bit 0、bit 1和bit 2为未定义的。表格 28‑1为在12位转换精度下温度输出数据与相对应温度之间的关系。
图 28‑4 温度寄存器格式
表格 28‑1 温度/数据对应关系表
温度(℃) |
数据输出(二进制) |
数据输出(十六进制) |
---|---|---|
+125 |
0000 0111 1101 0000 |
07D0h |
+85 |
0000 0101 0101 0000 |
0550h |
+25.0625 |
0000 0001 1001 0001 |
0191h |
+10.125 |
0000 0000 1010 0010 |
00A2h |
+0.5 |
0000 0000 0000 1000 |
0008h |
0 |
0000 0000 0000 0000 |
0000h |
-0.5 |
1111 1111 1111 1000 |
FFF8h |
-10.125 |
1111 1111 0101 1110 |
FF5Eh |
-25.0625 |
1111 1110 0110 1111 |
FF6Fh |
-55 |
1111 1100 1001 0000 |
FC90h |
上面说到温度是以二进制补码的形式存储在温度寄存器中,所以我们需要先求出其原码再去计算温度值。正数的原码反码补码都是一样的,而负数的补码就是对反码加一。当输出温度为正数时输出二进制转换为十进制数后乘以其精度即为其温度值。如:+125℃的十进制(通过二进制转得)输出为2000,乘以精度为:2000*0. 0625=125;+10.125十进制值为162,乘以精度为:162*0.0625=10.125 。当温度为负数时,需先求得其原码(补码取反加一即为其原码,和原码求补码是一样的),这里需要特别说明的是符号位只代表数据的正负,无论是取反还是求值它都是不算在里面的。如:-25.0625的二进制输出原码为1111 1001 1001 0001,其十进制数值为-401,乘以精度为:-401*0.0625=-20.0625 。同理其他温度值也可用该方法求得。另外上电复位时寄存器中的初始值为+85℃。
22.1.5. 配置寄存器¶
高速缓存器中第四个字节即为配置寄存器;如图 28‑5所示。用户通过改变R1和R0的值来配置DS18B20的分辨率。上电默认为R1=1以及R0=1(12位分辨率)。需要注意的是转换时间与分辨率时间是有关系的,如表格 28‑2所示。另外寄存器中最高位和低5位作为内部使用而保留使用,不可被写入。
图 28‑5 配置寄存器内部数据图
表格 28‑2 温度分辨率配置
R1 |
R0 |
转换位数 |
最大转换时间 |
---|---|---|---|
0 |
0 |
9 |
93.75ms (tconv/8) |
0 |
1 |
10 |
187.5ms (tconv/4) |
1 |
0 |
11 |
375ms (tconv/2) |
1 |
1 |
12 |
750ms (tconv) |
22.1.6. 64位光刻ROM编码¶
每个DS18B20的片内ROM都存有一个独一无二的64位编码,如图 28‑6所示。在该ROM编码的低8位保存有DS18B20的分类编码:28h。中间的48位是独一无二的序列号。最高8位是前面56位的CRC循环冗余校验码(CRC=X8+X5+X4+1)。因为每个DS18B20的序列号都不一样,所以一条总线上可以控制多个DS18B20。
图 28‑6 64位光刻ROM
22.1.7. DS18B20操作¶
上面已经对DS18B20的基础知识进行了讲解,那么到底如何操作DS18B20去进行对温度的转换以及读取呢?下面将分步为大家讲解。
22.1.7.1. 初始化¶
1-Wire总线上的所有事件都必须以初始化为开始。初始化序列由总线上的主设备发出的复位脉冲以及紧跟着从设备回应的存在脉冲构成。该存在脉冲是让总线主设备知道DS18B20在总线上并准备好运行。具体的初始化时序后面再详细介绍。
22.1.7.2. ROM命令¶
当初始化完成之后,就可以执行ROM命令。这些命令是对每个设备的64位ROM编码进行操作的,当总线上连接有多个设备时,可以通过这些命令识别各个设备。总共包含有5种ROM命令,每个命令的长度都是8bit。
搜索ROM[F0h]
当系统上电初始化后,主设备可识别该总线上所有的从设备的ROM编码,这样就可以使得主设备确定总线上的从设备的类型以及数量。
读ROM[33h]
该命令允许主设备读取DS18B20的64位ROM编码,只有在总线上只有一个DS18B20时才能使用这个命令。如果总线上存在多个从设备,发送此命令,则当所有从设备都会回应时,将会引起数据冲突。
匹配ROM[55h]
该匹配ROM命令之后接着发出64位ROM编码,使主设备在多点总线上定位一只特定的DS18B20。只有和64位ROM序列完全匹配的DS18B20才会做出响应。总线上的其他从设备都将等待下一个复位脉冲。此命令在总线上有单个或多个器件时都可以使用。
跳过ROM[CCh]
这条命令可以不用提供64位ROM编码就进行下一步操作,在单点总线(一个DS18B20传感器)情况下可以节省时间。如果总线上不止一个从设备,在跳过ROM命令之后跟着发一条读命令,则所有从设备将会同时执行温度转换,总线上就会发生数据冲突。
警报搜索[ECh]
该命令的操作与跳过ROM命令基本相同,但是不同的是只有温度高于TH或低于TL(达到报警条件)的从设备才会响应。只要不掉电,警报状态将一直保持,直到温度不在警报范围内为止。
22.1.7.3. 功能命令¶
当总线上的主设备通过ROM命令确定了哪个DS18B20可以进行通信时,主设备就可以向其中一个从设备发送功能命令。这些命令可以使得主设备操控从设备进行一系列的操作。下面对这些功能命令做个简单的描述。
温度转换[44h]
此命令为初始化单次温度转换,温度转换完后,转换的温度数据会寄存在高速缓存器的byte0(温度数据低八位)和byte1(温度数据高八位)中,之后DS18B20恢复到低功耗的闲置状态。如果总线在该命令后发出读时隙,若DS18B20正在进行温度转换则会响应“0”,若完成了温度转换则响应“1”。如果是用的“ 寄生电源”供电模式,则在命令发出后应立即强制拉高总线,拉高时间应大于表格 28‑2中的温度转换时间。
写入暂存器[4Eh]
该命令使得主设备向高速缓存器写入3个字节的数据。第一个字节写入高速缓存器的byte2中(TH寄存器),第二个字节的数据写入byte3中(TL寄存器),第三个字节的数据写入byte4中(配置寄存器)。所有的数据都是由低位到高位的顺序写入。复位可随时中断写入。
读取高速缓存器[BEh]
该命名是读取高速缓存器里的值,从byte0(温度低八位)开始一直读到byte8(CRC校验),每个字节的数据从低位开始传送。若是不想读取这么多数据则在读取数据时随时可以通过复位来终止。
复制高速缓存器[48h]
该命令是将高速缓存器中的TH(byte2)、TL(byte3)以及配置寄存器(byte4)里的值拷贝到非易失性的存储器EEPROM里。如果总线控制器在这条命令之后跟着发出读时隙,而DS18B20又正在忙于把暂存器拷贝到EEPROM存储器,DS18B20就会输出一个“0”,如果拷贝结束的话,DS18B 20则输出“1”。如果设备采用“寄生电源”供电模式,则在该命令发送后,必须立即强制拉高总线至少10ms。
召回EEPROM[B8h]
该命令将温度报警触发值(TH和TL)及配置寄存器的数据从EEPROM中召回至高速缓存器中。这个操作会在上电后自动执行一次,所以在上电期间暂存器中一直会存在有效的数据。若在召回命令之后启动读时隙,若DS18B20正在进行召回EEPROM则会响应“0”,若召回完成则响应“1”。
读取供电模式[B4h]
该命令可以读取总线上的DS18B20是否是由“寄生电源”供电。在读取数据时序中“0”表示“寄生电源供”模式供电,“1”表示外部电源供电。
22.1.8. 1-Wire总线时序控制¶
上一小节介绍了初始化以及各种命令,那么初始化和这些时序具体该如何操控呢?在本小节中将会为大家详细讲解。
DS18B20采用严谨的1-Wire总线通信协议来保证数据的完整性。该协议定义多个信号形式:复位脉冲,存在脉冲,写0,写1,读0,读1 。除了存在脉冲由从设备发出,其他信号都由主设备控制。
初始化—复位和存在脉冲
与DS18B20所有的通信都是由初始化开始的,初始化由主设备发出的复位脉冲及DS18B20响应的存在脉冲组成。如图 28‑7所示。当DS18B20响应复位信号的存在脉冲后,则其向主设备表明其在该总线上,并且已经做好了执行命令的准备。
在初始化状态,总线上的主设备通过拉低1-Wire总线最少480us来表示发送复位脉冲。发送完之后,主设备要释放总线进入接收模式。当总线释放后,5kΩ的上拉电阻将1-Wire总线拉至高电平。当DS18B20检测到该上升沿信号后,其等待15us至60us后将总线拉低60us至240us来实现发送一个存在 脉冲。
图 28‑7 初始化时序图
写时隙
主设备通过写时隙将命令写入DS18B20中,写时隙有两种情况:写“1”和写“0”时隙。主设备通过写1时隙来向DS18B20中写入逻辑1,通过写0时隙来向DS18B20中写入逻辑0。当主设备将总线从高电平拉至低电平时,启动写时隙,所有的写时隙持续时间最少为60us,每个写时隙间的恢复时间最少为1us。
当总线(DQ)拉低后,DS18B20在15us至60us之间对总线进行采样,如果采的DQ为高电平则发生写1,如果为低电平则发生写0,如图 28‑8所示(图中的总线控制器即为主设备)。
如果要产生写1时隙,必须先将总线拉至逻辑低电平然后释放总线,允许总线在写时隙开始后15us内上拉至高电平。若要产生写0时隙,必须将总线拉至逻辑低电平并保持不变最少60us。
图 28‑8 写时隙时序图
读时隙
当我们发送完读取供电模式[B4h]或读高速缓存器[BEh]命令时,必须及时地生成读时隙,只有在读时隙DS18B20才能向主设备传送数据。每个读时隙最小必须有60us的持续时间以及每个读时隙间至少要有1us的恢复时间。当主设备将总线从高电平拉至低电平超过1us,启动读时隙,如图 28‑9所示。
当启动读时隙后,DS18B20将会向主设备发送“0”或者“1”。DS18B20通过将总线拉高来发送1,将总线拉低来发送0 。当读时隙完成后,DQ引脚将通过上拉电阻将总线拉高至高电平的闲置状态。从DS18B20中输出的数据在启动读时隙后的15us内有效,所以,主设备在读时隙开始后的15us内必须释放总线,并且对总线进行采样。
图 28‑9 读时隙时序图
通过上面对1-Wire总线时序控制的学习相信大家对具体怎么去控制DS18B20已经有了具体的思路,下一小节将带领大家做一个基本的温度转换并显示的实验。
22.2. 实战演练¶
22.2.1. 实验目标¶
实验目标:控制DS18B20实现对实时温度的转换并读出来显示在数码管上。
22.2.2. 硬件资源¶
本次实验我们需使用到开发板上的数码管及DS18B20接口,其中数码管资源在前面章节已有介绍,这里就不再过多描述。开发板上的DS18B20接口如图 28‑10所示。
图 28‑10 DS18B20接口图
其原理图如图 28‑11所示,该接口是单总线接口,可接DS18B20(温度传感器),DHT11(温湿度传感器),DHT11传感器我们会在后面章节讲到。
图 28‑11 ds18b20接口原理图
22.3. 程序设计¶
22.3.1. 整体说明¶
根据实验目标,首先数码管动态显示的模块是必不可少的,所幸在前面我们已经对数码管的动态显示进行了学习,将其显示模块调用过来即可。有了数码管显示模块我们只需要一个DS18B20的控制模块就能控制DS18B20进行温度转换并显示出来了。下面给出实验的整体框图,如图 28‑12所示。
图 28‑12 DS18B20温度显示实验整体框图
由上图可知,本实验工程包括3个模块,各模块简介,具体见表格 28‑3。
表格 28‑3 ds18b20工程模块简介
模块名称 |
功能描述 |
---|---|
ds18b20_ctrl |
ds18b20控制模块 |
seg_595_dynamic |
数码管动态显示模块 |
ds18b20 |
ds18b20顶层模块 |
根据框图可以看到,该工程共分为三个模块,其中一个是顶层模块,另一个数码管动态显示模块前面已经讲解完了,所以我们只要把DS18B20控制模块完成,就能实现上面的实验要求了。
22.3.1.1. 数码管动态显示模块¶
在“数码管的动态显示”章节中我们已经对数码管动态显示模块做了详细的讲解,在此就不再讲解了,在这里我们直接调用这个模块即可。需要注意的是该模块下还有子模块我们没有在整体框图中画出,该模块框图如图 28‑13所示。
图 28‑13 数码管显示模块
22.3.1.2. DS18B20控制模块¶
模块框图
本小节我们对DS18B20控制模块进行详细的介绍,首先是模块框图,如图 28‑14所示。
图 28‑14 DS18B20控制模块框图
这个模块的作用就是将实时的环境温度转换出来,然后将转换出来的温度数据读出来,模块各个信号的简介,如表格 28‑4所示。
表格 28‑4 DS18B20控制模块输入输出信号描述
信号 |
位宽 |
类型 |
功能描述 |
---|---|---|---|
sys_clk |
1bit |
input |
系统时钟,频率50MHz |
sys_rst_n |
1bit |
input |
复位信号,低电平有效 |
dq |
1bit |
inout |
控制总线 |
sign |
1bit |
output |
输出温度符号位 |
data_out |
20bit |
output |
输出温度数据 |
系统时钟、复位信号、输出温度、输出符号,这些信号一目了然,就不过多介绍了。下面详细介绍一下dq信号。dq信号的类型为inout(输入输出型)既可以作为输入也可以作为输出,前面已经说过DS18B20是由单总线控制,所以其位宽自然就是1bit了。从前面的1-Wire控制协议我们知道,我们是通过dq信号去 进行初始化化、写命令的,同时DS18B20也是通过dq信号将温度数据传出来的。所以我们只要把dq信号控制好,就能实现这个模块的功能了。
状态机
在前面的理论学习中我们讲到,对DS18B20所有的操作都是由初始化开始的,初始化开始后就需要识别设备,因为本次实验只控制一个DS18B20,所以发送跳过ROM命令即可,识别完设备之后我们就可以对这个设备发送功能命令了,根据实验要求,我们需要对其发送温度转换命令,命令发送完之后我们等待一段时间让其将将 温度转换完成,同时拉高总线让其供电以适配“寄生电源”模式。虽然本次实验为外部供电方式,但是我们可以编写同样适用“寄生电源”供电模式来增加模块的复用性(若以后我们需要用到“寄生电源”供电模式同样可以直接调用)。当等待温度转换之后再一次初始化,跳过ROM,然后发送读取高速缓存器命令来读取温度,发送完读命 名之后进入读温度状态生成读时序,当读完两个字节的温度后我们就达到了我们读取温度的目的,就发送初始化的复位脉冲停止读数,同时进入下一个初始化开始下一轮的温度转换读取。这一整个过程我们可以用下面的流程框图来概括,如图 28‑15所示。
图 28‑15 DS18B20控制流程图
当知道了DS18B20的控制流程之后,我们可以借助状态机来进一步了解它是如何跳转的。如图 28‑16所示
图 28‑16 DS18B20控制模块状态跳转图
跳过ROM命令和温度跳转命令都是写0,写1的时序,过程是一样的所以我们可以让其在同一个状态进行写入。同理可以让跳过ROM和读温度命令也在同一个状态写入,相关的状态描述,如表格 28‑5所示:
表格 28‑5 DS18B20控制模块状态机状态描述
状态 |
状态描述 |
---|---|
S_INIT |
初始化状态 |
S_WR_CMD |
跳过ROM和温度转换命令状态 |
S_WAIT |
等待温度转换完成状态 |
S_INIT_AGAIN |
再次初始化状态 |
S_RD_CMD |
跳过ROM和读取温度命令状态 |
S_RD_TEMP |
读取温度状态 |
从状态跳转图可以看到状态机的状态跳转条件,下面再通过波形图来具体看看,初始化,写0,写1的时序是如何实现的以及怎么将温度读取出来的。
波形图绘制
图 28‑17 时钟分频波形图
在整个时序过程中无论是发送开始信号,还是“1”,“0”的时序组成我们都需要用到时间信号,而这些时间信号的最小单位为 us,所以我们先产生单位时钟为 1us 的时钟来作为 DHT11 的驱动时钟。如上波形图所示,产生方法在“分频器”章节已有详细的讲解,在此就不再过多介绍,不明白的读者可以回到“分频器”章节进行了解。
图 28‑18 DS18B20控制模块波形图(一)
如图 28‑18所示,刚开始的时候其状态为初始化状态,那如何才能使初始化成功呢?根据我们理论部分的介绍我们知道要想初始换成功,我们需先拉低总线最少480us。所以在这里需要用到一个计数器去产生这个信号,而在整个过程中不管是初始化还是写0,写1我们都需要用到时间信号,而且单位都是us,所以我们可以先将 系统时钟分频成单位时间为1us的驱动时钟(clk_1us),后面无论是状态跳转还是时间计数我们都用这个时钟去驱动。
clk_1us:该驱动时钟波形图及产生方法在上面已讲解完毕。
dq:dq为数据总线,其类型为输入输出型,定义为wire型变量,这里我们用组合逻辑对齐进行赋值,所以我们将借助dq_out(总线输入)和dq_en(总线使能信号)来给dq赋值,当dq_en为低时使dq的值为高阻态,当dq_en为高电平时,将dq_out的值赋给dq。
S_INIT:初始化状态,上面说到首先拉低总线至少480us,在这里我们将其拉低500us后再将总线释放。前面理论部分多次说到释放总线,那么代码编写中我们如何释放总线呢?代码中让总线处于高阻态即为释放总线,高阻态用z表示。总线释放后器件内置电阻会将其上拉,当DS18B20检测到上升沿后,等待15us 至60us后会拉低总线持续时间为60us至240us。在这里我们在计数器到570us后检测,即释放总线后的70us检测。检测到总线拉低了才表示DS18B20发送了存在脉冲。从初始化时序图可以看到,整个初始化的持续时间必须大于960us,所以这里我们在计数器计到1000us(该值也可设为其它值,但是需 大于960)时,同时在570us采到低电平(检测到存在脉冲)时才让其跳转。很明显我们在同一个上升沿踩不到这两个条件同时满足,所以我们需要借助一个标志信号来显示我们在570us采到低电平,然后使用这个标志信号标志是否检测到存在脉冲,这样在同一个时钟上升沿就可以同时采到这两个条件了。如图所示,我们产生一 个检测存在脉冲的标志信号(flag_pulse)。
flag_pulse:当采到存在脉冲后,拉高标志信号,让其持续到状态跳转时,作为状态跳转的判断条件。
S_WR_CMD:当满足初始化状态跳转后,让其跳转到写跳过ROM命令和温度转换命令状态,同时需要让cnt_1us计数器清0开始下一轮的计数。所有的命令都是由八位二进制数组成的,所以我们只要从低位到高位依次将八位二进制命令写进去就行。而我们一个状态是写两个命令所以我们一个状态需要写16位二进数,那如何 判断我们是要写0还是写1呢?所以这里又引入了一个bit计数器(bit_cnt),去对我们写入第几个bit进行判断,当bit_cnt等于0时表示此时写的是两个连起来命令的最低位,也就是第一个命令的最低位。当bit_cnt等于7时表示此时写的是两个连起来命令的第8位,也就是第一个命令的最高位。当bit_ cnt等于8时表示此时写的是两个连起来命令的第9位,也就是第二个命令的最低位,以此类推。当状态机转态跳转到S_WR_CMD时让cnt_1us计数器从0开始计数,计到64us让其清零并开始下一轮的65us计数。因为我们的写时隙最少为60us且每个写时隙间的恢复时间最少为1us,所以计数时间必须大于61 us,这里我们让其计了65s。由写时隙图可以知道无论是写0还是写1都必须先将总线拉低,若是写0就将总线一直拉低,直到写0结束,若是写1就将总线在拉低后15us内释放总线,释放总线后总线会自动拉高并不用我们自己去控制。当bit_cnt等于15且cnt_1us等于64时表示两个命令都已经写进去了,此时我 们跳转到下一状态等待温度转换完成,并且让计数器清零。
S_WAIT:跳转到这个状态后,计数器开始计数,计数时间如表格 28‑2 温度分辨率配置所示,由于本次实验的转化位数为12位,所以需等待750ms。如图所示当计数器计到750000(750ms)时让状态跳转且计数器清零。
图 28‑19 DS18B20控制模块波形图(二)
如图 28‑19所示:当温度转换完成之后,跳到下一个初始换状态,初始化状态完成之后,进入到写跳过ROM以及读温度命令,这两个状态的控制时序与S_INIT和S_WR_CMD的控制时序是一样的。在此就不再次重复叙述了。
图 28‑20 DS18B20控制模块波形图(三)
当读高速缓存器命令发送完之后我们需立即生成读时隙,这样DS18B20才会向总线发送温度数据。如图 28‑9 读时隙时序图可知,每个读时隙之间都需要最少60us的读时隙持续时间以及最少1us的恢复时间,无论是读1时隙还是读0时隙,都需要拉低总线超过1us来启动读时隙,当读时隙开始后DS18B20会发送 高速缓存器的值,从低位开始发送,读1和读0的数据有效时间都为15us,所以当读时隙开始后我们需要在15us内采集数据,如图 28‑20所示我们将总线拉低2us后释放总线启动读时隙,而当计数器计到13us时我们对数据进行采集,同时将采集的数据寄存在data_tmp中。当bit_cnt等于15且cnt_ 1us=64时说明我们高速缓存器中两个字节的温度值已经读出来了,后面的数据我们不需要读取,直接让状态机回到初始状态终止读数据。
当读完16bit的温度后,我们需要对其转换后才是我们的温度值。如图中所示,当其符号位为正时温度值不变,为负时温度值取反加一。此时data里的值显示的是没有转换为小数温度的值。我们在理论部分知道乘以其精度0.0625才是显示的温度值。数码管的动态显示章节中我们学习到给数码管动态显示的数字是整数的。我们 只能给相应的位数给小数点才能让其显示变为小数。因为数码管位数原因,我们让数码管显示温度值小数点后三位,第四位小数不显示。所以我们需要对data进行(data*625)/10操作才能满足数码管显示的数值要求。这样数码管就能显示DS18B20读出的温度值了。
本设计思路只做参考,并非唯一方法,读者可利用所学知识,按照自己思路进行设计。
代码编写
参照绘制波形图,编写模块代码。模块参考代码,具体见代码清单 28‑1。
代码清单 28‑1 DS18B20控制模块参考代码(ds18b20_ctrl.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | module ds18b20_ctrl
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
inout wire dq , //数据总线
output wire [19:0] data_out , //输出温度
output reg sign //输出温度符号位
);
////
//\* Parameter and Internal Signal \//
////
//parameter define
parameter S_INIT = 3'd1, //初始状态
S_WR_CMD = 3'd2, //给跳过ROM及温度转换指令
S_WAIT = 3'd3, //等待温度转换完成
S_INIT_AGAIN = 3'd4, //再次回到初始化
S_RD_CMD = 3'd5, //给跳过ROM及读温度转换指令
S_RD_TEMP = 3'd6; //读温度状态
parameter WR_44CC_CMD = 16'h44cc; //跳过ROM及温度转换命令,低位在前
parameter WR_BECC_CMD = 16'hbecc; //跳过ROM及读取温度命令,低位在前
parameter S_WAIT_MAX = 750000 ; //750ms
//reg define
reg clk_1us ; //分频时钟,单位时钟1us
reg [4:0] cnt ; //分频计数器
reg [2:0] state ; //状态机状态
reg [19:0] cnt_1us ; //微秒计数器
reg [3:0] bit_cnt ; //字节计数器
reg [15:0] data_tmp ; //读取ds18b20的温度
reg [19:0] data ; //判断完正负后的温度
reg flag_pulse ; //初始化存在脉冲标志信号
reg dq_out ; //输出总线数据,即FPGA给的总线数据值
reg dq_en ; //输出总线数据使能信号
////
//\* Main Code \//
////
//温度转换,由于数码管位数有限,在这里保留小数点后三位
assign data_out = (data \* 10'd625)/ 4'd10;
//当使能信号为1是总线的值为dq_out的值,为0时为高阻态
assign dq = (dq_en ==1 ) ? dq_out : 1'bz;
//cnt:分频计数器
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 5'b0;
else if(cnt == 5'd24)
cnt <= 5'b0;
else
cnt <= cnt + 1'b1;
//clk_1us:产生单位时钟为1us的时钟
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
clk_1us <= 1'b0;
else if(cnt == 5'd24)
clk_1us <= ~clk_1us;
else
clk_1us <= clk_1us;
//cnt_1us:1us时钟计数器,用于状态跳转
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_1us <= 20'b0;
else if(((state==S_WR_CMD \|\| state==S_RD_CMD \|\| state==S_RD_TEMP)
&& cnt_1us==20'd64) \|\| ((state==S_INIT \|\| state==S_INIT_AGAIN) &&
cnt_1us==20'd999) \|\| (state==S_WAIT && cnt_1us==S_WAIT_MAX))
cnt_1us <= 20'b0;
else
cnt_1us <= cnt_1us + 1'b1;
//bit_cnt:bit计数器,写1bit或读1bit加1,一次写完之后清零
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
bit_cnt <= 4'b0;
else if((state == S_RD_TEMP \|\| state == S_WR_CMD \|\|
state == S_RD_CMD) && (cnt_1us == 20'd64 && bit_cnt == 4'd15))
bit_cnt <= 4'b0;
else if((state == S_WR_CMD \|\| state == S_RD_CMD \|\|
state == S_RD_TEMP) && cnt_1us == 20'd64)
bit_cnt <= bit_cnt + 1'b1;
//初始化存在脉冲标志信号:初始化状态时,当总线发来存在脉冲才能初始化成功
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
flag_pulse <= 1'b0;
else if(cnt_1us == 20'd570 && dq == 1'b0 && (state == S_INIT \|\|
state == S_INIT_AGAIN))
flag_pulse <= 1'b1;
else if(cnt_1us == 999)
flag_pulse <= 1'b0;
else
flag_pulse <= flag_pulse;
//状态跳转
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
state <= S_INIT;
else
case(state)
//初始化最小时间为960us
S_INIT: //收到存在脉冲且时间需大于960us跳转
if(cnt_1us == 20'd999 && flag_pulse == 1'b1 )
state <= S_WR_CMD;
else
state <= S_INIT;
S_WR_CMD: //发送完跳过ROM和温度转换命令后跳转
if(bit_cnt == 4'd15 && cnt_1us == 20'd64 )
state <= S_WAIT;
else
state <= S_WR_CMD;
S_WAIT: //等待750ms后跳转
if(cnt_1us == S_WAIT_MAX)
state <= S_INIT_AGAIN;
else
state <= S_WAIT;
S_INIT_AGAIN: //再次初始化后跳转
if(cnt_1us == 20'd999 && flag_pulse == 1'b1 )
state <= S_RD_CMD;
else
state <= S_INIT_AGAIN;
S_RD_CMD: //发送完跳过ROM和读取温度命令后跳转
if(bit_cnt == 4'd15 && cnt_1us == 20'd64)
state <= S_RD_TEMP;
else
state <= S_RD_CMD;
S_RD_TEMP: //读完2字节的温度后跳转
if(bit_cnt == 4'd15 && cnt_1us == 20'd64)
state <= S_INIT;
else
state <= S_RD_TEMP;
default:
state <= S_INIT;
endcase
//给各状态下的总线相应的时序
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
dq_out <= 1'b0;
dq_en <= 1'b0;
end
else
case(state)
//初始化是最小480us低电平,然后释放总线
S_INIT:
if(cnt_1us < 20'd499)
begin
dq_out <= 1'b0;
dq_en <= 1'b1;
end
else
begin
dq_out <= 1'b0;
dq_en <= 1'b0;
end
//每一个写时段最少有60us的持续时间和最少1us的恢复时间
//写0:总线拉低后一直拉低,最少60us
//写1:总线拉低后必须在15us内释放总线
S_WR_CMD:
if(cnt_1us > 20'd62)
begin
dq_out <= 1'b0;
dq_en <= 1'b0;
end
else if(cnt_1us <= 20'b1)
begin
dq_out <= 1'b0;
dq_en <= 1'b1;
end
else if(WR_44CC_CMD[bit_cnt] == 1'b0)
begin
dq_out <= 1'b0;
dq_en <= 1'b1;
end
else if(WR_44CC_CMD[bit_cnt] == 1'b1)
begin
dq_out <= 1'b0;
dq_en <= 1'b0;
end
//为适应寄生电源,温度转换命令后将总线拉高
S_WAIT:
begin
dq_out <= 1'b1;
dq_en <= 1'b1;
end
//与第一次初始化时序一致
S_INIT_AGAIN:
if(cnt_1us < 20'd499)
begin
dq_out <= 1'b0;
dq_en <= 1'b1;
end
else
begin
dq_out <= 1'b0;
dq_en <= 1'b0;
end
//与发送跳过ROM和读取温度命的时序一致
S_RD_CMD:
if(cnt_1us > 20'd62)
begin
dq_out <= 1'b0;
dq_en <= 1'b0;
end
else if(cnt_1us <= 20'b1)
begin
dq_out <= 1'b0;
dq_en <= 1'b1;
end
else if(WR_BECC_CMD[bit_cnt] == 1'b0)
begin
dq_out <= 1'b0;
dq_en <= 1'b1;
end
else if(WR_BECC_CMD[bit_cnt] == 1'b1)
begin
dq_out <= 1'b0;
dq_en <= 1'b0;
end
//拉低总线超过1us后释放总线
S_RD_TEMP:
if(cnt_1us <=1)
begin
dq_out <= 1'b0;
dq_en <= 1'b1;
end
else
begin
dq_out <= 1'b0;
dq_en <= 1'b0;
end
default:;
endcase
//data_tmp:读出温度,寄存在data_tmp里
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data_tmp <= 12'b0;
//总线拉低后数据有效时间为15us
else if(state == S_RD_TEMP && cnt_1us == 20'd13)
data_tmp <= {dq,data_tmp[15:1]};
else
data_tmp <= data_tmp;
//温度判断,输出温度
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
data <= 20'b0;
else if(data_tmp[15] == 1'b0 && state == S_RD_TEMP &&
cnt_1us == 20'd60 && bit_cnt == 4'd15)
data <= data_tmp[10:0];
else if(data_tmp[15] == 1'b1 && state == S_RD_TEMP &&
cnt_1us == 20'd60 && bit_cnt == 4'd15)
data <= ~data_tmp[10:0] + 1'b1;
//温度判断,输出符号位
always@(posedge clk_1us or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
sign <= 1'b0;
else if(data_tmp[15] == 1'b0 && state == S_RD_TEMP &&
cnt_1us == 20'd60 && bit_cnt == 4'd15)
sign <= 1'b0;
else if(data_tmp[15] == 1'b1 && state == S_RD_TEMP &&
cnt_1us == 20'd60 && bit_cnt == 4'd15)
sign <= 1'b1;
endmodule
|
模块参考代码是参照绘制波形图进行编写的,在波形图绘制小节已经对模块各信号有了详细的说明,本小节不再过多叙述。
22.3.1.3. 顶层模块¶
模块框图
顶层模块较为简单,内部实例化各子功能模块,连接各自对应信号,顶层模块框图,具体见图 28‑21。
图 28‑21 ds18b20顶层模块框图
表格 28‑6 ds18b20顶层模块输入输出信号描述
信号 |
位宽 |
类型 |
功能描述 |
---|---|---|---|
sys_clk |
1bit |
input |
系统时钟,50MHz |
sys_rst_n |
1bit |
input |
复位信号,低有效 |
dq |
1bit |
inout |
DS18B20单总线 |
stcp |
1bit |
output |
存储寄存器时钟 |
shcp |
1bit |
output |
移位寄存器时钟 |
ds |
1bit |
output |
串行数据 |
oe |
1bit |
output |
输出使能,低有效 |
代码编写
代码编写较为容易,无需波形图的绘制。顶层参考代码,具体见代码清单 28‑2。
代码清单 28‑2 顶层模块参考代码(ds18b20.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | module ds18b20
(
input wire sys_clk , //系统时钟,频率50MHz
input wire sys_rst_n , //复位信号,低电平有效
inout wire dq , //数据总线
output wire stcp , //输出数据存储寄时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds , //串行数据输入
output wire oe
);
////
//\* Parameter and Internal Signal \//
////
//wire define
wire [19:0] data_out ;
wire sign ;
////
//\* Instantiation \//
////
//-------------ds18b20_ctrl_inst--------------
ds18b20_ctrl ds18b20_ctrl_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.dq (dq ), //数据总线
.data_out (data_out ), //输出温度
.sign (sign ) //输出温度符号位
);
//-------------seg7_dynamic_inst--------------
seg_595_dynamic
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低有效
.data (data_out ), //数码管要显示的值
.point (6'b001000), //小数点显示,高电平有效
.seg_en (1'b1 ), //数码管使能信号,高电平有效
.sign (sign ), //符号位,高电平显示负号
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
endmodule
|
代码第46行是对小数点进行赋值,因为显示的是三位小数,所以给让第四位的数码管显示小数点。在数码管动态显示模块对point的定义是高电平有效,所以给第四位为1即可。
22.3.1.4. RTL视图¶
实验工程代码编写完之后,使用Quartus软件对实验工程进行编译,编译完成后,我们查看一下RTL视图,RTL视图展示信息与顶层模块框图一致,具体见图 28‑22。
图 28‑22 实验工程RTL视图
22.3.1.5. 仿真验证¶
仿真代码编写
顶层模块参考代码编写完成,实验工程通过编译,整个实验工程也已介绍完毕,开始对实验工程进行整体仿真。顶层模块仿真参考代码,具体见代码清单 28‑3。
代码清单 28‑3 顶层模块仿真参考代码(tb_ds18b20.v)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | module tb_ds18b20();
////
//\* Parameter And Internal Signal \//
////
//wire define
wire stcp ;
wire shcp ;
wire ds ;
wire oe ;
wire dq ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
////
//\* Instantiation \//
////
//对sys_clk,sys_rst_n赋初始值
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#100
sys_rst_n <= 1'b1;
end
//clk:产生时钟
always #10 sys_clk <= ~sys_clk;
//重新定义参数值,缩短仿真时间
defparam ds18b20_inst.ds18b20_ctrl_inst.S_WAIT_MAX = 750;
//-------------ds18b20_inst-------------
ds18b20 ds18b20_inst
(
.sys_clk (sys_clk ), //系统时钟,频率50MHz
.sys_rst_n (sys_rst_n), //复位信号,低电平有效
.dq (dq ), //数据总线
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
endmodule
|
需要注意的是当我们仿真时我们需先将初始化完成的接收到存在脉冲的标志信号条件注释掉,如图 28‑23中和所示。因为这个标志信号是DS18B20发送的,不是由主设备产生的。所以需先将其注释掉,状态才能跳转,才能观察到我们给的总线时序情况。当跑工程的时候我们将其加回去即可。
图 28‑23 注释掉存在脉冲图
仿真波形分析
使用ModelSim软件对代码进行仿真,仿真结果如下。
图 28‑24 ds18b20仿真波形图
如图 28‑24所示为仿真总的波形图,可以看到状态机能正常跳转,初步判断dq的输入波形与预想中的输入大致相同,下面看看局部的仿真波形图。
图 28‑25 ds18b20局部仿真图(一)
如图 28‑25所示,图中标注处为写0时序,与我们绘制的写0时序波形图是一致的,可以达到我们的既定要求。
图 28‑26 ds18b20局部仿真图(二)
如图 28‑26所示,图中标注处为写1时序,与我们绘制的写1时序波形图是一致的,可以达到我们的既定要求。
图 28‑27 ds18b20局部仿真图(三)
图 28‑27中可以看到读时隙的状态,都是拉低总线(dq)来启动读时隙的状态,当读完15个bit之后回到初始状态。
根据上面三个仿真波形图可以观察到,输入的总线控制与之前所绘制的波形图是一样的,说明能达到总线的控制要求。
22.4. 上板调试¶
22.4.1. 引脚约束¶
仿真验证通过后,准备上板验证,上板验证之前先要进行引脚约束。工程中各输入输出信号与开发板引脚对应关系如表格 28‑7所示。
表格 28‑7 引脚分配表
信号名 |
信号类型 |
对应引脚 |
备注 |
---|---|---|---|
sys_clk |
input |
E1 |
时钟 |
sys_rst_n |
input |
M15 |
复位 |
dq |
inout |
L14 |
ds18b20单总线 |
stcp |
output |
K9 |
存储寄存器时钟 |
shcp |
output |
B1 |
移位寄存器时钟 |
ds |
output |
R1 |
串行数据 |
oe |
output |
L11 |
输出使能,低有效 |
下面进行管脚分配,管脚的分配方法在前面章节已有所讲解,在此就不再过多叙述,管脚的分配如下图 28‑28所示。
图 28‑28 管脚分配
22.4.1.1. 结果验证¶
管脚配置完成之后重新进行编译,编译完之后就可以进行下载验证了,在下载之前首先将电源与下载线与开发板连接好,然后再接上DS18B20温度传感器。连接好后上电,如图 28‑29、图 28‑30所示。
图 28‑29 下载连线图
图 28‑30 DS18B20温度传感器连接图
这里需要注意的是:DS18B20温度传感器不要接反了,需严格按照上图进行连接。
打开下载界面后,当检测到下载器(USB-Blaster)已连接之后,即可点击“Add File…”添加sof文件,添加好后点击“start”开始下载,随后界面会显示下载成功,如图 28‑31所示。
图 28‑31 下载成功界面
下载成功后即可以开始验证了。如果看到数码管显示了当前环境的温度值(三位小数),且数值合理,同时显示的数值会随温度的变化而变化,则说明验证正确。
22.5. 章末总结¶
到这里,本章节讲解完毕,通过实验相信大家已经学会怎么去控制ds18b20。不同的命令的操作都是大致相同的,只要大家掌握了初始化,写0写1以及读0读1的时序关系,就能够操作ds18b20数字温度传感器实现各种命令。