目录
写在前面:
相关参考文章:【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 ,
// key
input [2:0] key_in ,
// AHT10
inout sda ,
output scl ,
// UART
output uart_txd ,
// beep
output 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_ctrl
input req , // 读写请求
input [7:0] wr_din , // 需要发送的一字节数据
input [3:0] cmd , // 控制命令组合
output [7:0] rdout , // 读取的数据
output rdout_vld , // 读取数据有效标志
output rw_done , // 读写一字节完成标志
// EEPROM
input 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)begin
if(!rst_n)begin
state_c <= IDLE ;
end
else begin
state_c <= state_n ;
end
end
always @(*)begin
case (state_c)
IDLE: begin
if(idle2start)
state_n = START ;
else if(idle2wrdata)
state_n = WR_DATA ;
else if(idle2rddata)
state_n = RD_DATA ;
else
state_n = state_c ;
end
START: begin
if(start2wrdata)
state_n = WR_DATA ;
else if(start2rddata)
state_n = RD_DATA ;
else
state_n = state_c ;
end
WR_DATA: begin
if(wrdata2recack)
state_n = REC_ACK ;
else
state_n = state_c ;
end
RD_DATA: begin
if(rddata2sendack)
state_n = SEND_ACK ;
else
state_n = state_c ;
end
REC_ACK: begin
if(recack2idle)
state_n = IDLE ;
else if(recack2stop)
state_n = STOP ;
else
state_n = state_c ;
end
SEND_ACK: begin
if(sendack2idle)
state_n = IDLE ;
else if(sendack2stop)
state_n = STOP ;
else
state_n = state_c ;
end
STOP: begin
if(stop2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default: state_n = IDLE ;
endcase
end
// 状态转移条件
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)begin
if(!rst_n)begin
cnt_scl <= 0 ;
end
else if(add_cnt_scl)begin
if(end_cnt_scl)begin
cnt_scl <= 0 ;
end
else begin
cnt_scl <= cnt_scl + 1 ;
end
end
end
assign add_cnt_scl = state_c != IDLE ;
assign end_cnt_scl = add_cnt_scl && (cnt_scl == `SCL_PERIOD - 1) ;
// bit_num
always @(*)begin
if(state_c == RD_DATA || state_c == WR_DATA)begin
bit_num = 8 ;
end
else begin
bit_num = 1 ;
end
end
// cnt_bit
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_bit <= 0 ;
end
else if(add_cnt_bit)begin
if(end_cnt_bit)begin
cnt_bit <= 0 ;
end
else begin
cnt_bit <= cnt_bit + 1 ;
end
end
end
assign add_cnt_bit = end_cnt_scl ;
assign end_cnt_bit = add_cnt_bit && (cnt_bit == bit_num - 1) ;
// dout_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dout_data <= 0 ;
end
else if(req)begin
dout_data <= wr_din ;
end
end
// command
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
command <= 0 ;
end
else if(req)begin
command <= cmd ;
end
end
// scl_dout
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
scl_dout <= 1'b1 ;
end
else if(idle2start | idle2wrdata | idle2rddata)begin
scl_dout <= 1'b0 ;
end
else if(add_cnt_scl && cnt_scl == `SCL_HALF)begin
scl_dout <= 1'b1 ;
end
else if(end_cnt_scl && ~stop2idle)begin
scl_dout <= 1'b0 ;
end
end
// sda_dout_en
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_dout_en <= 1'b0 ;
end
else if(idle2start || idle2wrdata || rddata2sendack || sendack2stop || recack2stop)begin
sda_dout_en <= 1'b1 ;
end
else if(idle2rddata || start2rddata || wrdata2recack || stop2idle)begin
sda_dout_en <= 1'b0 ;
end
end
// sda_dout
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
sda_dout <= 1'b1 ;
end
else if(state_c == START)begin
if(cnt_scl == `HIGH_HALF)begin
sda_dout <= 1'b0 ;
end
else if(cnt_scl == `LOW_HALF)begin
sda_dout <= 1'b1 ;
end
end
else if(state_c == WR_DATA && cnt_scl == `LOW_HALF)begin
sda_dout <= dout_data[7 - cnt_bit] ;
end
else if(state_c == SEND_ACK && cnt_scl == `LOW_HALF)begin
sda_dout <= (command & `STO) ? 1'b1 : 1'b0 ;
end
else if(state_c == STOP)begin
if(cnt_scl == `LOW_HALF)begin
sda_dout <= 1'b0;
end
else if(cnt_scl == `HIGH_HALF)begin
sda_dout <= 1'b1;
end
end
else if(wrdata2recack | rddata2sendack)begin
sda_dout <= 1'b1;
end
end
// rx_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rx_data <= 0 ;
end
else if(state_c == RD_DATA && cnt_scl == `HIGH_HALF)begin
rx_data[7 - cnt_bit] <= sda_in ;
end
end
// ack_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ack_flag <= 1'b0 ;
end
else if(state_c == REC_ACK && cnt_scl == `HIGH_HALF)begin
ack_flag <= ~sda_in ;
end
else begin
ack_flag <= 1'b0 ;
end
end
// 输出
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_interface
output req , // 读写请求
output [7:0] wr_dout , // 发送指令数据
output [3:0] cmd , // 发送读写控制命令
input [7:0] rdin , // 读回的数据
input rw_done , // 读写一字节数据完成标志
input rdin_vld ,
// data_process
output [19:0] hum_data , // 湿度数据
output [19:0] tem_data , // 温度数据
output data_vld // 温湿度数据有效标志
);
// 参数定义
localparam WAIT = 8'b0000_0001 , // 上电等待40ms
INIT = 8'b0000_0010 , // 初始化
WAIT_INIT = 8'b0000_0100 , // 等待初始化完成
IDLE = 8'b0000_1000 , // 空闲
RD_REQ = 8'b0001_0000 , // 读数据请求
WAIT_MES = 8'b0010_0000 , // 等待测量完成80ms
READ = 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)begin
if(!rst_n)begin
state_c <= WAIT ;
end
else begin
state_c <= state_n ;
end
end
always @(*)begin
case (state_c)
WAIT: begin
if(wait2init)
state_n = INIT ;
else
state_n = state_c ;
end
INIT: begin
if(init2waitinit)
state_n = WAIT_INIT ;
else
state_n = state_c ;
end
WAIT_INIT: begin
if(waitinit2idle)
state_n = IDLE ;
else if(waitinit2init)
state_n = INIT ;
else
state_n = state_c ;
end
IDLE: begin
if(idle2rdreq)
state_n = RD_REQ ;
else
state_n = state_c ;
end
RD_REQ: begin
if(rdreq2waitmes)
state_n = WAIT_MES ;
else
state_n = state_c ;
end
WAIT_MES: begin
if(waitmes2read)
state_n = READ ;
else
state_n = state_c ;
end
READ: begin
if(read2done)
state_n = DONE ;
else
state_n = state_c ;
end
DONE: begin
if(done2idle)
state_n = IDLE ;
else
state_n = state_c ;
end
default: state_n = IDLE ;
endcase
end
// 状态转移描述
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) ;
// delay
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
delay <= `DELAY_40ms ;
end
else if(state_c == WAIT)begin
delay <= `DELAY_40ms ;
end
else if(state_c == WAIT_MES)begin
delay <= `DELAY_80ms ;
end
else if(state_c == IDLE)begin
delay <= `DELAY_500ms ;
end
end
// cnt_delay
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 0 ;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 0 ;
end
else begin
cnt_delay <= cnt_delay + 1 ;
end
end
end
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_byte
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0 ;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0 ;
end
else begin
cnt_byte <= cnt_byte + 1 ;
end
end
end
assign add_cnt_byte = rw_done ;
assign end_cnt_byte = add_cnt_byte && (cnt_byte == ((state_c == READ) ? 7:4) - 1) ;
// 使用task任务发送数据
always @(*)begin
case (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) ;
endcase
RD_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) ;
endcase
default: TX(1'b0,0,0) ;
endcase
end
// init_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
init_flag <= 1'b0 ;
end
else if(state_c == WAIT_INIT && rw_done && rdin[3])begin
init_flag <= 1'b1 ;
end
end
// rd_data
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
rd_data <= 0 ;
end
else if(state_c == READ && rw_done && cnt_byte > 0)begin
rd_data <= {rd_data[39:0],rdin} ;
end
end
// task
task 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_ctrl
input din_vld ,
input [19:0] tem_data ,
input [19:0] hum_data ,
// set_limit
input [7:0] tem_lim ,
input [7:0] hum_lim ,
// uart_tx
input ready ,
output [7:0] tx_data ,
output tx_data_vld ,
// beep
output 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 ; // 温度数据小数位1
wire [7:0] tem_dot2 ; // 温度数据小数位2
wire [7:0] hum_ten ; // 湿度数据十位
wire [7:0] hum_bit ; // 湿度数据个位
wire [7:0] hum_dot1 ; // 湿度数据小数位1
wire [7:0] hum_dot2 ; // 湿度数据小数位2
// fifo
wire wrreq ;
wire rdreq ;
wire empty ;
wire full ;
wire [7:0] q ;
wire [7:0] usedw ;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tem_data_r <= 0 ;
hum_data_r <= 0 ;
end
else 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 ;
end
end
// process_flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
process_flag <= 1'b0 ;
end
else if(din_vld)begin
process_flag <= 1'b1 ;
end
else if(end_cnt_byte)begin
process_flag <= 1'b0 ;
end
end
// cnt_byte
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 0 ;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 0 ;
end
else begin
cnt_byte <= cnt_byte + 1 ;
end
end
end
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_alarm
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
tem_alarm <= 1'b0 ;
end
else if(add_cnt_byte && tem_ten >= (tem_lim / 10))begin
tem_alarm <= 1'b1 ;
end
else if(add_cnt_byte && tem_ten < (tem_lim / 10))begin
tem_alarm <= 1'b0 ;
end
end
// hum_alarm
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
hum_alarm <= 1'b0 ;
end
else if(add_cnt_byte && hum_ten >= (hum_lim / 10))begin
hum_alarm <= 1'b1 ;
end
else if(add_cnt_byte && hum_ten < (hum_lim / 10))begin
hum_alarm <= 1'b0 ;
end
end
// flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag <= 1'b0 ;
end
else if(tem_alarm | hum_alarm)begin
flag <= 1'b1 ;
end
else if(~tem_alarm & ~hum_alarm)begin
flag <= 1'b0 ;
end
end
assign 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 ;
// data
always @(*)begin
case (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; // 小数位1
10: data <= tem_dot2 + 48; // 小数位2
11: data <= 8'ha1; // ℃ASCII码
12: data <= 8'he6;
13: data <= 9; // tab
14: 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; // 小数位1
23: data <= hum_dot2 + 48; // 小数位2
24: data <= 8'h25; // %ASCII码
default: data <= 0 ;
endcase
end
// 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_filter
input tem_add , // 设置温度报警值标志,每按一次+5摄氏度
input hum_add , // 设置湿度报警值标志,每按一次+5%
input set , // 设置模式,每按一次在温度和湿度设置之间切换
// seg_driver
output 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_tem
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_tem <= 0 ;
end
else if(add_cnt_tem)begin
if(end_cnt_tem)begin
cnt_tem <= 0 ;
end
else begin
cnt_tem <= cnt_tem + 10 ;
end
end
end
assign add_cnt_tem = tem_add ;
assign end_cnt_tem = add_cnt_tem && (cnt_tem == 100) ;
// cnt_hum
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_hum <= 0 ;
end
else if(add_cnt_hum)begin
if(end_cnt_hum)begin
cnt_hum <= 0 ;
end
else begin
cnt_hum <= cnt_hum + 10 ;
end
end
end
assign add_cnt_hum = hum_add ;
assign end_cnt_hum = add_cnt_hum && (cnt_hum == 100) ;
// flag
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
flag = 1'b0 ;
end
else if(set)begin
flag = ~flag ;
end
end
// 输出
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/1ps
module 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 begin
rst_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);
end
for (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;
end
repeat(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;
else
sda_i = {$random};
end
end
end
$stop;
end
endmodule
仿真结果:
(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:复位按键。