【FPGA】基于FPGA实现AHT10温湿度传感器数据采集
目录
- 一、需求分析
- 二、AHT10简介
- (一)AHT10特性
- (二)AHT10基本指令及测量步骤
- (三)数据转换
- 三、系统架构设计
- 四、模块划分及信号说明
- (一)模块划分
- (二)端口信号说明
- 五、状态转移描述
- 六、代码实现
- 七、仿真测试
- 八、板级验证
写在前面:
相关参考文章:【FPGA】FPGA实现IIC协议读写EEPROM
在本项目中所使用的开发板型号:Cyclone IV E (EP4CE6F17C8),温湿度传感器型号:AHT10。
一、需求分析
- 使用C4开发板实现控制AHT10温湿度传感器进行数据采集。
- 温度值以十进制形式的摄氏温度打印到终端,保留一位小数,显示形式例如xx.x℃。
- 湿度值以百分数形式打印到终端,保留一位小数,显示形式例如:xx.x%。
- 使用按键设置温湿度报警值,当采集的温湿度值超过预设的报警值时,触发蜂鸣器报警。
二、AHT10简介
(一)AHT10特性
AHT10是一款标准I2C接口的温湿度传感器。供电范围为1.8-3.6V,推荐电压为3.3V。电源( VDD)和接地(GND)之间须连接一个 10uF的去耦电容,器件引脚接口图如下:

SDA引脚用于传感器的数据输入和输出。当向传感器发送命令时,SDA在串行时钟( SCL)的上升沿有效,且当SCL为高电平时,SDA 必须保持稳定。在 SCL下降沿之后,SDA值可被改变。
AHT10读写遵循I2C协议,可参考IIC协议工程,时序图如下:


本工程中使用时钟频率为200KHz。
(二)AHT10基本指令及测量步骤
1、AHT10基本指令集:

2、AHT10测量步骤
(1)上电后等待40ms ,读取温湿度值之前,首先看状态字的校准使能位Bit[3]是否为1(通过发送0x71可以获取一个字节的状态字),如果不为1,要发送0xEi命令(初始化),此命令参数有两个字节,第一个字节为0x08,第二个字节为0x00。状态位说明如下:

(2)直接发送0xAC命令(触发测量),此命令参数有两个字节,第一个字节为0x33,第二个字节为0x00。

(3)等待80ms测量数据完成,发送0x71读取6字节温湿度数据。

(三)数据转换

按照此公式进行转换数据不正确,所以在数据转换代码中做了修改。
三、系统架构设计
本工程系统框图如下:

四、模块划分及信号说明
(一)模块划分
本工程将系统功能划分为8个模块,各模块功能描述如下:
- top:顶层设计模块。
- aht10_ctrl:控制驱动AHT10命令的发送、驱动i2c_interface接口模块与AHT10传感器进行数据交互。
- i2c_interface:AHT10使用标准的I2C接口协议,该模块实现aht10_ctrl控制模块通过I2C协议时序读取温湿度数据。
- data_process:温湿度数据处理模块,将aht10_ctrl控制模块读回的温湿度数据根据转换公式进行转换,并将转换的数据再次转换成ASCII码通过串口进行发送。
- uart_tx:串口数据发送模块,将data_process模块转换后的ASCII格式数据通过串口发送给上位机,通过串口调试助手打印温湿度数据信息。
- key_filter:按键消抖模块,将按键输入的进行进行消抖处理。
- set_limit:温湿度报警值设置模块,通过按键设置温湿度报警值。
- seg_driver:数码管显示模块,显示温湿度设置模式及温湿度设置的报警值。
(二)端口信号说明
- top模块

- aht10_ctrl模块

- i2c_interface模块

- data_process模块

- uart_tx模块

6 key_filter模块

- set_limit模块

- seg_driver模块

五、状态转移描述
- aht10_ctrl控制模块状态转移图

状态说明:
WAIT:上电等待40ms,等待结束后进入INIT状态。
INIT:初始化,发送初始化指令,进入WAIT_INIT状态。
WAIT_INIT:等待初始化,检查校准使能位是否为1,不为1则继续进行初始化,初始化完成后进入IDLE状态。
IDLE:空闲状态,等待读请求。
RD_REQ:发送读取温湿度数据指令,发送完成后进入WAIT_MES状态。
WAIT_MES:等待80ms测量温湿度数据完成,进入READ状态。
READ:读取温度数据,读取完成后进入IDLE状态,等待下一次读请求。 - i2c_interface接口模块状态转移图

