【FPGA】基于FPGA实现AHT10温湿度传感器数据采集

写在前面:
相关参考文章:【FPGA】FPGA实现IIC协议读写EEPROM
在本项目中所使用的开发板型号:Cyclone IV E (EP4CE6F17C8),温湿度传感器型号:AHT10。

一、需求分析

  1. 使用C4开发板实现控制AHT10温湿度传感器进行数据采集。
  2. 温度值以十进制形式的摄氏温度打印到终端,保留一位小数,显示形式例如xx.x℃。
  3. 湿度值以百分数形式打印到终端,保留一位小数,显示形式例如:xx.x%。
  4. 使用按键设置温湿度报警值,当采集的温湿度值超过预设的报警值时,触发蜂鸣器报警。

二、AHT10简介

(一)AHT10特性

AHT10是一款标准I2C接口的温湿度传感器。供电范围为1.8-3.6V,推荐电压为3.3V。电源( VDD)和接地(GND)之间须连接一个 10uF的去耦电容,器件引脚接口图如下:

在这里插入图片描述
SDA引脚用于传感器的数据输入和输出。当向传感器发送命令时,SDA在串行时钟( SCL)的上升沿有效,且当SCL为高电平时,SDA 必须保持稳定。在 SCL下降沿之后,SDA值可被改变。

AHT10读写遵循I2C协议,可参考IIC协议工程,时序图如下:
在这里插入图片描述
在这里插入图片描述
本工程中使用时钟频率为200KHz。

(二)AHT10基本指令及测量步骤

1、AHT10基本指令集:
在这里插入图片描述

2、AHT10测量步骤

(1)上电后等待40ms ,读取温湿度值之前,首先看状态字的校准使能位Bit[3]是否为1(通过发送0x71可以获取一个字节的状态字),如果不为1,要发送0xEi命令(初始化),此命令参数有两个字节,第一个字节为0x08,第二个字节为0x00。状态位说明如下:
在这里插入图片描述
(2)直接发送0xAC命令(触发测量),此命令参数有两个字节,第一个字节为0x33,第二个字节为0x00。
在这里插入图片描述
(3)等待80ms测量数据完成,发送0x71读取6字节温湿度数据。
在这里插入图片描述

(三)数据转换

在这里插入图片描述

按照此公式进行转换数据不正确,所以在数据转换代码中做了修改。

三、系统架构设计

本工程系统框图如下:
在这里插入图片描述

四、模块划分及信号说明

(一)模块划分

本工程将系统功能划分为8个模块,各模块功能描述如下:

  1. top:顶层设计模块。
  2. aht10_ctrl:控制驱动AHT10命令的发送、驱动i2c_interface接口模块与AHT10传感器进行数据交互。
  3. i2c_interface:AHT10使用标准的I2C接口协议,该模块实现aht10_ctrl控制模块通过I2C协议时序读取温湿度数据。
  4. data_process:温湿度数据处理模块,将aht10_ctrl控制模块读回的温湿度数据根据转换公式进行转换,并将转换的数据再次转换成ASCII码通过串口进行发送。
  5. uart_tx:串口数据发送模块,将data_process模块转换后的ASCII格式数据通过串口发送给上位机,通过串口调试助手打印温湿度数据信息。
  6. key_filter:按键消抖模块,将按键输入的进行进行消抖处理。
  7. set_limit:温湿度报警值设置模块,通过按键设置温湿度报警值。
  8. seg_driver:数码管显示模块,显示温湿度设置模式及温湿度设置的报警值。

(二)端口信号说明

  1. top模块
    在这里插入图片描述
  2. aht10_ctrl模块
    在这里插入图片描述
  3. i2c_interface模块
    在这里插入图片描述
  4. data_process模块
    在这里插入图片描述
  5. uart_tx模块
    在这里插入图片描述
    6 key_filter模块
    在这里插入图片描述
  6. set_limit模块
    在这里插入图片描述
  7. seg_driver模块
    在这里插入图片描述

五、状态转移描述

  1. aht10_ctrl控制模块状态转移图
    在这里插入图片描述
    状态说明:
    WAIT:上电等待40ms,等待结束后进入INIT状态。
    INIT:初始化,发送初始化指令,进入WAIT_INIT状态。
    WAIT_INIT:等待初始化,检查校准使能位是否为1,不为1则继续进行初始化,初始化完成后进入IDLE状态。
    IDLE:空闲状态,等待读请求。
    RD_REQ:发送读取温湿度数据指令,发送完成后进入WAIT_MES状态。
    WAIT_MES:等待80ms测量温湿度数据完成,进入READ状态。
    READ:读取温度数据,读取完成后进入IDLE状态,等待下一次读请求。
  2. i2c_interface接口模块状态转移图
    在这里插入图片描述
    状态说明:
    IDLE:空闲状态,等待读写请求。
    START:发送起始位,当控制命令cmd[5:0]中含有起始命令时,进行该状态发送起始位。
    WR_DATA:写数据状态,在写数据状态期间,将wr_din[7:0]按照IIC协议时序写入AHT10。
    RD_DATA:读数据状态,在读数据状态期间,按照IIC协议时序从AHT10中读取数据。
    REC_ACK:接收从机发送的ACK应答信号。
    SEND_ACK:发送ACK或者NACK信号。
    STOP:发送停止位,当控制命令cmd[5:0]中含有停止命令时,进行该状态发送停止位。

六、代码实现

