FPGA学习笔记——AHT20温湿度读取并在串口显示(IIC协议)

目录

一、任务

二、分析

1.需要了解的

2.需要用到的模块

3.流程分析

三、Visio图

四、代码

五、实验现象


一、任务

使用IIC协议通信的AHT20,将温湿度数据读取出来,并在串口助手上显示。


二、分析

1.需要了解的

需要了解IIC协议简介

也可以看看EEPROM的读写

我写的这个代码与上面的EEPROM的读写类似。

2.需要用到的模块

需要用到,AHT20模块IIC接口模块串口发送模块,读写控制模块 和**数据处理模块。**这里我没有使用CRC,但是,我在代码里面将它存储了,感兴趣的可以自己了解一下。

3.流程分析

根据上面的AHT20的读取流程,最好的是使用状态机来写,这里我分成5个状态:

WAIT
START
IDLE
READ
VLD

WAIT:上电之后延时5ms,跳入START状态,

START:发送4个字完成以后,跳入IDLE状态,

IDLE:延时1s(大于80ms),跳入READ状态

READ:发送完8个字节,跳入VLD状态,

VLD:数据简单处理,跳入START状态循环。


三、Visio图

以下是整体的框架。(可以自己去看看RTL视图)


四、代码

top.v

cpp 复制代码
module top( 
    input               sys_clk  ,
    input               sys_rst_n,
    output              uart_txd ,
    output              iic_scl  ,
    inout               iic_sda   
);
//---------<Parameter definition>-----------------------------------
    
//---------<Internal signal definition>-----------------------------
wire            sda_in      ;
wire            sda_out     ;
wire            sda_oe      ;
wire    [31:0]  humi_data   ;
wire    [31:0]  temp_data   ;
wire            data_vld    ;
wire    [7:0]   tx_data     ;
wire            tx_data_vld ;
wire            tx_done     ;
wire            ready       ;


//三态门描述
assign iic_sda = sda_oe ? sda_out : 1'bz;
assign sda_in = iic_sda;

aht20_top u_aht20_top(  
.clk       (sys_clk  ),
.rst_n     (sys_rst_n),
.humi_data (humi_data),
.temp_data (temp_data),
.data_vld  (data_vld ),
.iic_scl   (iic_scl  ),
.sda_in    (sda_in   ),
.sda_out   (sda_out  ),
.sda_oe    (sda_oe   )
);

data_process u_data_process( 
    .clk         (sys_clk     ),
    .rst_n       (sys_rst_n   ),
    .humi_data   (humi_data   ),
    .temp_data   (temp_data   ),
    .data_vld    (data_vld    ),
    .tx_data     (tx_data     ),
    .tx_data_vld (tx_data_vld ),
    .ready       (ready       ),
    .tx_done     (tx_done     )
);

uart_tx u_uart_tx( 
    .clk      (sys_clk    ),
    .rst_n    (sys_rst_n  ),
    .din      (tx_data    ),
    .start_en (tx_data_vld),//开始发送的使能信号
    .tx_done  (tx_done    ),//一帧数据发送完成
    .ready    (ready      ),//控制指示信号 空闲时ready为高,忙时ready为低
    .uart_txd (uart_txd   )
);
    
endmodule

uart_tx.v

cpp 复制代码
module uart_tx#(parameter BAUD = 115200,
                          SYS_CLOCK = 50_000_000
)( 
    input               clk      ,
    input               rst_n    ,
    input       [7:0]   din      ,
    input               start_en ,//开始发送的使能信号
    output  reg         tx_done  ,//一帧数据发送完成
    output              ready    ,//控制指示信号 空闲时ready为高,忙时ready为低
    output  reg         uart_txd 
);
//---------<Parameter definition>-----------------------------------
localparam BAUD_MAX = SYS_CLOCK/BAUD;//对应波特率情况下,传输1bit需要多少个系统时钟周期

//---------<Internal signal definition>-----------------------------
reg		[12:0]	cnt_baud	;//波特率周期计数器
wire			add_cnt_baud;
wire			end_cnt_baud;
reg		[3:0]	cnt_bit	   ;//bit计数器
wire			add_cnt_bit;
wire			end_cnt_bit;
reg     [7:0]   din_r      ;

reg             flag;//数据帧传输的指示信号

//din_r
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        din_r <= 'd0;
    end 
    else if(start_en)begin 
        din_r <= din;
    end 
