[FPGA] Spartan6 单总线协议 (One-Wire) 读取DS18B20温度传感器

最近项目需要使用FPGA读取DS18B20传感器的温度数据,通过阅读数据手册(DS18B20数据手册下载)发现该传感器使用的是1-wire bus协议,仅需一根数据线即可完成数据传输。此项目使用的DS18B20为TO-92封装,如下图所示:

1.One-Wire协议时序

单总线(One-Wire bus)是一种串行通信接口技术,由Maxim(Dallas)公司设计,DS18B20作为其典型应用,仅需一根数据线(DQ引脚)即可完成数据传输、设备寻址和指令控制等功能。单总线的核心时序包括三个部分:初始化时序,读时序和写时序。

1.1 初始化时序

数据手册中写明,初始化时序中包括主机发送的复位脉冲和DS18B20发送的应答脉冲。

结合文字和时序图一起看,主设备需要拉低总线至少480us,然后主设备释放总线,并进入接收模式。当总线被主设备释放后,总线会被上拉电阻拉高。当DS18B20检测到这个上升沿时,它会等待15-60us然后拉低60-240us总线来发送出一个应答脉冲。

1.2 写时序

写时序包括写0时隙和写1时隙,有些许不同。

主控制器通过写1时隙将逻辑1写到从设备,通过写0时隙将逻辑0写到从设备。所有的写时隙最少持续20us,各个写周期之间至少间隔1us。当主设备将总线从高拉低时,写时隙开始工作。从设备会在15-60us的窗口期间内对单总线采样。

1.2.1 写0时隙

主设备发出一个写0时隙,需要把数据线拉低并保持至少60us。

总结:

主设备持续拉低至少60us即可

1.2.2 写1时隙

主设备发出一个写1时隙,将单总线拉低后,主设备需要在15us内将总线释放。总线被释放后,上拉电阻会将总线拉高。

总结:

主设备拉低小于15us,然后释放,加起来总时间不少于60us即可

1.3 读时序

读时序包括读0时隙和读1时隙,对于主设备来说并无区别。

所有读时隙的持续时间至少为60us,两个读周期之间至少间隔1us。主设备将总线拉低至少1us,然后释放总线。主设备发出读时隙后,从设备通过拉高或拉低总线传输1或0。从设备输出的数据在主设备发出读时隙后的15us内有效。

总结:

主设备拉低总线1us,释放总线;从机占用总线,主机在15us内读取总线数据,总的读时隙时间大于60us即可。

2. DS18B20操作

使用DS18B20传感器,首先需要初始化,即上述所说的主设备发送复位脉冲,DS18B20发送应答脉冲,让主设备知道DS18B20已准备好运行;然后发送ROM操作指令,当总线上连接有多个设备时,可以通过ROM命令识别各个设备;最后发送功能指令。

由于此项目只搭载一个单总线设备,所以可以发送跳过ROM指令[CCh],发送完成后可以直接发送功能指令,如此项目中用的温度转换[44h]和读取暂存器指令[BEh]。

DS18B20默认是12位,从表格中可知,tCONV即转换温度的等待时间,12位的时候最少要等待750ms。

