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;//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.pswp.cn/news/919312.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/919312.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

项目会议怎么开才有效

要提高项目会议的有效性&#xff0c;需要做到以下几点&#xff1a;明确会议目的、制定具体的会议议程、合理控制会议时长、提前准备会议资料、选择合适的参会人员、设定清晰的会议目标、确保会议有决策和行动方案、会后及时跟进与落实。其中&#xff0c;明确会议目的尤为重要。…

计算机视觉第一课opencv(二)保姆级教

目录 简介 一、边界填充 1.函数说明 2.案例分析 二、图像运算 1.号运算 2.cv2.add()函数 3.图像加权运算 三、阈值处理 四、图像平滑处理 1.椒盐噪声 2.均值滤波&#xff08;Mean Filtering&#xff09; 3.方框滤波 4. 高斯滤波&#xff08;Gaussian Filtering&am…

母猪姿态转换行为识别:计算机视觉与行为识别模型调优指南

> 在现代智能化养殖中,母猪姿态识别是健康监测的关键技术。本文将带你从0到1构建高精度母猪姿态识别系统,准确率可达95%以上! ## 一、为什么母猪姿态识别如此重要? 母猪的行为姿态是其健康状况的重要指标: - **站立姿态**:可能表示发情期或进食需求 - **侧卧姿态**:…

循序渐进学 Spring (下):从注解、AOP到底层原理与整合实战

文章目录7. 自动装配 (Autowiring)7.1 XML 自动装配7.2 使用注解实现自动装配Autowired vs Resource8. 使用注解开发&#xff08;完全体&#xff09;8.1 定义 Bean (Component 及其衍生注解)8.2 注入属性 (Value)8.3 注入对象8.4 定义作用域 (Scope)8.5 小结&#xff1a;XML vs…

C#WPF实战出真汁06--【系统设置】--餐桌类型设置

1、系统设置的基本概念系统设置是用于配置和管理餐桌类型和菜品类型&#xff0c;是维护整个系统的基础数据。通过系统设置&#xff0c;用户可以调整餐桌类型的添加&#xff0c;删除&#xff0c;编辑&#xff0c;分页&#xff0c;查询&#xff0c;重置&#xff0c;列表&#xff…

旋钮键盘项目---foc讲解(闭环位置控制)

hello&#xff0c;周六休息了一天&#xff0c;出去打本了。趁着夜色&#xff0c;花费了几个小时&#xff0c;也是将闭环代码写完&#xff0c;参考了灯哥的思路。接下来介绍一下我的整个流程&#xff1a; 一、闭环位置控制思路&#xff1a; 其实懂得了开环&#xff0c;那么闭环…

为什么有些相机“即插即用”,而有些则需要采集卡?

在工业生产中&#xff0c;工业相机是“眼睛”&#xff0c;它帮助我们看到世界&#xff0c;但你知道吗&#xff1f;不同的工业相机接口就像不同的“通道”&#xff0c;有些“通道”直接就能与计算机连接&#xff0c;而有些则需要一个额外的小配件——图像采集卡。那么&#xff0…

【计算机网络 | 第7篇】物理层基本概念

文章目录物理层基本概念及数据通信系统解析一、物理层的核心定位&#x1f95d;二、物理层的功能&#x1f9fe;三、数据通信系统的模型&#x1f426;‍&#x1f525;&#xff08;一&#xff09;源系统&#xff08;二&#xff09;传输系统&#xff08;三&#xff09;目的系统四、…

一般情况下,python函数都会返回对象,但有时只调用一个函数,这是在修改这个信息

class Model:def __init__(self):self.training Truedef eval(self):self.training Falsereturn Nonem Model() print(m.training) # True m.eval() # 返回 None print(m.training) # False&#xff0c;模型内部状态已改变m.eval&#xff08;&#xff09;是在修改m的…

2025-08-17 李沐深度学习17——语义分割