end

//flag
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        flag <= 'd0;
    end 
    else if(start_en)begin 
        flag <= 1'b1;
    end 
    else if(end_cnt_bit)begin 
        flag <= 1'b0;
    end 
end

//cnt_baud
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_baud <= 'd0;
    end 
    else if(add_cnt_baud)begin 
        if(end_cnt_baud)begin 
            cnt_baud <= 'd0;
        end
        else begin 
            cnt_baud <= cnt_baud + 1'b1;
        end 
    end
end 

assign add_cnt_baud = flag;
assign end_cnt_baud = add_cnt_baud && cnt_baud == ((cnt_bit == 9) ? ((BAUD_MAX>>1) + (BAUD_MAX>>2)) : (BAUD_MAX - 1));

//cnt_bit
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_bit <= 'd0;
    end 
    else if(add_cnt_bit)begin 
        if(end_cnt_bit)begin 
            cnt_bit <= 'd0;
        end
        else begin 
            cnt_bit <= cnt_bit + 1'b1;
        end 
    end
end 

assign add_cnt_bit = end_cnt_baud;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 10 - 1;

//uart_txd 并转串
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        uart_txd <= 1'b1;
    end 
    else if(flag) begin 
        case (cnt_bit)
            0 : uart_txd <= 1'b0;//起始位
            1 : uart_txd <= din_r[0];//LSB
            2 : uart_txd <= din_r[1];
            3 : uart_txd <= din_r[2];
            4 : uart_txd <= din_r[3];
            5 : uart_txd <= din_r[4];
            6 : uart_txd <= din_r[5];
            7 : uart_txd <= din_r[6];
            8 : uart_txd <= din_r[7];
            9 : uart_txd <= 1'b1;//停止位
            default: ;
        endcase
    end 
    else begin 
        uart_txd <= 1'b1;
    end 
end

//tx_done
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_done <= 'd0;
    end 
    else begin 
        tx_done <= end_cnt_bit;
    end 
end

//ready 
assign ready = ~flag;

endmodule

data_process

cpp 复制代码
    input               clk         ,
    input               rst_n       ,
    //aht20_ctrl
    input       [31:0]  humi_data   ,
    input       [31:0]  temp_data   ,
    input               data_vld    ,
    //seg_driver
    // output reg  [23:0]  dig_data    ,
    // output reg          dig_data_vld,
    //uart_tx
    output reg  [7:0]   tx_data     ,
    output              tx_data_vld ,
    input               ready       ,
    input               tx_done     
);
//---------<Parameter definition>-----------------------------------
    
//---------<Internal signal definition>-----------------------------
wire    [11:0]  humi_bcd    ;
wire            humi_bcd_vld;
wire    [11:0]  temp_bcd    ;
wire            temp_bcd_vld;
reg		[7:0]	cnt	   ;//串口发送字节数量计数器
wire			add_cnt;
wire			end_cnt;
reg             tx_flag;//串口发送数据的标志信号

//*******************************************************************
//--温湿度二进制-->BCD码
//*******************************************************************
//湿度转换
bin2bcd u_bin2bcd_1(
    /*input                         */.clk         (clk         ),//时钟
    /*input                         */.rst_n       (rst_n       ),//复位
    /*input                         */.en          (data_vld    ),
    /*input          [DIN_W-1:0]    */.binary_din  (humi_data   ),//输入二进制数据
    /*output    reg  [DOUT_W-1:0]   */.bcd_dout    (humi_bcd    ),//输出BCD码数据
    /*output    reg                 */.bcd_dout_vld(humi_bcd_vld) 
);

//温度转换
bin2bcd u_bin2bcd_2(
    /*input                         */.clk         (clk         ),//时钟
    /*input                         */.rst_n       (rst_n       ),//复位
    /*input                         */.en          (data_vld    ),
    /*input          [DIN_W-1:0]    */.binary_din  (temp_data   ),//输入二进制数据
    /*output    reg  [DOUT_W-1:0]   */.bcd_dout    (temp_bcd    ),//输出BCD码数据
    /*output    reg                 */.bcd_dout_vld(temp_bcd_vld) 
);


//*******************************************************************
//--串口显示
//*******************************************************************
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt <= 'd0;
    end 
    else if(add_cnt)begin 
        if(end_cnt)begin 
            cnt <= 'd0;
        end
        else begin 
            cnt <= cnt + 1'b1;
        end 
    end
