FPGA读取AHT20温湿度模块思路及实现,包含遇到的问题(IIC协议)

发布于:2025-08-16 ⋅ 阅读:(21) ⋅ 点赞:(0)

一.阅读官方手册

手册在下方网址下载,该模块在各个网店平台均有销售

百度网盘 请输入提取码

手册重点关注IIC地址(读地址0x71,写地址0x70)、IIC命令和读写数据逻辑,手册写的比较简单(感觉很多细节没到位),可以自己去看看

二.设计模块思路,分层次设计

1.上层,模块控制(aht20_control.v)

我在一开始的时候模块分为3个功能,IDLE(复位和刚上电时状态)、初始化、读取数据(实际上后面发现这样划分存在一些问题)

我一开始根据手册是这样划分功能的

IDLE:上电和复位时各个引脚和寄存器应该配的值

延时40ms:手册上提到该模块上电后要等待20ms才会进入空闲态,我们这里等待40ms更加稳定

初始化:发送初始化命令,查看使能校准位是否为1,为1后我们再进入读取状态

读取:此时发送读取数据指令和读数据,在这里循环

在实际实现中我发现了几个问题

1.首先是使能校验位的问题,在没有正确初始化的时候使能校验位仍然显示正常,即Bit[3]==1;

发现这个问题的原因在下表中的记录的状态寄存器值

0001_1000:刚上电时读取值,可以看到此时Bit[3]==1,虽然说明已校准,但我在发送读取指令的时候是发送不过去的,读取到的数据全为0

1101_0111:发送初始化命令后马上读状态寄存器 Bit[3]==0 推测是初始化进行中,需等待

0001_1000:发送初始化命令后隔一会读状态寄存器,Bit[3]==1,说明已校准

通过上面的记录我们可以发现,查看状态寄存器如果显示校准,但不一定能读取指令

那我们如何判断初始化是否正常呢?

我发现初始化失败的通信,往往主机在发送iic时,在发送数据结束时,收到的ack为1,即iic发送消息从机未应答(未拉低sda),我们可以通过判断在iic的一次发送中ack是否恒为低电平,如果ack采集到高电平,那么证明发送失败,那我们需要重新发送初始化命令

2.每次发送完采集数据指令后需重新初始化,这一点手册上我并没有找到

在初始化后,我发现我指令只能发送一次完整的采集指令,重复发送采集指令ack会被置1,即iic通信失败;经过尝试,我选择了重复初始化+采集的步骤

最后实现的时序框图如下:

2.底层,IIC通信的实现(iic.v  、cmd.v)

设计思路:对于IIC通信,我们外部需要配置和得到的有指令和地址,读取的数据个数,写数据个数

可以将其写成这两种情况:

1.写:发送写地址,传入写数据

2.读:发送写地址,发送要读的寄存器地址,发送读地址,读数据

我们分两层模块实现:cmd模块查看IIC进行到哪一步,然后切换指令和读数据;iic模块将指令转化为SDA的高低电平,或者读取SDA高低电平存放在一个字节中,改变SCL,判断从机是否响应

下图实现了一个简单的iic控制逻辑,start置1后启动一次iic(start使用完后记得置0),iic发送的内容是根据此时select选择的功能实现,例如我设置select=0时,初始化AHT20;select=1时,查看状态寄存器;select=2时,发送指令采集温湿度;select=3时,获取温湿度数据;

实际上层不止start、select两个信号,图中省略了,例如cmd模块需要在读取完成时传回读取到的数据,也可以添加iic是否通信失败的信号,通信失败传一个error信号,上层就可以根据error信号来进行重传操作等

3.串口通信,将数据发送至电脑端

分为了串口发送模块和串口控制模块,串口发送模块的代码在网上很多,自己实现也比较简单;串口控制是数据按照我指定的格式发送,例如"WET:63.5%TEM:34.0/n",实现起来也比较简单,可以查看我的代码部分学习

三.Verilog代码设计

1.模块控制(aht20_control.v)

功能:代码采用了三段式状态机,同时在状态机里面加了一些时序的处理,iic启动的逻辑,串口发送启动的逻辑,同时对接收到的原始数据进行了处理,得到处理后的温湿度数据

