16
2022
02

基础实验十三,DS18B20温度传感器

//==========================================================================
// Author     : ChanRa1n
// Description: Training for Intel FPGA
// Web        : www.myfpga.cn
// CoryRight  :本项目是在stark-lin的开源文章下二次开发,遵循CC 4.0 BY-SA协议开源
//==========================================================================

module FUNCTION_SEG(
  input         sys_clk  ,//时钟,50MHz
  input         sys_rst,//复位信号,下降沿有效
  input    [15:0]   data_out,//DS18B20_data

  output reg [5:0]  io_seg_sel  ,//位选信号,六个数码管
  output reg [7:0]  io_seg_led  //段选信号,八段
  );

wire [15:0] sig_data;
reg         time_flag;
reg [5:0] temp_data;
reg [9:0] time_divider;
reg [4:0] value;//暂存真值
reg [2:0] current_state ;//当前状态
reg [2:0] next_state ;//下一个状态

always@(posedge sys_clk or negedge sys_rst)
  begin
    if(!sys_rst)
      begin//下降沿有效
        time_divider <= 0;//当前状态置0
      end
    else
      begin
        if(time_divider>=1000)
            begin
                time_divider<=0;
                time_flag<=1;
            end
        else
            begin
                time_flag<=0;
                time_divider <= time_divider+1;//状态跳转
            end
      end
  end

//状态跳转
always@(posedge sys_clk or negedge sys_rst)
  begin
    if(!sys_rst)
      begin//下降沿有效
        current_state <= 3'd0;//当前状态置0
      end
    else
      begin
        current_state <= next_state;//状态跳转
      end
  end

//下一个状态判断(组合逻辑)
always@(*)
  begin
    case(current_state)
      3'd0:
        if(time_flag)
            next_state = 3'd1;
        else
            next_state = 3'd0;
      3'd1:
        if(time_flag)
            next_state = 3'd2;
        else
            next_state = 3'd1;
      3'd2:
        if(time_flag)
            next_state = 3'd3;
        else
            next_state = 3'd2;
      3'd3:
        if(time_flag)
            next_state = 3'd4;
        else
            next_state = 3'd3;
      3'd4:
        if(time_flag)
            next_state = 3'd5;
        else
            next_state = 3'd4;
      3'd5:
        if(time_flag)
            next_state = 3'd0;
        else
            next_state = 3'd5;
      default:
        next_state= 3'd1;
    endcase
  end

//各个状态下的动作(可以组合也可以时序)
always@(posedge sys_clk or negedge sys_rst)
  begin
    if(!sys_rst)
      begin
        value = 3'd0;
        temp_data<=0;
      end
    else
      begin
        case(current_state)
          3'd0:
            begin
              io_seg_sel = 6'b111_110;//选择最右边数码管。 io_seg_sel低有效
              value<=20;//C
            end
          3'd1:
            begin
              io_seg_sel = 6'b111_101;
              temp_data <= data_out[3:0] * 11'd625;
              value<=temp_data/10;
            end
          3'd2:
            begin
              io_seg_sel = 6'b111_011;
              value<=temp_data%10;
            end
          3'd3:
            begin
              io_seg_sel = 6'b110_111;
              value = data_out[10:4]%10+10;//加小数点
            end
          3'd4:
            begin
              io_seg_sel = 6'b101_111;
              value = data_out[10:4]/10;
            end
          3'd5:
            begin
              io_seg_sel = 6'b011_111;
              value = 22;//NULL
            end
          default:;
        endcase
      end
  end

//数码管io_seg_led的输出
always@(*)
  begin
    if(!sys_rst)
      begin
        io_seg_led = 8'b11111111;
      end
    else
      begin
        case(value)
          5'd0:
            io_seg_led = 8'b11000000;
          5'd1:
            io_seg_led = 8'b11111001;
          5'd2:
            io_seg_led = 8'b10100100;
          5'd3:
            io_seg_led = 8'b10110000;
          5'd4:
            io_seg_led = 8'b10011001;
          5'd5:
            io_seg_led = 8'b10010010;
          5'd6:
            io_seg_led = 8'b10000010;
          5'd7:
            io_seg_led = 8'b11111000;
          5'd8:
            io_seg_led = 8'b10000000;
          5'd9:
            io_seg_led = 8'b10010000;

          //以下是带小数点的数
          5'd10:
            io_seg_led = 8'b01000000;
          5'd11:
            io_seg_led = 8'b01111001;
          5'd12:
            io_seg_led = 8'b00100100;
          5'd13:
            io_seg_led = 8'b00110000;
          5'd14:
            io_seg_led = 8'b00011001;
          5'd15:
            io_seg_led = 8'b00010010;
          5'd16:
            io_seg_led = 8'b00000010;
          5'd17:
            io_seg_led = 8'b01111000;
          5'd18:
            io_seg_led = 8'b00000000;
          5'd19:
            io_seg_led = 8'b00010000;
          //下面还可以写其他的,例如字母等
          5'd20:
            io_seg_led = 8'b10100111;//C
          default :
            io_seg_led = 8'b11111111;
        endcase
      end
  end