end 

assign add_cnt = tx_flag && ready;
assign end_cnt = add_cnt && cnt == 12 - 1;

//tx_flag
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_flag <= 'd0;
    end 
    else if(humi_bcd_vld)begin 
        tx_flag <= 1'b1;
    end 
    else if(end_cnt)begin 
        tx_flag <= 1'b0;
    end 
end

//tx_data
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        tx_data <= 'd0;
    end 
    else begin 
        case (cnt)
            0 : tx_data <= "0" + humi_bcd[11:8];//湿度数据的十位
            1 : tx_data <= "0" + humi_bcd[7:4];//湿度数据的个位
            2 : tx_data <= ".";
            3 : tx_data <= "0" + humi_bcd[3:0];//湿度数据小数点后1位
            4 : tx_data <= "%";
            5 : tx_data <= " ";
            6 : tx_data <= "0" + temp_bcd[11:8];
            7 : tx_data <= "0" + temp_bcd[7:4];
            8 : tx_data <= ".";
            9 : tx_data <= "0" + temp_bcd[3:0];
            10 : tx_data <= 8'hA1;//℃
            11 : tx_data <= 8'hE6;
            default: tx_data <= 0;
        endcase
    end 
end

assign tx_data_vld = tx_flag && ready;


endmodule

二进制转BCD