文章目录1 语义分割1.1 介绍1.2 语义分割应用1.3 实例分割2 转置卷积2.1 工作原理2.2 为什么叫“转置”卷积2.3 转置卷积也是一种卷积3 FCN3.1 核心思想3.2 网络架构4 样式迁移4.1 基于 CNN 的样式迁移4.2 工作流程1 语义分割 1.1 介绍 语义分割&#xff08;Semantic Segment…

《若依》权限控制

若依内置了强大的权限控制系统&#xff0c;为企业级项目提供了通用的解决方案 以CRM系统为例&#xff0c;演示权限功能&#xff08;URL&#xff1a;https://huike-crm.itheima.net) demo账号&#xff08;超级管理员&#xff09;查看所有功能菜单 zhangsan账号&#xff08;市…

云原生俱乐部-RH134知识点总结(3)

这个系列的第二篇写了将近5000字&#xff0c;而且还是删节内容后的&#xff0c;如RAID就没写&#xff0c;因为头已经很大了。第二篇从早上写到下午&#xff0c;因为偷懒了&#xff0c;写着写着就停笔了。不过好在总算磨完了&#xff0c;现在开始写RH134系列的最后一篇内容。我这…

股票常见K线

1.底部反弹摸线特点长下影线之后必须有实体阴线踩实之后才考虑。macd绿缓慢收窄过程中的不买&#xff0c;刚转红也不买。macd转红之后等股价跌回之前macd绿首次收窄的最低点附近&#xff0c;而且跌破了所有均线&#xff0c;可以买入此股票。之后股票一波突破之前平台震荡平台&a…

计算机网络 THU 考研专栏简介

本专栏专为清华大学计算机网络考研复习设计&#xff0c;内容系统全面&#xff0c;涵盖从基础概念到重点考点的完整知识体系。具体包括&#xff1a;基础理论&#xff1a;计算机网络概念、分类、性能指标及网络分层模型&#xff08;OSI 七层、TCP/IP 四层&#xff09;。协议与技术…

VSCode打开新的文件夹之后当前打开的文件夹被覆盖

文件--首选项--设置&#xff1a;搜索showtabs设置为如下&#xff1a;

mac 电脑安装类似 nvm 的工具,node 版本管理工具

前言 苹果电脑开发时&#xff0c;有时候需要切换node 版本&#xff0c;window版有nvm可以管理node 版本&#xff0c;mac版本可以用另外一种 //全局安装n 模块 sudo npm install n -g//输入后回车&#xff0c;提示输入电脑密码&#xff0c;输入完密码回车等待下载完成即可//安装…

spdlog框架的安装与使用

spdlog框架的安装与使用spdlog的安装spdlog的使用spdlog二次封装总结&#xff1a;spdlog的安装 sudo apt-get install libspdlog-devspdlog的使用 同步日志器sync.cc (输出到显示器/输出到指定文件) #include<spdlog/spdlog.h> #include<spdlog/sinks/stdout_color…

使用websockets中的一些问题和解决方法

&#xff08;1&#xff09;TypeError: echo() missing 1 required positional argument: path报错自己写的代码如下&#xff1a;async def echo(websocket, path):...async def main():server await websockets.serve(echo, "0.0.0.0", 666)await server.wait_close…

机器人相关基础知识

机器人简介下面给出一份机器人方向“从入门到进阶”的极简知识地图&#xff0c;按「数学 → 硬件 → 软件 → 算法 → 应用」五层展开&#xff0c;配合常用开源资源。你可以把它当作“字典”随时查阅。&#x1f539; 1. 数学层&#xff08;所有算法的地基&#xff09;概念一句话…

Windows Server 打开vGPU RDP HEVC编码

查看已安装的驱动[rootlocalhost:~] esxcli software vib list Name Version Vendor Acceptance Level Install Date Platforms ----------------------------- ------------------------------------ ------ -…