//负责控制时序
module aht20_control (
    input  sys_clk   ,
    input  rst_n     ,
    input  [7:0] read_data,
    input  iic_done ,
    input  done_recv,
    input  ack,
    output reg iic_start,
    output reg [1:0] select ,
    output reg [15:0] temper ,
    output reg [15:0] wet    ,
    output reg start_send   ,
    output [3:0] led
);  
    parameter CNT_TIMER=49_999_999;//1s测一次温湿度
    parameter CNT_40MS = 1_999_999;//40ms
    parameter CNT_10MS = 499_999;//10MS
    reg [27:0] cnt_time;//1s计时器
    reg [24:0] cnt_40ms;//40ms计时器
    reg [22:0] cnt_10ms;//40ms计时器

    reg ack_reg;
    reg ack_reg1;
    reg [4:0] cnt_recv;//查看接收到第几位

    
    reg [19:0] temper_reg;//温度值
    reg [19:0] wet_reg;//湿度值

    reg done_recv_reg;
    always @(posedge sys_clk) begin
        done_recv_reg<=done_recv;
    end
    reg [3:0] cur_state,next_state;
    assign led=cur_state[3:0];
    parameter IDLE          = 4'b0000,
              WAIT_40MS     = 4'b0001,//等待40ms
              INIT          = 4'b0011,//初始化
              WAIT_10MS     = 4'b0010,//初始化后等待10MS,同时查看INIT命令ACK是否常为0
              CAPTURE       = 4'b0110,//发送采集命令
              WAIT_500MS    = 4'b0111,//等待500ms
              GET_DATA      = 4'b0101,//得到数据
              WAIT_1S       = 4'b0100;//等到1s重复上面步骤
    always @(posedge sys_clk) begin
        if(!rst_n)
            cur_state<=IDLE;
        else
            cur_state<=next_state;
    end
    //状态跳转逻辑
    always @(*) begin
        if (!rst_n)
            next_state=IDLE;
        else
            case (cur_state)
                IDLE          : next_state=WAIT_40MS;
                WAIT_40MS     : if(cnt_40ms==CNT_40MS) 
                                    next_state=INIT;
                                else
                                    next_state=WAIT_40MS;
                INIT          : if(iic_done) 
                                    next_state=WAIT_10MS;
                                else
                                    next_state=INIT;
                WAIT_10MS     : if(ack_reg1)
                                    next_state=WAIT_40MS;
                                else if(cnt_10ms==CNT_10MS)
                                    next_state=CAPTURE;
                                else
                                    next_state=WAIT_10MS;
                CAPTURE       : if(iic_done) 
                                    next_state=WAIT_500MS;
                                else
                                    next_state=CAPTURE;
                WAIT_500MS    : if(cnt_time>CNT_TIMER/2) 
                                    next_state=GET_DATA;
                                else
                                    next_state=WAIT_500MS;
                GET_DATA      : if(iic_done) 
                                    next_state=WAIT_1S;
                                else
                                    next_state=GET_DATA;
                WAIT_1S       : if(cnt_time>=CNT_TIMER) 
                                    next_state=WAIT_40MS;
                                else
                                    next_state=WAIT_1S;
                default: next_state=IDLE;
            endcase
    end
    always @(posedge sys_clk) begin
        if(!rst_n)begin
            iic_start     <=0   ;//启动iic
            select        <=0   ;//选择调用的命令
            temper        <=0   ;//输出的温度
            wet           <=0   ;//输出的湿度
            start_send    <=0   ;//输出传输标志,用于触发串口传输
            temper_reg    <=0   ;//温度值
            wet_reg       <=0   ;//湿度值
            cnt_time      <=0   ;//1s计时器
            cnt_40ms      <=0   ;//40ms计时器
            cnt_10ms      <=0   ;//10ms计时器
            ack_reg       <=0   ;
            ack_reg1      <=0   ;
            cnt_recv      <=0   ;
        end
        else begin
            if(cur_state==IDLE)begin
                iic_start     <=0   ;//启动iic
                select        <=0   ;//选择调用的命令
                temper        <=0   ;//输出的温度
                wet           <=0   ;//输出的湿度
                start_send    <=0   ;//输出传输标志,用于触发串口传输
                temper_reg    <=0   ;//温度值
                wet_reg       <=0   ;//湿度值
                cnt_time      <=0   ;//1s计时器
                cnt_40ms      <=0   ;//40ms计时器
                cnt_10ms      <=0   ;//10ms计时器
                ack_reg       <=0   ;
                ack_reg1      <=0   ;
                cnt_recv      <=0   ;
            end
            else if(cur_state==WAIT_40MS)begin
                iic_start     <=0   ;//启动iic
                select        <=0   ;//选择调用的命令
                temper<=temper;//输出的温度
                wet<=wet;//输出的湿度
                start_send    <=0   ;//输出传输标志,用于触发串口传输
                temper_reg    <=temper_reg   ;//温度值
                wet_reg       <=wet_reg   ;//湿度值
                if(cnt_time>CNT_TIMER)
                    cnt_time<=0;
                else
                    cnt_time<=cnt_time+1;//1s计时器
                if(cnt_40ms<CNT_40MS)
                    cnt_40ms<=cnt_40ms+1;    //40ms计时器
                else if(cnt_40ms==CNT_40MS)
                    cnt_40ms<=cnt_40ms;
                else
                    cnt_40ms<=0;
                cnt_10ms      <=0   ;//10ms计时器
                ack_reg       <=0   ;
                ack_reg1      <=0   ;
                cnt_recv      <=0   ;
            end
            else if(cur_state==INIT)begin
                iic_start<=1;//启动iic
                select        <=0   ;//选择调用的命令
                temper<=temper;//输出的温度
                wet<=wet;//输出的湿度
                start_send    <=0   ;//输出传输标志,用于触发串口传输
                temper_reg    <=temper_reg   ;//温度值
                wet_reg       <=wet_reg   ;//湿度值
                if(cnt_time>CNT_TIMER)
                    cnt_time<=0;
                else
                    cnt_time<=cnt_time+1;//1s计时器
                cnt_40ms      <=0   ;//40ms计时器
                cnt_10ms      <=0   ;//10ms计时器
                if(ack==1)
                    ack_reg<=1;
                else
                    ack_reg<=ack_reg;
                ack_reg1<=ack_reg;
                cnt_recv      <=0   ;
            end
            else if(cur_state==WAIT_10MS)begin
                iic_start     <=0   ;//启动iic
                select        <=0   ;//选择调用的命令
                temper        <=temper   ;//输出的温度
                wet           <=wet   ;//输出的湿度
                start_send    <=0   ;//输出传输标志,用于触发串口传输
                temper_reg    <=temper_reg   ;//温度值
                wet_reg       <=wet_reg   ;//湿度值
                if(cnt_time>CNT_TIMER)
                    cnt_time<=0;
                else
                    cnt_time<=cnt_time+1;//1s计时器
                cnt_40ms      <=0   ;//40ms计时器
                if(cnt_10ms<CNT_10MS)
                    cnt_10ms<=cnt_10ms+1;
                else if(cnt_10ms==CNT_10MS)
                    cnt_10ms<=cnt_10ms;
                else
                    cnt_10ms<=0;
                ack_reg<=0   ;
                ack_reg1<=ack_reg1  ;//reg1记录了上一状态的ack值,如果ack至少有1个时钟为1,那么重发命令
                cnt_recv      <=0   ;
            end
            else if(cur_state==CAPTURE)begin
                iic_start     <=1   ;//启动iic
                select        <=2   ;//选择调用的命令
                temper<=temper;//输出的温度
                wet<=wet;//输出的湿度
                start_send    <=0   ;//输出传输标志,用于触发串口传输
                temper_reg    <=temper_reg   ;//温度值
                wet_reg       <=wet_reg   ;//湿度值
                if(cnt_time>CNT_TIMER)
                    cnt_time<=0;
                else
                    cnt_time<=cnt_time+1;//1s计时器
                cnt_40ms      <=0   ;//40ms计时器
                cnt_10ms      <=0   ;//10ms计时器
                ack_reg       <=0   ;
                ack_reg1      <=0   ;
                cnt_recv      <=0   ;
            end
            else if(cur_state==WAIT_500MS)begin
                iic_start     <=0   ;//启动iic
                select        <=0   ;//选择调用的命令
                temper<=temper;//输出的温度
                wet<=wet;//输出的湿度
                start_send    <=0   ;//输出传输标志,用于触发串口传输
                temper_reg    <=temper_reg   ;//温度值
                wet_reg       <=wet_reg   ;//湿度值
                cnt_time<=cnt_time+1;//1s计时器
                cnt_40ms      <=0   ;//40ms计时器
                cnt_10ms      <=0   ;//10ms计时器
                ack_reg       <=0   ;
                ack_reg1      <=0   ;
                cnt_recv      <=0   ;
            end
            else if(cur_state==GET_DATA)begin
                iic_start     <=1   ;//启动iic
                select        <=3   ;//选择调用的命令
                temper<=temper;//输出的温度
                wet<=wet;//输出的湿度
                start_send    <=0   ;//输出传输标志,用于触发串口传输
                if(done_recv)begin
                    if(cnt_recv==1)
                        wet_reg[19:12]<=read_data;
                    else if(cnt_recv==2)
                        wet_reg[11:4]<=read_data;
                    else if(cnt_recv==3)begin
                        wet_reg[3:0]<=read_data[7:4];
                        temper_reg[19:16]<=read_data[3:0];
                    end
                    else if(cnt_recv==4)
                        temper_reg[15:8]<=read_data;
                    else if(cnt_recv==5)
                        temper_reg[7:0]<=read_data;
                end
                    
                
                cnt_time<=cnt_time+1;//1s计时器
                cnt_40ms      <=0   ;//40ms计时器
                cnt_10ms      <=0   ;//10ms计时器
                ack_reg       <=0   ;
                ack_reg1      <=0   ;
                if(done_recv_reg)
                    cnt_recv<=cnt_recv+1;
            end
            else if(cur_state==WAIT_1S)begin
                iic_start     <=0   ;//启动iic
                select        <=0   ;//选择调用的命令
                temper<=temper_tem[15:0];//输出的温度
                wet<=wet_tem[15:0];//输出的湿度
                if(cnt_time==CNT_TIMER-3)
                    start_send    <=1   ;//输出传输标志,用于触发串口传输
                else
                    start_send    <=0   ;
                temper_reg    <=temper_reg   ;//温度值
                wet_reg       <=wet_reg    ;//湿度值

                cnt_time<=cnt_time+1;//1s计时器
                cnt_40ms      <=0   ;//40ms计时器
                cnt_10ms      <=0   ;//10ms计时器
                ack_reg       <=0   ;
                ack_reg1      <=0   ;
            end
        end
    end
    wire [26:0] temper_tem;
    assign temper_tem=temper_reg*2000/1024/1024-500;
    wire [26:0] wet_tem;
    assign wet_tem=wet_reg*1000/1024/1024;

);
endmodule