1. aht10读写顶层设计模块

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: AHT10    
//  Module Name: top         
//  Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)             
//  Tool versions: Quartus Prime 18.1             
//  Description: aht10读写顶层设计模块
//  **************************************************************

module top (
    input           clk             ,
    input           rst_n           ,
    //  key
    input   [2:0]   key_in          ,
    //  AHT10
    inout           sda             ,
    output          scl             ,
    //  UART
    output          uart_txd        ,
    //  beep
    output          beep            ,
    //  数码管
    output  [7:0]   seg_dig         ,
    output  [5:0]   seg_sel         
);

    wire            sda_in          ;
    wire            sda_out         ;
    wire            sda_out_en      ;

    assign sda = sda_out_en ? sda_out : 1'bz ;
    assign sda_in = sda ;

    wire            req             ;
    wire    [7:0]   wrdata          ;
    wire    [3:0]   cmd             ;
    wire    [7:0]   rddata          ;
    wire            rddata_vld      ;
    wire            done            ;

    wire    [19:0]  hum_data        ; 
    wire    [19:0]  tem_data        ; 
    wire            data_vld        ; 

    wire            ready           ;  
    wire    [7:0]   tx_data         ;
    wire            tx_data_vld     ;

    wire            tem_add         ;
    wire            hum_add         ;
    wire            set             ;
    wire            modle           ;
    wire    [7:0]   tem_lim         ;
    wire    [7:0]   hum_lim         ;

    key_filter u_key_filter1 (
    /*input           */.clk         (clk           ),
    /*input           */.rst_n       (rst_n         ),
    /*input           */.key_in      (key_in[0]     ),
    /*output    reg   */.key_out     (set           )
    );

    key_filter u_key_filter2 (
    /*input           */.clk         (clk           ),
    /*input           */.rst_n       (rst_n         ),
    /*input           */.key_in      (key_in[1]     ),
    /*output    reg   */.key_out     (tem_add       )
    );
    key_filter u_key_filter3 (
    /*input           */.clk         (clk           ),
    /*input           */.rst_n       (rst_n         ),
    /*input           */.key_in      (key_in[2]     ),
    /*output    reg   */.key_out     (hum_add       )
    );

    //  模块例化
    aht10_ctrl u_aht10_ctrl (
    /*input               */.clk                 (clk       ),
    /*input               */.rst_n               (rst_n     ),
    /*//  i2c_interface*/
    /*output              */.req                 (req       ),       //  读写请求
    /*output  [7:0]       */.wr_dout             (wrdata    ),       //  发送指令数据
    /*output  [3:0]       */.cmd                 (cmd       ),       //  发送读写控制命令
    /*input   [7:0]       */.rdin                (rddata    ),       //  读回的数据
    /*input               */.rdin_vld            (rddata_vld),
    /*input               */.rw_done             (done      ),        //  读写一字节数据完成标志
    //  data_process
    /*output  [19:0]      */.hum_data            (hum_data  ),       //  湿度数据
    /*output  [19:0]      */.tem_data            (tem_data  ),       //  温度数据
    /*output              */.data_vld            (data_vld  )        //  温湿度数据有效标志
    );

    i2c_interface u_i2c_interface (
    /*input               */.clk                 (clk           ),
    /*input               */.rst_n               (rst_n         ),

    /*//  eeprom_ctrl*/
    /*input               */.req                 (req           ),       //  读写请求
    /*input   [7:0]       */.wr_din              (wrdata        ),       //  需要发送的一字节数据
    /*input   [3:0]       */.cmd                 (cmd           ),       //  控制命令组合
    /*output  [7:0]       */.rdout               (rddata        ),       //  读取的数据
    /*output              */.rdout_vld           (rddata_vld    ),       //  读取数据有效标志
    /*output              */.rw_done             (done          ),       //  读写一字节完成标志
    /*//  EEPROM*/
    /*input               */.sda_in              (sda_in        ),
    /*output              */.sda_out             (sda_out       ),
    /*output              */.sda_out_en          (sda_out_en    ),
                            .scl                 (scl           )
    );

    data_process u_data_process (
    /*input           */.clk             (clk           ),
    /*input           */.rst_n           (rst_n         ),
    /*//  aht10_ctrl*/
    /*input           */.din_vld         (data_vld      ),
    /*input   [19:0]  */.tem_data        (tem_data      ),
    /*input   [19:0]  */.hum_data        (hum_data      ),
    /*//  set_limit*/
    /*input   [7:0]   */.tem_lim         (tem_lim       ),
    /*input   [7:0]   */.hum_lim         (hum_lim       ),
    /*//  uart_tx*/
    /*input           */.ready           (ready         ),
    /*output  [7:0]   */.tx_data         (tx_data       ),
    /*output          */.tx_data_vld     (tx_data_vld   ),
    /*//  beep*/
    /*output          */.beep            (beep          )    
    );

    set_limit u_set_limit (
        /*input           */.clk             (clk       ),
        /*input           */.rst_n           (rst_n     ),
        /*//  key_filter*/
        /*input           */.tem_add         (tem_add   ),       //  设置温度报警值标志,每按一次+5摄氏度
        /*input           */.hum_add         (hum_add   ),       //  设置湿度报警值标志,每按一次+5%
        /*input           */.set             (set       ),       //  设置模式,每按一次在温度和湿度设置之间切换
        /*//  seg_driver*/
        /*output          */.modle           (modle     ),       //  当前设置模式  0:设置温度   1:设置湿度
        /*output  [7:0]   */.tem_lim         (tem_lim   ),       //  温度报警值
        /*output  [7:0]   */.hum_lim         (hum_lim   )        //  湿度报警值
    );

    seg_driver u_seg_driver(
    /*input                   */.clk          (clk      ),
    /*input                   */.rst_n        (rst_n    ),
    /*// set_limit*/
    /*input   [7:0]           */.tem_lim      (tem_lim  ),
    /*input   [7:0]           */.hum_lim      (hum_lim  ),
    /*input                   */.modle        (modle    ),

    /*output  reg   [7:0]     */.seg_dig      (seg_dig  ),      //  段选信号
    /*output  reg   [5:0]     */.seg_sel      (seg_sel  )      //  片选信号
    );

    uart_tx u_uart_tx (
        /*input               */.clk             (clk           ),
        /*input               */.rst_n           (rst_n         ),
        /*//  control*/ 
        /*input   [7:0]       */.tx_din          (tx_data       ),
        /*input               */.tx_din_vld      (tx_data_vld   ),
        /*output              */.ready           (ready         ),       //  给control模块的握手信号,表示可以接收数据进行发送 
        /*//  上位机*/
        /*output              */.uart_tx         (uart_txd      )
    );


