当前位置: 首页 > news >正文

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

一.阅读官方手册

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

百度网盘 请输入提取码

手册重点关注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;//40msparameter CNT_10MS = 499_999;//10MSreg [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) begindone_recv_reg<=done_recv;endreg [3:0] cur_state,next_state;assign led=cur_state[3:0];parameter IDLE          = 4'b0000,WAIT_40MS     = 4'b0001,//等待40msINIT          = 4'b0011,//初始化WAIT_10MS     = 4'b0010,//初始化后等待10MS,同时查看INIT命令ACK是否常为0CAPTURE       = 4'b0110,//发送采集命令WAIT_500MS    = 4'b0111,//等待500msGET_DATA      = 4'b0101,//得到数据WAIT_1S       = 4'b0100;//等到1s重复上面步骤always @(posedge sys_clk) beginif(!rst_n)cur_state<=IDLE;elsecur_state<=next_state;end//状态跳转逻辑always @(*) beginif (!rst_n)next_state=IDLE;elsecase (cur_state)IDLE          : next_state=WAIT_40MS;WAIT_40MS     : if(cnt_40ms==CNT_40MS) next_state=INIT;elsenext_state=WAIT_40MS;INIT          : if(iic_done) next_state=WAIT_10MS;elsenext_state=INIT;WAIT_10MS     : if(ack_reg1)next_state=WAIT_40MS;else if(cnt_10ms==CNT_10MS)next_state=CAPTURE;elsenext_state=WAIT_10MS;CAPTURE       : if(iic_done) next_state=WAIT_500MS;elsenext_state=CAPTURE;WAIT_500MS    : if(cnt_time>CNT_TIMER/2) next_state=GET_DATA;elsenext_state=WAIT_500MS;GET_DATA      : if(iic_done) next_state=WAIT_1S;elsenext_state=GET_DATA;WAIT_1S       : if(cnt_time>=CNT_TIMER) next_state=WAIT_40MS;elsenext_state=WAIT_1S;default: next_state=IDLE;endcaseendalways @(posedge sys_clk) beginif(!rst_n)beginiic_start     <=0   ;//启动iicselect        <=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   ;endelse beginif(cur_state==IDLE)beginiic_start     <=0   ;//启动iicselect        <=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   ;endelse if(cur_state==WAIT_40MS)beginiic_start     <=0   ;//启动iicselect        <=0   ;//选择调用的命令temper<=temper;//输出的温度wet<=wet;//输出的湿度start_send    <=0   ;//输出传输标志,用于触发串口传输temper_reg    <=temper_reg   ;//温度值wet_reg       <=wet_reg   ;//湿度值if(cnt_time>CNT_TIMER)cnt_time<=0;elsecnt_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;elsecnt_40ms<=0;cnt_10ms      <=0   ;//10ms计时器ack_reg       <=0   ;ack_reg1      <=0   ;cnt_recv      <=0   ;endelse if(cur_state==INIT)beginiic_start<=1;//启动iicselect        <=0   ;//选择调用的命令temper<=temper;//输出的温度wet<=wet;//输出的湿度start_send    <=0   ;//输出传输标志,用于触发串口传输temper_reg    <=temper_reg   ;//温度值wet_reg       <=wet_reg   ;//湿度值if(cnt_time>CNT_TIMER)cnt_time<=0;elsecnt_time<=cnt_time+1;//1s计时器cnt_40ms      <=0   ;//40ms计时器cnt_10ms      <=0   ;//10ms计时器if(ack==1)ack_reg<=1;elseack_reg<=ack_reg;ack_reg1<=ack_reg;cnt_recv      <=0   ;endelse if(cur_state==WAIT_10MS)beginiic_start     <=0   ;//启动iicselect        <=0   ;//选择调用的命令temper        <=temper   ;//输出的温度wet           <=wet   ;//输出的湿度start_send    <=0   ;//输出传输标志,用于触发串口传输temper_reg    <=temper_reg   ;//温度值wet_reg       <=wet_reg   ;//湿度值if(cnt_time>CNT_TIMER)cnt_time<=0;elsecnt_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;elsecnt_10ms<=0;ack_reg<=0   ;ack_reg1<=ack_reg1  ;//reg1记录了上一状态的ack值,如果ack至少有1个时钟为1,那么重发命令cnt_recv      <=0   ;endelse if(cur_state==CAPTURE)beginiic_start     <=1   ;//启动iicselect        <=2   ;//选择调用的命令temper<=temper;//输出的温度wet<=wet;//输出的湿度start_send    <=0   ;//输出传输标志,用于触发串口传输temper_reg    <=temper_reg   ;//温度值wet_reg       <=wet_reg   ;//湿度值if(cnt_time>CNT_TIMER)cnt_time<=0;elsecnt_time<=cnt_time+1;//1s计时器cnt_40ms      <=0   ;//40ms计时器cnt_10ms      <=0   ;//10ms计时器ack_reg       <=0   ;ack_reg1      <=0   ;cnt_recv      <=0   ;endelse if(cur_state==WAIT_500MS)beginiic_start     <=0   ;//启动iicselect        <=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   ;endelse if(cur_state==GET_DATA)beginiic_start     <=1   ;//启动iicselect        <=3   ;//选择调用的命令temper<=temper;//输出的温度wet<=wet;//输出的湿度start_send    <=0   ;//输出传输标志,用于触发串口传输if(done_recv)beginif(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)beginwet_reg[3:0]<=read_data[7:4];temper_reg[19:16]<=read_data[3:0];endelse if(cnt_recv==4)temper_reg[15:8]<=read_data;else if(cnt_recv==5)temper_reg[7:0]<=read_data;endcnt_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;endelse if(cur_state==WAIT_1S)beginiic_start     <=0   ;//启动iicselect        <=0   ;//选择调用的命令temper<=temper_tem[15:0];//输出的温度wet<=wet_tem[15:0];//输出的湿度if(cnt_time==CNT_TIMER-3)start_send    <=1   ;//输出传输标志,用于触发串口传输elsestart_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   ;endendendwire [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工作时钟  -->100Khzparameter   delay       = clk/iicclk    ;//iic的工作周期parameter   MID         = delay/2       ;//iic周期的中点位置parameter   Q_MID       = delay/4       ;//iic周期的  1/4parameter   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  ,//0010ACK1      = 4'd3  ,SENDDATA  = 4'd4  ,//0100ACK2      = 4'd5  ,//0101STOP      = 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;elsecur_state<=next_state;//三段式状态机第二段always@(*)if(!rst_n)next_state=IDLE;elsecase(cur_state)IDLE     :beginif(start_reg==2'b11&&(sendnum!=0||recvnum!=0))//检测到高电平next_state=START;elsenext_state=cur_state;endSTART    :beginif(cnt==delay-1)//一个iic的工作周期就跳转next_state=ID;elsenext_state=cur_state;endID       :beginif(cnt_bit==7 && cnt==delay-1)//传完一个寻址地址(从机地址+读写方向位)next_state=ACK1;elsenext_state=cur_state;    endACK1     :beginif(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; endSENDDATA :beginif(cnt_bit==7 && cnt==delay-1)//写完一次数据next_state=ACK2;elsenext_state=cur_state;    endACK2     :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; endSTOP     :beginif(cnt==delay-1)next_state=IDLE;elsenext_state=cur_state;endRECVDATA :beginif(cnt_bit==7 && cnt==delay-1)//读完一次数据next_state=ACK3;elsenext_state=cur_state;    endACK3     :beginif(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;   endSTART1   :beginif(cnt==delay-1)//一个iic的工作周期就跳转next_state=ID1;elsenext_state=cur_state;endID1      :beginif(cnt_bit==7 && cnt==delay-1)//传完一个寻址地址(从机地址)next_state=ACK4;elsenext_state=cur_state;    endACK4     :beginif(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)begincnt_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;//时钟输出endelsecase(cur_state)IDLE      :begincnt_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;//时钟输出endSTART     :begincnt_send       <=0;//写数据的计数器cnt_recv       <=0;//读数据的计数器//data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持worr_reg       <=worr;//读写方向的寄存器              -------保持ack_flag       <=0;//应答信号的寄存器if(cnt==delay-1)//iic的工作周期计数器cnt            <=0;elsecnt            <=cnt+1;cnt_bit        <=0;//bit位计数器data_out       <=0;//读出去的数据sda_en         <=1;//数据使能--》开关打开if(cnt<=Q_MID)//前1/4个周期sda_out        <=1;//数据输出elsesda_out        <=0;scl_en         <=1;//时钟使能if(cnt<TQ_MID)//1/4~3/4处置为高电平scl_out        <=1;//时钟输出elsescl_out        <=0;endID        :begincnt_send       <=0;//写数据的计数器cnt_recv       <=0;//读数据的计数器//data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持worr_reg       <=worr;//读写方向的寄存器              -------保持ack_flag       <=0;//应答信号的寄存器if(cnt==delay-1)//iic的工作周期计数器cnt            <=0;elsecnt            <=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];elsesda_out        <=sda_out;scl_en         <=1;//时钟使能if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平scl_out        <=1;//时钟输出elsescl_out        <=0;endACK1      :begincnt_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;elsecnt            <=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;//时钟输出elsescl_out        <=0;endSENDDATA  :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;elsecnt            <=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;//时钟输出elsescl_out        <=0;endACK2      :beginif(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;elsecnt            <=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;//时钟输出elsescl_out        <=0;endSTOP      :begincnt_send       <=0;//写数据的计数器cnt_recv       <=0;//读数据的计数器data_in_reg    <=0;//写数据的寄存器    寄存数据 ------保持worr_reg       <=0;//读写方向的寄存器              -------保持ack_flag       <=0;//应答信号的寄存器if(cnt==delay-1)//iic的工作周期计数器cnt            <=0;elsecnt            <=cnt+1;cnt_bit        <=0;//bit位计数器data_out       <=0;//读出去的数据sda_en         <=1;if(cnt<=TQ_MID)//  3/4sda_out        <=0;elsesda_out        <=1;scl_en         <=1;//时钟使能if(cnt<=Q_MID)//1/4scl_out        <=0;//时钟输出elsescl_out        <=1;endRECVDATA  :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;elsecnt            <=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;//时钟输出elsescl_out        <=0;endACK3      :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;elsecnt            <=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;//时钟输出elsescl_out        <=0;endSTART1    :begincnt_send       <=0;//写数据的计数器cnt_recv       <=0;//读数据的计数器//data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持worr_reg       <=worr;//读写方向的寄存器              -------保持ack_flag       <=0;//应答信号的寄存器if(cnt==delay-1)//iic的工作周期计数器cnt            <=0;elsecnt            <=cnt+1;cnt_bit        <=0;//bit位计数器data_out       <=0;//读出去的数据sda_en         <=1;//数据使能--》开关打开if(cnt<=Q_MID)//前1/4个周期sda_out        <=1;//数据输出elsesda_out        <=0;scl_en         <=1;//时钟使能if(cnt<TQ_MID)//1/4~3/4处置为高电平scl_out        <=1;//时钟输出elsescl_out        <=0;endID1       :begincnt_send       <=0;//写数据的计数器cnt_recv       <=0;//读数据的计数器//data_in_reg    <=data_in;//写数据的寄存器    寄存数据 ------保持worr_reg       <=worr;//读写方向的寄存器              -------保持ack_flag       <=0;//应答信号的寄存器if(cnt==delay-1)//iic的工作周期计数器cnt            <=0;elsecnt            <=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];elsesda_out        <=sda_out;scl_en         <=1;//时钟使能if(cnt>Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平scl_out        <=1;//时钟输出elsescl_out        <=0;endACK4      :begincnt_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;elsecnt            <=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;//时钟输出elsescl_out        <=0;enddefault   :begincnt_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;//时钟输出endendcase   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,//初始化AHT20CAT  = 4'b0011,//查看状态寄存器CAP  = 4'b0010,//采集命令GET  = 4'b0110;//获取数据reg [3:0] cur_state,next_state;reg [3:0] cnt_cmd;always @(posedge sys_clk) beginif(!rst_n)cur_state<=IDLE;elsecur_state<=next_state;endalways @(*) beginif(!rst_n)next_state=IDLE;elsecase (select)0 : next_state=INIT;1 : next_state=CAT;2 : next_state=CAP;3 : next_state=GET;default: next_state=IDLE;endcaseendalways @(posedge sys_clk) beginif(!rst_n)beginworr     <=0;//worr==1读,==0写sendnum  <=0;recvnum  <=0;data_cmd <=8'hFF;cnt_cmd  <=0;endelse if(cur_state==INIT)begin//初始化:发送0xBEworr<=0;sendnum <=3;recvnum <=0;if(done_send==1)cnt_cmd <= cnt_cmd+1;else if(done_iic)cnt_cmd <= 0;elsecnt_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;    endelse if(cur_state==CAT)begin//查看状态寄存器:读地址第一位worr<=1;sendnum <=0;recvnum <=1;data_cmd<=8'h71;cnt_cmd  <=0;endelse if(cur_state==CAP)begin//发送采集命令:发送0xAC 0x33 0x00worr<=0;sendnum <=3;recvnum <=0;if(done_send==1)cnt_cmd <= cnt_cmd+1;else if(done_iic)cnt_cmd <= 0;elsecnt_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;endelse if(cur_state==GET)begin//查看6个字节的数据 状态寄存器 湿度 湿度 湿度温度各一半 温度 温度worr<=1;sendnum <=0;recvnum <=6;data_cmd<=8'hFF;cnt_cmd  <=0;endend
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) beginif(!rst_n)data_reg<=0;else if(en_flag==1&&en==0)data_reg<=data;elsedata_reg<=data_reg;end//使能信号开启 always @(posedge clk) beginif(!rst_n)en<=0;else beginif(en_flag==1)en<=1;else if(cntw==9&&cnt==5207)//5207en<=0;elseen<=en;endend//数据位和每帧时间计数always@(posedge clk)beginif(!rst_n)cntw<=0;else beginif(en==1&&cnt==5207)cntw<=cntw+1;else if(en==0)cntw<=0;elsecntw<=cntw;end endalways@(posedge clk)beginif(!rst_n)cnt<=0;else beginif(en==1)beginif(cnt==5207)cnt<=0;elsecnt<=cnt+1;endelsecnt<=0;end end//给tx输出赋值always @(posedge clk) beginif(!rst_n)tx<=1;else beginif(en==1)beginif(cntw==0)tx<=0;else if(cntw==9)tx<=1;elsetx<=data_reg[cntw-1];endelsetx<=1; endend//给txdone赋值always @(posedge clk) beginif(!rst_n)tx_done<=0;else beginif(cntw==9&&cnt==5207)tx_done<=1;elsetx_done<=0;endend
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) beginif(!rst_n)cnt<=0;else if(tx_done)cnt<=cnt+1;else if(start_send)cnt<=0;elsecnt<=cnt;endalways @(posedge sys_clk) beginif(!rst_n)start_flag<=0;else beginif (start_send)start_flag<=1;else if(tx_done&&cnt<17)start_flag<=1;elsestart_flag<=0;endendalways @(*) beginif(!rst_n)data=0;elsecase (cnt)0   :data=8'h57;//W1   :data=8'h45;//E2   :data=8'h54;//T3   :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;//T10  :data=8'h45;//E11  :data=8'h4D;//M12  :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;//换行\ndefault: data=0;endcaseend
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多°,可惜家里有人吹不了空调,我快热飞了