2.IIC模块(iic.v)

功能:实现了iic模块的通信,通过启动信号能够启动iic传、收数据,这里需要注意的是,sda和scl需配置为inout类型,同时添加en信号使其为输出的值和切换高阻态1'bz(接收值)

//iic协议
module iic(
    input                       sysclk          ,
    input                       rst_n           ,
    input                       start           ,//开始信号
    input                       worr            ,//读写使能--表示读写方向的   worr==0--->只有写   worr==1--->只有读   worr先是0再是1--->读写都有(多字节处理)
    input           [7:0]       sendnum         ,//写的数据个数   -->控制循环-->看到底写几个数据
    input           [7:0]       recvnum         ,//读的数据个数   -->控制循环-->看到底读几个数据
    input           [7:0]       data_in         ,//需要写的数据
    output     reg  [7:0]       data_out        ,//读数据-->通过iic读出的数据
    output                      done_recv       ,//读数据结束信号
    output                      done_send       ,//写数据的结束信号
    output                      done_iic        ,//iic的工作结束信号
    inout                       sda             ,//iic的数据总线
    inout                       scl             ,//iic的时钟总线
    output                      ack             ,
    output [3:0] led 
    );
    parameter   clk         = 50_000_000    ;//1s内部时钟周期数
    parameter   iicclk      = 100_000       ;//iic工作时钟  -->100Khz
    parameter   delay       = clk/iicclk    ;//iic的工作周期
    parameter   MID         = delay/2       ;//iic周期的中点位置
    parameter   Q_MID       = delay/4       ;//iic周期的  1/4
    parameter   TQ_MID      = MID + Q_MID   ;//iic周期的  3/4

    //--------?
    parameter   IDW         = 8'h70         ;//从机设备写地址
    parameter   IDR         = 8'h71         ;//从机设备读地址

    reg [7:0]   cnt_send                    ;//写数据的计数器-->看写状态写了多少个数据
    reg [7:0]   cnt_recv                    ;//读数据的计数器-->看读状态读了多少个数据
    reg [7:0]   data_in_reg                 ;//写数据的寄存器-->为了防止数据出问题
    reg         worr_reg                    ;//读写方向寄存器
    reg [1:0]   start_reg                   ;//开始信号寄存器
    reg         ack_flag                    ;//应答寄存器
    reg [8:0]   cnt                         ;//iic的工作周期计数器
    reg [3:0]   cnt_bit                     ;//数据位计数器-->表示具体传输到了哪一个bit了

    assign ack = ack_flag;
    //三态门
    reg         sda_en      ;//工作使能
    reg         sda_out     ;    
    wire        sda_in      ;

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

    reg         scl_en      ;//工作使能
    reg         scl_out     ;    
    wire        scl_in      ;

    assign scl_in = scl;
    assign scl    = scl_en?scl_out:1'bz;

    //开始信号说明--》用于检测沿和电平
    always@(posedge sysclk)
        if(!rst_n)
            start_reg<=0;
        else 
            start_reg<={start_reg[0],start};// 01 11 10 00

    //状态声明
    parameter IDLE      = 4'd0  ,//空闲态
              START     = 4'd1  ,//开始状态
              ID        = 4'd2  ,//0010
              ACK1      = 4'd3  ,
              SENDDATA  = 4'd4  ,//0100
              ACK2      = 4'd5  ,//0101
              STOP      = 4'd6  ,
              RECVDATA  = 4'd7  ,
              ACK3      = 4'd8  ,
              START1    = 4'd9  ,
              ID1       = 4'd10 ,
              ACK4      = 4'd11 ;

    reg [3:0] cur_state,next_state;

    //三段式状态机第一段
    always@(posedge sysclk)
        if(!rst_n)  
            cur_state<=IDLE;
        else
            cur_state<=next_state;

    //三段式状态机第二段
    always@(*)
        if(!rst_n)
            next_state=IDLE;
        else
            case(cur_state)
                IDLE     :begin
                            if(start_reg==2'b11&&(sendnum!=0||recvnum!=0))//检测到高电平
                                next_state=START;
                            else
                                next_state=cur_state;
                end

                START    :begin
                            if(cnt==delay-1)//一个iic的工作周期就跳转
                                next_state=ID;
                            else
                                next_state=cur_state;
                end

                ID       :begin
                            if(cnt_bit==7 && cnt==delay-1)//传完一个寻址地址(从机地址+读写方向位)
                                next_state=ACK1;
                            else
                                next_state=cur_state;    
                end

                ACK1     :begin
                            if(ack_flag==1)//应答无效
                                next_state=IDLE;//回到IDLE-->重新等待
                            else if(ack_flag==0 && worr_reg==1 &&cnt==delay-1)//应答有效 主机读 一个iic周期结束
                                next_state=RECVDATA;
                            else if(ack_flag==0 && worr_reg==0 &&cnt==delay-1)//应答有效 主机写 一个iic周期结束
                                next_state=SENDDATA;
                            else//除了以上条件
                                next_state=cur_state; 
                end

                SENDDATA :begin
                            if(cnt_bit==7 && cnt==delay-1)//写完一次数据
                                next_state=ACK2;
                            else
                                next_state=cur_state;    
                end

                ACK2     :begin
                            //if(ack_flag==1)//应答无效
                            //    next_state=IDLE;//回到IDLE-->重新等待
                            /*else if(ack_flag==0 && cnt==delay-1 && sendnum==0 && recvnum==0)//应答有效 一个iic周期结束 写完 没有读
                                next_state=IDLE;*/
                            if(cnt==delay-1 && cnt_send==sendnum && recvnum==0)//应答有效 一个iic周期结束 写完 没有读
                                next_state=STOP;
                            else if(cnt==delay-1 && cnt_send==sendnum && recvnum!=0)//应答有效 一个iic周期结束 写完 有读
                                next_state=START1;
                            else if(cnt==delay-1 && cnt_send<sendnum)//应答有效 一个iic周期结束  没有写完
                                next_state=SENDDATA;
                            else//除了以上条件
                                next_state=cur_state; 
                end

                STOP     :begin
                            if(cnt==delay-1)
                                next_state=IDLE;
                            else
                                next_state=cur_state;
                end

                RECVDATA :begin
                            if(cnt_bit==7 && cnt==delay-1)//读完一次数据
                                next_state=ACK3;
                            else
                                next_state=cur_state;    
                end

                ACK3     :begin
                            if(cnt==delay-1 && cnt_recv==recvnum)//iic一个工作周期  读完
                                next_state=STOP;
                            else if(cnt==delay-1 && cnt_recv<recvnum)//iic一个工作周期  没有读完
                                next_state=RECVDATA;
                            else    
                                next_state=cur_state;   
                end

                START1   :begin
                            if(cnt==delay-1)//一个iic的工作周期就跳转
                                next_state=ID1;
                            else
                                next_state=cur_state;
                end

                ID1      :begin
                            if(cnt_bit==7 && cnt==delay-1)//传完一个寻址地址(从机地址)
                                next_state=ACK4;
                            else
                                next_state=cur_state;    
                end

                ACK4     :begin
                            if(ack_flag==1)//应答无效
                                next_state=IDLE;
                            else if(ack_flag==0 && cnt==delay-1 )//iic一个工作周期  应答有效
                                next_state=RECVDATA;
                            else    
                                next_state=cur_state;   
                end     

                default  :next_state=IDLE;
            endcase
                    
    //三段式状态机第三段
    always@(posedge sysclk)
        if(!rst_n)begin
            cnt_send       <=0;//写数据的计数器
            cnt_recv       <=0;//读数据的计数器
            data_in_reg    <=0;//写数据的寄存器
            worr_reg       <=0;//读写方向的寄存器
            ack_flag       <=0;//应答信号的寄存器
            cnt            <=0;//iic的工作周期计数器
            cnt_bit        <=0;//bit位计数器
            data_out       <=0;//读出去的数据
            sda_en         <=0;//数据使能
            sda_out        <=0;//数据输出
            scl_en         <=0;//时钟使能
            scl_out        <=0;//时钟输出
        end
        else
            case(cur_state)
                IDLE      :begin
                            cnt_send       <=0;//写数据的计数器
                            cnt_recv       <=0;//读数据的计数器
                            data_in_reg    <=data_in;//写数据的寄存器    寄存数据
                            worr_reg       <=worr;//读写方向的寄存器
                            ack_flag       <=0;//应答信号的寄存器
                            cnt            <=0;//iic的工作周期计数器
                            cnt_bit        <=0;//bit位计数器
                            data_out       <=0;//读出去的数据
                            sda_en         <=1;//数据使能
                            sda_out        <=1;//数据输出
                            scl_en         <=1;//时钟使能
                            scl_out        <=1;//时钟输出
                end

                START     :begin
                            cnt_send       <=0;//写数据的计数器
                            cnt_recv       <=0;//读数据的计数器
                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持
                            ack_flag       <=0;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            cnt_bit        <=0;//bit位计数器
                            data_out       <=0;//读出去的数据

                            sda_en         <=1;//数据使能--》开关打开
                            if(cnt<=Q_MID)//前1/4个周期
                                sda_out        <=1;//数据输出
                            else
                                sda_out        <=0;

                            scl_en         <=1;//时钟使能
                            if(cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                ID        :begin
                            cnt_send       <=0;//写数据的计数器
                            cnt_recv       <=0;//读数据的计数器
                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持
                            ack_flag       <=0;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            if(cnt==delay-1)
                                cnt_bit        <=cnt_bit+1;//bit位计数器

                            data_out       <=0;//读出去的数据

                            sda_en         <=1;//数据使能--》开关打开
                            if(worr_reg==0 && cnt==1)//如果是写方向
                                sda_out        <=IDW[7-cnt_bit];//数据输出
                            else if(worr_reg==1 && cnt==1) //如果是读方向
                                sda_out        <=IDR[7-cnt_bit];
                            else
                                sda_out        <=sda_out;

                            scl_en         <=1;//时钟使能
                            if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                ACK1      :begin
                            cnt_send       <=0;//写数据的计数器
                            cnt_recv       <=0;//读数据的计数器
                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持

                            if(cnt==MID)
                                ack_flag       <=sda_in;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            
                            cnt_bit        <=0;//bit位计数器

                            data_out       <=0;//读出去的数据

                            sda_en         <=0;
                            sda_out        <=1;

                            scl_en         <=1;//时钟使能
                            if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                SENDDATA  :begin
                            //cnt_send       <=cnt_send;//写数据的计数器-->保持
                            cnt_recv       <=0;//读数据的计数器
                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持

                            ack_flag       <=0;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            
                            if(cnt==delay-1)
                                cnt_bit        <=cnt_bit+1;//bit位计数器

                            data_out       <=0;//读出去的数据

                            sda_en         <=1;
                            if(cnt==1)
                                sda_out        <=data_in_reg[7-cnt_bit];
                            
                            scl_en         <=1;//时钟使能
                            if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                ACK2      :begin
                            if(cnt==MID)
                                cnt_send       <=cnt_send+1;//写数据的计数器

                            cnt_recv       <=0;//读数据的计数器

                            data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              ------保持

                            if(cnt==MID)
                                ack_flag       <=sda_in;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            
                            cnt_bit        <=0;//bit位计数器

                            data_out       <=0;//读出去的数据

                            sda_en         <=0;
                            sda_out        <=1;

                            scl_en         <=1;//时钟使能
                            if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                STOP      :begin
                            cnt_send       <=0;//写数据的计数器

                            cnt_recv       <=0;//读数据的计数器
                            
                            data_in_reg    <=0;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=0;//读写方向的寄存器              -------保持


                            ack_flag       <=0;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            
                            cnt_bit        <=0;//bit位计数器

                            data_out       <=0;//读出去的数据

                            sda_en         <=1;
                            if(cnt<=TQ_MID)//  3/4
                                sda_out        <=0;
                            else
                                sda_out        <=1;

                            scl_en         <=1;//时钟使能
                            if(cnt<=Q_MID)//1/4
                                scl_out        <=0;//时钟输出
                            else
                                scl_out        <=1;
                end

                RECVDATA  :begin
                            //cnt_send       <=0;//写数据的计数器-->保持
                            //cnt_recv       <=cnt_recv;//读数据的计数器
                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持

                            ack_flag       <=0;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            
                            if(cnt==delay-1)
                                cnt_bit        <=cnt_bit+1;//bit位计数器

                            if(cnt==MID)
                                data_out[7-cnt_bit]       <=sda_in;//读出去的数据

                            sda_en         <=0;
                            sda_out        <=1;

                            scl_en         <=1;//时钟使能
                            if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                ACK3      :begin
                            //cnt_send       <=cnt_send+1;//写数据的计数器
                            if(cnt==MID)
                                cnt_recv       <=cnt_recv+1;//读数据的计数器

                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持

                            ack_flag       <=0;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            
                            cnt_bit        <=0;//bit位计数器

                            data_out       <=data_out;//读出去的数据

                            sda_en         <=1;
                            sda_out        <=0;

                            scl_en         <=1;//时钟使能
                            if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                START1    :begin
                            cnt_send       <=0;//写数据的计数器
                            cnt_recv       <=0;//读数据的计数器
                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持
                            ack_flag       <=0;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            cnt_bit        <=0;//bit位计数器
                            data_out       <=0;//读出去的数据

                            sda_en         <=1;//数据使能--》开关打开
                            if(cnt<=Q_MID)//前1/4个周期
                                sda_out        <=1;//数据输出
                            else
                                sda_out        <=0;

                            scl_en         <=1;//时钟使能
                            if(cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                ID1       :begin
                            cnt_send       <=0;//写数据的计数器
                            cnt_recv       <=0;//读数据的计数器
                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持
                            ack_flag       <=0;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            if(cnt==delay-1)
                                cnt_bit        <=cnt_bit+1;//bit位计数器

                            data_out       <=0;//读出去的数据

                            sda_en         <=1;//数据使能--》开关打开
                            if(cnt==1) //如果是读方向
                                sda_out        <=IDR[7-cnt_bit];
                            else
                                sda_out        <=sda_out;

                            scl_en         <=1;//时钟使能
                            if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                ACK4      :begin
                            cnt_send       <=0;//写数据的计数器
                            cnt_recv       <=0;//读数据的计数器

                            //data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持
                            worr_reg       <=worr;//读写方向的寄存器              -------保持

                            if(cnt==MID)
                                ack_flag       <=sda_in;//应答信号的寄存器

                            if(cnt==delay-1)//iic的工作周期计数器
                                cnt            <=0;
                            else
                                cnt            <=cnt+1;

                            
                            cnt_bit        <=0;//bit位计数器

                            data_out       <=0;//读出去的数据

                            sda_en         <=0;
                            sda_out        <=1;

                            scl_en         <=1;//时钟使能
                            if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平
                                scl_out        <=1;//时钟输出
                            else
                                scl_out        <=0;
                end

                default   :begin
                            cnt_send       <=0;//写数据的计数器
                            cnt_recv       <=0;//读数据的计数器
                            data_in_reg    <=data_in;//写数据的寄存器    寄存数据
                            worr_reg       <=worr;//读写方向的寄存器
                            ack_flag       <=0;//应答信号的寄存器
                            cnt            <=0;//iic的工作周期计数器
                            cnt_bit        <=0;//bit位计数器
                            data_out       <=0;//读出去的数据
                            sda_en         <=0;//数据使能
                            sda_out        <=1;//数据输出
                            scl_en         <=0;//时钟使能
                            scl_out        <=1;//时钟输出
                end
            endcase   

    assign    done_recv   = (cur_state==ACK3 && cnt==MID)?1:0;
    assign    done_send   = (cur_state==ACK2 && cnt==MID)?1:0;
    assign    done_iic    = (cur_state==STOP && cnt==delay-30)?1:0;

endmodule

3.指令控制模块(cmd.v)

功能:用于切换指令,以及确定每条指令需要收发多少条数据

//负责切换命令、控制发送接收数据数量
module cmd(
    input                   sys_clk         ,
    input                   rst_n           ,
    input   [7:0]           data_iic        ,//iic数据
    input                   done_recv       ,//iic接收完一个字节
    input                   done_send       ,//iic发送完一个字节
    input                   done_iic        ,//iic结束
    input   [1:0]           select          ,

    output  reg             worr            ,//读写位控制
    output  reg [7:0]       sendnum         ,//发送几个数据
    output  reg [7:0]       recvnum         ,//接收几个数据
    output  reg [7:0]       data_cmd        ,//数据命令
    output  reg             cmd_flag        ,
    input [3:0] led 
    );
    parameter IDLE = 4'b0000,
              INIT = 4'b0001,//初始化AHT20
              CAT  = 4'b0011,//查看状态寄存器
              CAP  = 4'b0010,//采集命令
              GET  = 4'b0110;//获取数据
    reg [3:0] cur_state,next_state;
    reg [3:0] cnt_cmd;
    always @(posedge sys_clk) begin
        if(!rst_n)
            cur_state<=IDLE;
        else
            cur_state<=next_state;
    end
    always @(*) begin
        if(!rst_n)
            next_state=IDLE;
        else
            case (select)
            0 : next_state=INIT;
            1 : next_state=CAT;
            2 : next_state=CAP;
            3 : next_state=GET;
                default: next_state=IDLE;
            endcase
    end
    always @(posedge sys_clk) begin
        if(!rst_n)begin
            worr     <=0;//worr==1读,==0写
            sendnum  <=0;
            recvnum  <=0;
            data_cmd <=8'hFF;
            cnt_cmd  <=0;
        end
        else if(cur_state==INIT)begin//初始化:发送0xBE
            worr<=0;
            sendnum <=3;
            recvnum <=0;
            if(done_send==1)
                cnt_cmd <= cnt_cmd+1;
            else if(done_iic)
                cnt_cmd <= 0;
            else
                cnt_cmd <= cnt_cmd;
            if(cnt_cmd==0)
                data_cmd<=8'hBE;
            else if(cnt_cmd==1)
                data_cmd<=8'h08;
            else if(cnt_cmd==2)
                data_cmd<=8'h00;    
        end
        else if(cur_state==CAT)begin//查看状态寄存器:读地址第一位
            worr<=1;
            sendnum <=0;
            recvnum <=1;
            data_cmd<=8'h71;
            cnt_cmd  <=0;
        end
        else if(cur_state==CAP)begin//发送采集命令:发送0xAC 0x33 0x00
            worr<=0;
            sendnum <=3;
            recvnum <=0;
            if(done_send==1)
                cnt_cmd <= cnt_cmd+1;
            else if(done_iic)
                cnt_cmd <= 0;
            else
                cnt_cmd <= cnt_cmd;
            if(cnt_cmd==0)
                data_cmd<=8'hAC;
            else if(cnt_cmd==1)
                data_cmd<=8'h33;
            else if(cnt_cmd==2)
                data_cmd<=8'h00;
        end
        else if(cur_state==GET)begin//查看6个字节的数据 状态寄存器 湿度 湿度 湿度温度各一半 温度 温度
            worr<=1;
            sendnum <=0;
            recvnum <=6;
            data_cmd<=8'hFF;
            cnt_cmd  <=0;
        end
    end
endmodule

4.串口模块(uart_tx.v、uart_tx_control.v)

功能:实现串口发送指定格式的数据

module uart_tx (
    input clk,
    input [7:0]data,
    input en_flag,
    input rst_n,
    output reg tx,
    output reg tx_done,
	output reg en
);
    reg [4:0] cntw;//0-9帧,0起始位,1-8数据位,9停止位
    reg [22:0] cnt;
    //数据时钟同步
    reg [7:0]data_reg;
    always @(posedge clk) begin
        if(!rst_n)
            data_reg<=0;
        else if(en_flag==1&&en==0)
            data_reg<=data;
        else
            data_reg<=data_reg;
    end
    //使能信号开启 
    always @(posedge clk) begin
        if(!rst_n)
           en<=0;
        else begin
            if(en_flag==1)
                en<=1;
            else if(cntw==9&&cnt==5207)//5207
                en<=0;
            else
                en<=en;
        end
    end

    //数据位和每帧时间计数
    always@(posedge clk)begin
        if(!rst_n)
            cntw<=0;
        else begin
            if(en==1&&cnt==5207)
                cntw<=cntw+1;
            else if(en==0)
                cntw<=0;
            else
                cntw<=cntw;
        end 
    end
    always@(posedge clk)begin
        if(!rst_n)
            cnt<=0;
        else begin
            if(en==1)begin
                if(cnt==5207)
                    cnt<=0;
                else
                    cnt<=cnt+1;
            end
            else
                cnt<=0;
        end 
    end
    //给tx输出赋值
    always @(posedge clk) begin
        if(!rst_n)
            tx<=1;
        else begin
            if(en==1)begin
                if(cntw==0)
                    tx<=0;
                else if(cntw==9)
                    tx<=1;
                else
                    tx<=data_reg[cntw-1];
            end
            else
                tx<=1; 
        end
    end
    //给txdone赋值
	 always @(posedge clk) begin
        if(!rst_n)
            tx_done<=0;
        else begin
            if(cntw==9&&cnt==5207)
					tx_done<=1;
            else
                tx_done<=0;
        end
    end
endmodule
module uart_tx_control (
    input sys_clk,
    input rst_n,
    input [15:0]wet,
    input [15:0]temper,
    input start_send,//发送一次串口数据
    input tx_done,
    input tx_en,
    output reg start_flag,
    output reg [7:0] data
);
    reg [7:0] cnt;
    always @(posedge sys_clk) begin
        if(!rst_n)
            cnt<=0;
        else if(tx_done)
            cnt<=cnt+1;
        else if(start_send)
            cnt<=0;
        else
            cnt<=cnt;
    end
    always @(posedge sys_clk) begin
        if(!rst_n)
            start_flag<=0;
        else begin
            if (start_send)
                start_flag<=1;
            else if(tx_done&&cnt<17)
                start_flag<=1;
            else
                start_flag<=0;
        end
    end
    always @(*) begin
        if(!rst_n)
            data=0;
        else
        case (cnt)
        0   :data=8'h57;//W
        1   :data=8'h45;//E
        2   :data=8'h54;//T
        3   :data=8'h3A;//:
        4   :data=wet/100%10+48;
        5   :data=wet/10%10+48;
        6   :data=8'h2E;//.
        7   :data=wet%10+48;//
        8   :data=8'h25;//%
        9   :data=8'h54;//T
        10  :data=8'h45;//E
        11  :data=8'h4D;//M
        12  :data=8'h3A;//:
        13  :data=temper/100%10+48;
        14  :data=temper/10%10+48;
        15  :data=8'h2E;//.
        16  :data=temper%10+48;
        17  :data=8'h0A;//换行\n
            default: data=0;
        endcase
    end
endmodule

5.顶层模块(top.v)

功能:实现各模块的连接与例化

module top(
        input sys_clk,
        input rst_n,
        inout sda,
        inout scl,
        output uart_txd,
        output [3:0] led 
    );
    
    wire [7:0] read_data,sendnum,recvnum,data_cmd;
    wire [15:0] temper,wet;
    wire [1:0] select;
    aht20_control aht20_control(
    .sys_clk    (sys_clk),
    .rst_n      (rst_n),
    .read_data  (read_data),
    .iic_done   (done_iic),
    .done_recv  (done_recv),
    .ack        (ack),
    .iic_start  (start_iic),
    .select     (select),
    .temper     (temper),
    .wet        (wet),
    .start_send (start_send),
    .led        (led)
    );
    cmd cmd(
    .sys_clk         (sys_clk),
    .rst_n           (rst_n),
    .data_iic        (read_data),//iic数据
    .done_recv       (done_recv),//iic接收完一个字节
    .done_send       (done_send),//iic发送完一个字节
    .done_iic        (done_iic) ,//iic结束
    .select          (select)   ,

    .worr            (worr)     ,//读写位控制
    .sendnum         (sendnum)  ,//发送几个数据
    .recvnum         (recvnum)  ,//接收几个数据
    .data_cmd        (data_cmd) ,//数据命令
    .cmd_flag        () ,
    .led        ()
    );
    iic iic(
    .sysclk          (sys_clk),
    .rst_n           (rst_n),
    .start           (start_iic),//开始信号
    .worr            (worr),     //读写使能--表示读写方向的   worr==0--->只有写   worr==1--->只有读   worr先是0再是1--->读写都有(多字节处理)
    .sendnum         (sendnum),  //写的数据个数   -->控制循环-->看到底写几个数据
    .recvnum         (recvnum),  //读的数据个数   -->控制循环-->看到底读几个数据
    .data_in         (data_cmd), //需要写的数据
    .data_out        (read_data),//读数据-->通过iic读出的数据
    .done_recv       (done_recv),//读数据结束信号
    .done_send       (done_send),//写数据的结束信号
    .done_iic        (done_iic), //iic的工作结束信号
    .sda             (sda),      //iic的数据总线
    .scl             (scl),      //iic的时钟总线
    .ack             (ack),
    .led        ()
);
wire [7:0]uart_tx_data;
uart_tx uart_tx(
    .clk            (sys_clk),     
    .rst_n          (rst_n),       
    .en_flag        (uart_tx_en),  
    .data           (uart_tx_data),
    .tx             (uart_txd),    
    .tx_done        (tx_done),     
    .en             ()
    );
uart_tx_control uart_tx_control(
    .sys_clk    (sys_clk),
    .rst_n  (rst_n),
    .wet    (wet),
    .temper(temper),
    .start_send(start_send),//发送一次串口数据
    .tx_done(tx_done),
    .tx_en(),
    .start_flag(uart_tx_en),
    .data(uart_tx_data)
);
endmodule

四.效果展示

可以看到,数据显示正常,1s发送一次;此外还要啰嗦两句,重庆夏天是真的热啊,室内30多°,可惜家里有人吹不了空调,我快热飞了


网站公告

今日签到

点亮在社区的每一天
去签到