基于FPGA的AHT10传感器温湿度读取

2023-11-20

一、系统框架

分为i2c接口、i2c控制、数据处理、串口四个部分
在这里插入图片描述
RTL视图
在这里插入图片描述

二、i2c接口

该传感器通过i2c协议进行通信。需要该接口实现i2c的数据收发。接口模块都是固定代码,不做讲解。
代码如下:

`include "param.v"

module i2c_intf(
    input               clk         ,
    input               rst_n       ,

    input               req         ,
    input       [3:0]   cmd         ,
    input       [7:0]   din         ,

    output      [7:0]   dout        ,
    output              done        ,
    output              slave_ack   ,
    output              i2c_scl     ,
    input               i2c_sda_i   ,
    output              i2c_sda_o   ,
    output              i2c_sda_oe     
    );

//状态机参数定义

    localparam  IDLE  = 7'b000_0001,
                START = 7'b000_0010,
                WRITE = 7'b000_0100,
                RACK  = 7'b000_1000,
                READ  = 7'b001_0000,
                SACK  = 7'b010_0000,
                STOP  = 7'b100_0000;

//信号定义

    reg     [6:0]       state_c     ;
    reg     [6:0]       state_n     ;

    reg     [8:0]       cnt_scl     ;//产生i2c时钟
    wire                add_cnt_scl ;
    wire                end_cnt_scl ;
    reg     [3:0]       cnt_bit     ;//传输数据 bit计数器
    wire                add_cnt_bit ;
    wire                end_cnt_bit ;
    reg     [3:0]       bit_num     ;
    
    reg                 scl         ;//输出寄存器
    reg                 sda_out     ;
    reg                 sda_out_en  ;

    reg     [7:0]       rx_data     ;
    reg                 rx_ack      ;
    reg     [3:0]       command     ;
    reg     [7:0]       tx_data     ;//发送数据

    wire                idle2start  ; 
    wire                idle2write  ; 
    wire                idle2read   ; 
    wire                start2write ; 
    wire                start2read  ; 
    wire                write2rack  ; 
    wire                read2sack   ; 
    wire                rack2stop   ; 
    wire                sack2stop   ; 
    wire                rack2idle   ; 
    wire                sack2idle   ; 
    wire                stop2idle   ; 


//状态机
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            state_c <= IDLE ;
        end
        else begin
            state_c <= state_n;
       end
    end
    
    always @(*) begin 
        case(state_c)  
            IDLE :begin
                if(idle2start)
                    state_n = START ;
                else if(idle2write)
                    state_n = WRITE ;
                else if(idle2read)
                    state_n = READ ;
                else 
                    state_n = state_c ;
            end
            START :begin
                if(start2write)
                    state_n = WRITE ;
                else if(start2read)
                    state_n = READ ;
                else 
                    state_n = state_c ;
            end
            WRITE :begin
                if(write2rack)
                    state_n = RACK ;
                else 
                    state_n = state_c ;
            end
            RACK :begin
                if(rack2stop)
                    state_n = STOP ;
                else if(rack2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            READ :begin
                if(read2sack)
                    state_n = SACK ;
                else 
                    state_n = state_c ;
            end
            SACK :begin
                if(sack2stop)
                    state_n = STOP ;
                else if(sack2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            STOP :begin
                if(stop2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            default : state_n = IDLE ;
        endcase
    end
    
    assign idle2start  = state_c==IDLE  && (req && (cmd&`CMD_START));
    assign idle2write  = state_c==IDLE  && (req && (cmd&`CMD_WRITE));
    assign idle2read   = state_c==IDLE  && (req && (cmd&`CMD_READ ));
    assign start2write = state_c==START && (end_cnt_bit && (command&`CMD_WRITE));
    assign start2read  = state_c==START && (end_cnt_bit && (command&`CMD_READ ));
    assign write2rack  = state_c==WRITE && (end_cnt_bit);
    assign read2sack   = state_c==READ  && (end_cnt_bit);
    assign rack2stop   = state_c==RACK  && (end_cnt_bit && (command&`CMD_STOP ));
    assign sack2stop   = state_c==SACK  && (end_cnt_bit && (command&`CMD_STOP ));
    assign rack2idle   = state_c==RACK  && (end_cnt_bit && (command&`CMD_STOP ) == 0);
    assign sack2idle   = state_c==SACK  && (end_cnt_bit && (command&`CMD_STOP ) == 0);
    assign stop2idle   = state_c==STOP  && (end_cnt_bit );
    
