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

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通信失败;经过尝试,我选择了重复初始化+采集的步骤(PS:后面做实验的时候又可以重复发送采集指令了,怀疑是当时时间没配好)

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

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_stateCNT_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 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 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 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 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读出的数据 outputdone_recv ,//读数据结束信号 outputdone_send ,//写数据的结束信号 outputdone_iic ,//iic的工作结束信号 inout sda ,//iic的数据总线 inout scl ,//iic的时钟总线 outputack , 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重新等待 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 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 Q_MID && cnt<TQ_MID)//1/4~3/4处置为高电平 scl_out <=1;//时钟输出 else scl_out <=0; end SENDDATA :begin //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 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 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 保持 //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 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 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 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 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 endendmodule

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 endendmodule
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 endendmodule

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多°,可惜家里有人吹不了空调,我快热飞了