基于FPGA的IIC控制AHT20读取温湿度

发布于:2025-07-23 ⋅ 阅读:(18) ⋅ 点赞:(0)

基于FPGA的IIC控制AHT20读取温湿度

一、IIC简介

采用串行总线可以简化系统硬件结构、减小系统体积、提高系统可靠性。常 用的串行总线有单总线(1-Wire Bus)、IIC(Inter-Integrated Circuit)、SPI(Serial Peripheral Interface)等。 IIC 总线是 Phlips 公司推出的一种同步串行总线,是一种支持多主机多从机 系统、总线仲裁以及高低速器件同步功能的高性能串行总线技术。 IIC 总线只有两根双向信号线:一根是数据线 SDA,一根是时钟线 SCL。
在这里插入图片描述
多主机多从机----在 IIC 总线系统上可以挂载多个主机设备与从机设备;
总线仲裁----对于总线上挂载有多主机设备的情况下,为避免多个主机同 时向总线发起通信,需要有相应的总线仲裁机制,避免出现总线冲突;
高低速器件同步–通过 IIC 总线连接的两个器件的传输速度不一定相同, 因此需要通过从机的时钟延展(clock stretching)功能保证两者的通信速度一致。
即:如果果从机跟不上主机的速率,IIC 协议规定从机可以通过将 SCL 时钟线拉 低来暂停传输直到从机释放掉 SCL 线,传输才能继续进行。

传输速率
标准模式:100Kbit/s
快速模式:400kbit/s
高速模式:3.4Mbit/s

工作原理
每一个连接到 IIC 总线上的器件都有一个唯一的器件地址(ID),主机通过 ID 建立多机通信机制,因此 IIC 总线节省了外围器件的片选信号线。虽然 IIC 总 线上可以连接多个主机和多个从机,但是同一时刻,只能有一个主机向总线上发起传输,如果有多个主机同时发起传输,则会根据 IIC 总线的仲裁机制,最终 只给一个主机授权,没有得到仲裁的主机则会停止传输。而且,IIC 总线上只能 由主机发起传输,从机无法主动发起传输,这也就意味着从机和从机之间也是无法直接交换数据的。
在这里插入图片描述

IIC 总线在传输数据时必须遵循规定的数据传输时序,一次完整的数据传输 过程中共有四类信号:起始信号、数据信号、应答信号和停止信号。
在这里插入图片描述
起始信号:在 SCL 为高电平时,SDA 的电平由高电平跳变为低电平,称为 I2C 总线的起始信号,标志着一次数据传输开始。起始信号由主机主动产生,在 产生起始信号之前 I2C 总线必须处于空闲状态。

停止信号:在 SCL 为高电平时,SDA 由低电平跳变为高电平,称为 I2C 总 线的停止信号,标志着一次数据传输终止。停止信号由主机主动产生,产生停止 信号之后 I2C 总线将返回空闲状态。

数据信号:IIC 总线传输数据时,在 SCL 为高电平时,SDA 必须保持稳定的 逻辑电平,高电平表示数据 1,低电平表示数据 0;只有在 SCL 为低电平时才允 许 SDA 上的电平状态改变。

应答信号:IIC 总线传输数据时,每传输一个字节后都必须有一个 SCL 周期 的应答信号;与应答信号相对应的时钟由主机产生,发送端必须释放 SDA 使其 处于高电平状态,以便接收端在这一个 SCL 周期发出应答信号。

应答信号有两种状态:应答(ACK)、非应答(NACK),接收端在应答位期 间输出低电平则为应答,输出高电平则为非应答。

当由于某种原因,从机设备不产生应答时,如从机在进行其它处理而无法接 收总线上的数据时,必须释放 SDA 总线,然后由主机产生一个停止信号以终止 数据传输。
当主机在接收数据时,接收到最后一个字节后,必须给从机发送一个非应答 信号,使从机释放总线以便主机可以发送停止信号,终止数据传输。

需要注意的是:在某些情况下,从机在收到一个完整的字节后,有可能因为 需要忙于其它工作(如处理内部中断服务)而无法立刻接收下一个字节,这时从 机需要将 SCL 拉为低电平,从而强制主机处于等待状态,直到从机准备好接收 下一个字节时,再释放 SCL 为高电平状态,从而可以继续传输数据。这种现象 称为时钟拉伸(Clock Stretching)。

数据地址
IIC 总线协议规定:起始信号表明一次传输开始,其后为寻址字节(高 7 位 为从机地址、最低 1 位为方向位,方向位表明主机与从机之间的数据传输方向, 0–主机发送数据给从机,1–从机发送数据给主机);在寻址字节后是对应的读、 写操作的数据字节和应答位;在数据传输完成后主机必须发送停止信号。
IIC 总线的数据传输方式有许多读、写组合方式:主机写操作、主机读操作、 主机读写操作。
在这里插入图片描述
在这里插入图片描述

二、AHT20简介