BEh\]命令可以读9个字节,其中 byte0:温度低字节 byte1:温度高字节 byte2:TH报警 byte3:TL报警 byte4:配置寄存器 byte5:保留 byte6:保留 byte7:保留 byte8:CRC校验码 数据会从低字节开始传输,此项目只需要读取byte0和byte1字节,传输完两字节(16位)之后,主机发送复位脉冲即可。 **总结:** **1. 主设备拉低500us,然后释放总线;在570us时间对数据线进行采样,看DS18B20是否将数据线拉低;总时长达到1000us,初始化时序结束** **2. 发送CCh 跳过ROM命令** **3.发送44h 温度转换命令,然后等待超过750ms** **4.再次执行1操作** **5.再次执行2操作** **6.发送BEh 读取暂存器** **7.读取16位温度数据,低字节先行,将数据拼接** **8.对温度数据进行转换,并通过串口输出** ## 3. FPGA verilog代码 ### 3.1 ds18b20_moore.v(三段Moore状态机) ```cpp module ds18b20_moore( input clk, //50Mhz时钟 input rst_n, //复位信号 inout dq, //dq单总线双向端口 output reg[15:0] data //16位原始温度数据 ); // 状态定义 localparam RESET1 = 4'b0000, //第一次复位 WRITE_CC = 4'b0001, //写指令 0xCC(跳过ROM) WRITE_44 = 4'b0011, //写指令 0x44(温度转换) WAIT_CONV = 4'b0010, //等待温度转换完成 RESET2 = 4'b0110, //第二次复位 WRITE_CC2 = 4'b0111, //写指令 0xCC(跳过ROM) WRITE_BE = 4'b0101, //写指令 0xBE(读暂存器) READ_T = 4'b0100; //读取16位温度数据 // 时间参数(单位:us) localparam T_INIT = 1000, //初始化总时长 T_WAIT = 780_000, //温度转换等待时间 T_BIT = 62, //单bit通信时长 T_ACK = 570, //应答信号采样时刻 T_READ = 10; //读数据采样点 localparam WR_CMD_CC = 8'hcc; //跳过ROM命令 localparam WR_CMD_44 = 8'h44; //温度转换命令 localparam WR_CMD_BE = 8'hbe; //读温度命令 reg [3:0] current_state; //现态 reg [3:0] next_state; //次态 reg [3:0] wr_bit_cnt; //写bit计数器 reg [4:0] rd_bit_cnt; //读bit计数器 reg reset_ack_valid; //复位应答标志 reg dq_en; //DQ输出使能 reg dq_out; //DQ输出值 reg [15:0] data_temp; //温度拼接 reg clk_us; //1Mhz时钟 reg [19:0] cnt_us; //微秒计数器 reg [4:0] cnt; //分频计数器 wire dq_in; //DQ输入采样 assign dq_in = dq; assign dq = dq_en ? dq_out : 1'bz; //三态门 // 1MHz分频(生成1us时钟) always @(posedge clk or negedge rst_n)begin if(!rst_n) cnt <= 5'd0; else if(cnt == 5'd24) cnt <= 5'd0; else cnt <= cnt + 5'd1; end always @(posedge clk or negedge rst_n)begin if(!rst_n) clk_us <= 1'b0; else if(cnt == 5'd24) clk_us <= ~clk_us; end // 现态 always @(posedge clk_us or negedge rst_n) begin if(!rst_n) current_state <= RESET1; else current_state <= next_state; end // 次态 always @(*) begin next_state = RESET1; case(current_state) RESET1: next_state = (cnt_us == T_INIT && reset_ack_valid) ? WRITE_CC : RESET1; WRITE_CC: next_state = (cnt_us == T_BIT && wr_bit_cnt == 7) ? WRITE_44 : WRITE_CC; WRITE_44: next_state = (cnt_us == T_BIT && wr_bit_cnt == 7) ? WAIT_CONV : WRITE_44; WAIT_CONV: next_state = (cnt_us == T_WAIT) ? RESET2 : WAIT_CONV; RESET2: next_state = (cnt_us == T_INIT && reset_ack_valid) ? WRITE_CC2 : RESET2; WRITE_CC2: next_state = (cnt_us == T_BIT && wr_bit_cnt == 7) ? WRITE_BE : WRITE_CC2; WRITE_BE: next_state = (cnt_us == T_BIT && wr_bit_cnt == 7) ? READ_T : WRITE_BE; READ_T: next_state = (cnt_us == T_BIT && rd_bit_cnt == 15) ? RESET1 : READ_T; default: next_state = RESET1; endcase end // 输出逻辑 always @(posedge clk_us or negedge rst_n)begin if(!rst_n)begin cnt_us <= 20'd0; reset_ack_valid <= 1'b0; dq_en <= 1'b0; dq_out <= 1'b0; data_temp <= 16'd0; data <= 16'd0; wr_bit_cnt <= 4'd0; rd_bit_cnt <= 5'd0; end else begin case(current_state) //第一次复位:拉低500us ---> 释放总线 ---> 等待应答脉冲 RESET1: begin cnt_us <= cnt_us + 1; if(cnt_us <= 499) begin dq_en <= 1; dq_out <= 0; end else if(cnt_us == T_INIT) begin cnt_us <= 0; reset_ack_valid <= 0; end else begin dq_en <= 0; if(cnt_us == T_ACK && !dq_in) reset_ack_valid <= 1; end end //写指令0xCC WRITE_CC: begin cnt_us <= cnt_us + 20'd1; if(cnt_us <= 1) begin dq_en <= 1'b1; dq_out <= 1'b0; end else if(cnt_us == T_BIT) begin cnt_us <= 0; dq_en <= 1'b0; if(wr_bit_cnt == 7) // 等于7时清零 wr_bit_cnt <= 0; else wr_bit_cnt <= wr_bit_cnt + 1; end else begin dq_en <= (WR_CMD_CC[wr_bit_cnt] == 1'b0) ? 1'b1 : 1'b0; dq_out <= 1'b0; end end //写指令0x44,温度转换 WRITE_44: begin cnt_us <= cnt_us + 20'd1; if(cnt_us <= 1) begin dq_en <= 1'b1; dq_out <= 1'b0; end else if(cnt_us == T_BIT) begin cnt_us <= 0; dq_en <= 1'b0; if(wr_bit_cnt == 7) // 等于7时清零 wr_bit_cnt <= 0; else wr_bit_cnt <= wr_bit_cnt + 1; end else begin dq_en <= (WR_CMD_44[wr_bit_cnt] == 1'b0) ? 1'b1 : 1'b0; dq_out <= 1'b0; end end //等待温度转换 WAIT_CONV: begin if(cnt_us == T_WAIT) cnt_us <= 20'd0; else cnt_us <= cnt_us + 20'd1; end //第二次复位 RESET2: begin cnt_us <= cnt_us + 1; if(cnt_us <= 499) begin dq_en <= 1; dq_out <= 0; end else if(cnt_us == T_INIT) begin cnt_us <= 0; reset_ack_valid <= 0; end else begin dq_en <= 0; if(cnt_us == T_ACK && !dq_in) reset_ack_valid <= 1; end end //再次写0xCC WRITE_CC2: begin cnt_us <= cnt_us + 20'd1; if(cnt_us <= 1) begin dq_en <= 1'b1; dq_out <= 1'b0; end else if(cnt_us == T_BIT) begin cnt_us <= 0; dq_en <= 1'b0; if(wr_bit_cnt == 7) // 等于7时清零 wr_bit_cnt <= 0; else wr_bit_cnt <= wr_bit_cnt + 1; end else begin dq_en <= (WR_CMD_CC[wr_bit_cnt] == 1'b0) ? 1'b1 : 1'b0; dq_out <= 1'b0; end end //写指令0xBE,读暂存器 WRITE_BE: begin cnt_us <= cnt_us + 20'd1; if(cnt_us <= 1) begin dq_en <= 1'b1; dq_out <= 1'b0; end else if(cnt_us == T_BIT) begin cnt_us <= 0; dq_en <= 1'b0; if(wr_bit_cnt == 7) // 等于7时清零 wr_bit_cnt <= 0; else wr_bit_cnt <= wr_bit_cnt + 1; end else begin dq_en <= (WR_CMD_BE[wr_bit_cnt] == 1'b0) ? 1'b1 : 1'b0; dq_out <= 1'b0; end end //读16位温度 READ_T: begin cnt_us <= cnt_us + 20'd1; if(cnt_us <= 1) begin dq_en <= 1'b1; dq_out <= 1'b0; end else if(cnt_us == T_BIT) begin cnt_us <= 20'd0; dq_en <= 1'b0; if(rd_bit_cnt == 15) begin data <= data_temp; rd_bit_cnt <= 0; end else begin rd_bit_cnt <= rd_bit_cnt + 5'd1; data <= data; end end else begin dq_en <= 1'b0; if(cnt_us == T_READ) data_temp <= {dq_in, data_temp[15:1]}; //移位读取 end end default: ; endcase end end endmodule ``` ### 3.2 top_ds18b20_uart.v ```cpp `timescale 1ns / 1ps // 功能:读取16位温度 → 解析正负、整数、小数 → 1秒发送一次温度字符串 module top_ds18b20_uart #( parameter CLK_FREQ = 50_000_000 // 50MHz时钟 ) ( input clk, // 系统时钟 input rst_n, // 复位 inout dq, // DS18B20单总线 output uart_tx // UART发送 ); wire [15:0] data; // DS18B20原始16位温度 //================ 温度解析 ==================== wire sign = data[15]; // 符号位(1=负,0=正) wire [11:0] abs_temp = sign ? (~data[11:0] + 1'b1) : data[11:0]; // 绝对值 wire [7:0] int_part = abs_temp[11:4];// 整数部分 wire [3:0] frac = abs_temp[3:0];// 小数部分(4bit) // 小数位转十进制 0.0~0.93 reg [3:0] dec_part1, dec_part2; always @(*) begin case(frac) 4'h0: begin dec_part1 = 4'd0; dec_part2 = 4'd0; end 4'h1: begin dec_part1 = 4'd0; dec_part2 = 4'd6; end 4'h2: begin dec_part1 = 4'd1; dec_part2 = 4'd2; end 4'h3: begin dec_part1 = 4'd1; dec_part2 = 4'd8; end 4'h4: begin dec_part1 = 4'd2; dec_part2 = 4'd5; end 4'h5: begin dec_part1 = 4'd3; dec_part2 = 4'd1; end 4'h6: begin dec_part1 = 4'd3; dec_part2 = 4'd7; end 4'h7: begin dec_part1 = 4'd4; dec_part2 = 4'd3; end 4'h8: begin dec_part1 = 4'd5; dec_part2 = 4'd0; end 4'h9: begin dec_part1 = 4'd5; dec_part2 = 4'd6; end 4'hA: begin dec_part1 = 4'd6; dec_part2 = 4'd2; end 4'hB: begin dec_part1 = 4'd6; dec_part2 = 4'd8; end 4'hC: begin dec_part1 = 4'd7; dec_part2 = 4'd5; end 4'hD: begin dec_part1 = 4'd8; dec_part2 = 4'd1; end 4'hE: begin dec_part1 = 4'd8; dec_part2 = 4'd7; end 4'hF: begin dec_part1 = 4'd9; dec_part2 = 4'd3; end default: begin dec_part1 = 4'd0; dec_part2 = 4'd0; end endcase end // 整数部分分解:百位、十位、个位 reg [3:0] int_hundreds, int_tens, int_ones; always @(*) begin int_hundreds = int_part / 100; int_tens = (int_part % 100) / 10; int_ones = int_part % 10; end //================ 1秒定时发送 ==================== reg [25:0] cnt_1s; reg send_en; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin cnt_1s <= 0; send_en <= 0; end else if(cnt_1s == 50000000 - 1) begin // 1秒到 cnt_1s <= 0; send_en <= 1; // 启动发送 end else begin cnt_1s <= cnt_1s + 1; send_en <= 0; end end //================ 串口发送控制(发送11个字符) ==================== reg [7:0] tx_data; reg tx_flag; wire tx_done; reg [3:0] send_cnt; reg tx_busy; always @(posedge clk or negedge rst_n) begin if(!rst_n) begin send_cnt <= 0; tx_data <= 0; tx_flag <= 0; tx_busy <= 0; end else if(send_en && !tx_busy) begin // 1秒到,开始发送 tx_busy <= 1; send_cnt <= 0; end else if(tx_busy) begin // 正在发送 if(tx_flag) begin // 等待发送完成 if(tx_done) begin tx_flag <= 0; send_cnt <= send_cnt + 1; // 下一个字符 end end else begin // 发送下一个字符 case(send_cnt) 0: begin tx_data <= sign ? 8'h2D : 8'h2B; tx_flag <= 1; end // +/- 1: begin tx_data <= int_hundreds + 8'h30; tx_flag <= 1; end // 百位 2: begin tx_data <= int_tens + 8'h30; tx_flag <= 1; end // 十位 3: begin tx_data <= int_ones + 8'h30; tx_flag <= 1; end // 个位 4: begin tx_data <= 8'h2E; tx_flag <= 1; end // 小数点 . 5: begin tx_data <= dec_part1 + 8'h30; tx_flag <= 1; end // 小数第一位 6: begin tx_data <= dec_part2 + 8'h30; tx_flag <= 1; end // 小数第二位 7: begin tx_data <= 8'h20; tx_flag <= 1; end // 空格 8: begin tx_data <= 8'h43; tx_flag <= 1; end // C 9: begin tx_data <= 8'h0D; tx_flag <= 1; end // 回车 10: begin tx_data <= 8'h0A; tx_flag <= 1; end // 换行 11: begin tx_busy <= 0; tx_flag <= 0; send_cnt <= 0; end // 结束 endcase end end end //================ DS18B20驱动 ==================== ds18b20_moore u_ds18b20 ( .clk (clk), .rst_n (rst_n), .dq (dq), .data (data) ); //================ UART发送模块 ==================== uart_send #( .UART_BPS(115200), .CLK_FREQ(50_000_000) ) u_uart_send ( .clk (clk), .sys_rst_n (rst_n), .pi_data (tx_data), .pi_flag (tx_flag), .tx (uart_tx), .tx_done (tx_done) ); endmodule ``` ### 3.3 uart_send.v ```cpp module uart_send #( parameter UART_BPS = 115200, // 波特率 parameter CLK_FREQ = 50000000 // 系统时钟频率 ) ( input clk, // 系统时钟 input sys_rst_n, // 复位 input [7:0] pi_data, // 待发送数据 input pi_flag, // 发送使能 output reg tx, // UART发送引脚 output reg tx_done // 发送完成标志 ); // 波特率计数器最大值 localparam BAUD_CNT_MAX = CLK_FREQ / UART_BPS; reg [15:0] baud_cnt; // 波特率计数器 reg [3:0] bit_cnt; // 数据位计数器 reg [7:0] data_buf; // 数据缓存 reg tx_en; // 发送使能 // 发送使能与数据锁存 always @(posedge clk or negedge sys_rst_n) begin if (!sys_rst_n) begin data_buf <= 8'd0; tx_en <= 1'b0; end else if (pi_flag) begin // 收到发送请求 data_buf <= pi_data; // 锁存数据 tx_en <= 1'b1; // 启动发送 end else if (tx_done) begin // 发送完成 tx_en <= 1'b0; // 关闭发送 end end // 波特率计数器 always @(posedge clk or negedge sys_rst_n) begin if (!sys_rst_n) baud_cnt <= 16'd0; else if (tx_en) begin // 发送中 if (baud_cnt == BAUD_CNT_MAX - 1) baud_cnt <= 16'd0; else baud_cnt <= baud_cnt + 1'b1; end else baud_cnt <= 16'd0; end // 数据位计数器 always @(posedge clk or negedge sys_rst_n) begin if (!sys_rst_n) bit_cnt <= 4'd0; else if (tx_en) begin // 发送中 if (baud_cnt == BAUD_CNT_MAX - 1) begin if (bit_cnt == 4'd9) bit_cnt <= 4'd0; else bit_cnt <= bit_cnt + 1'b1; end end else bit_cnt <= 4'd0; end // UART发送时序控制 always @(posedge clk or negedge sys_rst_n) begin if (!sys_rst_n) tx <= 1'b1; // 空闲高电平 else if (tx_en) begin case (bit_cnt) 4'd0: tx <= 1'b0; // 起始位 4'd1: tx <= data_buf[0]; // 位0 4'd2: tx <= data_buf[1]; // 位1 4'd3: tx <= data_buf[2]; // 位2 4'd4: tx <= data_buf[3]; // 位3 4'd5: tx <= data_buf[4]; // 位4 4'd6: tx <= data_buf[5]; // 位5 4'd7: tx <= data_buf[6]; // 位6 4'd8: tx <= data_buf[7]; // 位7 4'd9: tx <= 1'b1; // 停止位 default: tx <= 1'b1; endcase end else tx <= 1'b1; end // 发送完成标志 always @(posedge clk or negedge sys_rst_n) begin if (!sys_rst_n) tx_done <= 1'b0; else if (tx_en && (bit_cnt == 4'd9) && (baud_cnt == BAUD_CNT_MAX - 2)) tx_done <= 1'b1; // 发送完成 else tx_done <= 1'b0; end endmodule ``` ### 3.4 结果显示 将手指盖到DS18B20传感器上的效果 ![](https://i-blog.csdnimg.cn/direct/f32c7e96314e4e40bd4cbcbc5a2badf2.png) ## 4. 参考文章 [基于FPGA的DS18B20数字温度传感器测温实例](https://blog.csdn.net/wuzhikaidetb/article/details/119793669 "基于FPGA的DS18B20数字温度传感器测温实例") [深入解析:外设模块学习(5)------DS18B20温度传感器(STM32)](https://www.cnblogs.com/ljbguanli/p/19215154#5.3%20%E4%BE%9B%E7%94%B5%E6%A8%A1%E5%BC%8F "深入解析:外设模块学习(5)——DS18B20温度传感器(STM32)") [再学通信协议---(One-Wire协议,以读取DS18B20数据为例)](https://blog.csdn.net/wallwayj/article/details/143194912?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1-143194912-blog-103454455.235^v43^pc_blog_bottom_relevance_base5&spm=1001.2101.3001.4242.2&utm_relevant_index=3 "再学通信协议---(One-Wire协议,以读取DS18B20数据为例)")

相关推荐
s09071363 小时前
ZYNQ 软硬件协同踩坑日记:PS写BRAM后,PL端连续4个地址读出相同数据的原因与解决办法
fpga开发·zynq·硬件设计
tiger11920 小时前
FPGA独立实现LLM推理方案——FlighLLM
fpga开发·llm·fpga·ai推理
fei_sun21 小时前
Systemverilog和Verilog区别
fpga开发
史蒂芬_丁1 天前
TI F28P65 使用 ePWM 模块模拟 SPI 时钟的详细方法
单片机·嵌入式硬件·fpga开发
fei_sun1 天前
HDLBits-Verilog Practice
fpga开发
Aaron15881 天前
RFSOC+VU13P中在线部分可重构技术的应用分析
人工智能·算法·matlab·fpga开发·重构·信息与通信·信号处理
qxl_7999151 天前
PCB元件对位:相机采集+YOLO定位完整工程方案(含坐标转换公式)
数码相机·yolo·fpga开发
daxi1502 天前
Verilog入门实战——第5讲:Testbench 仿真编写 + 波形查看与分析
fpga开发
FPGA的花路2 天前
UDP协议
fpga开发·以太网·udp协议