cpp 复制代码
module bin2bcd#(parameter DIN_W = 32,DOUT_W = 12)(
    input                         clk         ,//时钟
    input                         rst_n       ,//复位
    input                         en          ,
    input          [DIN_W-1:0]    binary_din  ,//输入二进制数据
    //输出信号定义
    output    reg  [DOUT_W-1:0]   bcd_dout    ,//输出BCD码数据
    output    reg                 bcd_dout_vld 
);

    //状态机参数定义
    localparam  IDLE  = 4'b0001,
                READY = 4'b0010,
                SHIFT = 4'b0100,
                DONE  = 4'b1000;

    //信号定义
    reg     [3:0]           state_c      ;
    reg     [3:0]           state_n      ;
    
    reg     [DIN_W-1:0]     din_r        ;      //数据锁存
    reg     [5:0]           shift_cnt    ;      //移位次数计数器
    wire                    add_shift_cnt;
    wire                    end_shift_cnt;

    reg     [3:0]           mem_r0      ;
    reg     [3:0]           mem_r1      ;
    reg     [3:0]           mem_r2      ;
    reg     [3:0]           mem_r3      ;
    reg     [3:0]           mem_r4      ;
    reg     [3:0]           mem_r5      ;
    reg     [3:0]           mem_r6      ;
    reg     [3:0]           mem_r7      ;
    reg     [3:0]           mem_r8      ;
    reg     [3:0]           mem_r9      ;

    wire    [3:0]           mem_w0      ;
    wire    [3:0]           mem_w1      ;
    wire    [3:0]           mem_w2      ;
    wire    [3:0]           mem_w3      ;
    wire    [3:0]           mem_w4      ;
    wire    [3:0]           mem_w5      ;
    wire    [3:0]           mem_w6      ;
    wire    [3:0]           mem_w7      ;
    wire    [3:0]           mem_w8      ;
    wire    [3:0]           mem_w9      ;

    wire    [39:0]          bcd_res     ;
    wire                    idle2ready  ; 
    wire                    shift2done  ;

    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(idle2ready) 
                    state_n = READY;
                else 
                    state_n = state_c;
            end
            READY:begin 
                state_n = SHIFT;
            end 
            SHIFT :begin
                if(shift2done) 
                    state_n = DONE ;
                else 
                    state_n = state_c ;
            end
            DONE :begin
                    state_n = IDLE;
            end
            default : state_n = IDLE;
        endcase
    end
    
    assign idle2ready = state_c == IDLE  && (en);
    assign shift2done = state_c == SHIFT && (end_shift_cnt);

    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            shift_cnt <= 0;
        end
        else if(add_shift_cnt)begin
            if(end_shift_cnt)
                shift_cnt <= 0;
            else
                shift_cnt <= shift_cnt + 1;
        end
    end

    assign add_shift_cnt = state_c == SHIFT;       
    assign end_shift_cnt = add_shift_cnt && shift_cnt == DIN_W-1;   

    //din_r
    always  @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            din_r <= 0;
        end
        else if(en)begin 
            din_r <= binary_din;    
        end 
        else if(state_c == SHIFT)begin    //移位状态下,每个时钟周期向左移1位
            din_r <= din_r << 1'b1;
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            mem_r0 <= 0;
            mem_r1 <= 0;
            mem_r2 <= 0;
            mem_r3 <= 0;
            mem_r4 <= 0;
            mem_r5 <= 0;
            mem_r6 <= 0;
            mem_r7 <= 0;
            mem_r8 <= 0;
            mem_r9 <= 0;
        end
        else if(idle2ready)begin 
            mem_r0 <= 0;
            mem_r1 <= 0;
            mem_r2 <= 0;
            mem_r3 <= 0;
            mem_r4 <= 0;
            mem_r5 <= 0;
            mem_r6 <= 0;
            mem_r7 <= 0;
            mem_r8 <= 0;
            mem_r9 <= 0;
        end 
        else if(state_c == SHIFT)begin
            mem_r0 <= {mem_w0[2:0],din_r[DIN_W-1]};
            mem_r1 <= {mem_w1[2:0],mem_w0[3]};
            mem_r2 <= {mem_w2[2:0],mem_w1[3]};
            mem_r3 <= {mem_w3[2:0],mem_w2[3]};
            mem_r4 <= {mem_w4[2:0],mem_w3[3]};
            mem_r5 <= {mem_w5[2:0],mem_w4[3]};
            mem_r6 <= {mem_w6[2:0],mem_w5[3]};
            mem_r7 <= {mem_w7[2:0],mem_w6[3]};
            mem_r8 <= {mem_w8[2:0],mem_w7[3]};
            mem_r9 <= {mem_w9[2:0],mem_w8[3]};
        end
    end

    assign mem_w0 = (mem_r0 > 4'd4)?(mem_r0 + 4'd3):mem_r0;
    assign mem_w1 = (mem_r1 > 4'd4)?(mem_r1 + 4'd3):mem_r1;
    assign mem_w2 = (mem_r2 > 4'd4)?(mem_r2 + 4'd3):mem_r2;
    assign mem_w3 = (mem_r3 > 4'd4)?(mem_r3 + 4'd3):mem_r3;
    assign mem_w4 = (mem_r4 > 4'd4)?(mem_r4 + 4'd3):mem_r4;
	assign mem_w5 = (mem_r5 > 4'd4)?(mem_r5 + 4'd3):mem_r5;
    assign mem_w6 = (mem_r6 > 4'd4)?(mem_r6 + 4'd3):mem_r6;
    assign mem_w7 = (mem_r7 > 4'd4)?(mem_r7 + 4'd3):mem_r7;
    assign mem_w8 = (mem_r8 > 4'd4)?(mem_r8 + 4'd3):mem_r8;
    assign mem_w9 = (mem_r9 > 4'd4)?(mem_r9 + 4'd3):mem_r9;

    assign bcd_res = {mem_r9,mem_r8,mem_r7,mem_r6,mem_r5,mem_r4,mem_r3,mem_r2,mem_r1,mem_r0};
    
    always  @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            bcd_dout <= 0;
        end
        else if(state_c == DONE)begin
            bcd_dout <= bcd_res[DOUT_W-1:0];
        end
    end

    always  @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            bcd_dout_vld <= 1'b0;
        end
        else begin
            bcd_dout_vld <= state_c == DONE;
        end
    end


endmodule

ath20_top.v

cpp 复制代码
module aht20_top( 
    input               clk       ,
    input               rst_n     ,
    //data_process
    output      [31:0]  humi_data ,
    output      [31:0]  temp_data ,
    output              data_vld  ,
    //aht20
    output              iic_scl   ,
    input               sda_in    ,
    output              sda_out   ,
    output              sda_oe    
);
//---------<Internal signal definition>-----------------------------
wire            start_en  ;
wire    [3:0]   cmd       ;
wire    [7:0]   wr_data   ;
wire    [7:0]   rd_data   ;
wire            trans_done;

    
rw_ctrl u_rw_ctrl(  
.clk       (clk       ),
.rst_n     (rst_n     ),
.start_en  (start_en  ),
.cmd       (cmd       ),
.wr_data   (wr_data   ),
.rd_data   (rd_data   ),
.trans_done(trans_done),
.humi_data (humi_data ),
.temp_data (temp_data ),
.data_vld  (data_vld  )
);