AHT20,新一代温湿度传感器在尺寸与智能方面建立了新的标准:它嵌入了适于回流焊的双列扁平无引脚SMD 封装,底面 3 x 3mm ,高度1.0mm。传感器输出经过标定的数字信号,标准 IIC 格式。
在这里插入图片描述
传感器性能:
在这里插入图片描述
在这里插入图片描述
由图可知湿度测量范围为0~100%,分辨率为0.024%。
温度测量范围为-40℃~ +85℃,分辨率为0.01℃。

在启动传输后,随后传输的IIC首字节包括7位的IIC设备地址 0x38和一个SDA方向位 x(读R:‘1’,写W:‘0’)。在第8个SCL时钟下降沿之后,通过拉低 SDA引脚 (ACK位),指示传感器数据接收正常。 在发出初始化命令之后(‘1011’1110’)代表初始化,‘1010’1100’代表温湿度测量), 主机必须等到测量完成。

在这里插入图片描述
读出的数据可用以下公式进行转换。

湿度:
在这里插入图片描述
温度:
在这里插入图片描述

三、实现思路

AHT20采集数据,将数据处理后通过数码管显示,开关控制显示温度或者湿度,也能通过串口发送至电脑,将数据显示出来。
在这里插入图片描述

iic_driver模块设计为本篇文章复用性较高的写法,直接延用。

在这里插入图片描述
上图为iic_ctrl模块状态机设计

设置上电后为IDLE,等待50ms跳转中间状态START,进入step1;

step0:跳转至REST,读取温湿度值之前,发送0xBE命令(初始化),此命令参数有两个字节, 第一个字节为0x08,第二个字节为0x00;(这里为了方便理解,将REST与W_DATA写数据分开,实际应为一个状态,进行连续发数据的操作)所以step1复位操作应发四个字节数据。分别为:带起始信号的写(cmd=0011),两字节的普通的写(cmd=0010),带停止信号的写(cmd=1010),发送完成跳回START,进入step2;

step1:进行发送读取数据命令的操作,跳转至MSRE,直接发送 0xAC命令(触发测量),此命令参数有两个字节,第一个字节为 0x33,第二个字节为0x00,同为发送四个字节数据,与上述相同,发送完成跳回START,进入step3;

step2:读取温度,进行读取时要等待其数据采集完成,这里设置为等待1s,等待完成跳转至READ,将从机发送的6字节数据保存,在最后发送NACK加停止信号。6字节数据第一个为状态字节,接下来的20bit为湿度数据,后20bit为温度数据。数据读完跳回START,将step计数器置为1,重复上述操作。

四、仿真效果

在这里插入图片描述
50ms计数器结束(仿真将时间缩短),状态跳转至START,step为0进行复位操作。

在这里插入图片描述
起始信号正确,后面跟着AHT20器件地址0111_000加写操作’0’

在这里插入图片描述
第二字节为初始化命令0xBE为1011_1110,第三字节0x08,第四字节0x00,发送完成。

在这里插入图片描述
step计数器加一,进入第二步,发送读取温度命令第一字节为器件地址+写指令0为0x70,第二字节为读取温度命令0xAC,第三、四字节为命令后的参数数据0x33与0x00,停止信号正确,发送完成。

在这里插入图片描述
step计数器加一,1s计数器计数结束计数跳转至READ读取数据,先发送0x71,器件地址+1读操作,后续问读出数据,这里用的从机模型为EEPROM的模型,内部未写入数据,所以读不出数据。

在这里插入图片描述

读完step置为1,重复读取的操作。仿真验证成功。

五、上板验证

在这里插入图片描述
开关在下方为1,数码管显示温度,u表示+,C表示现在显示的是温度。

在这里插入图片描述
开关在上方为0,数码管显示湿度,o表示%,说明现在显示的为湿度。

在这里插入图片描述
通过串口显示处温度和湿度,每1s发送一次。

video_20250721_194723

六、代码

top.v

module top (
    input           clk,
    input           rst_n,
    input  [0:0]    sw,
    //---------<数码管>------------------------------------------------- 
    output  [5:0]   sel,  
    output  [7:0]   dig,
    //---------<iic>------------------------------------------------- 
    output          scl,
    inout           sda 
);

wire    [39:0]  rd_data_r;
wire    [19:0]    tm_data;
wire    [19:0]    hm_data;

//三态门
wire    sda_in;
wire    sda_out;
wire    sda_en;

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

iic_top inst_iic_top(
    .clk        (clk),
    .rst_n      (rst_n),
    .rd_data_r  (rd_data_r),
    .sda_in     (sda_in),
    .sda_out    (sda_out),
    .sda_en     (sda_en),
    .scl        (scl)
);

data_ctrl inst_data_ctrl(
    .clk      ( clk      ),
    .rst_n    ( rst_n    ),
    .rd_data_r( rd_data_r),
    .tm_data  ( tm_data  ),
    .hm_data  ( hm_data  )  
);

sel_driver inst_sel_driver(
    .clk    (clk    ),
    .rst_n  (rst_n  ),
    .sw     (sw     ),
    .tm_data(tm_data),
    .hm_data(hm_data),
    .sel    (sel    ),
    .dig    (dig    )
);