状态说明:
IDLE:空闲状态,等待读写请求。
START:发送起始位,当控制命令cmd[5:0]中含有起始命令时,进行该状态发送起始位。
WR_DATA:写数据状态,在写数据状态期间,将wr_din[7:0]按照IIC协议时序写入AHT10。
RD_DATA:读数据状态,在读数据状态期间,按照IIC协议时序从AHT10中读取数据。
REC_ACK:接收从机发送的ACK应答信号。
SEND_ACK:发送ACK或者NACK信号。
STOP:发送停止位,当控制命令cmd[5:0]中含有停止命令时,进行该状态发送停止位。
六、代码实现
1. aht10读写顶层设计模块
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: AHT10
// Module Name: top
// Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)
// Tool versions: Quartus Prime 18.1
// Description: aht10读写顶层设计模块
// **************************************************************module top (input clk ,input rst_n ,// keyinput [2:0] key_in ,// AHT10inout sda ,output scl ,// UARToutput uart_txd ,// beepoutput beep ,// 数码管output [7:0] seg_dig ,output [5:0] seg_sel
);wire sda_in ;wire sda_out ;wire sda_out_en ;assign sda = sda_out_en ? sda_out : 1'bz ;assign sda_in = sda ;wire req ;wire [7:0] wrdata ;wire [3:0] cmd ;wire [7:0] rddata ;wire rddata_vld ;wire done ;wire [19:0] hum_data ; wire [19:0] tem_data ; wire data_vld ; wire ready ; wire [7:0] tx_data ;wire tx_data_vld ;wire tem_add ;wire hum_add ;wire set ;wire modle ;wire [7:0] tem_lim ;wire [7:0] hum_lim ;key_filter u_key_filter1 (/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*input */.key_in (key_in[0] ),/*output reg */.key_out (set ));key_filter u_key_filter2 (/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*input */.key_in (key_in[1] ),/*output reg */.key_out (tem_add ));key_filter u_key_filter3 (/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*input */.key_in (key_in[2] ),/*output reg */.key_out (hum_add ));// 模块例化aht10_ctrl u_aht10_ctrl (/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*// i2c_interface*//*output */.req (req ), // 读写请求/*output [7:0] */.wr_dout (wrdata ), // 发送指令数据/*output [3:0] */.cmd (cmd ), // 发送读写控制命令/*input [7:0] */.rdin (rddata ), // 读回的数据/*input */.rdin_vld (rddata_vld),/*input */.rw_done (done ), // 读写一字节数据完成标志// data_process/*output [19:0] */.hum_data (hum_data ), // 湿度数据/*output [19:0] */.tem_data (tem_data ), // 温度数据/*output */.data_vld (data_vld ) // 温湿度数据有效标志);i2c_interface u_i2c_interface (/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*// eeprom_ctrl*//*input */.req (req ), // 读写请求/*input [7:0] */.wr_din (wrdata ), // 需要发送的一字节数据/*input [3:0] */.cmd (cmd ), // 控制命令组合/*output [7:0] */.rdout (rddata ), // 读取的数据/*output */.rdout_vld (rddata_vld ), // 读取数据有效标志/*output */.rw_done (done ), // 读写一字节完成标志/*// EEPROM*//*input */.sda_in (sda_in ),/*output */.sda_out (sda_out ),/*output */.sda_out_en (sda_out_en ),.scl (scl ));data_process u_data_process (/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*// aht10_ctrl*//*input */.din_vld (data_vld ),/*input [19:0] */.tem_data (tem_data ),/*input [19:0] */.hum_data (hum_data ),/*// set_limit*//*input [7:0] */.tem_lim (tem_lim ),/*input [7:0] */.hum_lim (hum_lim ),/*// uart_tx*//*input */.ready (ready ),/*output [7:0] */.tx_data (tx_data ),/*output */.tx_data_vld (tx_data_vld ),/*// beep*//*output */.beep (beep ) );set_limit u_set_limit (/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*// key_filter*//*input */.tem_add (tem_add ), // 设置温度报警值标志,每按一次+5摄氏度/*input */.hum_add (hum_add ), // 设置湿度报警值标志,每按一次+5%/*input */.set (set ), // 设置模式,每按一次在温度和湿度设置之间切换/*// seg_driver*//*output */.modle (modle ), // 当前设置模式 0:设置温度 1:设置湿度/*output [7:0] */.tem_lim (tem_lim ), // 温度报警值/*output [7:0] */.hum_lim (hum_lim ) // 湿度报警值);seg_driver u_seg_driver(/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*// set_limit*//*input [7:0] */.tem_lim (tem_lim ),/*input [7:0] */.hum_lim (hum_lim ),/*input */.modle (modle ),/*output reg [7:0] */.seg_dig (seg_dig ), // 段选信号/*output reg [5:0] */.seg_sel (seg_sel ) // 片选信号);uart_tx u_uart_tx (/*input */.clk (clk ),/*input */.rst_n (rst_n ),/*// control*/ /*input [7:0] */.tx_din (tx_data ),/*input */.tx_din_vld (tx_data_vld ),/*output */.ready (ready ), // 给control模块的握手信号,表示可以接收数据进行发送 /*// 上位机*//*output */.uart_tx (uart_txd ));endmodule
2. I2C接口驱动模块
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: AHT10
// Module Name: i2c_interface
// Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)
// Tool versions: Quartus Prime 18.1
// Description: I2C接口驱动模块
// **************************************************************
`include "param.v"module i2c_interface (input clk ,input rst_n ,// eeprom_ctrlinput req , // 读写请求input [7:0] wr_din , // 需要发送的一字节数据input [3:0] cmd , // 控制命令组合output [7:0] rdout , // 读取的数据output rdout_vld , // 读取数据有效标志output rw_done , // 读写一字节完成标志// EEPROMinput sda_in ,output sda_out ,output sda_out_en ,output scl
);// 参数定义localparam IDLE = 7'b000_0001 ,START = 7'b000_0010 ,WR_DATA = 7'b000_0100 ,RD_DATA = 7'b000_1000 ,REC_ACK = 7'b001_0000 ,SEND_ACK= 7'b010_0000 ,STOP = 7'b100_0000 ;// 信号定义reg [6:0] state_c ;reg [6:0] state_n ;reg [7:0] cnt_scl ; // scl周期计数器wire add_cnt_scl ;wire end_cnt_scl ;reg [3:0] bit_num ; // bit数reg [3:0] cnt_bit ; // bit计数器wire add_cnt_bit ;wire end_cnt_bit ;reg [7:0] dout_data ; // 发送数据寄存reg [3:0] command ; // 控制命令寄存reg scl_dout ; // SDA寄存 reg sda_dout ;reg sda_dout_en ;reg [7:0] rx_data ; // 接收读回的数据reg ack_flag ; // ack响应标志// 状态转移条件wire idle2start ;wire idle2wrdata ;wire idle2rddata ;wire start2wrdata ;wire start2rddata ;wire wrdata2recack ;wire rddata2sendack ;wire recack2idle ;wire recack2stop ;wire sendack2idle ;wire sendack2stop ;wire stop2idle ;// 状态机always @(posedge clk or negedge rst_n)beginif(!rst_n)beginstate_c <= IDLE ;endelse beginstate_c <= state_n ;endendalways @(*)begincase (state_c)IDLE: beginif(idle2start)state_n = START ;else if(idle2wrdata)state_n = WR_DATA ;else if(idle2rddata)state_n = RD_DATA ;elsestate_n = state_c ;endSTART: beginif(start2wrdata)state_n = WR_DATA ;else if(start2rddata)state_n = RD_DATA ;elsestate_n = state_c ;endWR_DATA: beginif(wrdata2recack)state_n = REC_ACK ;elsestate_n = state_c ;endRD_DATA: beginif(rddata2sendack)state_n = SEND_ACK ;elsestate_n = state_c ;endREC_ACK: beginif(recack2idle)state_n = IDLE ;else if(recack2stop)state_n = STOP ;elsestate_n = state_c ;endSEND_ACK: beginif(sendack2idle)state_n = IDLE ;else if(sendack2stop)state_n = STOP ;elsestate_n = state_c ;endSTOP: beginif(stop2idle)state_n = IDLE ;elsestate_n = state_c ;enddefault: state_n = IDLE ;endcaseend// 状态转移条件assign idle2start = state_c == IDLE && req && (cmd & `STA) ;assign idle2wrdata = state_c == IDLE && req && (cmd & `WRITE) ;assign idle2rddata = state_c == IDLE && req && (cmd & `READ) ;assign start2wrdata = state_c == START && end_cnt_bit && (command & `WRITE) ; assign start2rddata = state_c == START && end_cnt_bit && (command & `READ) ; assign wrdata2recack = state_c == WR_DATA && end_cnt_bit ;assign rddata2sendack = state_c == RD_DATA && end_cnt_bit ;assign recack2idle = state_c == REC_ACK && end_cnt_bit && (command & `STO) == 0 ;assign recack2stop = state_c == REC_ACK && end_cnt_bit && (command & `STO) ;assign sendack2idle = state_c == SEND_ACK && end_cnt_bit && (command & `STO) == 0 ;assign sendack2stop = state_c == SEND_ACK && end_cnt_bit && (command & `STO) ;assign stop2idle = state_c == STOP && end_cnt_bit ;// cnt_scl 200KHz 一个SCL周期为250个系统时钟周期always @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_scl <= 0 ;endelse if(add_cnt_scl)beginif(end_cnt_scl)begincnt_scl <= 0 ;end else begin cnt_scl <= cnt_scl + 1 ;end endend assign add_cnt_scl = state_c != IDLE ;assign end_cnt_scl = add_cnt_scl && (cnt_scl == `SCL_PERIOD - 1) ;// bit_numalways @(*)beginif(state_c == RD_DATA || state_c == WR_DATA)beginbit_num = 8 ;endelse beginbit_num = 1 ;endend// cnt_bitalways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_bit <= 0 ;endelse if(add_cnt_bit)beginif(end_cnt_bit)begincnt_bit <= 0 ;end else begin cnt_bit <= cnt_bit + 1 ;end endend assign add_cnt_bit = end_cnt_scl ;assign end_cnt_bit = add_cnt_bit && (cnt_bit == bit_num - 1) ;// dout_dataalways @(posedge clk or negedge rst_n)beginif(!rst_n)begindout_data <= 0 ;endelse if(req)begindout_data <= wr_din ;endend// commandalways @(posedge clk or negedge rst_n)beginif(!rst_n)begincommand <= 0 ;endelse if(req)begincommand <= cmd ;endend// scl_doutalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginscl_dout <= 1'b1 ;endelse if(idle2start | idle2wrdata | idle2rddata)beginscl_dout <= 1'b0 ;endelse if(add_cnt_scl && cnt_scl == `SCL_HALF)beginscl_dout <= 1'b1 ;endelse if(end_cnt_scl && ~stop2idle)beginscl_dout <= 1'b0 ;endend// sda_dout_enalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginsda_dout_en <= 1'b0 ;endelse if(idle2start || idle2wrdata || rddata2sendack || sendack2stop || recack2stop)beginsda_dout_en <= 1'b1 ;endelse if(idle2rddata || start2rddata || wrdata2recack || stop2idle)beginsda_dout_en <= 1'b0 ;endend// sda_doutalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginsda_dout <= 1'b1 ;endelse if(state_c == START)beginif(cnt_scl == `HIGH_HALF)beginsda_dout <= 1'b0 ;endelse if(cnt_scl == `LOW_HALF)beginsda_dout <= 1'b1 ;endendelse if(state_c == WR_DATA && cnt_scl == `LOW_HALF)beginsda_dout <= dout_data[7 - cnt_bit] ;endelse if(state_c == SEND_ACK && cnt_scl == `LOW_HALF)beginsda_dout <= (command & `STO) ? 1'b1 : 1'b0 ;endelse if(state_c == STOP)beginif(cnt_scl == `LOW_HALF)begin sda_dout <= 1'b0;endelse if(cnt_scl == `HIGH_HALF)begin sda_dout <= 1'b1; end endelse if(wrdata2recack | rddata2sendack)beginsda_dout <= 1'b1; endend// rx_dataalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginrx_data <= 0 ;endelse if(state_c == RD_DATA && cnt_scl == `HIGH_HALF)beginrx_data[7 - cnt_bit] <= sda_in ;endend// ack_flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginack_flag <= 1'b0 ;endelse if(state_c == REC_ACK && cnt_scl == `HIGH_HALF)beginack_flag <= ~sda_in ;endelse beginack_flag <= 1'b0 ;endend// 输出assign rdout = rx_data ; assign rdout_vld = rddata2sendack ;assign rw_done = stop2idle | sendack2idle | recack2idle ;assign sda_out = sda_dout ; assign sda_out_en = sda_dout_en ;assign scl = scl_dout ;endmodule
3. aht10读写控制模块
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: AHT10
// Module Name: aht10_ctrl
// Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)
// Tool versions: Quartus Prime 18.1
// Description: aht10读写控制模块
// **************************************************************
`include "param.v"
module aht10_ctrl (input clk ,input rst_n ,// i2c_interfaceoutput req , // 读写请求output [7:0] wr_dout , // 发送指令数据output [3:0] cmd , // 发送读写控制命令input [7:0] rdin , // 读回的数据input rw_done , // 读写一字节数据完成标志input rdin_vld ,// data_processoutput [19:0] hum_data , // 湿度数据output [19:0] tem_data , // 温度数据output data_vld // 温湿度数据有效标志
);// 参数定义localparam WAIT = 8'b0000_0001 , // 上电等待40msINIT = 8'b0000_0010 , // 初始化WAIT_INIT = 8'b0000_0100 , // 等待初始化完成IDLE = 8'b0000_1000 , // 空闲RD_REQ = 8'b0001_0000 , // 读数据请求WAIT_MES = 8'b0010_0000 , // 等待测量完成80msREAD = 8'b0100_0000 , // 读数据DONE = 8'b1000_0000 ; // 读操作完成// 信号定义reg [7:0] state_c ;reg [7:0] state_n ;reg [27:0] cnt_delay ; // 延时计数器wire add_cnt_delay ;wire end_cnt_delay ;reg [27:0] delay ;reg [3:0] cnt_byte ; // 字节计数器wire add_cnt_byte ;wire end_cnt_byte ;reg init_flag ; // 初始化完成标志reg tx_req ; // task发送请求reg [3:0] tx_cmd ; // task发送控制命令reg [7:0] tx_data ; // task发送数据reg [47:0] rd_data ; // 读回的温湿度数据寄存// 状态转移条件wire wait2init ;wire init2waitinit ;wire waitinit2idle ;wire waitinit2init ;wire idle2rdreq ;wire rdreq2waitmes ;wire waitmes2read ;wire read2done ;wire done2idle ;// 状态机always @(posedge clk or negedge rst_n)beginif(!rst_n)beginstate_c <= WAIT ;endelse beginstate_c <= state_n ;endendalways @(*)begincase (state_c)WAIT: beginif(wait2init)state_n = INIT ;elsestate_n = state_c ;endINIT: beginif(init2waitinit)state_n = WAIT_INIT ;elsestate_n = state_c ;endWAIT_INIT: beginif(waitinit2idle)state_n = IDLE ;else if(waitinit2init)state_n = INIT ;elsestate_n = state_c ;endIDLE: beginif(idle2rdreq)state_n = RD_REQ ;elsestate_n = state_c ;endRD_REQ: beginif(rdreq2waitmes)state_n = WAIT_MES ;elsestate_n = state_c ;endWAIT_MES: beginif(waitmes2read)state_n = READ ;elsestate_n = state_c ;endREAD: beginif(read2done)state_n = DONE ;elsestate_n = state_c ;endDONE: beginif(done2idle)state_n = IDLE ;elsestate_n = state_c ;enddefault: state_n = IDLE ;endcaseend// 状态转移描述assign wait2init = state_c == WAIT && end_cnt_delay ; assign init2waitinit = state_c == INIT && end_cnt_byte ; assign waitinit2idle = state_c == WAIT_INIT && init_flag ; assign waitinit2init = state_c == WAIT_INIT && ~init_flag && end_cnt_byte ; assign idle2rdreq = state_c == IDLE && end_cnt_delay ; assign rdreq2waitmes = state_c == RD_REQ && end_cnt_byte ; assign waitmes2read = state_c == WAIT_MES && end_cnt_delay ; assign read2done = state_c == READ && end_cnt_byte ; assign done2idle = state_c == DONE && (1'b1) ; // delayalways @(posedge clk or negedge rst_n)beginif(!rst_n)begindelay <= `DELAY_40ms ;endelse if(state_c == WAIT)begindelay <= `DELAY_40ms ;endelse if(state_c == WAIT_MES)begindelay <= `DELAY_80ms ;endelse if(state_c == IDLE)begindelay <= `DELAY_500ms ;endend// cnt_delayalways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_delay <= 0 ;endelse if(add_cnt_delay)beginif(end_cnt_delay)begincnt_delay <= 0 ;end else begin cnt_delay <= cnt_delay + 1 ;end endend assign add_cnt_delay = (state_c == WAIT) || (state_c == WAIT_MES) || (state_c == IDLE) ;assign end_cnt_delay = add_cnt_delay && (cnt_delay == delay - 1) ;// cnt_bytealways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_byte <= 0 ;endelse if(add_cnt_byte)beginif(end_cnt_byte)begincnt_byte <= 0 ;end else begin cnt_byte <= cnt_byte + 1 ;end endend assign add_cnt_byte = rw_done ;assign end_cnt_byte = add_cnt_byte && (cnt_byte == ((state_c == READ) ? 7:4) - 1) ;// 使用task任务发送数据always @(*)begincase (state_c)INIT : case(cnt_byte)0 :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b0}) ; // 发起始位、写控制字1 :TX(1'b1,`WRITE,`GET_STATE) ; 2 :TX(1'b1,`WRITE,`AHT10_INIT_1) ; 3 :TX(1'b1,{`WRITE | `STO} ,`AHT10_INIT_2) ; default :TX(1'b0,tx_cmd,tx_data) ;endcase WAIT_INIT:case(cnt_byte)0 :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b0}) ; // 发起始位、写控制字1 :TX(1'b1,`WRITE,`GET_STATE) ; // 发读取状态字命令2 :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b1}) ; // 发读控制字3 :TX(1'b1,{`READ | `STO},0) ; default :TX(1'b0,tx_cmd,tx_data) ;endcaseRD_REQ: case(cnt_byte)0 :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b0}) ; // 发起始位、写控制字1 :TX(1'b1,`WRITE,`AHT10_MEAS_0) ; 2 :TX(1'b1,`WRITE,`AHT10_MEAS_1) ; 3 :TX(1'b1,{`WRITE | `STO},`AHT10_MEAS_2) ; default :TX(1'b0,tx_cmd,tx_data) ;endcase READ : case(cnt_byte)0 :TX(1'b1,{`STA | `WRITE},{`AHT10_ADDR,1'b1}) ; // 发起始位、写控制字1 :TX(1'b1,`READ,0) ; 2 :TX(1'b1,`READ,0) ; 3 :TX(1'b1,`READ,0) ; 4 :TX(1'b1,`READ,0) ; 5 :TX(1'b1,`READ,0) ; 6 :TX(1'b1,{`READ | `STO},0) ;default :TX(1'b0,tx_cmd,tx_data) ;endcasedefault: TX(1'b0,0,0) ;endcaseend// init_flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)begininit_flag <= 1'b0 ;endelse if(state_c == WAIT_INIT && rw_done && rdin[3])begininit_flag <= 1'b1 ;endend// rd_dataalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginrd_data <= 0 ;endelse if(state_c == READ && rw_done && cnt_byte > 0)beginrd_data <= {rd_data[39:0],rdin} ;endend// tasktask TX; input req ;input [3:0] command ;input [7:0] data ;begin tx_req = req ;tx_cmd = command ;tx_data = data ;end endtask // 输出assign req = tx_req ; assign cmd = tx_cmd ; assign wr_dout = tx_data ; assign hum_data = rd_data[39:20] ;assign tem_data = rd_data[19:0] ;assign data_vld = read2done ;endmodule
4. aht10温湿度数据处理模块
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: AHT10
// Module Name: data_process
// Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)
// Tool versions: Quartus Prime 18.1
// Description: aht10温湿度数据处理模块
// **************************************************************
`include "param.v"module data_process (input clk ,input rst_n ,// aht10_ctrlinput din_vld ,input [19:0] tem_data ,input [19:0] hum_data ,// set_limitinput [7:0] tem_lim ,input [7:0] hum_lim ,// uart_txinput ready ,output [7:0] tx_data ,output tx_data_vld ,// beepoutput beep
);// 信号定义reg [19:0] tem_data_r ; // 温湿度数据寄存reg [19:0] hum_data_r ;reg [5:0] cnt_byte ; // 字节计数器wire add_cnt_byte ;wire end_cnt_byte ;reg process_flag ; // 数据处理标志reg [7:0] data ; // 串口显示数据reg flag ; // 蜂鸣器标志reg tem_alarm ;reg hum_alarm ;wire [7:0] tem_ten ; // 温度数据十位wire [7:0] tem_bit ; // 温度数据个位wire [7:0] tem_dot1 ; // 温度数据小数位1wire [7:0] tem_dot2 ; // 温度数据小数位2wire [7:0] hum_ten ; // 湿度数据十位wire [7:0] hum_bit ; // 湿度数据个位wire [7:0] hum_dot1 ; // 湿度数据小数位1wire [7:0] hum_dot2 ; // 湿度数据小数位2// fifowire wrreq ;wire rdreq ;wire empty ;wire full ;wire [7:0] q ;wire [7:0] usedw ;always @(posedge clk or negedge rst_n)beginif(!rst_n)begintem_data_r <= 0 ;hum_data_r <= 0 ;endelse if(din_vld)begin// tem_data_r <= ((tem_data * 200) >> 20) - 50 ; // hum_data_r <= (hum_data * 100) >> 20 ;tem_data_r <= (((tem_data * 2000) >> 12) - (500)); //扩大10倍hum_data_r <= ((hum_data * 1000) >> 12);// tem_data_r = ((((tem_data << 10)// + (tem_data << 9)// + (tem_data << 8)// + (tem_data << 7)// + (tem_data << 6)// + (tem_data << 4)) >> 20 ) - 500) * 10 ;// hum_data_r = (((hum_data << 9)// + (hum_data << 8)// + (hum_data << 7)// + (hum_data << 6)// + (hum_data << 5)// + (hum_data << 3)) >> 20) * 10 ;endend// process_flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginprocess_flag <= 1'b0 ;endelse if(din_vld)beginprocess_flag <= 1'b1 ;endelse if(end_cnt_byte)beginprocess_flag <= 1'b0 ;endend// cnt_bytealways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_byte <= 0 ;endelse if(add_cnt_byte)beginif(end_cnt_byte)begincnt_byte <= 0 ;end else begin cnt_byte <= cnt_byte + 1 ;end endend assign add_cnt_byte = process_flag;assign end_cnt_byte = add_cnt_byte && (cnt_byte == 24) ;// flag// always @(*)begin// if(!rst_n)begin// flag <= 1'b0 ;// end// else if(add_cnt_byte && (cnt_byte == 6 && tem_ten >= (tem_lim / 10)) || (cnt_byte == 18 && hum_ten >= (hum_lim / 10)))begin// flag <= 1'b1 ;// end// else if(add_cnt_byte && (cnt_byte == 6 && tem_ten < (tem_lim / 10)) || (cnt_byte == 18 && hum_ten < (hum_lim / 10)))begin// flag <= 1'b0 ;// end// end// tem_alarmalways @(posedge clk or negedge rst_n)beginif(!rst_n)begintem_alarm <= 1'b0 ;endelse if(add_cnt_byte && tem_ten >= (tem_lim / 10))begintem_alarm <= 1'b1 ;endelse if(add_cnt_byte && tem_ten < (tem_lim / 10))begintem_alarm <= 1'b0 ;endend// hum_alarmalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginhum_alarm <= 1'b0 ;endelse if(add_cnt_byte && hum_ten >= (hum_lim / 10))beginhum_alarm <= 1'b1 ;endelse if(add_cnt_byte && hum_ten < (hum_lim / 10))beginhum_alarm <= 1'b0 ;endend// flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginflag <= 1'b0 ;endelse if(tem_alarm | hum_alarm)beginflag <= 1'b1 ;endelse if(~tem_alarm & ~hum_alarm)beginflag <= 1'b0 ;endendassign tem_ten = (tem_data_r / 100 % 10 ) ;assign tem_bit = (tem_data_r % 100 / 10 ) ;assign tem_dot1 = (tem_data_r % 10 ) ;assign tem_dot2 = 0 ;assign hum_ten = (hum_data_r / 100 % 10 ) ;assign hum_bit = (hum_data_r % 100 / 10 ) ;assign hum_dot1 = (hum_data_r % 10 ) ;assign hum_dot2 = 0 ;// dataalways @(*)begincase (cnt_byte)1 : data <= 8'hce; // 温度ASCII码2 : data <= 8'hc2;3 : data <= 8'hb6;4 : data <= 8'hc8;5 : data <= 8'h3a; // :ASCII码6 : data <= tem_ten + 48; // 十位7 : data <= tem_bit + 48; // 个位8 : data <= 8'h2e; // .ASCII码9 : data <= tem_dot1 + 48; // 小数位110: data <= tem_dot2 + 48; // 小数位211: data <= 8'ha1; // ℃ASCII码12: data <= 8'he6;13: data <= 9; // tab14: data <= 8'hca; // 湿度ASCII码15: data <= 8'haa;16: data <= 8'hb6;17: data <= 8'hc8;18: data <= 8'h3a; // :ASCII码19: data <= hum_ten + 48; // 十位20: data <= hum_bit + 48; // 个位21: data <= 8'h2e; // .ASCII码22: data <= hum_dot1 + 48; // 小数位123: data <= hum_dot2 + 48; // 小数位224: data <= 8'h25; // %ASCII码default: data <= 0 ;endcaseend// fifo例化fifo fifo_inst (.aclr ( ~rst_n ),.clock ( clk ),.data ( data ),.rdreq ( rdreq ),.wrreq ( wrreq ),.empty ( empty ),.full ( full ),.q ( q ),.usedw ( usedw ));assign wrreq = ~full && process_flag && cnt_byte > 0;assign rdreq = ~empty && ready ;// 输出assign tx_data = q ;assign tx_data_vld = rdreq ;assign beep = ~flag ;endmodule
5. aht10设置温湿度报警值模块
// **************************************************************
// Author: Zhang JunYi
// Create Date: 2022.11.15
// Design Name: AHT10
// Module Name: set_limit
// Target Device: Cyclone IV E (EP4CE6F17C8), 温湿度传感器(AHT10)
// Tool versions: Quartus Prime 18.1
// Description: aht10设置温湿度报警值模块
// **************************************************************module set_limit (input clk ,input rst_n ,// key_filterinput tem_add , // 设置温度报警值标志,每按一次+5摄氏度input hum_add , // 设置湿度报警值标志,每按一次+5%input set , // 设置模式,每按一次在温度和湿度设置之间切换// seg_driveroutput modle , // 当前设置模式 0:设置温度 1:设置湿度output [7:0] tem_lim , // 温度报警值output [7:0] hum_lim // 湿度报警值
);// 信号定义reg [7:0] cnt_tem ;wire add_cnt_tem ;wire end_cnt_tem ;reg [7:0] cnt_hum ;wire add_cnt_hum ;wire end_cnt_hum ;reg flag ; // 设置模式// cnt_temalways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_tem <= 0 ;endelse if(add_cnt_tem)beginif(end_cnt_tem)begincnt_tem <= 0 ;end else begin cnt_tem <= cnt_tem + 10 ;end endend assign add_cnt_tem = tem_add ;assign end_cnt_tem = add_cnt_tem && (cnt_tem == 100) ;// cnt_humalways @(posedge clk or negedge rst_n)beginif(!rst_n)begincnt_hum <= 0 ;endelse if(add_cnt_hum)beginif(end_cnt_hum)begincnt_hum <= 0 ;end else begin cnt_hum <= cnt_hum + 10 ;end endend assign add_cnt_hum = hum_add ;assign end_cnt_hum = add_cnt_hum && (cnt_hum == 100) ;// flagalways @(posedge clk or negedge rst_n)beginif(!rst_n)beginflag = 1'b0 ;endelse if(set)beginflag = ~flag ;endend// 输出assign tem_lim = cnt_tem ;assign hum_lim = cnt_hum ;assign modle = flag ;endmodule
6. 参数文件
// IIC时钟参数
`define SCL_PERIOD 250
`define SCL_HALF 125
`define LOW_HALF 65
`define HIGH_HALF 190// 控制命令
`define WRITE 4'b1000 // 写
`define READ 4'b0100 // 读
`define STA 4'b0010 // 起始位
`define STO 4'b0001 // 停止位// AHT10命令参数
`define AHT10_ADDR 7'b0111_000 // 设备地址`define GET_STATE 8'b1110_0001 // 获取状态字`define AHT10_INIT_0 8'b1110_0001 // 初始化命令序列
`define AHT10_INIT_1 8'b0000_1000
`define AHT10_INIT_2 8'b0000_0000`define AHT10_MEAS_0 8'b1010_1100 // 触发测量命令序列
`define AHT10_MEAS_1 8'b0011_0011
`define AHT10_MEAS_2 8'b0000_0000`define AHT10_MEAS_4 8'b0111_0001 // 读数据命令`define DELAY_40ms 2_000_000 // 上电等待40ms
`define DELAY_80ms 4_000_000 // 测量数据80ms
`define DELAY_500ms 5000_0000 // 0.5s采集一次数据// UART参数配置文件
`define BAUD_RATE_115200`define CLK_FREQ 50_000_000`ifdef BAUD_RATE_9600`define BAUD `CLK_FREQ / 9600
`elsif BAUD_RATE_19200`define BAUD `CLK_FREQ / 19200
`elsif BAUD_RATE_38400`define BAUD `CLK_FREQ / 38400
`elsif BAUD_RATE_57600`define BAUD `CLK_FREQ / 57600
`elsif BAUD_RATE_115200`define BAUD `CLK_FREQ / 115200
`endif
7. 其他模块
按键消抖模块、数码管驱动模块、串口发送模块比较简单,这里不过多赘述。
七、仿真测试
由于没有AHT10的仿真模型,仿真模拟AHT10返回数据,下面只对比较重要的几个模块进行仿真测试。
仿真代码如下:
`timescale 1ns/1psmodule test_tb ();reg clk ;reg rst_n ;wire sda ;wire scl ;reg sda_i ;top u_top(.clk ( clk ),.rst_n ( rst_n ),.sda ( sda ),.scl ( scl ),.uart_txd ( uart_txd ));assign sda = u_top.sda_out_en ? 1'bz:sda_i;localparam CYCLE = 20;always #(CYCLE/2) clk=~clk;reg [ 10:0 ] i ;reg [ 10:0 ] j ;initial beginrst_n = 1'b1;clk = 1'b1;#(CYCLE * 2);rst_n = 1'b0;#(CYCLE * 2);#2;rst_n = 1'b1;@(posedge u_top.u_aht10_ctrl.init2waitinit); //初始化for (i = 0; i<4 ; i = i+1 ) begin@(posedge u_top.u_aht10_ctrl.add_cnt_byte);endfor (i = 0; i<9 ; i = i+1 ) begin //模拟初始化完成@(posedge u_top.u_i2c_interface.add_cnt_bit)if(i == 3)sda_i = 1;else if(i == 8)sda_i = 0;endrepeat(5) begin@(posedge u_top.u_aht10_ctrl.waitmes2read);//等待控制模块到达读取状态@(posedge u_top.u_aht10_ctrl.add_cnt_byte);for (i = 0; i<6 ; i = i+1 ) begin//发送6个数据@(posedge u_top.u_aht10_ctrl.add_cnt_byte);for (j = 0; j<9 ; j = j+1 ) begin//模拟从机回数据@(posedge u_top.u_i2c_interface.end_cnt_scl)if(j == 8)sda_i = 0;elsesda_i = {$random};endendend$stop;
endendmodule
仿真结果:
(1)aht10_ctrl模块

(2)i2c_interface模块

(3)data_process模块

(4)uart_tx模块

八、板级验证
终端信息打印:

上板之后,温湿度报警值默认是0℃和0%,需要手动设置报警界限,所以一开始上板蜂鸣器就会开始工作。通过按键设置好温湿度报警值之后就会正常工作,当温度或者湿度超过预设报警值时蜂鸣器开始工作,当温度或湿度同时低于预设报警值时,蜂鸣器停止工作。
按键操作说明:
key1:设置温湿度报警值模式,初始状态为设置温度报警值模式(数码管显示“TE 温度值”),按下key1切换为设置湿度报警值模式(数码管显示“HU 湿度值”),在两种设置模式之间切换。
key2:当处于设置温度报警值模式时(数码管显示“TE 温度值”),按下key2,温度报警值+10%,最高设置不超过90%。
key3:当处于设置湿度报警值模式时(数码管显示“HU 湿度值”),按下key3:湿度报警值+10℃,最高设置不超过90℃。
key4:复位按键。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