iic_intf u_iic_intf( 
.clk       (clk      ),
.rst_n     (rst_n    ),
.start_en  (start_en  ),//读/写使能信号
.cmd       (cmd       ),//命令信号
.wr_data   (wr_data   ),//写入的数据
.rd_data   (rd_data   ),//读出的数据
.trans_done(trans_done),//传输1字节完成的脉冲信号
.iic_scl   (iic_scl   ),
.sda_in    (sda_in    ),
.sda_out   (sda_out   ),
.sda_oe    (sda_oe    )
);
    
    
endmodule

rw_ctrl.v

cpp 复制代码
module rw_ctrl ( 
input               clk         ,
input               rst_n       ,
//iic_intf   
output  reg         start_en    ,
output  reg [3:0]   cmd         ,
output  reg [7:0]   wr_data     ,
input       [7:0]   rd_data     ,
input               trans_done  ,
//data_process
output  reg [31:0]  humi_data   ,
output  reg [31:0]  temp_data   ,
output  reg         data_vld     
);
//---------<状态定义>------------------------------------------------- 
localparam  WAIT  = 5'b00001, // 上电等待5ms
            START = 5'b00010, // 发送测量命令:0x70、0xAC、0x33、0x00 --->4个字节
            IDLE  = 5'b00100, // 等待AHT20数据处理完成--->延时1s
            READ  = 5'b01000, // 发送读数据指令,并接收数据--->发0x71、收:状态字、温湿度数据(5字节)、CRC--->8个字节
            VLD   = 5'b10000; // 数据简单处理
reg [4:0]   state_c , state_n;

//---------<常数定义>------------------------------------------------- 
localparam  delay_1s  = 50_000_000,  // 1s
            delay_5ms = 2_500_000 ;  // 5ms

localparam  start_bit   = 4, //4字节
            read_bit    = 8; //8字节
parameter   CMD_START = 4'b0001,
            CMD_WRITE = 4'b0010,
            CMD_READ  = 4'b0100,
            CMD_STOP  = 4'b1000;
//---------<状态跳变条件定义>------------------------------------------------- 
wire    wait2start;
wire    start2idle;
wire    idle2read ;
wire    read2vld  ;
wire    vld2idle  ;
//---------<寄存器定义>------------------------------------------------- 
//延时计数器
reg		[25:0]	cnt_delay	 ;//分时复用的延时计数器
wire			add_cnt_delay;
wire			end_cnt_delay;
reg     [25:0]  xx;
//字节计数器
reg		[3:0]	cnt_byte	;//分时复用的字节计数器
wire			add_cnt_byte;
wire			end_cnt_byte;
reg     [3:0]   yy;
//接收数据
reg     [7:0]   state_word;//状态字
reg     [7:0]   CRC       ;//CRC校验
//接收数据
reg     [7:0]   humi_high     ;
reg     [7:0]   humi_mid      ;
reg     [7:0]   humi_temp_byte; // 包含湿度低4位和温度高4位
reg     [7:0]   temp_mid      ;
reg     [7:0]   temp_low      ;
//数据寄存
reg     [19:0]  humi_data_r   ;
reg     [19:0]  temp_data_r   ;
reg     [1:0]   data_vld_r    ;

//---------<延时计数器复用>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_delay <= 'd0;
    end 
    else if(add_cnt_delay)begin 
        if(end_cnt_delay)begin 
            cnt_delay <= 'd0;
        end
        else begin 
            cnt_delay <= cnt_delay + 1'b1;
        end 
    end
end 

assign add_cnt_delay = state_c == WAIT || state_c == IDLE ;
assign end_cnt_delay = add_cnt_delay && cnt_delay == xx - 1;

always @(*)begin 
    case (state_c)
        WAIT : xx  = delay_5ms  ;
        IDLE : xx  = delay_1s   ;
        default: xx = 0;
    endcase
end

//---------<bit计数器复用>------------------------------------------------- 
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_byte <= 'd0;
    end 
    else if(add_cnt_byte)begin 
        if(end_cnt_byte)begin 
            cnt_byte <= 'd0;
        end
        else begin 
            cnt_byte <= cnt_byte + 1'b1;
        end 
    end
end 

assign add_cnt_byte =( state_c == START || state_c == READ) && trans_done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == yy - 1;