endmodule

iic_top.v

module iic_top (
    input           clk,
    input           rst_n,
    output  [39:0]  rd_data_r,
    //---------<iic>------------------------------------------------- 
    input           sda_in,
    output          sda_out,
    output          sda_en,
    output          scl
);
wire [7:0] rd_data;
wire       trans_done;
wire       slave_ack;
wire       rw_flag;
wire [3:0] cmd;
wire [7:0] wr_data;

iic_ctrl    inst_iic_ctrl(
    .clk       ( clk         ),  
    .rst_n     ( rst_n       ),  
    .rd_data   ( rd_data     ),  
    .trans_done( trans_done  ),  
    .wr_data   ( wr_data     ),  
    .cmd       ( cmd         ),  
    .rd_data_r ( rd_data_r   ),  
    .rw_flag   ( rw_flag     )
);

iic_driver  inst_iic_driver  (
    .clk       (clk       ),
    .rst_n     (rst_n     ),
    .wr_data   (wr_data   ),
    .cmd       (cmd       ),
    .rw_flag   (rw_flag   ),
    .sda_in    (sda_in    ),
    .sda_out   (sda_out   ),
    .sda_en    (sda_en    ),
    .scl       (scl       ),
    .rd_data   (rd_data   ),
    .slave_ack (slave_ack ),
    .trans_done(trans_done)
);

endmodule

iic_ctrl.v

module iic_ctrl(
    input                   clk         ,
    input                   rst_n       ,
    input       [7:0]       rd_data     ,
    input                   trans_done  ,
    output reg  [7:0]       wr_data     ,
    output reg  [3:0]       cmd         ,
    output reg  [39:0]      rd_data_r   ,
    output reg              rw_flag     
);

//状态机
reg     [2:0]   state_c ;
reg     [2:0]   state_n ;

localparam          IDLE    = 3'd0,
                    START   = 3'd1,
                    REST    = 3'd2,
                    MSRE    = 3'd3,
                    READ    = 3'd4;

wire                IDLE_2_START  ,
                    START_2_REST  ,
                    REST_2_START  ,
                    START_2_MSRE  ,
                    MSRE_2_START  ,
                    START_2_READ  ,
                    READ_2_START  ;

reg         [7:0]       state_data;

//步骤计数
reg         [1:0]       step            ;
wire                    add_step        ;
wire                    end_step        ;

//50ms计数
reg		[21:0]	cnt_50ms	            ;
wire			add_cnt_50ms            ;
wire			end_cnt_50ms            ;
parameter       TIME_50ms = 2_500_000   ;

//1s计数器
reg		[25:0]	cnt_1s	    ;
wire			add_cnt_1s   ;
wire			end_cnt_1s   ;
parameter       TIME_1s = 50_000_000;

//字节计数
reg     [2:0]   byte_num                ;
reg		[2:0]	cnt_byte	            ;
wire			add_cnt_byte            ;
wire			end_cnt_byte            ;

always @(*)begin
    case(state_c)
        REST,MSRE : byte_num = 3'd4     ;
        READ      : byte_num = 3'd7     ;
        default   : byte_num = 3'd0     ;
    endcase
end

//---------<step>------------------------------------------------- 

//步骤计数器
always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        step <= 2'd0;
    end 
    else if(add_step)begin 
        if(end_step)begin 
            step <= 2'd1;
        end
        else begin 
            step <= step + 1'b1;
        end 
    end
end 

assign add_step = (REST_2_START || MSRE_2_START || READ_2_START);
assign end_step = add_step && step == 2;

//---------<cnt_50ms>------------------------------------------------- 

always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_50ms <= 22'd0;
    end 
    else if(add_cnt_50ms)begin 
        if(end_cnt_50ms)begin 
            cnt_50ms <= 22'd0;
        end
        else begin 
            cnt_50ms <= cnt_50ms + 1'b1;
        end 
    end
end 

assign add_cnt_50ms = (state_c == IDLE);
assign end_cnt_50ms = add_cnt_50ms && cnt_50ms == (TIME_50ms - 1);

//---------<cnt_1s>------------------------------------------------- 

always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_1s <= 'd0;
    end 
    else if(add_cnt_1s)begin 
        if(end_cnt_1s)begin 
            cnt_1s <= 'd0;
        end
        else begin 
            cnt_1s <= cnt_1s + 1'b1;
        end 
    end
end 

assign add_cnt_1s = (step == 2) && (state_c == START);
assign end_cnt_1s = add_cnt_1s && cnt_1s == (TIME_1s -1);


//---------<cnt_byte>------------------------------------------------- 

always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_byte <= 3'd0;
    end 
    else if(add_cnt_byte)begin 
        if(end_cnt_byte)begin 
            cnt_byte <= 3'd0;
        end
        else begin 
            cnt_byte <= cnt_byte + 1'b1;
        end 
    end
end 

assign add_cnt_byte = trans_done && ((state_c == REST) || (state_c == MSRE) || (state_c == READ));
assign end_cnt_byte = add_cnt_byte && cnt_byte == (byte_num - 1);

