DS18B20 数字温度传感器提供 9 位至 12 位精度的温度测量,并具有非易失性用户可编程上下触发点报警功能。DS18B20 通过单总线通信,根据定义,只需要一条数据线(和地线)即可与单片机通信。此外,DS18B20 可以直接从数据线获得电源(“寄生电源”),消除了每个 DS18B20 都有一个唯一的 64 位串行代码,这允许多个 DS18B20 在同一条总线上工作。
1、模块来源
2、参数与框图
工作电压:2.5-5.5V
工作电流:750nA~1.5mA
测量分辨率:9位到12位可编程分辨率
温度量程: -55 ~ +125 ℃
测量精度:±0.4 ℃
通信协议:单总线
芯片内部框图如下:
其中高速缓存器结构示意图如下:
DS18B20 的高速缓冲器是一个临时存储温度测量数据和配置参数的小型存储器,用于在温度转换和数据通信过程中缓存必要的信息。 由上图可知缓冲器由 9 个字节组成, 其中包括测量的温度数据(前两个字节)、 配置寄存器(第四个字节)、 以及内部保留位等。 用户可通过配置寄存器设置分辨率, 而其他字节中的特定位被保留用于内部用途, 不可写入。 在配置寄存器的 8 个比特位中, 只有第 5 位(R0) 与 6 位(R1) 可以由用户配置, 其他位都保留给 DS18B20 内部使用。 R0 与 R1 的组合决定了传感器的分辨率, 以及温度转换所需的时间。 它们的作用可以理解为配置选项的二进制编码开关, 通过不同的值组合实现分辨率的选择。
详情请看下面的图片:
上电默认设置 R1 和 R0 的值为 1, 即采集到的温度信息使用 12 位分辨率。当 DS18B20 配置为 12 位分辨率的温度数据时,(高速缓冲器的第 0、 1 字节用于输出温度数据) 其中输出的温度数据最高位为符号位, 温度值实际占 11 位, 最低 4位表示小数部分。 FPGA 读取温度数据时, 将会一次读取 16 位(2 字节), 从中提取低 11 位的二进制值, 将其转换为十进制后乘以 0.0625 得到实际温度值。 需要注意,温度的正负由 BIT7 符号位决定, 只需判断其中任意一位即可:若符号位为 1, 表示温度为负值, 需对提取的二进制值取反加 1 后再乘以 0.0625; 若符号位为 0, 则直接乘以 0.0625。如下图所示:
3、时序说明
复位时序说明
控制器与 DS18B20 所有的通信都是由初始化开始的, 初始化由主设备发出的复位脉冲及 DS18B20 响应的存在脉冲组成。 当 DS18B20 响应复位信号的后, 向主设备表明其在该总线上, 并且已经做好了执行命令的准备。 在这个过程中, 总线上的主设备需要拉低总线最少 480us 来表示发送复位脉冲。 发送完之后, 主设备要释放总线进入接收模式。 当总线释放后, 上拉电阻将总线拉至高电平。 当 DS18B20 检测到该上升沿信号后, 其等待 15us 至 60us 后将总线拉低 60us 至 240us 来实现发送复。 其具体时序图如下
读/写时序
写过程中写入数据有两种情况: 写 1 和写 0 。 写两种数据需要遵循不同的时序。 当主设备将总线从高电平拉至低电平时, 启动写操作, 所有的写操作持续时间最少为 60us, 每个写操作间的恢复时间最少为 1us。 当总线( DQ) 拉低后,DS18B20 在 15us 至 60us 之间对总线进行采样, 如果采的 DQ 为高电平则发生写 1,如果为低电平则发生写 0, 如下图所示(图中的总线控制器即为主设备)。 如果主机要写 1, 必须先将总线拉至逻辑低电平然后释放总线, 允许总线在写操作开始后 15us内上拉至高电平。 若要写 0, 必须将总线拉至逻辑低电平并保持不变最少 60us。
读过程中也有有两种情况: 读出 1 和读出 0。 所有读时序必须最少60us,包括两个读周期间至少1us的恢复时间。 当主设备将总线从高电平拉至低电平至少保持 1us, 启动读操作。 当启动读操作后, DS18B20 将会向主设备发送 0 或者 1 。 DS18B20 通过将总线拉高来发送 1, 将总线拉低来发送 0。 当读时隙完成后, DQ 引脚将通过上拉电阻将总线拉高至高电平的闲置状态。 从 DS18B20 中输出的数据在启动读时隙后的 15us 内有效, 所以, 主设备在读时隙开始后的 15us内必须释放总线, 并且对总线进行采样。 其具体时序图如下:
4、通讯步骤
通过单总线访问DS18B20的执行序列如下:
步骤1、初始化
步骤2、ROM操作指令
步骤3、DS18B20功能指令
每一次DS18B20的操作都必须满足以上步骤,若是缺少步骤或是顺序混乱,器件将不会有返回值。搜索ROM命令和报警搜索命令除外。当这两个命令执行时,主控制器必须返回步骤1。
1、初始化
通过单总线的所有执行操作都从一个初始化程序序列开始。初始化序列包含一个由总线控制器发出的复位脉冲和其后由从机发出的存在脉冲。存在脉冲让总线控制器知道DS18B20在总线上且已经准备好操作。
2、 ROM 操作指令
一旦总线控制器检测到一个存在脉冲,它就发出一条 ROM 指令。如果总线上挂有多颗 DS18B20,这些指令将给予器件独有的 64 位 ROM 序列码,使得总线控制器选出特定要进行操作的器件。这些指令同样也可以使总线控制器识别有多少颗,什么型号的器件挂在总线上,同样,他们也可以识别哪些器件已经符合报警条件。ROM 指令有 5条,都是 8 位长度。总线控制器在发起一条 DS18B20功能指令之前发出一条 ROM 指令。ROM 命令共有 5 种, 每种命令长度均为 8 位,如下所示:
- 搜索 ROM [F0h]
当系统上电初始化的时候,总线控制器必须通过识别总线上所有ROM序列码去得到从机的数目和型号。总线控制器通过搜索ROM指令多次循环搜索ROM编码,以确认所有从机器件。如果总线上只有一个从机,那么可以用较为简单的读取ROM指令(见下文)代替搜索ROM指令。在每次搜索ROM指令之后,总线控制器必须返回步骤1(初始化)。
- 读ROM [33h]
只有在总系上存在单颗DS18B20的时候才能使用这条命令。该命令允许总线控制器在不使用Search ROM指令的情况下读取从机的64位序列码。如果总线上有不止一个从机而使用该命令时,所有从机试图同时传送信号时就会发生数据冲突。
- 匹配 ROM[55h]
MATCH ROM指令后跟着64位ROM序列号,总线控制器在多点总线上定位一颗特定的从器件。只有和64位ROM序列号完全匹配的DS18B20才能响应随后的存储器操作指令;所有和64位ROM序列号不匹配的从机都将等待复位脉冲。
- 跳过 ROM[CCh]
允许主设备跳过 64 位 ROM 编码直接执行下一步操作, 适用于单点总线(只有一个 DS18B20) 的情况(本次实验就是此种情况) , 可节省时间。 但在多点总线中, 若发送跳过 ROM 命令后执行读操作, 则所有从设备将同时响应, 导致数据冲突。
- 警报搜索[ECh]
这条指令的操作流程和搜索ROM指令相同,只有满足报警条件的从机才会对该命令作出响应。该命令允许主设备确定在最近一次的温度转换期间是否有任何DS18B20经历了报警状态。在每次报警搜索指令周期之后,总线控制器必须返回步骤1。
3、DS18B20功能指令
当主设备通过 写ROM 命令确认某个 DS18B20 可以通信后, 便可向目标从设备发送功能命令, 以执行特定操作。 以下是 DS18B20 的功能命令及其作用:
- 温度转换 [44h]
这条命令时用于启动一次温度转换。温度转换指令被执行后,产生的温度转换结果数据以2个字节的形式被存储在温度寄存器中,而后DS18B20保持低功耗的等待状态。如果在寄生供电模式下发出该指令,在温度转换期间(tCONV),必须在10us(最多)内给单总线一个强上拉,见DS18B20供电节。如果DS18B20以外部电源供电,总线控制器在出该命令后跟着发出读时序,DS18B20如处于转换中,则总线返回0,若温度转换完成,则返回1。在寄生供电模式下,总线被强上拉拉高前这样的通信方式不会被使用。
- 写入暂存器 [4Eh]
向DS18B20的寄存器写入数据,开始位置在TH寄存器(寄存器的第2个字节),接下来写入TL寄存器(寄存器的第3个字节),最后写入配置寄存器(寄存器的第4个字节),数据以最低有效位开始传送。上述三个字节的写入必须发生在总线控制器发出复位命令前,否则会发生数据冲突。
- 读取高速缓存器 [BEh]
这条命令时主机读取寄存器命令。读取将从字节0的最低有效位开始,一直进行下去,直到第9字节(字节8,CRC)读完,如果不想读完所有字节,控制器可以在任何时候发出复位命令来中止读取。
- 复制高速缓存器 [48h]
将高速缓存器中的高温触发值(byte2)、低温触发值(byte3)和配置寄存器(byte4)的值复制到非易失性存储器(EEPROM) 中, 若命令后主机发出读请求, DS18B20 会返回 0 :表示复制操作正在进行。 1 :表示复制完成。需要注意的是如果使用寄生电源模式,发送该命令后,必须立即强制拉高总线至少 10ms。
- 召回 EEPROM [B8h]
这条命令把TH, TL以及配置的数据从EEPROM拷回寄存器。总线控制器在发出该命令后发读时序,DS18B20会输出拷回标识:0标识正在拷回,1标识拷回结束。该操作在DS18B20上电时自动执行,这样器件一上电寄存器里马上就存在有效的数据了。
- 读取供电模式 [B4h]
该命令用于判断 DS18B20 的供电方式若返回 0 : 表示使用寄生电源模式。 返回 1 : 表示使用外部电源模式。
5、实战任务
逻辑派FPGA-G1开发板通过单总线与DS18B20进行通讯,使用按键触发对环境温度的测量并通过串口向上位机发送当前测量到的温度。
6、系统框图
7、程序编写
注: 单总线的时序要求较为严格,不同厂家的时序可能有所不同。如果无法正常使用,建议自行核对并调整时序设置。
module ds18b20_top(
input sys_clk ,
input sys_rst_n ,
input key_in ,
inout dq ,
output uart_txd
);
parameter CNT_MAX = 25'd999_999 ; //消抖计数器
parameter STR = 128'hb5_b1_c7_b0_bb_b7_be_b3_ce_c2_b6_c8_ce_aa_a3_ba; // "当前环境温度为:" 的 GB2312 编码
//信号定义
wire data_vld ;
wire [ 31: 0] temperature_data ;
wire uart_tx_busy ;
wire uart_tx_done ;
reg [ 7: 0] uart_tx_data ;
reg uart_tx_req ;
reg work_en ;
reg [ 6: 0] tx_byte_cnt ;
reg [ 24: 0] cnt_20ms ;//消抖计数器
reg key_flag ;
reg [ 7: 0] ascii_table [0:11];//储存阿拉伯数字0~9与符号+-的ASCII值
wire [ 3: 0] s_bf ;//百分位
wire [ 3: 0] s_sf ;//十分位
wire [ 3: 0] s_sw ;//十位
wire [ 3: 0] s_gw ;//个位
wire [ 5: 0] data_symbol ;
//cnt_20ms:如果时钟的上升沿检测到外部按键输入的值为低电平时,计数器开始计数
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
cnt_20ms <= 20'b0;
else if(key_in == 1'b1)
cnt_20ms <= 20'b0;
else if(cnt_20ms == CNT_MAX && key_in == 1'b0)
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 1'b1;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
key_flag <= 1'b0;
else if(cnt_20ms == CNT_MAX - 1'b1)
key_flag <= 1'b1;
else
key_flag <= 1'b0;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
work_en <= 1'b0;
else if(data_vld == 1'b1)
work_en <= 1'b1;
else if(tx_byte_cnt== 7'd23&&uart_tx_done)
work_en <= 1'b0;
else
work_en <= work_en;
//串口发送逻辑
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
tx_byte_cnt <= 7'b0;
end else if (work_en) begin
if (tx_byte_cnt == 7'd23&&uart_tx_done) // 总共发送 22 字节
tx_byte_cnt <= 7'b0;
else if(uart_tx_done)
tx_byte_cnt <= tx_byte_cnt + 1'b1;
end
end
// 串口发送数据控制
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
uart_tx_req <= 1'b0;
uart_tx_data <= 8'b0;
ascii_table[0] = 8'd48; //'0'
ascii_table[1] = 8'd49; //'1'
ascii_table[2] = 8'd50; //'2'
ascii_table[3] = 8'd51; //'3'
ascii_table[4] = 8'd52; //'4'
ascii_table[5] = 8'd53; //'5'
ascii_table[6] = 8'd54; //'6'
ascii_table[7] = 8'd55; //'7'
ascii_table[8] = 8'd56; //'8'
ascii_table[9] = 8'd57; //'9'
ascii_table[10] = 8'd43; //'+'
ascii_table[11] = 8'd45; //'-'
end else if (work_en && !uart_tx_busy) begin
case (tx_byte_cnt) // 根据当前字节计数器发送数据
7'd1: uart_tx_data <= STR[127:120]; // "当"
7'd2: uart_tx_data <= STR[119:112]; // "当"
7'd3: uart_tx_data <= STR[111:104]; // "前"
7'd4: uart_tx_data <= STR[103:96]; // "前"
7'd5: uart_tx_data <= STR[95:88]; // "环"
7'd6: uart_tx_data <= STR[87:80]; // "环"
7'd7: uart_tx_data <= STR[79:72]; // "境"
7'd8: uart_tx_data <= STR[71:64]; // "境"
7'd9: uart_tx_data <= STR[63:56]; // "温"
7'd10: uart_tx_data <= STR[55:48]; // "温"
7'd11: uart_tx_data <= STR[47:40]; // "度"
7'd12: uart_tx_data <= STR[39:32]; // "度"
7'd13: uart_tx_data <= STR[31:24]; // "为"
7'd14: uart_tx_data <= STR[23:16]; // "为"
7'd15: uart_tx_data <= STR[15:8]; // ":"
7'd16: uart_tx_data <= STR[7:0]; // ":"
7'd17: uart_tx_data <= ascii_table[data_symbol]; // 温度符号
7'd18: uart_tx_data <= ascii_table[s_sw]; // 温度十位
7'd19: uart_tx_data <= ascii_table[s_gw]; // 温度个位
7'd20: uart_tx_data <= 8'd46;//小数点
7'd21: uart_tx_data <= ascii_table[s_sf]; // 温度十分位
7'd22: uart_tx_data <= ascii_table[s_bf]; // 温度百分位
7'd23: uart_tx_data <= 8'h0A; // 换行符
default: uart_tx_data <= 8'b0;
endcase
uart_tx_req <= 1'b1; // 拉高请求信号
end else begin
uart_tx_req <= 1'b0; // 请求信号拉高仅一个时钟周期
end
end
ds18b20 u_ds18b20(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.cap_flag (key_flag ),
.dq (dq ),
.s_bf (s_bf ), //百分位
.s_sf (s_sf ), //十分位
.s_sw (s_sw ), //十位
.s_gw (s_gw ), //个位
.data_symbol (data_symbol ), // 指示温度符号
.data_vld (data_vld ), // 温度有效标志
.data_out (data_out )
);
uart_tx u_uart_tx(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.uart_tx_req (uart_tx_req&work_en ),// 发送使能请求
.uart_tx_data (uart_tx_data ),// UART要发送的数据
.uart_txd (uart_txd ),// UART发送端口
.uart_tx_done (uart_tx_done ),//8bit数据发送完成
.uart_tx_busy (uart_tx_busy ) // 发送忙
);
endmodule
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
module ds18b20
(
input wire clk ,
input wire rst_n ,
input wire cap_flag /* synthesis PAP_MARK_DEBUG="true" */,
inout wire dq /* synthesis PAP_MARK_DEBUG="true" */,
output reg [ 3: 0] s_bf ,//百分位
output reg [ 3: 0] s_sf ,//十分位
output reg [ 3: 0] s_sw ,//十位
output reg [ 3: 0] s_gw ,//个位
output reg [ 5: 0] data_symbol /* synthesis PAP_MARK_DEBUG="true" */,//指示温度符号
output reg data_vld /* synthesis PAP_MARK_DEBUG="true" */,//温度有效标志
output wire [ 19: 0] data_out /* synthesis PAP_MARK_DEBUG="true" */
);
parameter WAIT_TRI = 7'b0000_001,
INIT = 7'b0000_010,
WR_CMD = 7'b0000_100,
WAIT = 7'b0001_000,
INIT_AGAIN = 7'b0010_000,
RD_CMD = 7'b0100_000,
RD_TEMP = 7'b1000_000;
parameter WAIT_MAX = 20'd750_000;
parameter WR_CC_44 = 16'h44_cc,
WR_CC_BE = 16'hbe_cc;
reg clk_us ;
reg [ 5: 0] cnt ;
reg [ 6: 0] state /* synthesis PAP_MARK_DEBUG="true" */;
reg [ 19: 0] us_cnt /* synthesis PAP_MARK_DEBUG="true" */;
reg flag ;
reg [ 3: 0] bit_cnt /* synthesis PAP_MARK_DEBUG="true" */;
reg [ 15: 0] data_temp ;
reg [ 19: 0] data ;
reg dq_en ;
reg dq_out ;
reg [ 19: 0] temperature ;
assign dq = (dq_en == 1'b1) ? dq_out : 1'bz;
assign data_out = temperature;
// 脉冲扩展
reg [ 6: 0] counter ;
reg cap_start ;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cap_start <= 1'b0;
counter <= 0;
end else begin
if (cap_flag) begin
cap_start <= 1'b1;
counter <= 200;
end else if (counter > 0) begin
cap_start <= 1'b1; // 计数器递减,维持高电平
counter <= counter - 1;
end else begin
cap_start <= 1'b0; // 计数结束,输出恢复低电平
end
end
end
//cnt,计数50次,实现1us
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
cnt <= 6'd0;
else if(cnt == 6'd49)
cnt <= 6'd0;
else
cnt <= cnt + 1'b1;
//clk_us,1us的时钟
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
clk_us <= 1'b0;
else if(cnt == 6'd49)
clk_us <= ~clk_us;
else
clk_us <= clk_us;
//us_cnt,1us 时钟计数器,用于状态跳转
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)
us_cnt <= 20'd0;
else if(((state == INIT || state == INIT_AGAIN) && (us_cnt == 20'd959))
|| ((state == WR_CMD || state == RD_CMD || state == RD_TEMP) && (us_cnt == 20'd64))
|| ((state == WAIT && us_cnt == WAIT_MAX))||(state == WAIT_TRI ))
us_cnt <= 20'd0;
else
us_cnt <= us_cnt + 1'b1;
//bit_cnt,bit 计数器,写 1bit 或读 1bit 加 1,一次写完之后清零
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)
bit_cnt <= 4'd0;
else if((state == WR_CMD || state == RD_CMD || state == RD_TEMP)
&& (bit_cnt == 4'd15) && (us_cnt == 20'd64))
bit_cnt <= 4'd0;
else if((state == WR_CMD || state == RD_CMD || state == RD_TEMP)
&& (us_cnt == 20'd64))
bit_cnt <= bit_cnt + 1'b1;
//flag
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)
flag <= 1'b0;
else if((state == INIT || state == INIT_AGAIN) && (us_cnt == 20'd570) && (dq == 1'b0))
flag <= 1'b1;
else if(us_cnt == 20'd959)
flag <= 1'b0;
else
flag <= flag;
//state????
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)
state <= WAIT_TRI;
else
case(state)
WAIT_TRI :
if(cap_start)
state <= INIT;
else
state <= WAIT_TRI;
INIT :
if(us_cnt == 20'd959 && flag == 1'b1)
state <= WR_CMD;
else
state <= INIT;
WR_CMD :
if(bit_cnt == 4'd15 && us_cnt == 20'd64)
state <= WAIT;
else
state <= WR_CMD;
WAIT :
if(us_cnt == WAIT_MAX)
state <= INIT_AGAIN;
else
state <= WAIT;
INIT_AGAIN :
if(us_cnt == 20'd959 && flag == 1'b1)
state <= RD_CMD;
else
state <= INIT_AGAIN;
RD_CMD :
if(bit_cnt == 4'd15 && us_cnt == 20'd64)
state <= RD_TEMP;
else
state <= RD_CMD;
RD_TEMP :
if(bit_cnt == 4'd15 && us_cnt == 20'd64)
state <= WAIT_TRI;
else
state <= RD_TEMP;
default:state <= WAIT_TRI;
endcase
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
else
case(state)
WAIT_TRI :
begin
dq_en <= 1'b1;
dq_out <= 1'b1;
end
INIT :
if(us_cnt < 20'd499)
begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
WR_CMD :
if(us_cnt > 20'd62)
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
else if(us_cnt <= 20'd1)
begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(WR_CC_44[bit_cnt] == 1'b0)
begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(WR_CC_44[bit_cnt] == 1'b1)
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
WAIT :
begin
dq_en <= 1'b1;
dq_out <= 1'b1;
end
INIT_AGAIN :
if(us_cnt < 20'd499)
begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
RD_CMD :
if(us_cnt > 20'd62)
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
else if(us_cnt <= 20'd1)
begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(WR_CC_BE[bit_cnt] == 1'b0)
begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else if(WR_CC_BE[bit_cnt] == 1'b1)
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
RD_TEMP :
if(us_cnt <= 1)
begin
dq_en <= 1'b1;
dq_out <= 1'b0;
end
else
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
default:
begin
dq_en <= 1'b0;
dq_out <= 1'b0;
end
endcase
//data_temp
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)
data_temp <= 16'b0;
else if((state == RD_TEMP) && (us_cnt == 20'd13))
data_temp <= {dq,data_temp[15:1]};
else
data_temp <= data_temp;
//data
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)begin
data <= 20'd0;
data_symbol <= 6'd10;
end
else if((state == RD_TEMP) && (bit_cnt == 4'd15) && (us_cnt == 20'd60)
&& (data_temp[15] == 1'b0))begin //如果是正数直接输出
data <= data_temp[10:0];
data_symbol <= 6'd10; //ASCII "+"
end
else if((state == RD_TEMP) && (bit_cnt == 4'd15) && (us_cnt == 20'd60)
&& (data_temp[15] == 1'b1))begin //如果是负数
data <= ~data_temp[10:0] + 1;
data_symbol <= 6'd11; //ASCII "-"
end
reg rev_done ;
reg rev_done_dl ;
wire rev_done_fall ;
assign rev_done_fall = rev_done_dl&~rev_done;
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)
rev_done <= 1'b0;
else if((state == RD_TEMP) && (bit_cnt == 4'd15) && (us_cnt == 20'd60))
rev_done <= 1'b1;
else
rev_done <= 1'b0;
always@(posedge clk or negedge rst_n)
if(rst_n == 1'b0)
rev_done_dl <= 1'b0;
else
rev_done_dl <= rev_done;
always@(posedge clk_us or negedge rst_n)
if(rst_n == 1'b0)
temperature <= 20'b0;
else if(rev_done == 1'b1)
temperature <= (data * 625) / 100;
always @(posedge clk or negedge rst_n) begin
if (rst_n == 1'b0) begin
s_bf <= 0;
s_sf <= 0;
s_sw <= 0;
data_vld<= 1'b0;
end else begin
s_bf <= temperature % 10;
s_sf <= (temperature / 10) % 10;
s_gw <= (temperature / 100) % 10;
s_sw <= (temperature / 1000) % 10;
data_vld<= rev_done_fall;
end
end
endmodule
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
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
module uart_tx(
input clk ,
input rst_n ,
input uart_tx_req ,//发送使能请求
input [ 7: 0] uart_tx_data ,//UART要发送的数据
output reg uart_txd ,//UART发送端口
output reg uart_tx_done,
output reg uart_tx_busy //发送忙
);
//parameter define
parameter SYS_CLK_FREQ = 50000000;
parameter UART_BPS = 115200;
localparam BAUD_CNT_MAX = SYS_CLK_FREQ/UART_BPS; //根据时钟和波特率计算波特计数器的值
//reg define
reg [ 7: 0] tx_data_reg ;//发送数据寄存器
reg [ 3: 0] tx_data_cnt ;//发送数据计数器
reg [ 15: 0] baud_cnt ;//波特率计数器
//当uart_tx_en为高时,寄存输入的并行数据,并拉高发送忙信号
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
tx_data_reg <= 8'b0;
uart_tx_busy <= 1'b0;
end
else if(uart_tx_req == 'd1) begin
tx_data_reg <= uart_tx_data;
uart_tx_busy <= 1'b1;
end
else if(tx_data_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - 1) begin
tx_data_reg <= 8'b0;
uart_tx_busy <= 1'b0;
end
else begin
tx_data_reg <= tx_data_reg;
uart_tx_busy <= uart_tx_busy;
end
end
always @(posedge clk or negedge rst_n)
if(!rst_n)
uart_tx_done<=1'b0;
else if(tx_data_cnt == 4'd9 && baud_cnt == BAUD_CNT_MAX - 1)
uart_tx_done<=1'b1;
else
uart_tx_done<=1'b0;
//波特率计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
baud_cnt <= 16'd0;
else if(uart_tx_req)
baud_cnt <= 16'd0;
else if(uart_tx_busy) begin
if(baud_cnt < BAUD_CNT_MAX - 1'b1)
baud_cnt <= baud_cnt + 16'b1;
else
baud_cnt <= 16'd0;
end
else
baud_cnt <= 16'd0;
end
//发送位计数器
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
tx_data_cnt <= 4'd0;
else if(uart_tx_req)
tx_data_cnt <= 16'd0;
else if(uart_tx_busy) begin
if(baud_cnt == BAUD_CNT_MAX - 1'b1)
tx_data_cnt <= tx_data_cnt + 1'b1;
else
tx_data_cnt <= tx_data_cnt;
end
else
tx_data_cnt <= 4'd0; //发送完成清零
end
//使用case给发送端口赋值
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
uart_txd <= 1'b1;
else if(uart_tx_busy) begin
case(tx_data_cnt)
4'd0 : uart_txd <= 1'b0 ; //起始位
4'd1 : uart_txd <= tx_data_reg[0];
4'd2 : uart_txd <= tx_data_reg[1];
4'd3 : uart_txd <= tx_data_reg[2];
4'd4 : uart_txd <= tx_data_reg[3];
4'd5 : uart_txd <= tx_data_reg[4];
4'd6 : uart_txd <= tx_data_reg[5];
4'd7 : uart_txd <= tx_data_reg[6];
4'd8 : uart_txd <= tx_data_reg[7];
4'd9 : uart_txd <= 1'b1 ; //停止位
default : uart_txd <= 1'b1;
endcase
end
else
uart_txd <= 1'b1; //否则为空闲
end
endmodule
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
`timescale 1ns / 1ps // 设置仿真时间单位和时间
module ds18b20_mod();
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg key_in ;
//wire define
wire uart_txd ;
wire dq ;
always #10 sys_clk = ~sys_clk; // 每 10ns 生成一个时钟周期
initial begin
#10
sys_clk = 1'd0 ;
sys_rst_n = 1'd0 ;
key_in = 1'd1 ;
#200
key_in = 1'd0 ;
sys_rst_n = 1'd1 ;
end
ds18b20_top u_ds18b20_top(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.dq (dq ),
.key_in (key_in ),
.uart_txd (uart_txd )
);
endmodule
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
8、仿真
由于本实验需要与DS18B20通过总线交互信息,在测试文件中编写DS18B20的应答逻辑比较麻烦,我们可以将DS18B20驱动模块中的关键信号打上debug标记,分析DS18B20驱动模块是否正确工作,同时检查DS18B20是否正确应答我们通过总线发送的信息。
我们将顶层模块发送拉低采样触发信号cap_flag、总线信号dq、温度符号指示data_symbol、温度有效标志data_vld、温度数据data_out、状态机信号state、微秒计数器us_cnt、接收数据比特计数器bit_cnt添加上debug标记进行分析。
首先我们设置触发模式为cap_flag = 1,按下按键捕获到波形,观察状态机是否正确响应触发信号。波形如下图所示,状态机正确跳转到初始化状态,对DS18B20进行初始化。
然后设置触发模式为(state = 7’b0000_010&&us_cnt = 959)(状态机处于DS18B20初始化状态,接收响应完成),DS18B20总线复位时序中我们有说明,一整个复位操作包括主机发送复位与从机应答总共960us,我们以此作为条件观察复位是否成功,按下按键观察捕获到的波形如下:
由上图可知当us_cnt = 959时,状态机跳转到下一状态,同时总线拉低,符合我们的复位时序。
接下来将触发模式设置为(state = 7’b0000_100&&bit_cnt = 15&&us_cnt =64)(状态机发送16bit命令状态,并且发送完成),捕获到的波形图如下图:
由上图可知在发送完成16bit命令[CCh][44h]之后状态机跳转到等待状态,等待DS18B20进行温度转换,共等待750ms,此时总线拉高。由此可见状态跳转逻辑正确。接下来就是再次进行初始化,发送命令[CCh][BEh],状态机跳转到接收温度数据状态,开始读数据。我们将触发模式设置为(state = 7’b1000_000&&bit_cnt = 15&&us_cnt =64)(状态机接收温度数据,并且接收完成),捕获到的波形图如下:
在这张图中,状态机接收数据完成,并计算出当前温度为17.18度,同时data_vld信号拉高,表示当前温度数据解析完成,数据有效。之后状态机跳转到等待触发状态,等待顶层触发信号再次到来。由此可见,模块功能正确。
9、IO 绑定
接下来使用 Gowin 对引脚进行分配并上板验证。管脚分配如下表所示:
信号 | 方向 | 引脚 | 端口作用 | 电平标准 |
---|---|---|---|---|
sys_clk | input | T7 | 时钟 | LVCMOS33 |
sys_rst_n | input | D11 | 复位 | LVCMOS33 |
key_in | input | F10 | 按键 | LVCMOS33 |
dq | inout | N14 | 单总线 | LVCMOS33 |
10、程序下载
连接开发板的下载器,下载码流文件到开发板里面,下载完成之后,使用下载器FPGA端的性能串口,打开串口助手, 按下开发板按键 F10 按键, 等待温度采样完成之后, 开发板将会返回当前环境温度的测量值。 具体现象如下: