AHT10温湿度传感器设计

  • Post author:
  • Post category:其他




概念

传感器输出经过标定的数字信号输出,通过标准的I2C接口传输数据;

相对湿度的分辨率在

0.024%RH

,工作范围为

0~100%RH



温度值的分辨率在

0.01℃

,工作范围为

-40~85℃



AHT10的供电范围为

1.8~3.6V

,推荐使用

3.3V

供电;

接口包含了完全静态逻辑,因而不存在最小串行时钟SCL频率;



IIC协议,同步半双工通信协议


起始位

:主机在时钟高电平期间拉低总线;


数据位

:主机在时钟低电平期间发送数据,主机在高电平期间保持不变;从机在时钟高电平期间采样数据;


应答为

:主机收到数据后发送应答,从机才会发送下一字节数据;


停止位

:主机在时钟高电平期间释放总线,主机在低电平期间保持不变;

  • 可使用的I2C通信模式有经典型和高速模式,经典模式最小时钟周期是250us,高速模式最小时钟周期是100us;



AHT10配置与通信

I2C通信通过设备地址与从机进行通信,

在这里插入图片描述

  1. 首先上电启动传感器,启动后需要先等待40ms(设备才开始正常工作),然后发送

    8‘h71

    来获取状态字节,状态寄存器说明如下:

    在这里插入图片描述

    获取到校准使能位后,查看其是否已校准,若已校准则跳过当前步骤;若未校准则发送

    8‘hE1

    ,进行初始化,然后发送

    8’h08



    8‘h00

  2. 接着开始触发测量,测量先发送

    8’hAC

    ,然后发送

    8‘h33



    8'h00

  3. 测量命令发送完成后,需要等待80ms,用于温湿度的测量;之后再发送命令

    8‘h71

    ,以读取状态寄存器是否处于空闲状态(

    bit7 => idle

    );若是空闲状态,可以直接读取之后六个字节的温湿度数值;
  4. 读取温湿度数据构成

    在这里插入图片描述
  5. 相对湿度和温度转换公式

    将接收到的湿度值转换成%RH的格式:





    R

    H

    [

    %

    ]

    =

    (

    S

    R

    H

    /

    2

    20

    )

    RH [\%] = (SRH/2^{20})






    R


    H


    [


    %


    ]




    =








    (


    SR


    H


    /



    2











    20










    )







    温度转换成℃表示:





    T

    (

    )

    =

    (

    S

    T

    /

    2

    20

    )

    T(℃) = (ST/2^{20}).






    T


    (





    )




    =








    (


    ST


    /



    2











    20










    )










设计框架

  • 整个模块的设计,首先是上位机通过UART,发送命令打开AHT10驱动控制模块、数码管显示模块以及串口模块;设备驱动用控制模块实现,通信接口使用I2C与AHT10进行通信,然后将读取的温湿度值先进行数值转换并取整,并转换成ASCII码方便查看温湿度值;然后将数据缓存到FIFO中,当缓存了一组完整的温度值数据后进行输出,发送给上位机,或是直接通过数码管显示;

    在这里插入图片描述



I2C模块

在这里插入图片描述

I2C接口模块状态机如上图所示,从空闲状态可以先发送起始位再读写一个字节数据,或直接读写一个字节数据,然后是收发应答位;最后发送停止位,就完成了一组数据的读写;

对于I2C通信,数据的传输速率选择的是

50M/250 Bps

;