//---------<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   : state_n = (IDLE_2_START) ? START : state_c;
        START  : begin
                if(START_2_REST)begin
                    state_n = REST;
                end
                else if(START_2_MSRE)begin
                    state_n = MSRE;
                end
                else if(START_2_READ)begin
                    state_n = READ;
                end
                else begin
                    state_n = state_c;
                end
        end
        REST   : state_n = (REST_2_START)  ? START : state_c;
        MSRE   : state_n = (MSRE_2_START)  ? START : state_c;
        READ   : state_n = (READ_2_START)  ? START : state_c;
        default : state_n = state_c;
    endcase
end

assign      IDLE_2_START    =   (state_c == IDLE)   && end_cnt_50ms;
assign      START_2_REST    =   (state_c == START)  && (step == 0) ;
assign      REST_2_START    =   (state_c == REST)   && end_cnt_byte;
assign      START_2_MSRE    =   (state_c == START)  && (step == 1) ;
assign      MSRE_2_START    =   (state_c == MSRE)   && end_cnt_byte;
assign      START_2_READ    =   (state_c == START)  && (step == 2) && end_cnt_1s;
assign      READ_2_START    =   (state_c == READ)   && end_cnt_byte;

//---------<wr_data cmd rw_flag>------------------------------------------------- 
    
always @(*)begin
    if(state_c == REST)begin
        case(cnt_byte)
            0       :   begin  wr_data = 8'h70 ; cmd = 4'b0011; rw_flag = 1'b1 ; end
            1       :   begin  wr_data = 8'hbe ; cmd = 4'b0010; rw_flag = 1'b1 ; end
            2       :   begin  wr_data = 8'h08 ; cmd = 4'b0010; rw_flag = 1'b1 ; end
            3       :   begin  wr_data = 8'h00 ; cmd = 4'b1010; rw_flag = 1'b1 ; end
            default :   begin  wr_data = 'd0   ; cmd = 'd0    ; rw_flag = 'd0  ; end
        endcase
    end
    else if(state_c == MSRE)begin
        case(cnt_byte)
            0       :   begin  wr_data = 8'h70 ; cmd = 4'b0011; rw_flag = 1'b1 ; end
            1       :   begin  wr_data = 8'hac ; cmd = 4'b0010; rw_flag = 1'b1 ; end
            2       :   begin  wr_data = 8'h33 ; cmd = 4'b0010; rw_flag = 1'b1 ; end
            3       :   begin  wr_data = 8'h00 ; cmd = 4'b1010; rw_flag = 1'b1 ; end
            default :   begin  wr_data = 'd0   ; cmd = 'd0    ; rw_flag = 'd0  ; end
        endcase
    end
    else if(state_c == READ)begin
        case(cnt_byte)
            0       :   begin  wr_data = 8'h71 ; cmd = 4'b0011; rw_flag = 1'b1 ; end
            1       :   begin  wr_data = 8'h00 ; cmd = 4'b0100; rw_flag = 1'b1 ; end
            2       :   begin  wr_data = 8'h00 ; cmd = 4'b0100; rw_flag = 1'b1 ; end
            3       :   begin  wr_data = 8'h00 ; cmd = 4'b0100; rw_flag = 1'b1 ; end
            4       :   begin  wr_data = 8'h00 ; cmd = 4'b0100; rw_flag = 1'b1 ; end
            5       :   begin  wr_data = 8'h00 ; cmd = 4'b0100; rw_flag = 1'b1 ; end
            6       :   begin  wr_data = 8'h00 ; cmd = 4'b1100; rw_flag = 1'b1 ; end
            default :   begin  wr_data = 'd0   ; cmd = 'd0    ; rw_flag = 'd0  ; end
        endcase
    end
    else begin
        wr_data     =   'd0;
        cmd         =   'd0;
        rw_flag     =   'd0;
    end
end

//---------<rd_data_r state_data>------------------------------------------------- 

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        rd_data_r <= 40'd0;
        state_data <= 8'd0;
    end
    else if(cmd[2] && trans_done)begin
        case (cnt_byte)
            1       :   state_data          <= rd_data ;
            2       :   rd_data_r [39:32]   <= rd_data ;
            3       :   rd_data_r [31:24]   <= rd_data ;
            4       :   rd_data_r [23:16]   <= rd_data ;
            5       :   rd_data_r [15:8 ]   <= rd_data ;
            6       :   rd_data_r [7 :0 ]   <= rd_data ;
        endcase
    end
    else begin
        rd_data_r <= rd_data_r;
        state_data <= state_data;
    end
end
endmodule

iic_driver.v

module iic_driver #(parameter SYS_CLK = 50_000_000,
                              IIC_RATE = 200_000)(
    input                   clk         ,
    input                   rst_n       ,
    input [7:0]             wr_data     ,
    input [3:0]             cmd         ,
    input                   rw_flag     ,
    input                   sda_in      ,
    output reg              sda_out     ,
    output reg              sda_en      ,
    output reg              scl         ,
    output reg [7:0]        rd_data     ,
    output reg              slave_ack   ,
    output                  trans_done
);

