一.理论原理
- I2C 接口模块实现读写数据的流程,将 done 信号(读写一次完成信号)作为输出。AHT10 控制模块例化 I2C 接口模块,将 done 信号作为条件使用,在对应的状态输入对应的命令,最终得到一个传感器温度转换得来的 40 位位宽的数据。将 40 位位宽的数据作为输入传输给数据处理模块,在此模块中,将 40 位位宽的数据转换为温湿度分开的两个 20 位位宽的数据,并将二进制转换为 BCD 码,再将 BCD 码转换为 ASCII 码。将转换完成的 8bit 数据作为输入传输给串口 TX 模块,最终输出到 PC 端上,显示出温湿度情况。
二.手册分析
2.1命令及读取流程
2.2器件地址
2.3读写数据格式
2.4数据转换
2.5测量周期
2.6考察校准
三.设计
3.1总体框图设计
3.2状态转移图
3.2.1 iic_interface
3.2.2 aht10_ctrl
3.3时序图
3.3.1 iic_interface
3.3.2 aht10_ctrl
因为图片太长,分两部分截图
四.代码
4.1 aht10_ctrl.v
(iic_interface可直接用前文IIC读写EEPROM一文的接口模块)
/**************************************功能介绍***********************************
Date :
Author : Alegg xy.
Version :
Description:
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module aht10_ctrl (
input clk ,
input rst_n ,
input [6:0] device_id,
output [39:0] data ,
output data_vld,
output ready ,
output scl ,
inout sda
);
//---------<参数定义>---------------------------------------------------------
//接口模块控制命令
`define START_BIT 5'b00001
`define WRITE_BIT 5'b00010
`define READ_BIT 5'b00100
`define STOP_BIT 5'b01000
`define ACK_BIT 5'b10000
//状态机参数定义
localparam IDLE = 'b0000000001,
INIT_REQ = 'b0000000010,
INIT = 'b0000000100,
WAIT = 'b0000001000,
RD_REQ = 'b0000010000,
RD = 'b0000100000,
DELAY = 'b0001000000,
READ_REQ = 'b0010000000,
READ = 'b0100000000,
DONE = 'b1000000000;
localparam AHT10_WRIGHT = 8'b0111_0000;// 写数据命令
localparam AHT10_READ = 8'b0111_0001;// 读数据命令
localparam AHT10_INIT_0 = 8'hE1;// 初始化命令序列
localparam AHT10_INIT_1 = 8'h08;
localparam AHT10_INIT_2 = 8'h00;
localparam AHT10_MEAS_0 = 8'hAC;// 触发测量命令序列
localparam AHT10_MEAS_1 = 8'h33;
localparam AHT10_MEAS_2 = 8'h00;
parameter MAX_40MS = 'd200_000,
MAX_80MS = 'd400_000,
MAX_2S = 'd100_000_000;
//---------<内部信号定义>-----------------------------------------------------
reg [9:0] cstate ;//现态
reg [9:0] nstate ;//次态
wire IDLE_INIT_REQ ;
wire INIT_REQ_INIT ;
wire INIT_INIT_REQ ;
wire INIT_WAIT ;
wire WAIT_RD_REQ ;
wire RD_REQ_RD ;
wire RD_RD_REQ ;
wire RD_DELAY ;
wire DELAY_READ_REQ ;
wire READ_REQ_READ ;
wire READ_READ_REQ ;
wire READ_DONE ;
wire DONE_WAIT ;
//例化接口模块信号
wire done ;
reg [4:0] cmd ;
reg cmd_vld ;
reg [7:0] op_wr_data ;
//延时计数器
reg [27:0] cnt_delay ;
wire add_cnt_delay ;
wire end_cnt_delay ;
//字节计数器
reg [3:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [27:0] MAX ;//计时最大值
reg [3:0] cmd_num ;
//数据处理
wire [7:0] rd_data;
reg [47:0] rd_data_r;
wire rd_data_vld;
//延时计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd0;
end
else if(add_cnt_delay)begin
if(end_cnt_delay)begin
cnt_delay <= 'd0;
end
else begin
cnt_delay <= cnt_delay + 1'd1;
end
end
end
assign add_cnt_delay = (cstate == IDLE) || (cstate == DONE) || (cstate == DELAY);
assign end_cnt_delay = add_cnt_delay && cnt_delay == MAX - 1'd1;
//考察MAX情况
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
MAX <= MAX_40MS;
end
else if (cstate == IDLE) begin
MAX <= MAX_40MS;//上电延时40ms
end
else if (cstate == DELAY) begin
MAX <= MAX_80MS;//测量延时80ms
end
else if (cstate == DONE) begin
MAX <= MAX_2S;
end
end
//字节计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 'd0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 'd0;
end
else begin
cnt_byte <= cnt_byte + 1'd1;
end
end
end
assign add_cnt_byte = done;
assign end_cnt_byte = add_cnt_byte && cnt_byte == cmd_num - 1;
//考察cmd_num
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cmd_num <= 0;
end
else if (cstate == INIT) begin
cmd_num <= 4;
end
else if (cstate == RD) begin
cmd_num <= 4;
end
else if (cstate == READ) begin
cmd_num <= 7;
end
end
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE :begin
if (IDLE_INIT_REQ) begin
nstate = INIT_REQ;
end
else begin
nstate = cstate;
end
end
INIT_REQ :begin
if (INIT_REQ_INIT) begin
nstate = INIT;
end
else begin
nstate = cstate;
end
end
INIT :begin
if (INIT_WAIT) begin
nstate = WAIT;
end
else if (INIT_INIT_REQ) begin
nstate = INIT_REQ;
end
else begin
nstate = cstate;
end
end
WAIT :begin
if (WAIT_RD_REQ) begin
nstate = RD_REQ;
end
else begin
nstate = cstate;
end
end
RD_REQ :begin
if (RD_REQ_RD) begin
nstate = RD;
end
else begin
nstate = cstate;
end
end
RD :begin
if (RD_DELAY) begin
nstate = DELAY;
end
else if (RD_RD_REQ) begin
nstate = RD_REQ;
end
else begin
nstate = cstate;
end
end
DELAY :begin
if (DELAY_READ_REQ) begin
nstate = READ_REQ;
end
else begin
nstate = cstate;
end
end
READ_REQ :begin
if (READ_REQ_READ) begin
nstate = READ;
end
else begin
nstate = cstate;
end
end
READ :begin
if (READ_DONE) begin
nstate = DONE;
end
else if (READ_READ_REQ) begin
nstate = READ_REQ;
end
else begin
nstate = cstate;
end
end
DONE :begin
if (DONE_WAIT) begin
nstate = WAIT;
end
else begin
nstate = cstate;
end
end
default :;
endcase
end
assign IDLE_INIT_REQ = (cstate == IDLE) && end_cnt_delay;
assign INIT_REQ_INIT = (cstate == INIT_REQ) && 1'b1 ;
assign INIT_INIT_REQ = (cstate == INIT) && done ;
assign INIT_WAIT = (cstate == INIT) && end_cnt_byte ;
assign WAIT_RD_REQ = (cstate == WAIT) && 1'b1 ;
assign RD_REQ_RD = (cstate == RD_REQ) && 1'b1 ;
assign RD_RD_REQ = (cstate == RD) && done ;
assign RD_DELAY = (cstate == RD) && end_cnt_byte ;
assign DELAY_READ_REQ = (cstate == DELAY) && end_cnt_delay;
assign READ_REQ_READ = (cstate == READ_REQ) && 1'b1 ;
assign READ_READ_REQ = (cstate == READ) && done ;
assign READ_DONE = (cstate == READ) && end_cnt_byte ;
assign DONE_WAIT = (cstate == DONE) && end_cnt_delay;
//发送指令
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
TX(0,0,0);
end
else begin
case (cstate)
INIT_REQ:begin//发送0xE1
case (cnt_byte)
0:TX(1,(`START_BIT | `WRITE_BIT),AHT10_WRIGHT);
1:TX(1,(`WRITE_BIT),AHT10_INIT_0);
2:TX(1,(`WRITE_BIT),AHT10_INIT_1);
3:TX(1,(`WRITE_BIT | `STOP_BIT),AHT10_INIT_2);
default: TX(0,cmd,op_wr_data);
endcase
end
RD_REQ:begin//发送0xAC
case (cnt_byte)
0:TX(1,{`WRITE_BIT | `START_BIT},AHT10_WRIGHT);
1:TX(1,{`WRITE_BIT},AHT10_MEAS_0);
2:TX(1,{`WRITE_BIT},AHT10_MEAS_1);
3:TX(1,{`WRITE_BIT | `STOP_BIT},AHT10_MEAS_2);
default: TX(0,cmd,op_wr_data);
endcase
end
READ_REQ:begin//接受数据
case (cnt_byte)
0:TX(1,{`WRITE_BIT | `START_BIT},AHT10_READ);
1:TX(1,{`READ_BIT},8'h00);
2:TX(1,{`READ_BIT},8'h00);
3:TX(1,{`READ_BIT},8'h00);
4:TX(1,{`READ_BIT},8'h00);
5:TX(1,{`READ_BIT},8'h00);
6:TX(1,{`READ_BIT | `ACK_BIT | `STOP_BIT},8'h00);
default: TX(0,cmd,op_wr_data);
endcase
end
default: TX(0,cmd,op_wr_data);
endcase
end
end
//数据处理
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rd_data_r <= 'd0;
end
else if (rd_data_vld) begin
rd_data_r <= {rd_data_r[39:0],rd_data};
end
end
//接口模块例化
i2c_interface u_i2c_interface(
.clk (clk),
.rst_n (rst_n),
.cmd (cmd),
.cmd_vld (cmd_vld),
.done (done),
.wr_data (op_wr_data),
.rd_data (rd_data),
.rd_data_vld(rd_data_vld),
.scl (scl),
.sda (sda)
);
task TX;
input task_cmd_vld;
input [4:0] task_cmd;
input [7:0] task_wr_data;
begin
cmd_vld = task_cmd_vld;
cmd = task_cmd;
op_wr_data = task_wr_data;
end
endtask
assign data = rd_data_r[39:0];
assign data_vld = READ_DONE;
assign ready = cstate == WAIT;
endmodule
4.2 data_process(数据处理模块)
/**************************************功能介绍***********************************
Date :
Author : Alegg xy.
Version :
Description:
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module data_process(
input clk ,
input rst_n ,
input [39:0] data ,
input data_vld,
input ready ,
output [7:0] tx_data ,
output tx_data_vld
);
//---------<参数定义>---------------------------------------------------------
//状态机参数定义
localparam IDLE = 'b01,//
DATA = 'b10;//
reg [19:0] data_hum;//湿度数据
reg [19:0] data_temp;//温度数据
reg [40:0] data_hum_r;
reg [40:0] data_temp_r;
wire [7:0] data_hum_w0;
wire [7:0] data_hum_w1;
wire [7:0] data_hum_w2;
wire [7:0] data_temp_w0;
wire [7:0] data_temp_w1;
wire [7:0] data_temp_w2;
reg data_vld_0;
reg data_vld_1;
reg [1:0] cstate ;//现态
reg [1:0] nstate ;//次态
wire IDLE_DATA ;
wire DATA_IDLE ;
reg [4:0] cnt_byte ;
wire add_cnt_byte ;
wire end_cnt_byte ;
reg [7:0] num ;
//---------<内部信号定义>-----------------------------------------------------
//数据有效信号打拍
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_vld_0 <= 0;
data_vld_1 <= 0;
end
else begin
data_vld_0 <= data_vld;
data_vld_1 <= data_vld_0;
end
end
//数据寄存
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_hum <= 0;
data_temp <= 0;
end
else if (data_vld) begin
data_hum <= data[39:20];
data_temp <= data[19:0];
end
else begin
data_hum <= data_hum;
data_temp <= data_temp;
end
end
//二进制转BCD
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_hum_r <= 12'd123;
end
else if (data_vld_1) begin
data_hum_r <= ((data_hum*10000)>>20);
end
end
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_temp_r <= 12'd123;
end
else if (data_vld_1) begin
data_temp_r <= (((data_temp*20000)>>20)-5000);
end
end
//每一位数据
assign data_hum_w0 = (data_hum_r/1000)%10;
assign data_hum_w1 = (data_hum_r/100)%10;
assign data_hum_w2 = (data_hum_r/10)%10;
assign data_temp_w0 = (data_temp_r/1000)%10;
assign data_temp_w1 = (data_temp_r/100)%10;
assign data_temp_w2 = (data_temp_r/10)%10;
//byte计数器
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 4'd0;
end
else if(add_cnt_byte)begin
if(end_cnt_byte)begin
cnt_byte <= 4'd0;
end
else begin
cnt_byte <= cnt_byte + 1'b1;
end
end
end
assign add_cnt_byte = (cstate == DATA) && ready;
assign end_cnt_byte = add_cnt_byte && cnt_byte == 18;
//第一段:时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cstate <= IDLE;
end
else begin
cstate <= nstate;
end
end
//第二段:组合逻辑描述状态转移规律和状态转移条件
always @(*) begin
case(cstate)
IDLE:begin
if (IDLE_DATA) begin
nstate = DATA;
end
else begin
nstate = cstate;
end
end
DATA:begin
if (DATA_IDLE) begin
nstate = IDLE;
end
else begin
nstate = cstate;
end
end
default : nstate = IDLE;
endcase
end
assign IDLE_DATA = (cstate == IDLE) && ready && data_vld;
assign DATA_IDLE = (cstate == DATA) && end_cnt_byte;
//BCD转ASCII码
always @(*) begin
if (cstate == DATA) begin
case (cnt_byte)
0: num = 8'hCE;
1: num = 8'hC2;
2: num = 8'hB6;
3: num = 8'hC8;
4: num = ":";
5: num = data_temp_w0 + "0";
6: num = data_temp_w1 + "0";
7: num = ".";
8: num = data_temp_w2 + "0";
9: num = 8'hCA;
10: num = 8'hAA;
11: num = 8'hB6;
12: num = 8'hC8;
13: num = ":";
14: num = data_hum_w0 + "0";
15: num = data_hum_w1 + "0";
16: num = ".";
17: num = data_hum_w2 + "0";
18: num = 8'h0a;
default: num = "0";
endcase
end
end
assign tx_data = num;
assign tx_data_vld = add_cnt_byte;
endmodule
五.仿真
5.1仿真代码
`timescale 1ns/1ns
module tb_top();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
//输出信号定义
wire tx ;
wire scl ;
wire sda ;
pullup(sda);
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
defparam u_top.u_aht10_ctrl.MAX_40MS = 20,
u_top.u_aht10_ctrl.MAX_80MS = 40,
u_top.u_aht10_ctrl.MAX_2S = 1000;
//模块例化
top u_top(
.clk (tb_clk ),
.rst_n (tb_rst_n ),
.tx (tx),
.scl (scl),
.sda (sda)
);
i2c_slave_model u_i2c_slave_model(
.scl(scl),
.sda(sda)
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
//产生激励
initial begin
tb_rst_n = 1'b1;
#(CLOCK_CYCLE*2);
tb_rst_n = 1'b0;
#(CLOCK_CYCLE*20);
tb_rst_n = 1'b1;
#5000000;
$stop;
end
endmodule
其中用到了仿真模型,完整工程可以通过https://download.csdn.net/download/weixin_67803687/88384435获取
5.2仿真效果
5.2.1 scl & sda关系
5.2.2 不同状态计bit数不同
读数据和写数据时 8bit,起始位,校验位,停止位都是 1bit
5.2.3 不同状态命令值不同
由图可知我们不同状态的命令不同,需要符合起始位/写数据/读数据/停止位/校验位,对应的关系存在如下情况
5.2.4 初始化指令 & 温度测量指令
5.2.5 aht10_ctrl模块初始化命令状态循环
温度测量命令同理,读数据更改为 7 个字节,也就是循环 7 次