//计数器
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_scl <= 0; 
        end
        else if(add_cnt_scl) begin  
            if(end_cnt_scl)
                cnt_scl <= 0; 
            else
                cnt_scl <= cnt_scl+1 ;
       end
    end
    assign add_cnt_scl = (state_c != IDLE);
    assign end_cnt_scl = add_cnt_scl  && cnt_scl == (`SCL_PERIOD)-1 ;

    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            cnt_bit <= 0; 
        end
        else if(add_cnt_bit) begin
            if(end_cnt_bit)
                cnt_bit <= 0; 
            else
                cnt_bit <= cnt_bit+1 ;
       end
    end
    assign add_cnt_bit = (end_cnt_scl);
    assign end_cnt_bit = add_cnt_bit  && cnt_bit == (bit_num)-1 ;

    always  @(*)begin
        if(state_c == WRITE | state_c == READ) begin
            bit_num = 8;
        end
        else begin 
            bit_num = 1;
        end 
    end
//command
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            command <= 0;
        end
        else if(req)begin
            command <= cmd;
        end
    end

//tx_data
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            tx_data <= 0;
        end
        else if(req)begin
            tx_data <= din;
        end
    end

//scl
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            scl <= 1'b1;
        end
        else if(idle2start | idle2write | idle2read)begin//开始发送时,拉低
            scl <= 1'b0;
        end
        else if(add_cnt_scl && cnt_scl == `SCL_HALF-1)begin 
            scl <= 1'b1;
        end 
        else if(end_cnt_scl && ~stop2idle)begin 
            scl <= 1'b0;
        end 
    end

//sda_out
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sda_out <= 1'b1;
        end
        else if(state_c == START)begin          //发起始位
            if(cnt_scl == `LOW_HLAF)begin       //时钟低电平时拉高sda总线
                sda_out <= 1'b1;
            end
            else if(cnt_scl == `HIGH_HALF)begin    //时钟高电平时拉低sda总线 
                sda_out <= 1'b0;                //保证从机能检测到起始位
            end 
        end 
        else if(state_c == WRITE && cnt_scl == `LOW_HLAF)begin  //scl低电平时发送数据   并串转换
            sda_out <= tx_data[7-cnt_bit];      
        end 
        else if(state_c == SACK && cnt_scl == `LOW_HLAF)begin  //发应答位
            sda_out <= (command&`CMD_STOP)?1'b1:1'b0;
        end 
        else if(state_c == STOP)begin //发停止位
            if(cnt_scl == `LOW_HLAF)begin       //时钟低电平时拉低sda总线
                sda_out <= 1'b0;
            end
            else if(cnt_scl == `HIGH_HALF)begin    //时钟高电平时拉高sda总线 
                sda_out <= 1'b1;                //保证从机能检测到停止位
            end 
        end 
    end

//sda_out_en  总线输出数据使能
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            sda_out_en <= 1'b0;
        end
        else if(idle2start | idle2write | read2sack | rack2stop)begin
            sda_out_en <= 1'b1;
        end
        else if(idle2read | start2read | write2rack | stop2idle)begin 
            sda_out_en <= 1'b0;
        end 
    end

//rx_data       接收读入的数据
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            rx_data <= 0;
        end
        else if(state_c == READ && cnt_scl == `HIGH_HALF)begin
            rx_data[7-cnt_bit] <= i2c_sda_i;    //串并转换
        end
    end