localparam  CMD_START = 4'b0001, //开始命令
            CMD_SEND  = 4'b0010, //发送命令
            CMD_RECV  = 4'b0100, //接收命令
            CMD_STOP  = 4'b1000; //停止命令
//状态描述
localparam  IDLE    = 3'd0,
            START   = 3'd1,
            SEND    = 3'd2,
            RECV    = 3'd3,
            R_ACK   = 3'd4,
            S_ACK   = 3'd5,
            STOP    = 3'd6;

wire        IDLE_2_START,
            IDLE_2_SEND,
            IDLE_2_RECV,
            START_2_SEND,
            START_2_RECV,
            SEND_2_RACK,
            RECV_2_SACK,
            RACK_2_IDLE,
            RACK_2_STOP,
            SACK_2_IDLE,
            SACK_2_STOP,
            STOP_2_IDLE;

reg [2:0] state_c;  //现态
reg [2:0] state_n;  //次态

//时钟计数
reg [7:0] cnt_scl;
wire      add_cnt_scl;
wire      end_cnt_scl;
parameter CNT_SCL_MAX = SYS_CLK/IIC_RATE;

//bit计数
reg [2:0] cnt_bit;
wire      add_cnt_bit;
wire      end_cnt_bit;
parameter CNT_BIT_MAX = 4'd8;

//时钟计数器
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'b1;
        end 
    end
    else begin
        cnt_scl <= cnt_scl;
    end
end

assign add_cnt_scl = (state_c !== IDLE);
assign end_cnt_scl = add_cnt_scl && (cnt_scl == (CNT_SCL_MAX -1));

//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'b1;
        end
    end
    else begin
        cnt_bit <= cnt_bit;
    end
end

assign add_cnt_bit = end_cnt_scl && (state_c == SEND || state_c == RECV);
assign end_cnt_bit = add_cnt_bit && (cnt_bit == (CNT_BIT_MAX-1));

//状态机一段
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 (IDLE_2_START) begin
                state_n = START;
            end 
            else if(IDLE_2_SEND)begin
                state_n = SEND;
            end
            else if(IDLE_2_RECV)begin
                state_n = RECV;
            end
            else begin
                state_n = state_c;
            end
        end
        START: begin 
            if (START_2_SEND) begin
                state_n = SEND;
            end 
            else if(START_2_RECV)begin
                state_n = RECV;
            end
            else begin
                state_n = state_c;
            end
        end
        SEND: state_n = (SEND_2_RACK) ? R_ACK : state_c;
        RECV: state_n = (RECV_2_SACK) ? S_ACK : state_c;
        R_ACK: begin
            if (RACK_2_IDLE) begin
                state_n = IDLE;
            end
            else if(RACK_2_STOP)begin
                state_n = STOP;
            end
            else begin
                state_n = state_c;
            end
        end
        S_ACK: begin
            if (SACK_2_IDLE) begin
                state_n = IDLE;
            end
            else if(SACK_2_STOP)begin
                state_n = STOP;
            end
            else begin
                state_n = state_c;
            end
        end
        STOP: state_n = (STOP_2_IDLE) ? IDLE : state_c;
        default : state_n = state_c;
    endcase
end

//描述转移条件
assign  IDLE_2_START = (state_c == IDLE) && (rw_flag) && cmd[0];
assign  IDLE_2_SEND  = (state_c == IDLE) && (rw_flag) && !cmd[0] && cmd[1];
assign  IDLE_2_RECV  = (state_c == IDLE) && (rw_flag) && !cmd[0] && cmd[2];
assign  START_2_SEND = (state_c == START) && (end_cnt_scl) && cmd[1];
assign  START_2_RECV = (state_c == START) && (end_cnt_scl) && cmd[2];
assign  SEND_2_RACK  = (state_c == SEND) && (end_cnt_bit) ;
assign  RECV_2_SACK  = (state_c == RECV) && (end_cnt_bit) ;
assign  RACK_2_IDLE  = (state_c == R_ACK) && (end_cnt_scl) && !cmd[3];
assign  SACK_2_IDLE  = (state_c == S_ACK) && (end_cnt_scl) && !cmd[3];
assign  RACK_2_STOP  = (state_c == R_ACK) && (end_cnt_scl) &&  cmd[3];
assign  SACK_2_STOP  = (state_c == S_ACK) && (end_cnt_scl) &&  cmd[3];
assign  STOP_2_IDLE  = (state_c == STOP) && (end_cnt_scl) ;

//scl描述
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        scl <= 1'b1;
    end 
    else if(state_c != IDLE)begin
        if(cnt_scl < ((CNT_SCL_MAX-1) >> 1))begin
            scl <= 1'b0;
        end
        else begin
            scl <= 1'b1;
        end
    end
    else begin
        scl <= scl;
    end
end