http://www.dtcms.com/a/331844.html

相关文章:

  • 举例说明环境变量及 PATH 的作用
  • ODE-by-Matlab-01-人口增长模型
  • Java进阶学习之Stream流的基本概念以及使用技巧
  • 不用编程不用组态,实现各种PLC之间数据通讯的网络结构示意图
  • Cookie、Session、Token详解
  • week1-[分支嵌套]公因数
  • P1281 [CERC1998] 书的复制
  • 跨域及解决方案
  • Product Hunt 每日热榜 | 2025-08-14
  • httpx 设置速率控制 limit 时需要注意 timeout 包含 pool 中等待时间
  • Effective C++ 条款40:明智而审慎地使用多重继承
  • 20道Vue框架相关前端面试题及答案
  • Uniapp 中 uni.request 的二次封装
  • stm32f103rct6开发板引脚图
  • 芯伯乐1MHz高频低功耗运放芯片MCP6001/2/4系列,微安级功耗精密信号处理
  • UML函数原型中stereotype的含义,有啥用?
  • 打靶日常-CSRF
  • 中国车企全球化数字转型标杆案例:SAP系统多项目整合升级实践
  • 考研408《计算机组成原理》复习笔记,第五章(2)——CPU指令执行过程
  • Day 11: 预训练语言模型基础 - 理论精华到实战应用的完整指南
  • k8s+isulad 网络问题
  • 【奔跑吧!Linux 内核(第二版)】第7章:系统调用的概念
  • 基本电子元件:电阻器
  • 读书笔记:《我看见的世界》
  • 日志系统(log4cpp)
  • 主进程如何将客户端连接分配到房间进程
  • Android UI(一)登录注册 - Compose
  • 基于Python和Dify的成本对账系统开发
  • OpenCV Canny 边缘检测
  • 软考中级【网络工程师】第6版教材 第3章 局域网 (上)