[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传感器上的效果

4. 参考文章

基于FPGA的DS18B20数字温度传感器测温实例

深入解析:外设模块学习(5)------DS18B20温度传感器(STM32)

再学通信协议---(One-Wire协议,以读取DS18B20数据为例)

相关推荐
Saniffer_SH19 小时前
【高清视频】Gen6 服务器还没到,Gen6 SSD 怎么测?Emily 现场演示三种测试环境
人工智能·驱动开发·测试工具·缓存·fpga开发·计算机外设·压力测试
zlinear数据采集卡1 天前
双核架构深度解析:ARM+FPGA如何让数据采集卡实现500Ksps高性能?
arm开发·fpga开发·架构
9527华安1 天前
FPGA实现GTH Transceivers Wizard传输2路视频,基于aurora 8b10b编解码架构,提供4套工程源码和技术支持
fpga开发·gth·aurora 8b10b·transceivers
FPGA小徐2 天前
FPGA 数字信号处理(二):并行 FIR 滤波器的 Verilog 全流程设计与实现
fpga开发
国科安芯2 天前
基于AS32S601ZIT2型抗辐照MCU的商业航天卫星姿态确定与控制系统研究
单片机·嵌入式硬件·安全·fpga开发·架构·risc-v
ALINX技术博客2 天前
【黑金云课堂】FPGA技术教程FPGA基础:I2C 总线通信技术
fpga开发·i2c
Hello-FPGA2 天前
Xilinx KU040 FPGA Camera Link 图像采集
c++·fpga开发
明德扬2 天前
AD采集卡应用示例交流:从传感器采集到高速信号验证
fpga开发
傻童:CPU2 天前
PS与PL之间的交互
fpga开发
神奇元创2 天前
商用级光路加速卡:大模型推理的极速落地方案
python·神经网络·fpga开发·dsp开发