//描述数据
always @(posedge clk or negedge rst_n) begin
    if(!rst_n)begin
        sda_out <= 1'b1;
        sda_en <= 1'b0;
        rd_data <= 0;
        slave_ack <= 1;
    end
    else begin
        case(state_c)
            IDLE: begin
                sda_out <= 1'b1;
                sda_en <= 1'b0;
            end
            START: begin
                sda_en <= 1'b1;
                if(cnt_scl >= (((CNT_SCL_MAX-1) >> 1) + ((CNT_SCL_MAX-1) >>2)))begin
                    sda_out <= 1'b0;
                end
                else begin
                    sda_out <= 1'b1;
                end
            end
            SEND: begin 
                sda_en <= 1'b1;
                if(cnt_scl == ((CNT_SCL_MAX-1)>>2))
                    sda_out <= wr_data[7-cnt_bit];
            end
            RECV: begin
                sda_en <= 1'b0;
                sda_out <= 1'b0;
                if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))begin
                    rd_data[7-cnt_bit] <= sda_in;
                end
            end
            R_ACK: begin
                sda_en <= 1'b0;
                sda_out <= 1'b0;
                if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))
                    slave_ack <= sda_in;
            end
            S_ACK: begin
                sda_en <= 1'b1;
                sda_out <= cmd[3] ? 1'b1 : 1'b0;
            end
            STOP: begin
                sda_en <= 1'b1;
                if(cnt_scl == ((CNT_SCL_MAX-1) >>2))begin
                    sda_out <= 1'b0;
                end
                else if(cnt_scl == ((CNT_SCL_MAX-1) >>1)+((CNT_SCL_MAX-1) >>2))begin
                    sda_out <= 1'b1;
                end
                else begin
                    sda_out <= sda_out;
                end
            end
        endcase 
    end
end

assign trans_done = RACK_2_IDLE || SACK_2_IDLE ||STOP_2_IDLE;
endmodule

tx_top.v

module tx_top (
    input       clk         ,
    input       rst_n       ,
    input [1:0] sw          ,
    input [19:0]tm_data     ,
    input [19:0]hm_data     ,
    input       data_done   ,
    output      tx      
);

wire    [7:0]   tx_data ;
wire            tx_done ;
wire            tx_start;
    
tx_ctrl inst_tx_ctrl(
    .clk         (clk      ),
    .rst_n       (rst_n    ),
    .tx_done     (tx_done  ),
    .tm_data     (tm_data  ),
    .hm_data     (hm_data  ),
    .data_done   (data_done),
    .tx_start    (tx_start ),
    .tx_data     (tx_data  )
);

uart_tx inst_uart_tx(
    .clk         (clk     ),
    .rst_n       (rst_n   ),
    .tx_data     (tx_data ),
    .tx_start    (tx_start),
    .sw          (sw      ),
    .tx          (tx      ),
    .tx_done     (tx_done )
);
endmodule

tx_ctrl.v

module tx_ctrl (
    input                       clk         ,
    input                       rst_n       ,
    input                       tx_done     ,
    input           [19:0]      tm_data     ,
    input           [19:0]      hm_data     ,
    input                       data_done   ,
    output   reg                tx_start    ,
    output   reg    [ 7:0]      tx_data 
);

//reg     [7:0]   tx_data_r       ;

//字节计数器

reg		[5:0]	cnt_byte	    ;
wire			add_cnt_byte    ;
wire			end_cnt_byte    ;
reg             cnt_byte_flg    ;

wire            byte_flg        ;

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 = tx_done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == 39-1;

always @(posedge clk or negedge rst_n)begin 
    if(!rst_n)begin
        cnt_byte_flg <= 'd0;
    end 
    else if(data_done)begin 
        cnt_byte_flg <= 'd1;
    end 
    else if(end_cnt_byte)begin 
        cnt_byte_flg <= 'd0;
    end 
end

//1s计数器
reg		[25:0]	cnt_1s	   ;
wire			add_cnt_1s;
wire			end_cnt_1s;
localparam      TIME_1s = 50_000_000;

always @(posedge clk or negedge rst_n)begin 
   if(!rst_n)begin
        cnt_1s <= 'd0;
    end 
    else if(add_cnt_1s)begin 
        if(end_cnt_1s)begin 
            cnt_1s <= 'd0;
        end
        else begin 
            cnt_1s <= cnt_1s + 1'b1;
        end 
    end
end 

assign add_cnt_1s = end_cnt_byte;
assign end_cnt_1s = add_cnt_1s && cnt_1s == (TIME_1s - 1);

