一.阅读官方手册
手册在下方网址下载,该模块在各个网店平台均有销售
手册重点关注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多°,可惜家里有人吹不了空调,我快热飞了