//rx_ack
    always  @(posedge clk or negedge rst_n)begin
        if(~rst_n)begin
            rx_ack <= 1'b1;
        end
        else if(state_c == RACK && cnt_scl == `HIGH_HALF)begin
            rx_ack <= i2c_sda_i;
        end
    end


//输出信号

    assign i2c_scl    = scl         ;
    assign i2c_sda_o  = sda_out     ;
    assign i2c_sda_oe = sda_out_en  ;
   
    assign dout = rx_data;
    assign done = rack2idle | sack2idle | stop2idle;
    assign slave_ack = rx_ack;

endmodule


三、i2c控制模块

该模块负责采集AHT10传感器的数据,并把采集到的数据输出到数据处理模块。

状态机设计

状态转移图

在这里插入图片描述

START

上电、复位后默认状态。根据手册信息可知,等待40ms后跳转到init初始化状态

INIT

根据手册信息可知,等待40ms后需要进行初始化,在该状态控制i2c接口发送对应命令即可.发送完毕进入CHECK_INIT状态.在这里插入图片描述

CHECK_INIT

发送玩初始化命令后,通过发送命令0x71检测是否初始化成功,如果失败则回到INIT状态,成功则进入IDLE状态

IDLE

空闲状态,当传感器初始化完成后会进入空闲状态,在空闲状态持续0.5s后将会进入TRIGGER发送测量命令状态

TRIGGER

通过i2c接口发送如下命令即可,发送完毕进入WAIT状态
在这里插入图片描述

WAIT

等待80ms后进入READ状态
在这里插入图片描述

READ

通过i2c接口接受传感器发送的六个字节数据,根据状态为判断数据是否有效,有效则进行输出,无效则忽略。读取完毕后回到IDLE状态。
在这里插入图片描述

代码

`include "param.v"

module i2c_master (
    input               clk     ,
    input               rst_n   ,
    
    input               din_vld ,
    output              req     ,
    output      [3:0]   cmd     ,
    output      [7:0]   data    ,
    input               done    , //传输完成标志
    input       [7:0]   rd_data ,
    output		[ 19:0 ]	hum_data			,//湿度
    output      [ 19:0 ]	temp_data			,//温度	
    output				    dout_vld			
    );


//状态机参数
    localparam      START =      7'b000_0001    , //等待40ms
                    INIT =       7'b000_0010    , //初始化
                    CHECK_INIT = 7'b000_0100    , //检测是否初始化完成
                    IDLE =       7'b000_1000    , //空闲
                    TRIGGER =    7'b001_0000    , //触发测量
                    WAIT =       7'b010_0000    , //等待80ms测量完成
                    READ =       7'b100_0000    ; //读取温湿度

    parameter	DELAY_40MS = 200_0000;//40ms
    parameter	DELAY_80MS = 400_0000;//80ms
    parameter	DELAY_500MS = 2500_0000;//0.5s

//信号定义
    reg     [7:0]   state_c         ;
    reg     [7:0]   state_n         ;

    reg     [2:0]   cnt_byte        ;//数据传输 字节计数器
    wire            add_cnt_byte    ;
    wire            end_cnt_byte    ;

    reg             tx_req          ;//请求
    reg     [3:0]   tx_cmd          ;
    reg     [7:0]   tx_data         ;
    reg			[ 47:0 ]			read_data			;

    
    reg			[ 27:0 ]			cnt			;//40ms 80ms计数器
    wire							add_cnt			;
    wire							end_cnt			;

    reg							    finish_init			;
    wire							start_2_init		;//等待40ms后进入初始化
    wire							init_2_check		;//检查是否初始化成功
    wire							check_2_idle			;//初始化完成进入空闲
    wire							check_2_init			;//初始化失败重新初始化
    wire							idle_2_trigger			;//发送读取温湿度指令
    wire							trigger_2_wait			;//等待转换完成
    wire							wait_2_read			;//读取温湿度
    wire							read_2_idle			;//读取完毕进入空闲

    assign start_2_init = state_c == START && (end_cnt);
    assign init_2_check = state_c == INIT && (end_cnt_byte);
    assign check_2_idle = state_c == CHECK_INIT && (finish_init);
    assign check_2_init = state_c == CHECK_INIT && (~finish_init);
    assign idle_2_trigger = state_c == IDLE && (end_cnt);
    assign trigger_2_wait = state_c == TRIGGER && (end_cnt_byte);
    assign wait_2_read = state_c == WAIT && (end_cnt);
    assign read_2_idle = state_c == READ && (end_cnt_byte);

//状态机设计
    always @(posedge clk or negedge rst_n) begin 
        if (rst_n==0) begin
            state_c <= START ;
        end
        else begin
            state_c <= state_n;
       end
    end
//状态跳转   
    always @(*) begin 
        case(state_c)  
            START :begin
                if(start_2_init)
                    state_n = INIT ;
                else 
                    state_n = state_c ;
            end
            INIT :begin
                if(init_2_check)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end
            CHECK_INIT :begin
                if(check_2_idle)
                    state_n = IDLE ;
                else if(check_2_init) begin
                    state_n = INIT;
                end
                else 
                    state_n = state_c ;
            end
            IDLE :begin
                if(idle_2_trigger)
                    state_n = TRIGGER ;
                else 
                    state_n = state_c ;
            end
            TRIGGER :begin
                if(trigger_2_wait)
                    state_n = WAIT ;
                else 
                    state_n = state_c ;
            end
            WAIT :begin
                if(wait_2_read)
                    state_n = READ ;
                else 
                    state_n = state_c ;
            end
            READ :begin
                if(read_2_idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end

            default : state_n = START ;
        endcase
    end

    reg			[ 24:0 ]			delay			;
    
//延时数据寄存器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            delay <= DELAY_40MS;
        end
        else if(state_c == START) begin
            delay <= DELAY_40MS;
        end
        else if(state_c == WAIT) begin
            delay <= DELAY_80MS;
        end
        else if(state_c == IDLE) begin
            delay <= DELAY_500MS;
        end
    end

//延时计数器
    always @( posedge clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            cnt <= 0;
        end
        else if ( add_cnt ) begin
            if ( end_cnt ) begin
                cnt <= 0;
            end
            else begin
                cnt <= cnt + 1;
            end
        end
        else begin
            cnt <= 0;
        end
    end
    assign add_cnt = state_c == START || state_c == WAIT || state_c == IDLE;
    assign end_cnt = cnt == delay - 1 && add_cnt;

//字节计数器
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt_byte <= 0;
        end
        else if(add_cnt_byte) begin
            if(end_cnt_byte) begin
                cnt_byte <= 0;
            end
            else
                cnt_byte <= cnt_byte + 1;
        end
    end
    assign add_cnt_byte = (state_c == INIT || state_c == CHECK_INIT ||state_c == TRIGGER || state_c == READ) && done;
    assign end_cnt_byte = cnt_byte == (state_c == READ?6:3) && add_cnt_byte;

/*必须使用组合,接口模块done信号延后一个时钟,req等数据需提前
根据当前状态控制i2c接口发送数据*/
    always  @(*)begin
        case (state_c)
            INIT : 
                case(cnt_byte)
                    0           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,1'b0});//发起始位、地址
                    1           :TX(1'b1, `CMD_WRITE ,`CMD_INIT);  //发数据,结束位
                    2           :TX(1'b1,`CMD_WRITE ,8'b000_1000);  //发数据,结束位
                    3           :TX(1'b1,{`CMD_WRITE | `CMD_STOP} ,8'b0000_0000);  //发数据,结束位
                    default     :TX(1'b0,tx_cmd,tx_data);
                endcase 
            CHECK_INIT:
                case(cnt_byte)
                    0           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,1'b0});//发起始位、写控制字
                    1           :TX(1'b1,`CMD_WRITE ,`CMD_CHECK);  //发数据
                    2           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,1'b1});//发起始位、读控制字
                    3           :TX(1'b1,{`CMD_READ | `CMD_STOP},0);  //最后一个字节时 读数据、发停止位
                    default     :TX(1'b0,tx_cmd,tx_data);
                endcase
            TRIGGER: 
                case(cnt_byte)
                    0           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,1'b0});//发起始位、写控制字
                    1           :TX(1'b1,`CMD_WRITE ,`CMD_TRIGGER);  //发数据
                    2           :TX(1'b1,`CMD_WRITE ,`DATA_0);  //发数据
                    3           :TX(1'b1,{`CMD_WRITE | `CMD_STOP},`DATA_1);  //最后一个字节时、发停止位
                    default     :TX(1'b0,tx_cmd,tx_data);
                endcase    
            READ :            
                case(cnt_byte)
                    0           :TX(1'b1,{`CMD_START | `CMD_WRITE},{`I2C_ADR,1'b1});//发起始位、写控制字
                    1           :TX(1'b1,`CMD_READ ,0);  //读数据
                    2           :TX(1'b1,`CMD_READ ,0);  //读数据
                    3           :TX(1'b1,`CMD_READ ,0);  //读数据
                    4           :TX(1'b1,`CMD_READ ,0);  //读数据
                    5           :TX(1'b1,`CMD_READ ,0);  //读数据
                    6           :TX(1'b1,{`CMD_READ | `CMD_STOP},0);
                    default     :TX(1'b0,tx_cmd,tx_data);
                endcase
            default: TX(1'b0,0,0);
        endcase
    end
//初始化完成标志
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            finish_init <= 0;
        end
        else if(state_c == CHECK_INIT && done && rd_data[3]) begin
            finish_init <= 1;
        end
    end
//i2c 接口返回数据
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            read_data <= 0;
        end
        else if(state_c == READ && cnt_byte >0 && done) begin
            read_data <= {read_data[39:0],rd_data};
        end
    end
//用task发送请求、命令、数据(地址+数据)
    task TX;   
        input                   req     ;
        input       [3:0]       command ;
        input       [7:0]       data    ;
        begin 
            tx_req  = req;
            tx_cmd  = command;
            tx_data = data;
        end 
    endtask   
//输出

    assign req     = tx_req ; 
    assign cmd     = tx_cmd ; 
    assign data    = tx_data; 
    assign hum_data = read_data[39:20];
    assign temp_data = read_data[19:0];
    assign dout_vld = read_2_idle;



endmodule //camera_config_ctrl

串口模块代码

module uart_tx(input			wire						clk,
               input			wire						rst_n,
               input			wire						tx_enable, // 发送使能
               input			wire		[ 07:0 ]		data_in, // 需要发送的数据
               input			wire		[ 19:0 ]		tx_bps, // 发送的波特率
               output			wire						data, // 发送的数据
               output			wire						tx_done);
    
    localparam MAX_BIT = 10;
    
    reg			[ 09:0 ]			data_r			; // 数据寄存器
    reg			[ 12:0 ]			cnt_bps			; // 波特率计数器
    reg			[ 03:0 ]			cnt_bit			; // 数据位计数器
    
    wire		[ 12:0 ]			max_bps			; // 波特率对应频率

    wire							flag_clear_cnt_bps			; // 计数器归零
    wire							flag_add_cnt_bit			; // 计数器+1
    wire							flag_clear_cnt_bit			; 
    reg								flag_send_data			    ; //发送数据标志
    
    //输入数据寄存
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            data_r <= 10'b0;
        end
        else if(tx_enable) begin
            data_r <={1'b1, data_in, 1'b0};
        end

    end
    
    // 波特率计数器
    always @( posedge clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            cnt_bps <= 0;
        end
        else if ( flag_send_data ) begin
            if ( flag_clear_cnt_bps ) begin
                cnt_bps <= 0;
            end
            else begin
                cnt_bps <= cnt_bps + 1;
            end
        end
        else begin
            cnt_bps <= 0;
        end
        
    end

    assign flag_clear_cnt_bps  = cnt_bps >= max_bps -1;
    assign max_bps             = 50_000_000 / tx_bps;
    
    // 数据位计数器
    always @( posedge clk or negedge rst_n ) begin
        if ( !rst_n ) begin
            cnt_bit <= 0;
        end
        else if ( flag_send_data ) begin
            if ( flag_clear_cnt_bit ) begin
                cnt_bit <= 0;
            end
            else if ( flag_add_cnt_bit )begin
                cnt_bit <= cnt_bit + 1;
            end
            else begin
                cnt_bit <= cnt_bit;
            end
        end
        else begin
            cnt_bit <= 0;
        end
    end

    assign flag_add_cnt_bit   = flag_clear_cnt_bps;
    assign flag_clear_cnt_bit = cnt_bit >= MAX_BIT - 1 && flag_add_cnt_bit ;


    //发送数据标志
    always @(posedge clk or negedge rst_n) begin
        if(!rst_n) begin
            flag_send_data <= 0;
        end
        else if(tx_enable) begin
            flag_send_data <= 1;
        end
        else if(flag_clear_cnt_bit) begin
            flag_send_data <= 0;
        end
        else begin
            flag_send_data <= flag_send_data;
        end
    end
    //发送数据
    assign data = flag_send_data ? data_r[cnt_bit]:1;
    assign tx_done = ~flag_send_data  ;
    //发送状态
    // always @(*) begin
    //     if(!rst_n) begin
    //         tx_done = 1;
    //     end
    //     else if(flag_clear_cnt_bit) begin
    //         tx_done = 1;
    //     end
    //     else if(flag_send_data) begin
    //         tx_done = 0;
    //     end
    //     else begin
    //         tx_done = tx_done;
    //     end
    // end

 
endmodule

四、数据处理模块

根据手册信息可以得到数据处理的公式
在这里插入图片描述
但是按照公式进行转换数据并不正确,考虑到数据涉及到小数运算,先把测量到的数据进行左移8位转为整数,再套入公式可以得到较为合理的数据。最终的公式为:
在这里插入图片描述
考虑需要一位小数,则先把原数据进行扩大10倍后再进行上述运算。最终处理后得到的数据我们只取后三位代表十位、个位和小数位。

串口

串口数据发送必须以字节为单位,则需要把处理完得到数据一位一位的取出来再转为ascii码进行发送。按如下格式进行数据发送。为了保证发送正确,这里添加一个fifo进行缓存需要发送的数据,让串口模块每次都去取fifo里面的数据进行发送。
在这里插入图片描述

代码

module data_drive (
    input			wire						clk,
    input			wire						rst_n,
    input			wire						din_vld,
    input			wire		[ 19:0 ]		temp_data,
    input			wire		[ 19:0 ]		hum_data,
    output			wire						tx_data
);


reg								send_flag			;
reg			[ 19:0 ]			temp_data_r			;
reg			[ 19:0 ]			hum_data_r			;
reg			[ 7:0 ]			    dout_r			;
wire		[ 15:0 ]			tmep			;
wire		[ 15:0 ]			hum			;
reg			[ 5:0 ]			    cnt			;
wire							add_cnt			;
wire							end_cnt			;
wire							rdreq			;
wire							wrreq			;
wire							empty			;
wire							full			;
wire							tx_done			;
wire		[ 7:0 ]				q		;
assign rdreq = tx_done && ~empty;
assign wrreq = ~full && send_flag && cnt > 0;


//串口模块
uart_tx u_uart_tx(
    .clk       ( clk       ),
    .rst_n     ( rst_n     ),
    .tx_enable ( rdreq      ),
    .data_in   ( q   ),
    .tx_bps    ( 115200    ),
    .data      ( tx_data      ),
    .tx_done   ( tx_done   )
);

//用于缓存通过串口发送的数据
fifo	tx_fifo_inst (
	.aclr ( ~rst_n ),
	.clock ( clk ),
	.data ( dout_r ),
	.rdreq ( rdreq ),
	.wrreq ( wrreq ),
	.empty ( empty ),
	.full ( full ),
	.q ( q ),
	.usedw ( usedw_sig )
	);

//温湿度数据寄存
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        temp_data_r <= 0;
        hum_data_r <= 0;
    end
    else if(din_vld) begin
        temp_data_r <= (((temp_data*2000)>>12) - (500)); //扩大10倍
        hum_data_r <= ((hum_data *1000) >> 12);
    end
end

//数据处理标志
always @(posedge clk or negedge rst_n) begin
    if(!rst_n) begin
        send_flag <= 0;
    end
    else if(din_vld) begin
        send_flag <= 1;
    end
    else if(end_cnt) begin
        send_flag <= 0;
    end
end

//计数器
always @( posedge clk or negedge rst_n ) begin
    if ( !rst_n ) begin
        cnt <= 0;
    end
    else if ( add_cnt ) begin
        if ( end_cnt ) begin
            cnt <= 0;
        end
        else begin
            cnt <= cnt + 1;
        end
    end
end
assign add_cnt = send_flag;
assign end_cnt = add_cnt && cnt == 22;
// CE C2 B6 C8 CA AA B6 C8
//根据计数器发送不同数据
always @(posedge clk or negedge rst_n) begin
    case (cnt)
        1 : dout_r <= 8'hce; // 温度
        2 : dout_r <= 8'hc2;
        3 : dout_r <= 8'hb6;
        4 : dout_r <= 8'hc8;
        5 : dout_r <= 8'h3a; // :
        6 : dout_r <= (temp_data_r / 100 % 10 )+48;//十位
        7 : dout_r <= (temp_data_r / 10 % 10  )+48;//个位
        8 : dout_r <= 8'h2e;//.
        9 : dout_r <= (temp_data_r % 10  )+48;
        10 : dout_r <= 8'ha1; //℃
        11 : dout_r <= 8'he6;
        12: dout_r <= 9;     //tab
        13: dout_r <= 8'hca; //湿度
        14: dout_r <= 8'haa;
        15: dout_r <= 8'hb6;
        16: dout_r <= 8'hc8;
        17: dout_r <= 8'h3a;
        18: dout_r <= (hum_data_r / 100 % 10 )+48;
        19: dout_r <= (hum_data_r / 10 % 10 )+48;
        20: dout_r <= 8'h2e;
        21: dout_r <= (hum_data_r % 10  )+48;
        22: dout_r <= 8'h25;//%
        default: dout_r <= 0;
    endcase
end
endmodule //data_drive

五、仿真

testbench设计

通过模拟传感器回数据进行测试代码的逻辑正确与否
代码如下:

`timescale 1ns/1ps

module tb ();

reg clk;
reg rst_n;

wire								sda			;
reg								sda_i			;
aht10_top u_aht10_top(
    .clk        ( clk        ),
    .rst_n      ( rst_n      ),
    .sda        ( sda        ),
    .scl        ( scl        ),
    .uart_txd   ( uart_txd   )
);
assign sda = u_aht10_top.i2c_sda_oe?1'bz:sda_i;
defparam u_aht10_top.u_i2c_master.DELAY_40MS = 400;
defparam u_aht10_top.u_i2c_master.DELAY_80MS = 800;
defparam u_aht10_top.u_i2c_master.DELAY_500MS = 5000;
localparam CLK_PERIOD = 20;
always #(CLK_PERIOD/2) clk=~clk;

reg			[ 10:0 ]			i			;
reg			[ 10:0 ]			j			;
initial begin
    rst_n<=1'b0;
    clk<=1'b0;
    # CLK_PERIOD;
    rst_n<=1;


    @(posedge u_aht10_top.u_i2c_master.init_2_check); //初始化
    for (i = 0; i<3 ; i = i+1 ) begin
        @(posedge u_aht10_top.u_i2c_master.add_cnt_byte);
    end

    for (i = 0; i<9 ; i = i+1 ) begin //模拟初始化完成
        @(posedge u_aht10_top.u_i2c_intf.add_cnt_bit)
        if(i == 3)
            sda_i = 1;
        else if(i == 8)
            sda_i = 0;
    end

    repeat(5) begin
        @(posedge u_aht10_top.u_i2c_master.wait_2_read);//等待控制模块到达读取状态
        @(posedge u_aht10_top.u_i2c_master.add_cnt_byte);
        for (i = 0; i<6 ; i = i+1 ) begin//发送6个数据
            @(posedge u_aht10_top.u_i2c_master.add_cnt_byte);
            for (j = 0; j<9 ; j = j+1 ) begin//模拟从机回数据
                @(posedge u_aht10_top.u_i2c_intf.end_cnt_scl)
                if(j == 8)
                    sda_i = 0;
                else
                    sda_i = {$random};
            end
        end
    end



    $stop;
end

endmodule

仿真波形

由于中文和部分特殊符号需要两位ascii码,故出现乱码,但是可以看到温湿度可以正常显示,通过随机数进行模拟,仅供参考。
在这里插入图片描述

六、效果

在这里插入图片描述

七、源码

https://github.com/TangtangSix/AHT10

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

基于FPGA的AHT10传感器温湿度读取 的相关文章

  • APP环信集成 -JAVA后端

    环信的集成有两种方式 一种是先创建IM账号 然后在创建客服账号 在客服账号中新建渠道中 点击关联IM账号 这样创造出的关联以IM为主 收费要收取客服和IM两项费用 官方论坛里有给出这种方式的JAVA demo这里不过的赘述 这种场景适用于类

随机推荐

  • object.definepProperty使用方法,vue2双向绑定原理

    首先要介绍的是definepProperty的三个参数 object definepProperty 对象名 属性名 属性值 再者要介绍的就是属性值了 object definepProperty person age value 18 此
  • 【微服务架构设计】微服务不是魔术:处理超时

    微服务很重要 它们可以为我们的架构和团队带来一些相当大的胜利 但微服务也有很多成本 随着微服务 无服务器和其他分布式系统架构在行业中变得更加普遍 我们将它们的问题和解决它们的策略内化是至关重要的 在本文中 我们将研究网络边界可能引入的许多棘
  • std::chrono::steady_clock 实现精准休眠

    include
  • 【PAT】B1032 挖掘机技术哪家强 (20 分)_C语言实现

    1 挖掘机技术哪家强 20 分 为了用事实说明挖掘机技术到底哪家强 P A T PAT PAT 组织了一场挖掘机技能大赛 现请你根据比赛结果统计出技术最强的那个学校 输入格式 输入在第 1
  • 诡异至极的SQL Server推送数据到MQ日期早48小时的生产问题排查

    背景 应用迁移 即旧版应用下线 新版应用上线 停掉旧版应用里面的quartz任务 开启新版的xxl job调度任务 数据推送源头是SQL Server 目的地是MQ 问题爆出 今天iview的自动导出任务从老系统迁移到新系统 下午2点40
  • 【设计模式】工厂模式(Factory Pattern)

    1 概述 工厂模式 Factory Pattern 是最常用的设计模式之一 它属于创建类型的设计模式 它提供了一种创建对象的最佳方式 在工厂模式中 我们在创建对象时不会对客户端暴露创建逻辑 并且是通过一个共同的接口来指向新创建的对象 工厂模
  • docker入门

    Docker基础 docker保姆级教程 https github com yeasy docker practice blob master SUMMARY md Docker系统有两个程序 docker服务端和docker客户端 其中d
  • 安装并配置HBase集群(5个节点)

    安装并配置HBase 集群规划 HBase2 2 5安装 将安装包拷贝到5台机器上并解压缩 配置环境变量 配置HBase 时间同步 修改 usr local src hbase 2 2 5 conf hbase env sh 文件 修改 h
  • SitePoint播客#61:HTML5 =厨房水槽

    Episode 61 of The SitePoint Podcast is now available This week your hosts are Patrick O Keefe iFroggy Stephan Segraves s
  • AWS动手实验 - 创建一个Web3网站

    实验操作和录播 亚马逊云科技开发者社区 web3 dApp demo README CN md at main Chen188 web3 dApp demo GitHub 注意事项 按照操作手册进行即可 需要注意到的几个地方 1 EC2 的
  • C#使用Socket建立连接、通信,主动发送Close关闭, 随后进行下一次的连接,此时会出错,通信端口被占用

    C 使用Socket建立连接 通信之后 主动发送Close关闭 随后进行下一次的连接 此时会出错 通信端口被占用 当你关闭一个Socket连接后 操作系统会在一段时间内保持该端口处于TIME WAIT状态 在这个状态下 该端口是不可用的 直
  • Qt数据类型与强制转换(转)

    类型转换 把QString转换为 double类型 方法1 QString str 123 45 double val str toDouble val 123 45 方法2 很适合科学计数法形式转换 bool ok double d d
  • java源文件命名规则

    Java程序源文件的命名不是随意的 Java文件的命名必须满足如下规则 Java程序源文件的扩展名必须是 java 不能是其他文件扩展名 在通常情况下 Java程序源文件的主文件名可以是任意的 但有一种情况例外 如果Java程序源代码里定义
  • SpringMVC加载流程

    这节介绍SpringMVC SpringMVC是一种基于Java的实现MVC设计模式的请求驱动类型的轻量级Web框架 本章会介绍相关概念 流程 再从源码进行讲解 1 MVC MVC Model View Controller 是一种软件设计
  • Zookeeper(三)—分布式锁实现

    一 独占锁原理 独占锁是利用zk同一目录下不能创建多个相同名称的节点这个特性 来实现分布式锁的功能 竞争锁的分布式系统 都在zk根目录下创建一个名为lock的节点 创建节点成功的系统 说明抢到了这把锁 没有创建成功的系统 说明这个节点已经被
  • 星星之火-22: 什么是手机小区重选?跳槽

    小区重选 cell reselection 指手机在空闲模式下 通过监测邻区和当前小区的信号质量以选择一个最好的小区提供服务信号的过程 选择了一家新公司 并不意味着永久待在一家公司 当前服务的公司 有可能由于经营状况变变糟 薪资水平下降 也
  • 【树莓派4B】darknet-nnpack的安装及使用

    文章目录 前言 步骤 1 下载依赖项 2 安装NNPACK darknet 3 下载darknet nnpack 4 使用YOLO进行预测 检测图像 检测视频 检测视频流 错误处理 make 时报错 undefined reference
  • (二)webpack-server

    宗旨 为了更好的开发和调试 1 package json npm init y 生成package json 2 安装server npm install webpack dev server D 3 修改配置 在package json文
  • canvas绘制一个圆分成六等分颜色随机

  • 基于FPGA的AHT10传感器温湿度读取

    文章目录 一 系统框架 二 i2c接口 三 i2c控制模块 状态机设计 状态转移图 START INIT CHECK INIT IDLE TRIGGER WAIT READ 代码 四 数据处理模块 串口 代码 五 仿真 testbench设