always @(*)begin 
   case (state_c)
    START : yy = start_bit; 
    READ  : yy = read_bit ;
    default: yy = 0;
   endcase 
end

//---------<描述状态转移>------------------------------------------------- 
always @(posedge clk) begin
    if(!rst_n)
        state_c <= WAIT;
    else
        state_c <= state_n;
end

//---------<描述状态转移规律>------------------------------------------------- 
always @(*) begin
    case(state_c)
        WAIT :begin
            if(wait2start)
                state_n = START; 
            else
                state_n = state_c;
        end
        START:begin
            if(start2idle)
                state_n = IDLE;
            else 
                state_n = state_c;
        end
        IDLE :begin
            if(idle2read)
                state_n = READ;
            else
                state_n = state_c;
        end
        READ :begin
            if(read2vld)
                state_n = VLD;
            else
                state_n = state_c;
        end
        VLD  :begin
            if(vld2idle)
                state_n = START;
            else
                state_n = state_c;
        end
        default : state_n = WAIT;
    endcase
end

//---------<状态条件赋值>------------------------------------------------- 
assign  wait2start = (state_c == WAIT)  && (end_cnt_delay) ;
assign  start2idle = (state_c == START) && (end_cnt_byte) ;
assign  idle2read  = (state_c == IDLE)  && (end_cnt_delay) ;
assign  read2vld   = (state_c == READ)  && (end_cnt_byte) ;
assign  vld2idle   = (state_c == VLD);

//---------<状态输出>------------------------------------------------- 
//start_en  cmd  wr_data
always @(*)begin 
    case (state_c)
        START : begin
            case (cnt_byte)
                 0 : begin start_en = 1'b1;cmd = CMD_START | CMD_WRITE;wr_data = 8'h70;end
                 1 : begin start_en = 1'b1;cmd = CMD_WRITE            ;wr_data = 8'hAC;end
                 2 : begin start_en = 1'b1;cmd = CMD_WRITE            ;wr_data = 8'h33;end
                 3 : begin start_en = 1'b1;cmd = CMD_STOP | CMD_WRITE ;wr_data = 8'h00;end
                default: begin start_en = 1'b0;cmd = 0 ;wr_data = 8'h00;end
            endcase
        end
        READ : begin
            case (cnt_byte)
                 0 : begin start_en = 1'b1;cmd = CMD_START | CMD_WRITE;wr_data = 8'h71;end
                 7 : begin start_en = 1'b1;cmd = CMD_STOP  | CMD_READ ;wr_data = 8'h00;end
                default: begin start_en <= 1'b1;cmd <= CMD_READ  ;wr_data <= 8'h00;end
            endcase
        end
        default: begin start_en <= 1'b0;cmd <= 4'd0 ;wr_data <= 8'h00;end
    endcase
end

//data
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        // templete_data <= 'd0;
        state_word     <= 'd0;
        humi_high      <= 'd0;
        humi_mid       <= 'd0;
        humi_temp_byte <= 'd0;
        temp_mid       <= 'd0;
        temp_low       <= 'd0;
        CRC            <= 'd0;
    end 
    else if(state_c == READ && cnt_byte >= 1 && trans_done)begin 
        case (cnt_byte)
            1 : state_word     <= rd_data;
            2 : humi_high      <= rd_data;
            3 : humi_mid       <= rd_data;
            4 : humi_temp_byte <= rd_data;
            5 : temp_mid       <= rd_data;
            6 : temp_low       <= rd_data;
            7 : CRC            <= rd_data;
            default: ;
        endcase
        // templete_data[39-(cnt_byte-2)*8 -:8] <= rd_data;
    end 
end

//data_vld_r
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        data_vld_r <= 'd0;
    end 
    else begin 
        data_vld_r <= {data_vld_r[0],read2vld};
    end 
end

//流水线处理
always @(*)begin 
    if(!rst_n)begin
        humi_data_r = 'd0;
        temp_data_r = 'd0;
    end 
    else if(data_vld_r[0])begin 
        humi_data_r = {humi_high,humi_mid,humi_temp_byte[7:4]};
        temp_data_r = {humi_temp_byte[3:0],temp_mid,temp_low};
    end 
    else begin
        humi_data_r = 'd0;
        temp_data_r = 'd0;
    end 
end

