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