assign byte_flg   = add_cnt_1s ? 1'b0 : (end_cnt_1s ? 1'b1 : byte_flg);

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        tx_data     <= 0;
        tx_start    <= 0;
    end
    else if(cnt_byte_flg)   begin
        tx_start    <= 1'b1;
        case(cnt_byte)
            0       :   tx_data   <=  8'h0a;
            1       :   tx_data   <=  8'hb5;
            2       :   tx_data   <=  8'hb1;
            3       :   tx_data   <=  8'hc7;
            4       :   tx_data   <=  8'hb0;
            5       :   tx_data   <=  8'hce;
            6       :   tx_data   <=  8'hc2;
            7       :   tx_data   <=  8'hb6;
            8       :   tx_data   <=  8'hc8;
            9       :   tx_data   <=  8'h3a;
            10      :   tx_data   <=  8'h20;
            11      :   tx_data   <=  (tm_data[19:16] == 4'ha) ? 8'h2b : 8'h2d;
            12      :   tx_data   <=   tm_data[15:12] + 8'd48;
            13      :   tx_data   <=   tm_data[11:8] + 8'd48;
            14      :   tx_data   <=  8'h2e;
            15      :   tx_data   <=   tm_data[7:4] + 8'd48;
            16      :   tx_data   <=   tm_data[3:0] + 8'd48;
            17      :   tx_data   <=  8'ha1;
            18      :   tx_data   <=  8'he6;
            19      :   tx_data   <=  8'h0d;
            20      :   tx_data   <=  8'h0a;
            21      :   tx_data   <=  8'hb5;
            22      :   tx_data   <=  8'hb1;
            23      :   tx_data   <=  8'hc7;
            24      :   tx_data   <=  8'hb0;
            25      :   tx_data   <=  8'hca;
            26      :   tx_data   <=  8'haa;
            27      :   tx_data   <=  8'hb6;
            28      :   tx_data   <=  8'hc8;
            29      :   tx_data   <=  8'h3a;
            30      :   tx_data   <=  8'h20;
            31      :   tx_data   <=  8'h20;
            32      :   tx_data   <=   hm_data[19:16] + 8'd48;
            33      :   tx_data   <=   hm_data[15:12] + 8'd48;
            34      :   tx_data   <=  8'h2e;
            35      :   tx_data   <=   hm_data[11:8] + 8'd48;
            36      :   tx_data   <=   hm_data[7:4] + 8'd48;
            37      :   tx_data   <=  8'h25;
            38      :   tx_data   <=  8'h0a;
            default :   tx_data   <=  8'h0;
        endcase
    end
    else begin
        tx_data     <=  8'h0;
        tx_start    <=  8'h0;
    end
end
    
endmodule

uart_tx.v

module uart_tx (
    input               clk     ,
    input               rst_n   ,
    input    [7:0]      tx_data ,
    input               tx_start,
    input    [1:0]      sw      ,
    output   reg        tx      ,
    output              tx_done 
);
//奇偶校验位
wire                  tx_check;
//波特率计数器参数
reg     [12:0]        cnt;
reg                   add_cnt;
wire                  end_cnt;

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

reg     [12:0]        baud;
reg     [10:0]         data;//数据寄存器

always @(*) begin
    case(sw[1:0])
    2'b00:      baud = 5208;          //9600波特率
    2'b01:      baud = 3472;          //14400波特率
    2'b10:      baud = 1302;          //38400波特率
    2'b11:      baud = 434;           //115200波特率
    default:    baud = 5208;           
    endcase
end

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

assign end_cnt = add_cnt && (cnt == baud-1)||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1));

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        add_cnt <= 1'b0;
    end
    else if(tx_start)begin
        add_cnt <= 1'b1;
    end
    else if(end_cnt_bit||(cnt_bit == 4'd10 && cnt == ((baud-1)>>1)))begin
        add_cnt <= 1'b0;
    end
end


//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
    else begin
        cnt_bit <= cnt_bit;
    end
end

assign add_cnt_bit = end_cnt;
assign end_cnt_bit = add_cnt_bit && cnt_bit == 4'd11-1;

assign tx_check = ~^tx_data;

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        data<=11'b11111111111;
    end
    else if(tx_start)begin
        data<={1'b1,tx_check,tx_data,1'b0};           //数据加启示位
    end
    else begin
        data<=data;
    end
end

always @(posedge clk or negedge rst_n)begin
    if(!rst_n)begin
        tx<=1'b1;
    end
    else if(end_cnt)begin
        tx<=data[cnt_bit];
    end
    else if(end_cnt_bit)begin
        tx<=1'b1;
    end
    else begin
        tx<=tx;
    end
end

assign tx_done = end_cnt_bit;
endmodule

data_ctrl.v

module data_ctrl (
    input           clk         ,
    input           rst_n       ,
    input [39:0]    rd_data_r   ,
    output[19:0]    tm_data     ,
    output[19:0]    hm_data
);

//湿度数据
wire    [16:0]   hm_data_r  ;
wire    [3:0]    hm_b001    ;//小数点后三位
wire    [3:0]    hm_b01     ;//小数点后二位
wire    [3:0]    hm_b1      ;//小数点后一位
wire    [3:0]    hm_1       ;//个位
wire    [3:0]    hm_10      ;//十位

//温度数据
wire    [16:0]   tm_data_rr ;
wire    [16:0]   tm_data_r  ;
wire    [3:0]    tm_sign    ;//符号(温度正负)
wire    [3:0]    tm_b01     ;//小数点后二位
wire    [3:0]    tm_b1      ;//小数点后一位
wire    [3:0]    tm_1       ;//个位
wire    [3:0]    tm_10      ;//十位

assign  hm_data_r = rd_data_r[39:20]* 10_0000 /41'h10_0000;

assign  tm_data_rr= rd_data_r[19:0] * 2_0000  /41'h10_0000;
assign  tm_data_r = (tm_data_rr >= 5000) ? (tm_data_rr-5000) : (5000-tm_data_rr);
assign  tm_sign   = (tm_data_rr >= 5000) ? 4'hA : 4'hB;

assign  hm_b001 =   hm_data_r%10;
assign  hm_b01  =   hm_data_r/10%10;
assign  hm_b1   =   hm_data_r/100%10;
assign  hm_1    =   hm_data_r/1000%10;
assign  hm_10   =   hm_data_r/10000;

assign  tm_b01  =   tm_data_r%10;
assign  tm_b1   =   tm_data_r/10%10;
assign  tm_1    =   tm_data_r/100%10;
assign  tm_10   =   tm_data_r/1000;

assign  hm_data = {hm_10,hm_1,hm_b1,hm_b01,hm_b001};
assign  tm_data = {tm_sign,tm_10,tm_1,tm_b1,tm_b01};
    
endmodule

sel_driver.v

module sel_driver(
    input               clk     ,
    input               rst_n   ,
    input      [0:0]    sw      ,
    input      [19:0]   tm_data ,
    input      [19:0]   hm_data ,
    output reg [5:0]    sel     ,       //片选
    output reg [7:0]    dig             //段选
);

    parameter zero = 7'b100_0000,
              one  = 7'b111_1001,
              two  = 7'b010_0100,
              three= 7'b011_0000,
              four = 7'b001_1001,
              five = 7'b001_0010,
              six  = 7'b000_0010,
              seven= 7'b111_1000,
              eight= 7'b000_0000,
              nine = 7'b001_0000,
              A    = 7'b001_1101,   //显示正号
              B    = 7'b010_1011,   //负号
              C    = 7'b100_0110,   //摄氏度符号C
              o    = 7'b010_0011;   //o代表%

    parameter TIME_20us=1000;       //数码管切换

    reg  [9:0]  cnt;
    wire        add_cnt;
    wire        end_cnt;

    reg         dot;                //小数点
    reg   [3:0]      data;               //寄存数字            

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            cnt=0;
        end
        else if(add_cnt)begin
            if(end_cnt)begin
                cnt<=0;
            end
            else begin
                cnt<=cnt+1;
            end
        end
        else begin
            cnt<=cnt;
        end
    end
    assign add_cnt=1'b1;
    assign end_cnt=add_cnt&&cnt==TIME_20us-1;

    always @(posedge clk or negedge rst_n) begin
        if(!rst_n)begin
            sel<=6'b011_111;
        end
        else if(end_cnt)begin
            sel<={sel[0],sel[5:1]};
        end
        else begin
            sel<=sel;
        end
    end
    always @(posedge clk or negedge rst_n)begin
        if(!rst_n)begin
            dot<=1'b1;
            data<=4'hf;
        end
        else begin
            case(sel)
                6'b011_111:begin data<= sw[0] ? 4'hC           : 4'hD           ;   dot<=1'b1;end
                6'b101_111:begin data<= sw[0] ? tm_data[3:0]   : hm_data[3:0]   ;   dot<=1'b0;end 
                6'b110_111:begin data<= sw[0] ? tm_data[7:4]   : hm_data[7:4]   ;   dot<=1'b1;end
                6'b111_011:begin data<= sw[0] ? tm_data[11:8]  : hm_data[11:8]  ;   dot<= sw[0] ? 1'b0 : 1'b1;end
                6'b111_101:begin data<= sw[0] ? tm_data[15:12] : hm_data[15:12] ;   dot<= sw[0] ? 1'b1 : 1'b0;end
                6'b111_110:begin data<= sw[0] ? tm_data[19:16] : hm_data[19:16] ;   dot<=1'b1;end
                default   :begin data<=4'hf             ;   dot<=1'b1;end
            endcase
        end
    end

    always @(*)begin
        if(!rst_n)begin
            dig<=8'hff;
        end
        else begin

            case(data)
               4'd0       :   dig<={dot,zero };
               4'd1       :   dig<={dot,one  };
               4'd2       :   dig<={dot,two  };
               4'd3       :   dig<={dot,three};
               4'd4       :   dig<={dot,four };
               4'd5       :   dig<={dot,five };
               4'd6       :   dig<={dot,six  };
               4'd7       :   dig<={dot,seven};
               4'd8       :   dig<={dot,eight};
               4'd9       :   dig<={dot,nine };
               4'hA       :   dig<={dot,A    };
               4'hB       :   dig<={dot,B    };
               4'hC       :   dig<={dot,C    };
               4'hD       :   dig<={dot,o    };
                default :   dig<=8'hff;
            endcase
        end        
    end
endmodule

七、总结

IIC控制AHT20相对来说比较简单,主要考验对IIC协议掌握的程度;AHT20采集的湿度数据为相对湿度,会受到温度的影响,故不会常用;本次的项目为一次练手项目,为个人全程自主完成。


网站公告

今日签到

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