always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        humi_data <= 'd0;
        temp_data <= 'd0;
        data_vld <= 'd0;
    end 
    else if(data_vld_r[0])begin 
        //RH[%] = (Srh / 2^^20) * 100% -- 扩大10倍
        //2^^9 + 2^^8 + 2^^7 + 2^^6 + 2^^5 + 2^^3 == 1000
        humi_data <= ((humi_data_r<<9) + (humi_data_r<<8) + (humi_data_r<<7) + 
                    (humi_data_r<<6) + (humi_data_r<<5) + (humi_data_r<<3))>>20;
        //T(℃) = (St / 2^^20) * 200 - 50--扩大10倍
        //2^^10 + 2^^9 + 2^^8 + 2^^7 + 2^^6 + 2^^4 == 2000
        temp_data <= (((temp_data_r<<10) +  (temp_data_r<<9) + (temp_data_r<<8) + (temp_data_r<<7) +
                     (temp_data_r<<6) + (temp_data_r<<4))>>20) - 500;
        data_vld <= 1'b1;
    end 
    else begin 
        data_vld <= 1'b0;
    end 
end


endmodule

iic_intf.v

cpp 复制代码
module iic_intf#(parameter IIC_RATE = 100_000,
                           SYS_CLOCK = 50_000_000
)( 
    //user
    input               clk       ,
    input               rst_n     ,
    input               start_en  ,//读/写使能信号
    input       [3:0]   cmd       ,//命令信号
    input       [7:0]   wr_data   ,//写入的数据
    output  reg [7:0]   rd_data   ,//读出的数据
    output              trans_done,//传输1字节完成的脉冲信号
    //iic
    output  reg         iic_scl   ,
    input               sda_in    ,
    output  reg         sda_out   ,
    output  reg         sda_oe    
    
);
//---------<Parameter definition>-----------------------------------
localparam  IDLE    = 7'b000_0001,//空闲状态
            START   = 7'b000_0010,//起始信号状态
            WR_DATA = 7'b000_0100,//写数据状态
            R_ACK   = 7'b000_1000,//接收应答状态
            RD_DATA = 7'b001_0000,//读数据状态
            S_ACK   = 7'b010_0000,//发送应答状态
            STOP    = 7'b100_0000;//停止信号状态

localparam SCL_MAX = SYS_CLOCK/IIC_RATE;
localparam HALF_SCL = SCL_MAX>>1,
           CHANGE_TIME = SCL_MAX>>2,
           SAMPLE_TIME = HALF_SCL + CHANGE_TIME;

//---------<Internal signal definition>-----------------------------
reg     [6:0]   state_c ;
reg     [6:0]   state_n ;
reg		[8:0]	cnt_scl	   ;//串行时钟计数器
wire			add_cnt_scl;
wire			end_cnt_scl;
reg		[2:0]	cnt_bit	   ;//比特计数器
wire			add_cnt_bit;
wire			end_cnt_bit;

//状态转移条件 
wire        idle2start   ;
wire        idle2wr_data ;
wire        idle2rd_data ;
wire        start2wr_data;
wire        wr_data2r_ack;
wire        r_ack2idle   ;
wire        r_ack2stop   ;
wire        rd_data2s_ack;
wire        s_ack2idle   ;
wire        s_ack2stop   ;
wire        stop2idle    ;

//*******************************************************************
//--State Machine
//*******************************************************************
//第一段:同步时序描述状态转移
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(idle2wr_data)
                state_n = WR_DATA;
            else if(idle2rd_data)
                state_n = RD_DATA;
            else 
                state_n = state_c;
        end 
        START   : begin 
            if(start2wr_data)
                state_n = WR_DATA;
            else 
                state_n = state_c;
        end 
        WR_DATA : begin 
            if(wr_data2r_ack)
                state_n = R_ACK;
            else 
                state_n = state_c;
        end 
        R_ACK   : begin 
            if(r_ack2idle)
                state_n = IDLE;
            else if(r_ack2stop)
                state_n = STOP;
            else 
                state_n = state_c;
        end 
        RD_DATA : begin 
            if(rd_data2s_ack)
                state_n = S_ACK;
            else 
                state_n = state_c;
        end 
        S_ACK   : begin 
            if(s_ack2idle)
                state_n = IDLE;
            else if(s_ack2stop)
                state_n = STOP;
            else 
                state_n = state_c;
        end 
        STOP    : begin 
            if(stop2idle)
                state_n = IDLE;
            else 
                state_n = state_c;
        end 
        default : ;
    endcase