endmodule

2. I2C接口驱动模块

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: AHT10    
//  Module Name: i2c_interface         
//  Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)          
//  Tool versions: Quartus Prime 18.1             
//  Description: I2C接口驱动模块
//  **************************************************************
`include "param.v"

module i2c_interface (
    input               clk                 ,
    input               rst_n               ,
    //  eeprom_ctrl
    input               req                 ,       //  读写请求
    input   [7:0]       wr_din              ,       //  需要发送的一字节数据
    input   [3:0]       cmd                 ,       //  控制命令组合
    output  [7:0]       rdout               ,       //  读取的数据
    output              rdout_vld           ,       //  读取数据有效标志
    output              rw_done             ,       //  读写一字节完成标志
    //  EEPROM
    input               sda_in              ,
    output              sda_out             ,
    output              sda_out_en          ,
    output              scl
);
    //  参数定义
    localparam  IDLE    =   7'b000_0001 ,
                START   =   7'b000_0010 ,
                WR_DATA =   7'b000_0100 ,
                RD_DATA =   7'b000_1000 ,
                REC_ACK =   7'b001_0000 ,
                SEND_ACK=   7'b010_0000 ,
                STOP    =   7'b100_0000 ;

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

    reg     [7:0]       cnt_scl         ;       //  scl周期计数器
    wire                add_cnt_scl     ;
    wire                end_cnt_scl     ;

    reg     [3:0]       bit_num         ;       //  bit数
    reg     [3:0]       cnt_bit         ;       //  bit计数器
    wire                add_cnt_bit     ;
    wire                end_cnt_bit     ;

    reg     [7:0]       dout_data       ;       //  发送数据寄存
    reg     [3:0]       command         ;       //  控制命令寄存

    reg                 scl_dout         ;       //  SDA寄存     
    reg                 sda_dout        ;
    reg                 sda_dout_en     ;

    reg     [7:0]       rx_data         ;       //  接收读回的数据
    reg                 ack_flag        ;       //  ack响应标志


    //  状态转移条件
    wire                idle2start      ;
    wire                idle2wrdata     ;
    wire                idle2rddata     ;
    wire                start2wrdata    ;
    wire                start2rddata    ;
    wire                wrdata2recack   ;
    wire                rddata2sendack  ;
    wire                recack2idle     ;
    wire                recack2stop     ;
    wire                sendack2idle    ;
    wire                sendack2stop    ;
    wire                stop2idle       ;

    //  状态机
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)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(idle2wrdata)
                    state_n = WR_DATA ;
                else if(idle2rddata)
                    state_n = RD_DATA ;
                else
                    state_n = state_c ;
            end
            START: begin
                if(start2wrdata)
                    state_n = WR_DATA ;
                else if(start2rddata)
                    state_n = RD_DATA ;
                else
                    state_n = state_c ;
            end
            WR_DATA: begin
                if(wrdata2recack)
                    state_n = REC_ACK ;
                else
                    state_n = state_c ;
            end
            RD_DATA: begin
                if(rddata2sendack)
                    state_n = SEND_ACK ;
                else
                    state_n = state_c ;
            end
            REC_ACK: begin
                if(recack2idle)
                    state_n = IDLE ;
                else if(recack2stop)
                    state_n = STOP ;
                else
                    state_n = state_c ;
            end
            SEND_ACK: begin
                if(sendack2idle)
                    state_n = IDLE ;
                else if(sendack2stop)
                    state_n = STOP ;
                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 & `STA)                      ;
    assign  idle2wrdata     = state_c == IDLE       && req && (cmd & `WRITE)                    ;
    assign  idle2rddata     = state_c == IDLE       && req && (cmd & `READ)                     ;
    assign  start2wrdata    = state_c == START      && end_cnt_bit && (command & `WRITE)        ;   
    assign  start2rddata    = state_c == START      && end_cnt_bit && (command & `READ)         ;   
    assign  wrdata2recack   = state_c == WR_DATA    && end_cnt_bit                              ;
    assign  rddata2sendack  = state_c == RD_DATA    && end_cnt_bit                              ;
    assign  recack2idle     = state_c == REC_ACK    && end_cnt_bit && (command & `STO) == 0     ;
    assign  recack2stop     = state_c == REC_ACK    && end_cnt_bit && (command & `STO)          ;
    assign  sendack2idle    = state_c == SEND_ACK   && end_cnt_bit && (command & `STO) == 0     ;
    assign  sendack2stop    = state_c == SEND_ACK   && end_cnt_bit && (command & `STO)          ;
    assign  stop2idle       = state_c == STOP       && end_cnt_bit                              ;

    //  cnt_scl  200KHz  一个SCL周期为250个系统时钟周期
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_scl <= 0 ;
        end
        else if(add_cnt_scl)begin
            if(end_cnt_scl)begin
                cnt_scl <= 0 ;
            end				
            else begin	    
                cnt_scl <= cnt_scl + 1 ;
            end 		    
        end
    end                 
    assign	add_cnt_scl	= state_c != IDLE ;
    assign	end_cnt_scl	= add_cnt_scl && (cnt_scl == `SCL_PERIOD - 1) ;

    //  bit_num
    always @(*)begin
        if(state_c == RD_DATA || state_c == WR_DATA)begin
            bit_num = 8 ;
        end
        else begin
            bit_num = 1 ;
        end
    end

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

    //  dout_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            dout_data <= 0 ;
        end
        else if(req)begin
            dout_data <= wr_din ;
        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

    //  scl_dout
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            scl_dout <= 1'b1 ;
        end
        else if(idle2start | idle2wrdata | idle2rddata)begin
            scl_dout <= 1'b0 ;
        end
        else if(add_cnt_scl && cnt_scl == `SCL_HALF)begin
            scl_dout <= 1'b1 ;
        end
        else if(end_cnt_scl && ~stop2idle)begin
            scl_dout <= 1'b0 ;
        end
    end

    //  sda_dout_en
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sda_dout_en <= 1'b0 ;
        end
        else if(idle2start || idle2wrdata || rddata2sendack || sendack2stop || recack2stop)begin
            sda_dout_en <= 1'b1 ;
        end
        else if(idle2rddata || start2rddata || wrdata2recack || stop2idle)begin
            sda_dout_en <= 1'b0 ;
        end
    end

    //  sda_dout
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            sda_dout <= 1'b1 ;
        end
        else if(state_c == START)begin
            if(cnt_scl == `HIGH_HALF)begin
                sda_dout <= 1'b0 ;
            end
            else if(cnt_scl == `LOW_HALF)begin
                sda_dout <= 1'b1 ;
            end
        end
        else if(state_c == WR_DATA && cnt_scl == `LOW_HALF)begin
            sda_dout <= dout_data[7 - cnt_bit] ;
        end
        else if(state_c == SEND_ACK && cnt_scl == `LOW_HALF)begin
            sda_dout <= (command & `STO) ? 1'b1 : 1'b0 ;
        end
        else if(state_c == STOP)begin
            if(cnt_scl == `LOW_HALF)begin       
                sda_dout <= 1'b0;
            end
            else if(cnt_scl == `HIGH_HALF)begin    
                sda_dout <= 1'b1;               
            end 
        end
        else if(wrdata2recack | rddata2sendack)begin
            sda_dout <= 1'b1;  
        end
    end
    //  rx_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rx_data <= 0 ;
        end
        else if(state_c == RD_DATA && cnt_scl == `HIGH_HALF)begin
            rx_data[7 - cnt_bit] <= sda_in ;
        end
    end

    //  ack_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            ack_flag <= 1'b0 ;
        end
        else if(state_c == REC_ACK && cnt_scl == `HIGH_HALF)begin
            ack_flag <= ~sda_in ;
        end
        else begin
            ack_flag <= 1'b0 ;
        end
    end

    //  输出
    assign rdout = rx_data ;   
    assign rdout_vld = rddata2sendack ;
    assign rw_done = stop2idle | sendack2idle | recack2idle ;
    assign sda_out = sda_dout ; 
    assign sda_out_en = sda_dout_en ;
    assign scl = scl_dout ;
    
endmodule     

3. aht10读写控制模块

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: AHT10    
//  Module Name: aht10_ctrl         
//  Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)             
//  Tool versions: Quartus Prime 18.1             
//  Description: aht10读写控制模块
//  **************************************************************
`include "param.v"
module aht10_ctrl (
    input               clk                 ,
    input               rst_n               ,
    //  i2c_interface
    output              req                 ,       //  读写请求
    output  [7:0]       wr_dout             ,       //  发送指令数据
    output  [3:0]       cmd                 ,       //  发送读写控制命令
    input   [7:0]       rdin                ,       //  读回的数据
    input               rw_done             ,       //  读写一字节数据完成标志
    input               rdin_vld            ,
    //  data_process
    output  [19:0]      hum_data            ,       //  湿度数据
    output  [19:0]      tem_data            ,       //  温度数据
    output              data_vld                    //  温湿度数据有效标志
);

    //  参数定义
    localparam  WAIT      =   8'b0000_0001 ,     //  上电等待40ms
                INIT      =   8'b0000_0010 ,     //  初始化
                WAIT_INIT =   8'b0000_0100 ,     //  等待初始化完成
                IDLE      =   8'b0000_1000 ,     //  空闲
                RD_REQ    =   8'b0001_0000 ,     //  读数据请求
                WAIT_MES  =   8'b0010_0000 ,     //  等待测量完成80ms
                READ      =   8'b0100_0000 ,     //  读数据
                DONE      =   8'b1000_0000 ;     //  读操作完成

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

    reg     [27:0]      cnt_delay           ;       //  延时计数器
    wire                add_cnt_delay       ;
    wire                end_cnt_delay       ;
    reg     [27:0]      delay               ;

    reg     [3:0]       cnt_byte            ;       //  字节计数器
    wire                add_cnt_byte        ;
    wire                end_cnt_byte        ;

    reg                 init_flag           ;       //  初始化完成标志

    reg                 tx_req              ;       //  task发送请求
    reg     [3:0]       tx_cmd              ;       //  task发送控制命令
    reg     [7:0]       tx_data             ;       //  task发送数据

    reg     [47:0]      rd_data             ;       //  读回的温湿度数据寄存


    //  状态转移条件
    wire                wait2init           ;
    wire                init2waitinit       ;
    wire                waitinit2idle       ;
    wire                waitinit2init       ;
    wire                idle2rdreq          ;
    wire                rdreq2waitmes       ;
    wire                waitmes2read        ;
    wire                read2done           ;
    wire                done2idle           ;
    //  状态机
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            state_c <= WAIT ;
        end
        else begin
            state_c <= state_n ;
        end
    end

    always @(*)begin
        case (state_c)
            WAIT: begin
                if(wait2init)
                    state_n = INIT ;
                else
                    state_n = state_c ;
            end
            INIT: begin
                if(init2waitinit)
                    state_n = WAIT_INIT ;
                else
                    state_n = state_c ;
            end
            WAIT_INIT: begin
                if(waitinit2idle)
                    state_n = IDLE ;
                else if(waitinit2init)
                    state_n = INIT ;
                else
                    state_n = state_c ;
            end
            IDLE: begin
                if(idle2rdreq)
                    state_n = RD_REQ ;
                else
                    state_n = state_c ;
            end
            RD_REQ: begin
                if(rdreq2waitmes)
                    state_n = WAIT_MES ;
                else
                    state_n = state_c ;
            end
            WAIT_MES: begin
                if(waitmes2read)
                    state_n = READ ;
                else
                    state_n = state_c ;
            end
            READ: begin
                if(read2done)
                    state_n = DONE ;
                else
                    state_n = state_c ;
            end
            DONE: begin
                if(done2idle)
                    state_n = IDLE ;
                else
                    state_n = state_c ;
            end
            default: state_n = IDLE ;
        endcase
    end

    //  状态转移描述
    assign  wait2init      = state_c == WAIT        && end_cnt_delay    ; 
    assign  init2waitinit  = state_c == INIT        && end_cnt_byte     ; 
    assign  waitinit2idle  = state_c == WAIT_INIT   && init_flag        ; 
    assign  waitinit2init  = state_c == WAIT_INIT   && ~init_flag && end_cnt_byte       ; 
    assign  idle2rdreq     = state_c == IDLE        && end_cnt_delay    ; 
    assign  rdreq2waitmes  = state_c == RD_REQ      && end_cnt_byte     ; 
    assign  waitmes2read   = state_c == WAIT_MES    && end_cnt_delay    ; 
    assign  read2done      = state_c == READ        && end_cnt_byte     ; 
    assign  done2idle      = state_c == DONE        && (1'b1)           ; 

    //  delay
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            delay <= `DELAY_40ms ;
        end
        else if(state_c == WAIT)begin
            delay <= `DELAY_40ms ;
        end
        else if(state_c == WAIT_MES)begin
            delay <= `DELAY_80ms ;
        end
        else if(state_c == IDLE)begin
            delay <= `DELAY_500ms ;
        end
    end

    //  cnt_delay
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_delay <= 0 ;
        end
        else if(add_cnt_delay)begin
            if(end_cnt_delay)begin
                cnt_delay <= 0 ;
            end				
            else begin	    
                cnt_delay <= cnt_delay + 1 ;
            end 		    
        end
    end             
    assign	add_cnt_delay = (state_c == WAIT) || (state_c == WAIT_MES) || (state_c == IDLE) ;
    assign	end_cnt_delay = add_cnt_delay && (cnt_delay == delay - 1) ;

    //  cnt_byte
    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 begin	    
                cnt_byte <= cnt_byte + 1 ;
            end 		    
        end
    end             
    assign	add_cnt_byte = rw_done ;
    assign	end_cnt_byte = add_cnt_byte && (cnt_byte == ((state_c == READ) ? 7:4) - 1) ;

    //  使用task任务发送数据
    always  @(*)begin
        case (state_c)
            INIT : 
                case(cnt_byte)
                    0           :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b0}) ;       //  发起始位、写控制字
                    1           :TX(1'b1,`WRITE,`GET_STATE) ;                        
                    2           :TX(1'b1,`WRITE,`AHT10_INIT_1) ;  
                    3           :TX(1'b1,{`WRITE | `STO} ,`AHT10_INIT_2) ;  
                    default     :TX(1'b0,tx_cmd,tx_data) ;
                endcase 
            WAIT_INIT:
                case(cnt_byte)
                    0           :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b0}) ;       //  发起始位、写控制字
                    1           :TX(1'b1,`WRITE,`GET_STATE) ;                        //  发读取状态字命令
                    2           :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b1}) ;       //  发读控制字
                    3           :TX(1'b1,{`READ | `STO},0) ;  
                    default     :TX(1'b0,tx_cmd,tx_data) ;
                endcase
            RD_REQ: 
                case(cnt_byte)
                    0           :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b0}) ;       //  发起始位、写控制字
                    1           :TX(1'b1,`WRITE,`AHT10_MEAS_0) ;  
                    2           :TX(1'b1,`WRITE,`AHT10_MEAS_1) ;  
                    3           :TX(1'b1,{`WRITE | `STO},`AHT10_MEAS_2) ;  
                    default     :TX(1'b0,tx_cmd,tx_data) ;
                endcase    
            READ :            
                case(cnt_byte)
                    0           :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b1}) ;       //  发起始位、写控制字
                    1           :TX(1'b1,`READ,0) ;  
                    2           :TX(1'b1,`READ,0) ;  
                    3           :TX(1'b1,`READ,0) ;  
                    4           :TX(1'b1,`READ,0) ;  
                    5           :TX(1'b1,`READ,0) ;  
                    6           :TX(1'b1,{`READ | `STO},0) ;
                    default     :TX(1'b0,tx_cmd,tx_data) ;
                endcase
            default: TX(1'b0,0,0) ;
        endcase
    end

    //  init_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            init_flag <= 1'b0 ;
        end
        else if(state_c == WAIT_INIT && rw_done && rdin[3])begin
            init_flag <= 1'b1 ;
        end
    end

    //  rd_data
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            rd_data <= 0 ;
        end
        else if(state_c == READ && rw_done && cnt_byte > 0)begin
            rd_data <= {rd_data[39:0],rdin} ;
        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 wr_dout = tx_data ; 
    assign hum_data  = rd_data[39:20] ;
    assign tem_data  = rd_data[19:0] ;
    assign data_vld  = read2done ;

endmodule

4. aht10温湿度数据处理模块

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: AHT10    
//  Module Name: data_process         
//  Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)             
//  Tool versions: Quartus Prime 18.1             
//  Description: aht10温湿度数据处理模块
//  **************************************************************
`include "param.v"

module data_process (
    input           clk             ,
    input           rst_n           ,
    //  aht10_ctrl
    input           din_vld         ,
    input   [19:0]  tem_data        ,
    input   [19:0]  hum_data        ,
    //  set_limit
    input   [7:0]   tem_lim         ,
    input   [7:0]   hum_lim         ,
    //  uart_tx
    input           ready           ,
    output  [7:0]   tx_data         ,
    output          tx_data_vld     ,
    //  beep
    output          beep                
);

    //  信号定义
    reg     [19:0]      tem_data_r          ;       //  温湿度数据寄存
    reg     [19:0]      hum_data_r          ;

    reg     [5:0]       cnt_byte            ;       //  字节计数器
    wire                add_cnt_byte        ;
    wire                end_cnt_byte        ;

    reg                 process_flag        ;       //  数据处理标志

    reg     [7:0]       data                ;       //  串口显示数据

    reg                 flag                ;       //  蜂鸣器标志
    reg                 tem_alarm           ;
    reg                 hum_alarm           ;

    wire    [7:0]       tem_ten             ;       //  温度数据十位
    wire    [7:0]       tem_bit             ;       //  温度数据个位
    wire    [7:0]       tem_dot1            ;       //  温度数据小数位1
    wire    [7:0]       tem_dot2            ;       //  温度数据小数位2

    wire    [7:0]       hum_ten             ;       //  湿度数据十位
    wire    [7:0]       hum_bit             ;       //  湿度数据个位
    wire    [7:0]       hum_dot1            ;       //  湿度数据小数位1
    wire    [7:0]       hum_dot2            ;       //  湿度数据小数位2

    //  fifo
    wire                wrreq               ;
    wire                rdreq               ;
    wire                empty               ;
    wire                full                ;
    wire    [7:0]       q                   ;
    wire    [7:0]       usedw               ;

    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            tem_data_r <= 0 ;
            hum_data_r <= 0 ;
        end
        else if(din_vld)begin
            // tem_data_r <= ((tem_data * 200) >> 20) - 50 ;   
            // hum_data_r <= (hum_data * 100) >> 20 ;
            tem_data_r <= (((tem_data * 2000) >> 12) - (500)); //扩大10倍
            hum_data_r <= ((hum_data * 1000) >> 12);
            // tem_data_r = ((((tem_data << 10)
            //              + (tem_data << 9)
            //              + (tem_data << 8)
            //              + (tem_data << 7)
            //              + (tem_data << 6)
            //              + (tem_data << 4)) >> 20 ) - 500) * 10 ;
            // hum_data_r = (((hum_data << 9)
            //             + (hum_data << 8)
            //             + (hum_data << 7)
            //             + (hum_data << 6)
            //             + (hum_data << 5)
            //             + (hum_data << 3)) >> 20) * 10 ;
        end
    end

    //  process_flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            process_flag <= 1'b0 ;
        end
        else if(din_vld)begin
            process_flag <= 1'b1 ;
        end
        else if(end_cnt_byte)begin
            process_flag <= 1'b0 ;
        end
    end

    //  cnt_byte
    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 begin	    
                cnt_byte <= cnt_byte + 1 ;
            end 		    
        end
    end             
    assign	add_cnt_byte =  process_flag;
    assign	end_cnt_byte = add_cnt_byte && (cnt_byte == 24) ;

    //  flag
    // always @(*)begin
    //     if(!rst_n)begin
    //         flag <= 1'b0 ;
    //     end
    //     else if(add_cnt_byte && (cnt_byte == 6 && tem_ten >= (tem_lim / 10)) || (cnt_byte == 18 && hum_ten >= (hum_lim / 10)))begin
    //         flag <= 1'b1 ;
    //     end
    //     else if(add_cnt_byte && (cnt_byte == 6 && tem_ten < (tem_lim / 10)) || (cnt_byte == 18 && hum_ten < (hum_lim / 10)))begin
    //         flag <= 1'b0 ;
    //     end
    // end

    //  tem_alarm
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            tem_alarm <= 1'b0 ;
        end
        else if(add_cnt_byte && tem_ten >= (tem_lim / 10))begin
            tem_alarm <= 1'b1 ;
        end
        else if(add_cnt_byte && tem_ten < (tem_lim / 10))begin
            tem_alarm <= 1'b0 ;
        end
    end

    //  hum_alarm
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            hum_alarm <= 1'b0 ;
        end
        else if(add_cnt_byte && hum_ten >= (hum_lim / 10))begin
            hum_alarm <= 1'b1 ;
        end
        else if(add_cnt_byte && hum_ten < (hum_lim / 10))begin
            hum_alarm <= 1'b0 ;
        end
    end

    //  flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            flag <= 1'b0 ;
        end
        else if(tem_alarm | hum_alarm)begin
            flag <= 1'b1 ;
        end
        else if(~tem_alarm & ~hum_alarm)begin
            flag <= 1'b0 ;
        end
    end

    assign tem_ten = (tem_data_r / 100 % 10 ) ;
    assign tem_bit = (tem_data_r % 100 / 10  ) ;
    assign tem_dot1 = (tem_data_r % 10  ) ;
    assign tem_dot2 = 0 ;

    assign hum_ten = (hum_data_r / 100 % 10 ) ;
    assign hum_bit = (hum_data_r % 100 / 10  ) ;
    assign hum_dot1 = (hum_data_r % 10 ) ;
    assign hum_dot2 = 0 ;

    //  data
    always @(*)begin
        case (cnt_byte)
            1 : data <= 8'hce;      //  温度ASCII码
            2 : data <= 8'hc2;
            3 : data <= 8'hb6;
            4 : data <= 8'hc8;
            5 : data <= 8'h3a;      //   :ASCII码
            6 : data <= tem_ten + 48;       //  十位
            7 : data <= tem_bit + 48;       //  个位
            8 : data <= 8'h2e;      //   .ASCII码
            9 : data <= tem_dot1 + 48;            //  小数位1
            10: data <= tem_dot2 + 48;            //  小数位2
            11: data <= 8'ha1;      //   ℃ASCII码
            12: data <= 8'he6;
            13: data <= 9;          //   tab
            14: data <= 8'hca;      //   湿度ASCII码
            15: data <= 8'haa;
            16: data <= 8'hb6;
            17: data <= 8'hc8;
            18: data <= 8'h3a;      //   :ASCII码
            19: data <= hum_ten + 48;      //  十位
            20: data <= hum_bit + 48;       //  个位
            21: data <= 8'h2e;      //   .ASCII码
            22: data <= hum_dot1 + 48;           //  小数位1
            23: data <= hum_dot2 + 48;           //  小数位2
            24: data <= 8'h25;      //   %ASCII码
            default: data <= 0 ;
        endcase
    end

    //  fifo例化
    fifo	fifo_inst (
	    .aclr   ( ~rst_n        ),
	    .clock  ( clk           ),
	    .data   ( data          ),
	    .rdreq  ( rdreq         ),
	    .wrreq  ( wrreq         ),

	    .empty  ( empty         ),
	    .full   ( full          ),
	    .q      ( q             ),
	    .usedw  ( usedw         )
	);
    assign  wrreq = ~full && process_flag && cnt_byte > 0;
    assign  rdreq = ~empty && ready ;

    //  输出
    assign tx_data = q ;
    assign tx_data_vld = rdreq ;
    assign beep = ~flag ;
    
endmodule

5. aht10设置温湿度报警值模块

//  **************************************************************
//  Author: Zhang JunYi
//  Create Date: 2022.11.15                        
//  Design Name: AHT10    
//  Module Name: set_limit         
//  Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)             
//  Tool versions: Quartus Prime 18.1             
//  Description: aht10设置温湿度报警值模块
//  **************************************************************

module set_limit (
    input           clk             ,
    input           rst_n           ,
    //  key_filter
    input           tem_add         ,       //  设置温度报警值标志,每按一次+5摄氏度
    input           hum_add         ,       //  设置湿度报警值标志,每按一次+5%
    input           set             ,       //  设置模式,每按一次在温度和湿度设置之间切换
    //  seg_driver
    output          modle           ,       //  当前设置模式  0:设置温度   1:设置湿度
    output  [7:0]   tem_lim         ,       //  温度报警值
    output  [7:0]   hum_lim                 //  湿度报警值
);

    //  信号定义

    reg     [7:0]   cnt_tem         ;
    wire            add_cnt_tem     ;
    wire            end_cnt_tem     ;

    reg     [7:0]   cnt_hum         ;
    wire            add_cnt_hum     ;
    wire            end_cnt_hum     ;

    reg             flag            ;       //  设置模式

    //  cnt_tem
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_tem <= 0 ;
        end
        else if(add_cnt_tem)begin
            if(end_cnt_tem)begin
                cnt_tem <= 0 ;
            end				
            else begin	    
                cnt_tem <= cnt_tem + 10 ;
            end 		    
        end
    end             
    assign	add_cnt_tem = tem_add ;
    assign	end_cnt_tem = add_cnt_tem && (cnt_tem == 100) ;

    //  cnt_hum
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            cnt_hum <= 0 ;
        end
        else if(add_cnt_hum)begin
            if(end_cnt_hum)begin
                cnt_hum <= 0 ;
            end				
            else begin	    
                cnt_hum <= cnt_hum + 10 ;
            end 		    
        end
    end             
    assign	add_cnt_hum = hum_add ;
    assign	end_cnt_hum = add_cnt_hum && (cnt_hum == 100) ;

    //  flag
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            flag = 1'b0 ;
        end
        else if(set)begin
            flag = ~flag ;
        end
    end

    //  输出
    assign tem_lim = cnt_tem ;
    assign hum_lim = cnt_hum ;
    assign modle = flag ;
    
endmodule

6. 参数文件

//  IIC时钟参数
`define  SCL_PERIOD  250
`define  SCL_HALF    125
`define  LOW_HALF    65 
`define  HIGH_HALF   190

//  控制命令
`define  WRITE    4'b1000     //  写
`define  READ     4'b0100     //  读
`define  STA      4'b0010     //  起始位
`define  STO      4'b0001     //  停止位

//  AHT10命令参数
`define AHT10_ADDR 7'b0111_000      //  设备地址

`define GET_STATE    8'b1110_0001     //  获取状态字

`define AHT10_INIT_0 8'b1110_0001     //  初始化命令序列
`define AHT10_INIT_1 8'b0000_1000
`define AHT10_INIT_2 8'b0000_0000

`define AHT10_MEAS_0 8'b1010_1100       //  触发测量命令序列    
`define AHT10_MEAS_1 8'b0011_0011
`define AHT10_MEAS_2 8'b0000_0000

`define AHT10_MEAS_4 8'b0111_0001     //  读数据命令


`define DELAY_40ms  2_000_000        //  上电等待40ms
`define DELAY_80ms  4_000_000        //  测量数据80ms
`define DELAY_500ms 5000_0000       //  0.5s采集一次数据

//  UART参数配置文件
`define BAUD_RATE_115200

`define CLK_FREQ 50_000_000

`ifdef BAUD_RATE_9600
    `define BAUD `CLK_FREQ / 9600
`elsif BAUD_RATE_19200
    `define BAUD `CLK_FREQ / 19200
`elsif BAUD_RATE_38400
    `define BAUD `CLK_FREQ / 38400
`elsif BAUD_RATE_57600
    `define BAUD `CLK_FREQ / 57600
`elsif BAUD_RATE_115200
    `define BAUD `CLK_FREQ / 115200
`endif 

7. 其他模块
按键消抖模块、数码管驱动模块、串口发送模块比较简单,这里不过多赘述。

七、仿真测试

由于没有AHT10的仿真模型,仿真模拟AHT10返回数据,下面只对比较重要的几个模块进行仿真测试。
仿真代码如下:

`timescale 1ns/1ps

module test_tb ();

    reg             clk         ;
    reg             rst_n       ;

    wire			sda			;
    wire			scl			;
    reg				sda_i	    ;
    top u_top(
        .clk        ( clk        ),
        .rst_n      ( rst_n      ),
        .sda        ( sda        ),
        .scl        ( scl        ),
        .uart_txd   ( uart_txd   )
    );
    assign sda = u_top.sda_out_en ? 1'bz:sda_i;

    localparam CYCLE = 20;
    always #(CYCLE/2) clk=~clk;

    reg			[ 10:0 ]			i			;
    reg			[ 10:0 ]			j			;

    initial begin
        rst_n = 1'b1;
        clk = 1'b1;
        #(CYCLE * 2);
        rst_n = 1'b0;
        #(CYCLE * 2);
        #2;
        rst_n = 1'b1;

        @(posedge u_top.u_aht10_ctrl.init2waitinit); //初始化

        for (i = 0; i<4 ; i = i+1 ) begin
            @(posedge u_top.u_aht10_ctrl.add_cnt_byte);
        end

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

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

    $stop;
end

endmodule

仿真结果:
(1)aht10_ctrl模块
在这里插入图片描述
(2)i2c_interface模块
在这里插入图片描述
(3)data_process模块
在这里插入图片描述
(4)uart_tx模块
在这里插入图片描述

八、板级验证

终端信息打印:
在这里插入图片描述
上板之后,温湿度报警值默认是0℃和0%,需要手动设置报警界限,所以一开始上板蜂鸣器就会开始工作。通过按键设置好温湿度报警值之后就会正常工作,当温度或者湿度超过预设报警值时蜂鸣器开始工作,当温度或湿度同时低于预设报警值时,蜂鸣器停止工作。

按键操作说明:

key1:设置温湿度报警值模式,初始状态为设置温度报警值模式(数码管显示“TE 温度值”),按下key1切换为设置湿度报警值模式(数码管显示“HU 湿度值”),在两种设置模式之间切换。
key2:当处于设置温度报警值模式时(数码管显示“TE 温度值”),按下key2,温度报警值+10%,最高设置不超过90%。
key3:当处于设置湿度报警值模式时(数码管显示“HU 湿度值”),按下key3:湿度报警值+10℃,最高设置不超过90℃。
key4:复位按键。

评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值