include "param.v"
module i2c_master(
    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

40ms

初始化

读取状态

状态位bit3==0

状态位bit3==1

等待80ms

读取温度值

START

IDLE

INIT

CHECK_INIT

TRIIGER

WAIT

DATA

`include "param.v"
module aht_ctrl (
    input               	sys_clk    		,
    input               	rst_n   		,
	input					driver_en		,		
    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, 
                INIT =       7'b000_0010, 
                CHECK_INIT = 7'b000_0100, 
                IDLE =       7'b000_1000, 
                TRIGGER =    7'b001_0000, 
                WAIT =       7'b010_0000, 
                READ =       7'b100_0000; 
    parameter	DELAY_40MS = 200_0000  ,
                DELAY_80MS = 400_0000  ,
                DELAY_500MS = 2500_0000;

    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				;
    wire				add_cnt			;
    wire				end_cnt			;
    reg		[24:0]		delay			;
    reg					finish_init		;
    wire				start2init		;
    wire				init2check		;
    wire				check2idle		;
    wire				check2init		;
    wire				idle2trigger	;
    wire				trigger2wait	;
    wire				wait2read		;
    wire				read2idle		;
	reg					driver_en_r1	;
	reg					driver_en_r2	;
	wire				ne_driver		;
// ne_driver
	always@(posedge sys_clk or negedge rst_n)begin
		if(!rst_n) begin
			driver_en_r1  <= 0 ; 
			driver_en_r2  <= 0 ; 
		end
		else begin
			driver_en_r1  <= driver_en ;
			driver_en_r2  <= driver_en_r1 ;
		end
	end
	assign ne_driver = (~driver_en_r1 && driver_en_r2) ;
//state
    always @(posedge sys_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(ne_driver) begin
					state_n = state_c ; 
				end	
				else if(start2init) begin
                    state_n = INIT ;
				end
                else begin 
                    state_n = state_c ;
				end
            end
            INIT :begin
				if(ne_driver) begin
					state_n = START ;
				end
                else if(init2check)begin
                    state_n = IDLE ;
				end
                else 
                    state_n = state_c ;
            end
            CHECK_INIT :begin
				if(ne_driver) begin
					state_n = START ;
				end
                else if(check2idle)begin
                    state_n = IDLE ;
				end
                else if(check2init) begin
                    state_n = INIT;
                end
                else 
                    state_n = state_c ;
            end
            IDLE :begin
				if(ne_driver)begin
					state_n = START ;
				end
                else if(idle2trigger)begin
                    state_n = TRIGGER ;
				end
                else 
                    state_n = state_c ;
            end
            TRIGGER :begin
				if(ne_driver)begin
					state_n = START ;
				end
                else if(trigger2wait)begin
                    state_n = WAIT ;
				end
                else 
                    state_n = state_c ;
            end
            WAIT :begin
                if(ne_driver)begin
					state_n = START ;
				end
                else if(wait2read)
                    state_n = READ ;
                else 
                    state_n = state_c ;
            end
            READ :begin
                if(ne_driver)begin
					state_n = START ;
				end
                else if(read2idle)
                    state_n = IDLE ;
                else 
                    state_n = state_c ;
            end

            default : state_n = START ;
        endcase
    end
	
    assign start2init = state_c == START && (end_cnt);
    assign init2check = state_c == INIT && (end_cnt_byte);
    assign check2idle = state_c == CHECK_INIT && (finish_init) && end_cnt_byte;
    assign check2init = state_c == CHECK_INIT && (~finish_init)&& end_cnt_byte;
    assign idle2trigger = state_c == IDLE && (end_cnt);
    assign trigger2wait = state_c == TRIGGER && (end_cnt_byte);
    assign wait2read = state_c == WAIT && (end_cnt);
    assign read2idle = state_c == READ && (end_cnt_byte);
    
// delay
    always @(posedge sys_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

// cnt
    always @( posedge sys_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) && driver_en_r2;
    assign end_cnt = cnt == delay - 1 && add_cnt || ne_driver ;

// cnt_byte
    always @(posedge sys_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) || ne_driver ;

//
    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
// finish_init
    always @(posedge sys_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
// read_data
    always @(posedge sys_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 TX;   
        input                   req     ;
        input       [3:0]       command ;
        input       [7:0]       data    ;
        begin 
            tx_req  = req;
            tx_cmd  = command;
            tx_data = data;
        end 
    endtask   
//out

    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 = read2idle;

endmodule 



其它模块

然后将温湿度数值按照上述格式转换:

temp_data_r <= (((temp_data*2000)>>12) - (500)); 
hum_data_r <= ((hum_data *1000) >> 12);

再将数据转换成ASCII码:

always@(posedge sys_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; 
        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

最后将数值通过FIFO缓存完整的22字节数据后输出即可;

同时还可以将数据转换成bcd码显示到数码管上;



版权声明:本文为qq_50670020原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。