end

assign idle2start    = (state_c == IDLE) && start_en && cmd[0];
assign idle2wr_data  = (state_c == IDLE) && start_en && cmd[1];
assign idle2rd_data  = (state_c == IDLE) && start_en && cmd[2];
assign start2wr_data = (state_c == START) && end_cnt_scl;
assign wr_data2r_ack = (state_c == WR_DATA) && end_cnt_bit;
assign r_ack2idle    = (state_c == R_ACK) && end_cnt_scl && ~cmd[3]; 
assign r_ack2stop    = (state_c == R_ACK) && end_cnt_scl && cmd[3];
assign rd_data2s_ack = (state_c == RD_DATA) && end_cnt_bit; 
assign s_ack2idle    = (state_c == S_ACK) && end_cnt_scl && cmd[3] == 0; 
assign s_ack2stop    = (state_c == S_ACK) && end_cnt_scl && cmd[3];
assign stop2idle     = (state_c == STOP) && end_cnt_scl; 

//cnt_scl
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_scl <= 'd0;
    end 
    else if(add_cnt_scl)begin 
        if(end_cnt_scl)begin 
            cnt_scl <= 'd0;
        end
        else begin 
            cnt_scl <= cnt_scl + 1'b1;
        end 
    end
end 

assign add_cnt_scl = state_c != IDLE;
assign end_cnt_scl = add_cnt_scl && cnt_scl == SCL_MAX - 1;

//cnt_bit
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_bit <= 'd0;
    end 
    else if(add_cnt_bit)begin 
        if(end_cnt_bit)begin 
            cnt_bit <= 'd0;
        end
        else begin 
            cnt_bit <= cnt_bit + 1'b1;
        end 
    end
end 

assign add_cnt_bit = end_cnt_scl && (state_c == WR_DATA || state_c == RD_DATA);
assign end_cnt_bit = add_cnt_bit && cnt_bit == 8 - 1;

//iic_scl
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        iic_scl <= 1'b1;
    end 
    else if(state_c != IDLE)begin 
        if(cnt_scl < HALF_SCL)
            iic_scl <= 1'b0;
        else 
            iic_scl <= 1'b1;
    end 
    else begin 
        iic_scl <= 1'b1;
    end 
end

//sda_out sda_oe
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        sda_oe <= 1'b0;
        sda_out <= 1'b1;
    end 
    else begin 
        case (state_c)
            IDLE    : begin
                sda_oe <= 1'b0;
                sda_out <= 1'b1;
            end
            START   : begin
                sda_oe <= 1'b1;
                sda_out <= (cnt_scl < SAMPLE_TIME) ? 1'b1 : 1'b0;
                // if(cnt_scl < SAMPLE_TIME)
                //     sda_out <= 1'b1;
                // else 
                //     sda_out <= 1'b0;
            end
            WR_DATA : begin
                sda_oe <= 1'b1;
                if(cnt_scl == CHANGE_TIME - 1)//并转串
                    sda_out <= wr_data[7-cnt_bit];
                else 
                    sda_out <= sda_out;
            end
            R_ACK   : begin
                sda_oe <= 1'b0;
                sda_out <= 1'b1;
            end
            RD_DATA : begin
                sda_oe <= 1'b0;
                sda_out <= 1'b1;
            end
            S_ACK   : begin
                sda_oe <= 1'b1;
                if(cnt_scl == CHANGE_TIME - 1)
                    sda_out <= cmd[3] ? 1'b1 : 1'b0;
            end
            STOP    : begin
                sda_oe <= 1'b1;
                if(cnt_scl == CHANGE_TIME - 1)
                    sda_out <= 1'b0;
                else if(cnt_scl == SAMPLE_TIME - 1)
                    sda_out <= 1'b1;
            end 
            default: ;
        endcase
    end 
end

//rd_data
always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        rd_data <= 'd0;
    end 
    else if(state_c == RD_DATA && cnt_scl == SAMPLE_TIME - 1)begin // 串转并
        rd_data[7-cnt_bit] <= sda_in;
    end 
end

//trans_done
assign trans_done = r_ack2idle | s_ack2idle | stop2idle;
    
endmodule

五、实验现象

以下就是串口助手显示的温湿度数据。


以上就是AHT20温湿度读取并在串口显示(IIC协议)。(如果有错误,还请大家指出来,谢谢!)