endmodule


//模块来自电子森林

module DS18B20Z

(
    input               clk_in,         //系统时钟
    input               rst_n_in,       //系统复位,低有效
    inout               one_wire,       //DS18B20Z传感器单总线,双向管脚
    output  reg [15:0]  data_out        //DS18B20Z有效温度数据输出
);
 
    /*
    本设计通过驱动DS18B20Z芯片获取温度数据,
    需要了解inout类型的接口如何实现双向通信,
    中间涉及各种不同的延时和寄存器指令操作,注释部分以作简要说明,更多详情需参考数据手册
    */
 
    localparam  IDLE    =   3'd0;
    localparam  MAIN    =   3'd1;
    localparam  INIT    =   3'd2;
    localparam  WRITE   =   3'd3;
    localparam  READ    =   3'd4;
    localparam  DELAY   =   3'd5;
 
    //计数器分频产生1MHz的时钟信号
    reg                 clk_1mhz;
    reg     [5:0]       cnt_1mhz;
    always@(posedge clk_in or negedge rst_n_in) begin
        if(!rst_n_in) begin
            cnt_1mhz <= 6'd0;
            clk_1mhz <= 1'b0;
        end else if(cnt_1mhz >= 6'd25) begin
            cnt_1mhz <= 6'd0;
            clk_1mhz <= ~clk_1mhz;  //产生1MHz分频
        end else begin
            cnt_1mhz <= cnt_1mhz + 1'b1;
        end
    end
 
    reg     [2:0]       cnt;
    reg                 one_wire_buffer;
    reg     [3:0]       cnt_main;
    reg     [7:0]       data_wr;
    reg     [7:0]       data_wr_buffer;
    reg     [2:0]       cnt_init;
    reg     [19:0]      cnt_delay;
    reg     [19:0]      num_delay;
    reg     [3:0]       cnt_write;
    reg     [2:0]       cnt_read;
    reg     [15:0]      temperature;
    reg     [7:0]       temperature_buffer;
    reg     [2:0]       state = IDLE;
    reg     [2:0]       state_back = IDLE;
    //使用1MHz时钟信号做触发完成下面状态机的功能
    always@(posedge clk_1mhz or negedge rst_n_in) begin
        if(!rst_n_in) begin
            state <= IDLE;
            state_back <= IDLE;
            cnt <= 1'b0;
            cnt_main <= 1'b0;
            cnt_init <= 1'b0;
            cnt_write <= 1'b0;
            cnt_read <= 1'b0;
            cnt_delay <= 1'b0;
            one_wire_buffer <= 1'bz;
            temperature <= 16'h0;
        end else begin
            case(state)
                IDLE:begin      //IDLE状态,程序设计的软复位功能,各状态异常都会跳转到此状态
                        state <= MAIN;  //软复位完成,跳转之MAIN状态重新工作
                        state_back <= MAIN;
                        cnt <= 1'b0;
                        cnt_main <= 1'b0;
                        cnt_init <= 1'b0;
                        cnt_write <= 1'b0;
                        cnt_read <= 1'b0;
                        cnt_delay <= 1'b0;
                        one_wire_buffer <= 1'bz;
                    end
                MAIN:begin      //MAIN状态控制状态机在不同状态间跳转,实现完整的温度数据采集
                        if(cnt_main >= 4'd11) cnt_main <= 1'b0;
                        else cnt_main <= cnt_main + 1'b1;
                        case(cnt_main)
                            4'd0: begin state <= INIT; end  //跳转至INIT状态进行芯片的复位及验证
                            4'd1: begin data_wr <= 8'hcc;state <= WRITE; end    //主设备发出跳转ROM指令
                            4'd2: begin data_wr <= 8'h44;state <= WRITE; end    //主设备发出温度转换指令
                            4'd3: begin num_delay <= 20'd750000;state <= DELAY;state_back <= MAIN; end  //延时750ms等待转换完成
 
                            4'd4: begin state <= INIT; end  //跳转至INIT状态进行芯片的复位及验证
                            4'd5: begin data_wr <= 8'hcc;state <= WRITE; end    //主设备发出跳转ROM指令
                            4'd6: begin data_wr <= 8'hbe;state <= WRITE; end    //主设备发出读取温度指令
 
                            4'd7: begin state <= READ; end  //跳转至READ状态进行单总线数据读取
                            4'd8: begin temperature[7:0] <= temperature_buffer; end //先读取的为低8位数据
 
                            4'd9: begin state <= READ; end  //跳转至READ状态进行单总线数据读取
                            4'd10: begin temperature[15:8] <= temperature_buffer; end   //后读取的为高8为数据
 
                            4'd11: begin state <= IDLE;data_out <= temperature; end //将完整的温度数据输出并重复以上所有操作
                            default: state <= IDLE;
                        endcase
                    end
                INIT:begin      //INIT状态完成DS18B20Z芯片的复位及验证功能
                        if(cnt_init >= 3'd6) cnt_init <= 1'b0;
                        else cnt_init <= cnt_init + 1'b1;
                        case(cnt_init)
                            3'd0: begin one_wire_buffer <= 1'b0; end    //单总线复位脉冲拉低
                            3'd1: begin num_delay <= 20'd500;state <= DELAY;state_back <= INIT; end //复位脉冲保持拉低500us时间
                            3'd2: begin one_wire_buffer <= 1'bz; end    //单总线复位脉冲释放,自动上拉
                            3'd3: begin num_delay <= 20'd100;state <= DELAY;state_back <= INIT; end //复位脉冲保持释放100us时间
                            3'd4: begin if(one_wire) state <= IDLE; else state <= INIT; end //根据单总线的存在检测结果判定是否继续
                            3'd5: begin num_delay <= 20'd400;state <= DELAY;state_back <= INIT; end //如果检测正常继续保持释放400us时间
                            3'd6: begin state <= MAIN; end  //INIT状态操作完成,返回MAIN状态
                            default: state <= IDLE;
                        endcase
                    end
                WRITE:begin     //按照DS18B20Z芯片单总线时序进行写操作
                        if(cnt <= 3'd6) begin   //共需要发送8bit的数据,这里控制循环的次数
                            if(cnt_write >= 4'd6) begin cnt_write <= 1'b1; cnt <= cnt + 1'b1; end
                            else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
                        end else begin
                            if(cnt_write >= 4'd8) begin cnt_write <= 1'b0; cnt <= 1'b0; end //两个变量都恢复初值
                            else begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
                        end
                        //对于WRITE状态中cnt_write来讲,执行过程为:0;[1~6]*8;7;8;
                        case(cnt_write)
                            //lock data_wr
                            4'd0: begin data_wr_buffer <= data_wr; end  //将需要写出的数据缓存
                            //发送 1bit 数据的用时在60~120us之间,参考数据手册
                            4'd1: begin one_wire_buffer <= 1'b0; end    //总线拉低
                            4'd2: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end  //延时2us时间,保证15us以内
                            4'd3: begin one_wire_buffer <= data_wr_buffer[cnt]; end //先发送数据最低位
                            4'd4: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end //延时80us时间
                            4'd5: begin one_wire_buffer <= 1'bz; end    //总线释放
                            4'd6: begin num_delay <= 20'd2;state <= DELAY;state_back <= WRITE; end  //延时2us时间
                            //back to main
                            4'd7: begin num_delay <= 20'd80;state <= DELAY;state_back <= WRITE; end //延时80us时间
                            4'd8: begin state <= MAIN; end  //返回MAIN状态
                            default: state <= IDLE;
                        endcase
                    end
                READ:begin      //按照DS18B20Z芯片单总线时序进行读操作
                        if(cnt <= 3'd6) begin   //共需要接收8bit的数据,这里控制循环的次数
                            if(cnt_read >= 3'd5) begin cnt_read <= 1'b0; cnt <= cnt + 1'b1; end
                            else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
                        end else begin
                            if(cnt_read >= 3'd6) begin cnt_read <= 1'b0; cnt <= 1'b0; end   //两个变量都恢复初值
                            else begin cnt_read <= cnt_read + 1'b1; cnt <= cnt; end
                        end
                        case(cnt_read)
                            //读取 1bit 数据的用时在60~120us之间,总线拉低后15us时间内读取数据,参考数据手册
                            3'd0: begin one_wire_buffer <= 1'b0; end    //总线拉低
                            3'd1: begin num_delay <= 20'd2;state <= DELAY;state_back <= READ; end   //延时2us时间
                            3'd2: begin one_wire_buffer <= 1'bz; end    //总线释放
                            3'd3: begin num_delay <= 20'd5;state <= DELAY;state_back <= READ; end   //延时5us时间
                            3'd4: begin temperature_buffer[cnt] <= one_wire; end    //读取DS18B20Z返回的总线数据,先收最低位
                            3'd5: begin num_delay <= 20'd60;state <= DELAY;state_back <= READ; end  //延时60us时间
                            //back to main
                            3'd6: begin state <= MAIN; end  //返回MAIN状态
                            default: state <= IDLE;
                        endcase
                    end
                DELAY:begin     //延时控制
                        if(cnt_delay >= num_delay) begin    //延时控制,延时时间由num_delay指定
                            cnt_delay <= 1'b0;
                            state <= state_back;    //很多状态都需要延时,延时后返回哪个状态由state_back指定
                        end else cnt_delay <= cnt_delay + 1'b1;
                    end
            endcase
        end
    end
 
    assign  one_wire = one_wire_buffer;
 
endmodule


« 上一篇 下一篇 »

返回顶部
请先 登录 再评论,若不是会员请先 注册