一、需求分析
Aht10温湿度传感器的驱动,并将采集的温湿度数据通过串口发送到PC端
AHT10有道云笔记
二、AHT10简介
(一)AHT10特性
AHT10是一款标准I2C接口的温湿度传感器。供电范围为1.8-3.6V,推荐电压为3.3V。电源( VDD)和接地(GND)之间须连接一个 10uF的去耦电容,器件引脚接口图如下:
SDA引脚用于传感器的数据输入和输出。当向传感器发送命令时,SDA在串行时钟( SCL)的上升沿有效,且当SCL为高电平时,SDA 必须保持稳定。在 SCL下降沿之后,SDA值可被改变。
开发板硬件管脚配置表:
Direction | Node Name | Location |
Input | clk | PIN E1 |
Input | rst n | PIN E15 |
Input | rx | PIN M2 |
Output | tx | PIN G1 |
Output | iic_scl | PIN D9 |
Bidir | iic_sda | PIN E9 |
AHT10读写遵循I2C协议,可参考IIC协议工程IIC有道云笔记,时序图如下:
每个传输序列都以 Start状态作为开始并以Stop状态作为结束,如图:
启动传输状态:在SCL 高电平期间,SDA 拉低。开始状态是由主机控制的一种特殊的总线状态,指示从机传输开始 (Start 之后,总线一般被认为处于占线状态)
停止传输状态:在SCL 高电平期间,SDA 释放。停止状态是由主机控制的一种特殊的总线状态,指示从机传输结束 (Stop 之后,总线一般被认为处于闲置状态)
(二)AHT10基本指令及测量步骤
1、AHT10基本指令集:
2、AHT10测量步骤
(1)上电后等待40ms ,读取温湿度值之前,首先看状态字的校准使能位Bit[3]是否为1(通过发送0x71可以获取一个字节的状态字),如果不为1,要发送0xEi命令(初始化),此命令参数有两个字节,第一个字节为0x08,第二个字节为0x00。状态位说明如下:
(2)直接发送0xAC命令(触发测量),此命令参数有两个字节,第一个字节为0x33,第二个字节为0x00。
(3)等待80ms测量数据完成,发送0x71读取6字节温湿度数据。
因此,将AHT10温度传感器进行模块划分,主要包括控制模块和i2c接口模块,由于之前已经学过并且完成了i2c接口模块的仿真,所以在AHT10控制模块中直接进行调用。
(三)数据转换
按照此公式进行转换数据不正确,所以在数据转换代码中做了修改。由于在FPGA中不能进行小数运算,所以我们对计算公式进行了打拍和扩大倍数的处理(流水线处理)
/****************************************************************
接收数据处理
****************************************************************/
//流水线处理数据
reg [33:0] humi_data_r1;
reg [34:0] temp_data_r1;
reg [14:0] humi_data_r2;
reg [14:0] temp_data_r2;
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
humi_data_r1 <= 0;
temp_data_r1 <= 0;
end
elsebegin
humi_data_r1 <= humi_data * 10000; //保留两位小数
temp_data_r1 <= temp_data * 20000;
end
always@(posedge clk or negedge rst_n)
if(!rst_n)begin
humi_data_r2 <= 0;
temp_data_r2 <= 0;
end
elsebegin
humi_data_r2 <= humi_data_r1 >> 20;
temp_data_r2 <= (temp_data_r1 >> 20) - 5000;
end
assign tem_data = temp_data_r2;
assign hum_data = humi_data_r2;
三、系统架构设计
模块框架图:
本工程将系统功能划分为5个子模块,对于数据处理的两个子模块下面包含各有一个次子模块(可调用多次),其中hex2ascii这个模块下面还包含了hex4bit2ascii模块(循环调用8次),各模块功能描述如下:
top :顶层设计模块。
aht10_ctrl :控制驱动AHT10命令的发送、驱动i2c_interface接口模块与AHT10传感器进行数据交互。
i2c_interface:AHT10使用标准的I2C接口协议,该模块实现aht10_ctrl控制模块通过I2C协议时序读取温湿度数据。
control : 通过调用两次binary_bcd模块将aht10_ctrl控制模块读回的温湿度数据,将二进制数转为bcd码,tem_bcd_data与hum_bcd_data拼接后传入num_porcess模块,共 5byte 共 30bit 数据,高 15 位为温度,低 15 位为湿度。
binary_bcd :实现二进制转换为bcd码的模块;
num_process :温湿度数据处理模块,调用hex2ascii模块将转换的数据ASCII码通过串口进行发送。
hex2ascii :利用generate语句对hex4bit2ascii循环调用八次,将接收到的bcd数据转换为ASCII码。
tx_fsm_ uart :串口数据发送模块,将data_process模块转换后的ASCII格式数据通过串口发送给上位机,通过串口调试助手,打印温湿度数据信息。
为例节约资源,将采集的温湿度数据进行二进制转bcd码后传入数据处理模块通过hex2ascii和hex4bit2ascii两个子模块的调用将数据处理后可在串口上显示为:
温度 :XX.XX ℃ 湿度 : XX.XX RH 这样的格式。
四、模块说明
(一)主要模块端口信号列表
top模块 | |
clk | 时钟信号 |
rst_n | 复位信号 |
tx | 发送数据到PC端 |
iic_scl | SCL时钟总线 |
iic_sda | SDA数据总线 |
aht_ctrl模块 | |
clk | 时钟信号 |
rst_n | 复位信号 |
tem_data | 输出温度数据 |
hum_data | 输出湿度数据 |
data_vld | 输出数据使能 |
iic_scl | 串行SCL时钟信号 |
iic_sda | iic三态门 |
iic_interface模块 | |
clk | 时钟信号 |
rst_n | 复位信号 |
cmd | 接口命令 |
done | 输出读完一次数据结束使能 |
rev_ack | 输出接收到的响应信号 |
wr_data | 输入写数据 |
rd_data | 输出读数据 |
rd_data_vld | 输出读使能 |
iic_scl | 串行SCL时钟信号 |
iic_sda | iic三态门 |
control模块 | |
clk | 时钟信号 |
rst_n | 复位信号 |
hum_data | 输入湿度数据 |
tem_data | 输入温度数据 |
tem_data_vld | 输入数据使能 |
tem_bcd | 输出温度数据bcd码 |
bcd_tem_data_vld | 输出温度数据使能 |
hum_bcd | 输出湿度数据bcd码 |
bcd_hum_data_vld | 输出湿度数据使能 |
num_proces模块 | |
clk | 时钟信号 |
rst_n | 复位信号 |
tx_ready | 接受串口反馈信号 |
tem_bcd_data | 输入温度数据bcd码 |
bcd_tem_data_vld | 温度使能 |
hum_bcd_data | 输入湿度数据bcd码 |
bcd_hum_data_vld | 湿度使能信号 |
tx_data | 输出数据发送到串口 |
tx_data_vld | 串口使能信号 |
tx_fsm_uart | |
端口 | 说明 |
clk | 时钟信号 |
rst_n | 复位信号 |
tx_data | 输入转换后的数据(8bit) |
tx_data_vld | 接收模块使能信号 |
tx_ready | 输出反馈使能 |
tx | 输出数据发送到PC端(1bit) |
(二)状态转移
通过对AHT10传感器读取数据流程的掌握,可将控制模块状态机梳理出来:
(三)时序图
五、完整代码
(1)top
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description: 顶层文件
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module top(
input clk ,
input rst_n ,
output iic_scl ,
inout iic_sda ,
output tx
);
//---------<参数定义>---------------------------------------------------------
//---------<内部信号定义>-----------------------------------------------------
wire [14:0] hum_data ;
wire [14:0] tem_data ;
wire ready ;
wire [7:0] tx_data ;
wire tx_data_vld ;
wire [15:0] tem_bcd ;
wire [15:0] hum_bcd ;
wire data_vld ;
wire bcd_tem_data_vld ;
wire bcd_hum_data_vld ;
aht10_ctrl aht10_ctrl_inst (
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* output [14:0] */.tem_data( tem_data ),
/* output [14:0] */.hum_data( hum_data ),
/* output */.data_vld( data_vld ),
/* output */.iic_scl ( iic_scl ),
/* inout */.iic_sda ( iic_sda )
);
control control_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input [14:0] */.hum_data ( hum_data ),
/* input [14:0] */.tem_data ( tem_data ),
/* input */.tem_data_vld ( data_vld ),
/* output [15:0] */.tem_bcd ( tem_bcd ),
/* output */.bcd_tem_data_vld ( bcd_tem_data_vld ),
/* output [15:0] */.hum_bcd ( hum_bcd ),
/* output */.bcd_hum_data_vld ( bcd_hum_data_vld )
);
num_proces num_proces_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input */.tx_ready ( ready ),
/* input [15:0] */.tem_bcd_data ( tem_bcd ),
/* input */.bcd_tem_data_vld( bcd_tem_data_vld ),
/* input [15:0] */.hum_bcd_data ( hum_bcd ),
/* input */.bcd_hum_data_vld( bcd_hum_data_vld ),
/* output reg [7:0] */.tx_data ( tx_data ),
/* output */.tx_data_vld ( tx_data_vld )
);
tx_fsm_uart#(
.CHECK_BIT ( "None" ) , //校验方法,“None”无校验,“Odd”奇校验,“E
.BPS ( 115200 ) ,
.CLK ( 50_000_000 )
)tx_fsm_uart_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input */.tx_data_vld ( tx_data_vld ),
/* input [7:0] */.tx_data ( tx_data ),
/* output */.ready ( ready ),
/* output reg */.tx ( tx )
);
endmodule
(2)aht10_ctrl
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description:
*********************************************************************************/
//aht10_ctrl aht10_ctrl_inst (
// /* input */.clk (clk ),
// /* input */.rst_n (rst_n ),
// /* output [19:0] */.tem_data(tem_data),
// /* output [19:0] */.hum_data(hum_data),
// /* output */.data_vld(data_vld),
// /* input */.iic_scl (iic_scl ),
// /* inout */.iic_sda (iic_sda )
//);
//---------<模块及端口声名>------------------------------------------------------
module aht10_ctrl (
input clk ,
input rst_n ,
output [14:0] tem_data,
output [14:0] hum_data,
output data_vld,
output iic_scl ,
inout iic_sda
);
//---------<参数定义>---------------------------------------------------------
//状态机参数定义
localparam
WAIT = 0, //上电等待40ms,等待结束后进入INIT状态。
INIT = 1, //初始化,发送初始化指令,进入WAIT_INIT状态。
IDLE = 2, //空闲状态,等待读请求。
RD_REQ = 3, //发送读取温湿度数据指令,发送完成后进入WAIT_MES状态。
WAIT_MES = 4, //等待80ms测量温湿度数据完成,进入READ状态。
READ = 5, //读取温度数据,读取完成后进入IDLE状态,等待下一次读请求。
DEALY = 6; //延时两秒
parameter WAIT_20MS = 1_000_000;
//接口模块控制命令
`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
// 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 // 读数据命令
//---------<内部信号定义>-----------------------------------------------------
wire [7:0] rd_data ;
wire rd_data_vld ;
wire done ;
reg [4:0] cmd ;
reg [7:0] op_wr_data ;
reg [2:0] state ;//现态
reg [27:0] cnt ;
reg [27:0] max ;
wire add_cnt,end_cnt ;
reg [3:0] cnt_byte ;
wire add_byte_cnt,end_byte_cnt ;
reg [2:0] byte_max ;
wire wait2init ;
wire init2idle ;
wire idle2re_req ;
wire rd_req2wait_mes ;
wire wait_mes2read ;
wire read2delay ;
wire dealy2idle ;
/****************************************************************
fsm
****************************************************************/
// 时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state <= WAIT;
end
else case(state)
WAIT : if(wait2init)
state <= INIT;
INIT : if(init2idle)
state <= IDLE;
IDLE : if(idle2re_req)
state <= RD_REQ;
RD_REQ : if(rd_req2wait_mes)
state <= WAIT_MES;
WAIT_MES: if(wait_mes2read)
state <= READ;
READ : if(read2delay)
state <= DEALY;
DEALY : if(dealy2idle)
state <= IDLE;
default : state <= WAIT;
endcase
end
assign wait2init = state == WAIT && end_cnt;
assign init2idle = state == INIT && end_byte_cnt;
assign idle2re_req = state == IDLE;
assign rd_req2wait_mes = state == RD_REQ && end_byte_cnt;
assign wait_mes2read = state == WAIT_MES && end_cnt;
assign read2delay = state == READ && end_byte_cnt;
assign dealy2idle = state == DEALY && end_cnt;
/****************************************************************
延时计数
****************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 'd0;
end
else if(add_cnt)begin
if(end_cnt)begin
cnt <= 'd0;
end
else begin
cnt <= cnt + 1'b1;
end
end
end
assign add_cnt = (state == WAIT || state == DEALY || state == WAIT_MES);
assign end_cnt = add_cnt && cnt == max - 1;
always @(*)begin
case (state)
WAIT : max = WAIT_20MS*4;
DEALY : max = WAIT_20MS*100;
WAIT_MES: max = WAIT_20MS*2;
default: max = 1 ;
endcase
end
/****************************************************************
cnt_byte
****************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_byte <= 'd0;
end
else if(add_byte_cnt)begin
if(end_byte_cnt)begin
cnt_byte <= 'd0;
end
else begin
cnt_byte <= cnt_byte + 1'b1;
end
end
end
assign add_byte_cnt = done;
assign end_byte_cnt = add_byte_cnt && cnt_byte == byte_max-1;
always@(*)
case(state)
INIT : byte_max = 4;
RD_REQ : byte_max = 4;
READ : byte_max = 7;
default : byte_max = 1;
endcase
//模块例化
iic_interface #(
.SPEED ("100K" ), //低速模式:"100K" 快速模式:"400K"
.SYS_CLK(50_000_000) //系统时钟频率
)iic_interface_inst(
/* input */.clk ( clk ),
/* input */.rst_n ( rst_n ),
/* input [4:0] */.cmd ( cmd ),
/* output */.done ( done ), //操作结束
/* output reg */.rev_ack ( ), //接收到的响应信号
/* input [7:0] */.wr_data ( op_wr_data ), //伴随cmd_vld信号一起接收写数据
/* output [7:0] */.rd_data ( rd_data ),
/* output */.rd_data_vld ( rd_data_vld ),
/* output reg */.iic_scl ( iic_scl ),
/* inout */.iic_sda ( iic_sda )
);
task TX;
input [4:0] task_cmd ;
input [7:0] task_wr_data ;
begin
cmd = task_cmd ;
op_wr_data = task_wr_data ;
end
endtask
assign ready = state == IDLE;
// 使用task任务发送数据
always @(*)begin
case (state)
INIT :
case(cnt_byte)
0 :TX({`START_BIT | `WRITE_BIT},{`AHT10_ADDR,1'b0}) ;// 发起始位、写控制字
1 :TX(`WRITE_BIT,`GET_STATE) ;
2 :TX(`WRITE_BIT,`AHT10_INIT_1) ;
3 :TX({`WRITE_BIT | `STOP_BIT} ,`AHT10_INIT_2) ;
default :TX(0,0) ;
endcase
RD_REQ:
case(cnt_byte)
0 :TX({`START_BIT | `WRITE_BIT },{`AHT10_ADDR,1'b0}) ; // 发起始位、写控制字
1 :TX(`WRITE_BIT,`AHT10_MEAS_0) ;
2 :TX(`WRITE_BIT,`AHT10_MEAS_1) ;
3 :TX({`WRITE_BIT | `STOP_BIT},`AHT10_MEAS_2) ;
default :TX(0,0) ;
endcase
READ :
case(cnt_byte)
0 :TX({`START_BIT | `WRITE_BIT},{`AHT10_ADDR,1'b1}) ;// 发起始位、写控制字
1 :TX((`READ_BIT),8'h00);
2 :TX((`READ_BIT),8'h00);
3 :TX((`READ_BIT),8'h00);
4 :TX((`READ_BIT),8'h00);
5 :TX((`READ_BIT),8'h00);
6 :TX({`ACK_BIT |`READ_BIT | `STOP_BIT},8'h00) ;
default :TX(0,0) ;
endcase
default: TX(0,0) ;
endcase
end
/**************************************************************
接收温湿度数据
**************************************************************/
wire [19:0] humi_data;
wire [19:0] temp_data;
reg [39:0] aht10_data ;
wire aht10_data_vld ;
always@(posedge clk or negedge rst_n)
if(!rst_n)
aht10_data <= 0;
else if(state == READ && cnt_byte > 0 && rd_data_vld)
aht10_data <= {aht10_data[31:0],rd_data};
assign humi_data = aht10_data[39:20];
assign temp_data = aht10_data[19:0];
assign aht10_data_vld = read2delay;
/****************************************************************
接收数据处理
****************************************************************/
//流水线处理数据
reg [33:0] humi_data_r1;
reg [34:0] temp_data_r1;
reg [14:0] humi_data_r2;
reg [14:0] temp_data_r2;
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
humi_data_r1 <= 0;
temp_data_r1 <= 0;
end
else begin
humi_data_r1 <= humi_data * 10000; //保留两位小数
temp_data_r1 <= temp_data * 20000;
end
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
humi_data_r2 <= 0;
temp_data_r2 <= 0;
end
else begin
humi_data_r2 <= humi_data_r1 >> 20;
temp_data_r2 <= (temp_data_r1 >> 20) - 5000;
end
assign tem_data = temp_data_r2;
assign hum_data = humi_data_r2;
//有效信号打拍
reg data_vld_r1 ;
reg data_vld_r2 ;
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
data_vld_r1 <= 0;
data_vld_r2 <= 0;
end
else begin
data_vld_r1 <= aht10_data_vld;
data_vld_r2 <= data_vld_r1;
end
assign data_vld = data_vld_r2;
endmodule
(3)iic_interface
/**************************************************************
@File : iic_interface.v
@Time : 2023/08/28 15:48:01
@Author : lingxioaxiao
@EditTool: VS Code
@Font : UTF-8
@Function: IIC接口模块
接口命令列表(cmd):
0 1
bit0(起始位) NO YES
bit1(写数据) NO YES
bit2(读数据) NO YES
bit3(停止位) NO YES
bit4(响应位) ACK NO_ACK 此位只针对读数据
以上命令位可以实现任意组合,需要注意读写数据不能同时有效!!!
**************************************************************/
// iic_interface #(
// .SPEED ("100K" ), //低速模式:"100K" 快速模式:"400K"
// .SYS_CLK(50_000_000) //系统时钟频率
// )iic_interface_inst(
// /* input */.clk ( ),
// /* input */.rst_n ( ),
// /* input [4:0] */.cmd ( ),
// /* input */.cmd_vld ( ),
// /* output */.done ( ), //操作结束
// /* output reg */.rev_ack ( ), //接收到的响应信号
// /* input [7:0] */.wr_data ( ), //伴随cmd_vld信号一起接收写数据
// /* output [7:0] */.rd_data ( ),
// /* output */.rd_data_vld ( ),
// /* output reg */.iic_scl ( ),
// /* inout */.iic_sda ( )
// );
module iic_interface #(
parameter SPEED = "100K" , //低速模式:"100K" 快速模式:"400K"
parameter SYS_CLK = 50_000_000 //系统时钟频率
)(
input clk ,
input rst_n ,
//控制接口
input [4:0] cmd ,
output done , //操作结束
output reg rev_ack , //接收到的响应信号
//数据接口
input [7:0] wr_data , //伴随cmd_vld信号一起接收写数据
output [7:0] rd_data ,
output rd_data_vld ,
output reg iic_scl ,
inout iic_sda
);
parameter DIV_CNT_MAX = (SPEED == "100K")? SYS_CLK/100_000
:(SPEED == "400K")? SYS_CLK/400_000
: SYS_CLK/100_000;
localparam SCL_HIGH_HALF = (DIV_CNT_MAX*3/4) - 1;
localparam SCL_LOW_HALF = (DIV_CNT_MAX*1/4) - 1;
localparam DIV_CNT_BIT = clog2(DIV_CNT_MAX - 1);
`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
`define ACK 0
`define NO_ACK 1
// reg [4:0] cmd_r ;
// reg [7:0] wr_data_r ;
reg [3:0] state;
localparam IDLE = 4'd0 ,
START = 4'd1 ,
WR_DATA = 4'd2 ,
CHECK_ACK = 4'd3 ,
RD_DATA = 4'd4 ,
SEND_ACK = 4'd5 ,
STOP = 4'd6 ;
wire idle2start ;
wire idle2wr_data ;
wire idle2rd_data ;
wire state2wr_data ;
wire wr_data2check_ack ;
wire check_ack2stop ;
wire check_ack2idle ;
wire rd_data2send_ack ;
wire send_ack2stop ;
wire send_ack2idle ;
wire stop2idle ;
reg [DIV_CNT_BIT-1:0] cnt_div ;
wire add_div_cnt ;
wire end_div_cnt ;
reg [3:0] cnt_bit;
wire add_bit_cnt ;
wire end_bit_cnt ;
reg [3:0] cnt_bit_max ;
reg sda_en ;
reg sda_out ;
wire sda_in ;
reg [7:0] rev_data ;
/**************************************************************
计算数值的二进制位数
**************************************************************/
//clog2对应计算数字的二进制位数
function integer clog2(input integer caclnum);
begin
for(clog2 = 0; caclnum > 0; clog2 = clog2 + 1)
caclnum = caclnum >> 1;
end
endfunction
/**************************************************************
寄存端口信息
**************************************************************/
// always@(posedge clk or negedge rst_n)
// if(!rst_n) begin
// cmd_r <= 0;
// wr_data_r <= 0;
// end
// else if(cmd_vld) begin
// cmd_r <= cmd;
// wr_data_r <= wr_data;
// end
/**************************************************************
状态机
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
state <= IDLE;
else case (state)
IDLE : if(idle2start)
state <= START;
else if(idle2wr_data)
state <= WR_DATA;
else if(idle2rd_data)
state <= RD_DATA;
START : if(state2wr_data)
state <= WR_DATA;
//写数据部分
WR_DATA : if(wr_data2check_ack)
state <= CHECK_ACK;
CHECK_ACK : if(check_ack2stop)
state <= STOP;
else if(check_ack2idle)
state <= IDLE;
//读数据部分
RD_DATA : if(rd_data2send_ack)
state <= SEND_ACK;
SEND_ACK : if(send_ack2stop)
state <= STOP;
else if(send_ack2idle)
state <= IDLE;
STOP : if(stop2idle)
state <= IDLE;
default : state <= IDLE;
endcase
assign idle2start = state == IDLE /* && cmd_vld */ && (cmd & `START_BIT); //命令中包含起始位
assign idle2wr_data = state == IDLE /* && cmd_vld */ && (cmd & `WRITE_BIT); //命令中包含写数据
assign idle2rd_data = state == IDLE /* && cmd_vld */ && (cmd & `READ_BIT ); //命令中包含读数据
assign state2wr_data = state == START && end_bit_cnt && (cmd & `WRITE_BIT); //命令中包含写数据、1bit的起始位发送完成
assign wr_data2check_ack = state == WR_DATA && end_bit_cnt;
assign check_ack2stop = state == CHECK_ACK && end_bit_cnt && (cmd & `STOP_BIT);
assign check_ack2idle = state == CHECK_ACK && end_bit_cnt && !(cmd & `STOP_BIT);
assign rd_data2send_ack = state == RD_DATA && end_bit_cnt;
assign send_ack2stop = state == SEND_ACK && end_bit_cnt && (cmd & `STOP_BIT);
assign send_ack2idle = state == SEND_ACK && end_bit_cnt && !(cmd & `STOP_BIT);
assign stop2idle = state == STOP && end_bit_cnt;
/**************************************************************
IIC工作时钟
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_div <= 'd0;
else if(add_div_cnt) begin
if(end_div_cnt)
cnt_div <= 'd0;
else
cnt_div <= cnt_div + 1'b1;
end
assign add_div_cnt = state != IDLE;
assign end_div_cnt = add_div_cnt && cnt_div == DIV_CNT_MAX - 1;
always@(posedge clk or negedge rst_n)
if(!rst_n)
iic_scl <= 1;
else if(cnt_div == (DIV_CNT_MAX - 1) >> 1 || stop2idle)
iic_scl <= 1;
else if(end_div_cnt)
iic_scl <= 0;
/**************************************************************
bit计数器
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
cnt_bit <= 'd0;
else if(add_bit_cnt) begin
if(end_bit_cnt)
cnt_bit <= 'd0;
else
cnt_bit <= cnt_bit + 1'b1;
end
assign add_bit_cnt = end_div_cnt;
assign end_bit_cnt = add_bit_cnt && cnt_bit == cnt_bit_max - 1;
always@(*)
case(state)
IDLE : cnt_bit_max = 1;
START : cnt_bit_max = 1;
WR_DATA : cnt_bit_max = 8;
CHECK_ACK : cnt_bit_max = 1;
RD_DATA : cnt_bit_max = 8;
SEND_ACK : cnt_bit_max = 1;
STOP : cnt_bit_max = 1;
default : cnt_bit_max = 1;
endcase
/**************************************************************
sda三态门控制
**************************************************************/
assign iic_sda = sda_en? sda_out : 1'bz;
assign sda_in = iic_sda;
always@(posedge clk or negedge rst_n)
if(!rst_n)
sda_en <= 0;
else if(idle2start || state2wr_data || idle2wr_data || check_ack2stop || rd_data2send_ack)
sda_en <= 1;
else if(idle2rd_data || wr_data2check_ack || stop2idle)
sda_en <= 0;
/**************************************************************
数据发送
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n)
sda_out <= 1;
else case(state)
IDLE : sda_out <= 1;
START : if(cnt_div == SCL_LOW_HALF)
sda_out <= 1;
else if(cnt_div == SCL_HIGH_HALF)
sda_out <= 0;
WR_DATA : if(cnt_div == SCL_LOW_HALF)
sda_out <= wr_data[7-cnt_bit]; //MSB 先发送高位数据
SEND_ACK : if(cnt_div == SCL_LOW_HALF) begin
if(cmd & `ACK_BIT) //判断响应位是ACK还是NACK 结果是NACK
sda_out <= `NO_ACK;
else
sda_out <= `ACK;
end
STOP : if(cnt_div == SCL_LOW_HALF) //兼容在发送停止位之前sda为1的情况,避免stop位的上升沿无法生效
sda_out <= 0;
else if(cnt_div == SCL_HIGH_HALF)
sda_out <= 1;
default : sda_out <= 1;
endcase
/**************************************************************
数据接收
**************************************************************/
always@(posedge clk or negedge rst_n)
if(!rst_n) begin
rev_data <= 8'd0;
rev_ack <= 0;
end
else case(state)
CHECK_ACK : if(cnt_div == SCL_HIGH_HALF) //采样从机应答信号
rev_ack <= sda_in;
RD_DATA : if(cnt_div == SCL_HIGH_HALF) //采样从机的数据
rev_data[7-cnt_bit] <= sda_in;
default : ;
endcase
/**************************************************************
用户接口输出
**************************************************************/
assign done = send_ack2idle || check_ack2idle || stop2idle;
assign rd_data = rev_data;
assign rd_data_vld = send_ack2idle || send_ack2stop;
endmodule
(4)binary2bcd
// binary2bcd #(.binary_width(5),.bcd_width(8)) binary2bcd_inst(
// /*input */.sys_clk (sys_clk),
// /*input */.sys_rst_n (sys_rst_n),
// /*input [binary_width - 1 : 0] */.binary (),
// /*input */.binary_vliad(),
// /*output reg [bcd_width - 1 : 0] */.bcd (),
// /*output reg */.bcd_vliad ()
// );
module binary2bcd #(
parameter binary_width = 5,
parameter bcd_width = 8
)(
input sys_clk ,
input sys_rst_n ,
input [binary_width - 1 : 0] binary ,
input binary_vliad,
output reg [bcd_width - 1 : 0] bcd ,
output reg bcd_vliad
);
parameter SHIFT_MAX = binary_width; //移位次数与二进制的位宽相等
parameter IDLE = 3'b001,
SHIFT = 3'b010,
DONE = 3'b100;
reg [2:0] state;
/* 6个BCD编码,对应输出的BCD编码 */
reg [3:0] bcd_data0,bcd_data1,bcd_data2,bcd_data3,bcd_data4,bcd_data5;
wire [3:0] bcd_temp0,bcd_temp1,bcd_temp2,bcd_temp3,bcd_temp4,bcd_temp5;
//移位次数控制
reg [binary_width - 1 : 0] shift_cnt; //移位的次数,与二进制的位宽相同,可以保证位宽足够使用
wire add_shift_cnt,end_shift_cnt;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
shift_cnt <= 'd0;
else if(add_shift_cnt) begin
if(end_shift_cnt)
shift_cnt <= 'd0;
else
shift_cnt <= shift_cnt + 1'b1;
end
assign add_shift_cnt = state == SHIFT;
assign end_shift_cnt = add_shift_cnt && shift_cnt == SHIFT_MAX - 1; //达到最大的移位次数
/* 状态机 */
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
state <= IDLE;
else case(state)
IDLE : if(binary_vliad == 1'b1)
state <= SHIFT;
SHIFT : if(end_shift_cnt) //达到最大的移位次数
state <= DONE;
DONE : state <= IDLE;
default : state <= IDLE;
endcase
//二进制数据移位
reg [binary_width - 1 : 0] shitf_data;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
shitf_data <= 'd0;
else if(state == SHIFT) //处于移位状态时,每个时钟左移对应计数值
shitf_data <= shitf_data << 1'b1;
else //其他状态,保持最新的二进制数据
shitf_data <= binary;
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n) begin
bcd_data0 <= 4'd0;
bcd_data1 <= 4'd0;
bcd_data2 <= 4'd0;
bcd_data3 <= 4'd0;
bcd_data4 <= 4'd0;
bcd_data5 <= 4'd0;
end
else if(state == SHIFT) begin
bcd_data0 <= {bcd_temp0[2:0],shitf_data[binary_width - 1]}; //更新二进制位宽的最高一位
bcd_data1 <= {bcd_temp1[2:0],bcd_temp0[3]}; //保存上一个BCD缓冲区溢出的一位
bcd_data2 <= {bcd_temp2[2:0],bcd_temp1[3]}; //保存上一个BCD缓冲区溢出的一位
bcd_data3 <= {bcd_temp3[2:0],bcd_temp2[3]}; //保存上一个BCD缓冲区溢出的一位
bcd_data4 <= {bcd_temp4[2:0],bcd_temp3[3]}; //保存上一个BCD缓冲区溢出的一位
bcd_data5 <= {bcd_temp5[2:0],bcd_temp4[3]}; //保存上一个BCD缓冲区溢出的一位
end
else begin
bcd_data0 <= 4'd0;
bcd_data1 <= 4'd0;
bcd_data2 <= 4'd0;
bcd_data3 <= 4'd0;
bcd_data4 <= 4'd0;
bcd_data5 <= 4'd0;
end
//如果当前的BCD缓冲区的数据大于4,就会将该缓冲区的数据加3
assign bcd_temp0 = (bcd_data0 > 4'd4)? (bcd_data0 + 4'd3) : bcd_data0;
assign bcd_temp1 = (bcd_data1 > 4'd4)? (bcd_data1 + 4'd3) : bcd_data1;
assign bcd_temp2 = (bcd_data2 > 4'd4)? (bcd_data2 + 4'd3) : bcd_data2;
assign bcd_temp3 = (bcd_data3 > 4'd4)? (bcd_data3 + 4'd3) : bcd_data3;
assign bcd_temp4 = (bcd_data4 > 4'd4)? (bcd_data4 + 4'd3) : bcd_data4;
assign bcd_temp5 = (bcd_data5 > 4'd4)? (bcd_data5 + 4'd3) : bcd_data5;
//BCD编码数据输出
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bcd <= 'd0;
else if(state == DONE)
bcd <= {bcd_data5,bcd_data4,bcd_data3,bcd_data2,bcd_data1,bcd_data0};
//输出一个时钟周期的有效信号
always@(posedge sys_clk or negedge sys_rst_n)
if(!sys_rst_n)
bcd_vliad <= 1'b0;
else if(state == DONE)
bcd_vliad <= 1'b1;
else
bcd_vliad <= 1'b0;
endmodule
(5)control
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description: 实现温度数据转换至bcd码
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module control(
input clk ,
input rst_n ,
input [14:0] hum_data ,
input [14:0] tem_data ,
input tem_data_vld ,
output [15:0] tem_bcd ,
output bcd_tem_data_vld ,
output [15:0] hum_bcd ,
output bcd_hum_data_vld
);
//---------<内部信号定义>-----------------------------------------------------
binary2bcd #(.binary_width(15),.bcd_width(16)) binary2bcd_inst1(
/*input */.sys_clk (clk ),
/*input */.sys_rst_n (rst_n ),
/*input [binary_width - 1 : 0] */.binary (tem_data ),
/*input */.binary_vliad(tem_data_vld ),
/*output reg [bcd_width - 1 : 0] */.bcd (tem_bcd ),
/*output reg */.bcd_vliad (bcd_tem_data_vld )
);
binary2bcd #(.binary_width(15),.bcd_width(16)) binary2bcd_inst2(
/*input */.sys_clk (clk ),
/*input */.sys_rst_n (rst_n ),
/*input [binary_width - 1 : 0] */.binary (hum_data ),
/*input */.binary_vliad(tem_data_vld ),
/*output reg [bcd_width - 1 : 0] */.bcd (hum_bcd ),
/*output reg */.bcd_vliad (bcd_hum_data_vld )
);
endmodule
(6)hex2ascii
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description: 实现16进制转换成ASCII码
*********************************************************************************/
//hex2ascii #(
// .hex_width ( 24) ,
// .ascii_width ( 48)
//)hex2ascii_inst(
// /* input */.clk (),
// /* input */.rst_n (),
// /* input [hex_width-1:0] */.hex_din (),
// /* input */.hex_din_vld (),
// /* output [ascii_width-1:0] */.ascii_dout (),
// /* output */.ascii_dout_vld ()
//);
//---------<模块及端口声名>------------------------------------------------------
module hex2ascii#(
parameter hex_width = 24 ,
parameter ascii_width = 48
)(
input clk ,
input rst_n ,
input [hex_width-1:0] hex_din ,
input hex_din_vld ,
output [ascii_width-1:0] ascii_dout ,
output ascii_dout_vld
);
//---------<内部信号定义>-----------------------------------------------------
wire [ascii_width-1:0] ascii_data ;
wire [hex_width/4-1:0] ascii_data_vld ;
genvar i;
generate
for (i = 0;i < hex_width/4 ;i = i+1 ) begin : hex_n2ascii_nbyte
hex4bit2ascii u_hex4bit2ascii (
.clk ( clk ),
.rst_n ( rst_n ),
.hex_din ( hex_din[i*4+3:i*4] ),
.hex_din_vld ( hex_din_vld ),
.ascii_dout ( ascii_data[i*8+7:i*8] ),
.ascii_dout_vld ( ascii_data_vld[i] )
);
end
endgenerate
assign ascii_dout = ascii_data ;
assign ascii_dout_vld = |ascii_data_vld;
endmodule
/****************************************************************
hex4bit2ascii
****************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module hex4bit2ascii(
input clk ,
input rst_n ,
input [3:0] hex_din ,
input hex_din_vld ,
output [7:0] ascii_dout ,
output ascii_dout_vld
);
//---------<内部信号定义>-----------------------------------------------------
reg [7:0] ascii_data ;
reg ascii_data_vld;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ascii_data <= 'd0;
end
else if(hex_din >= 4'h0 && hex_din <= 4'h9 )begin
ascii_data <= hex_din + 8'h30;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
ascii_data_vld <= 0;
end
else begin
ascii_data_vld <= hex_din_vld;
end
end
assign ascii_dout = ascii_data;
assign ascii_dout_vld = ascii_data_vld;
endmodule
(7)num_proces
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description: 数据处理后发送到串口
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module num_proces(
input clk ,
input rst_n ,
input tx_ready ,
input [15:0] tem_bcd_data ,
input bcd_tem_data_vld,
input [15:0] hum_bcd_data ,
input bcd_hum_data_vld ,
output reg [7:0] tx_data ,
output tx_data_vld
);
//---------<参数定义>----------------------------------------------------------
//状态机参数定义
localparam IDLE = 'b01,//
DATA = 'b10;//
parameter NUM_MAX = 27;
//---------<内部信号定义>-----------------------------------------------------
reg [4:0] cnt_num ;
wire add_cnt_num,end_cnt_num ;
wire [63:0] ascii_data ;
wire ascii_data_vld ;
reg [1:0] state ;//现态
/****************************************************************
例化
****************************************************************/
hex2ascii#(
.hex_width ( 32 ),
.ascii_width ( 64 )
)hex2ascii_inst(
.clk ( clk ),
.rst_n ( rst_n ),
.hex_din ({tem_bcd_data,hum_bcd_data}),
.hex_din_vld ( bcd_tem_data_vld ),
.ascii_dout ( ascii_data ),
.ascii_dout_vld ( ascii_data_vld )
);
/****************************************************************
状态机
****************************************************************/
// 时序逻辑描述状态转移
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state <= IDLE;
end
else case(state)
IDLE : if(ascii_data_vld /* && enable */)
state <= DATA;
DATA : if(end_cnt_num)
state <= IDLE;
default : state <= IDLE;
endcase
end
/****************************************************************
发送数据计数
****************************************************************/
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_num <= 'd0;
end
else if(add_cnt_num)begin
if(end_cnt_num)begin
cnt_num <= 'd0;
end
else begin
cnt_num <= cnt_num + 1'b1;
end
end
end
assign add_cnt_num = state == DATA && tx_ready;
assign end_cnt_num = add_cnt_num && cnt_num == NUM_MAX - 1;
always@(*)
case(cnt_num)
0 : tx_data = 8'h00;
1 : tx_data = 8'hce; //温
2 : tx_data = 8'hc2;
3 : tx_data = 8'hb6; //度
4 : tx_data = 8'hc8;
5 : tx_data = 8'h3A; // :
6 : tx_data = ascii_data[56 +: 8];
7 : tx_data = ascii_data[48 +: 8];
8 : tx_data = 8'h2E;
9 : tx_data = ascii_data[40 +: 8];
10 : tx_data = ascii_data[32 +: 8];
11 : tx_data = 8'ha1; //℃
12 : tx_data = 8'he6;
13 : tx_data = 9; //\t
14 : tx_data = 8'hca; //湿
15 : tx_data = 8'haa;
16 : tx_data = 8'hb6; //度
17 : tx_data = 8'hc8;
18 : tx_data = 8'h3A; // :
19 : tx_data = ascii_data[24 +: 8];
20 : tx_data = ascii_data[16 +: 8];
21 : tx_data = 8'h2E;
22 : tx_data = ascii_data[ 8 +: 8];
23 : tx_data = ascii_data[ 0 +: 8];
24 : tx_data = "R";
25 : tx_data = "H";
26 : tx_data = 8'h0d;
default : tx_data = 0;
endcase
assign tx_data_vld = state == DATA;
endmodule
(8)tx_fsm_uart
/**************************************功能介绍***********************************
Date :
Author : linxiaoxiao.
Version :
Description: uart数据发送端
*********************************************************************************/
//tx_fsm_uart#(
// .CHECK_BIT ( "Odd" ) , //校验方法,“None”无校验,“Odd”奇校验,“Even”偶校验
// .BPS ( 115200 ) ,
// .CLK ( 50_000_000 )
// )tx_fsm_uart_inst(
// /* input */.clk (clk ),
// /* input */.rst_n (rst_n ),
// /* input */.tx_data_vld ( ),
// /* input [7:0] */.tx_data ( ),
// /* output */.ready (ready ),
// /* output reg */.tx (tx )
//);
//---------<模块及端口声名>------------------------------------------------------
module tx_fsm_uart#(
parameter CHECK_BIT = "Odd", //校验方法,“None”无校验,“Odd”奇校验,“Even”偶校验
parameter BPS = 115200,
parameter CLK = 50_000_000
)(
input clk ,
input rst_n ,
input tx_data_vld ,
input [7:0] tx_data ,
output ready ,
output reg tx
);
//---------<参数定义>---------------------------------------------------------
parameter BPS_MAX = CLK/BPS;
//---------<内部信号定义>-----------------------------------------------------
//状态机参数定义
localparam IDLE = 5'b00001,//空闲
STARE = 5'b00010,//起始位
DATA = 5'b00100,//数据位
CHECK = 5'b01000,
STOP = 5'b10000;//停止位
wire check_vld ;
reg [4:0] state_c ;//现态
reg [4:0] state_n ;//次态
wire idle2stare ;
wire stare2data ;
wire data2stop ;
wire data2check ;
wire check2stop ;
wire stop2idle ;
reg [12:0] bps_cnt ;
wire add_bps_cnt,end_bps_cnt ;
reg [3:0] bit_max ;
reg [7:0] tx_data_r ;
reg [3:0] bit_cnt ;
wire add_bit_cnt,end_bit_cnt ;
//****************************************************************
//--fsm
//****************************************************************
//第一段:时序逻辑描述状态转移
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 : if (idle2stare) begin
state_n = STARE;
end
else begin
state_n = state_c;
end
STARE : if (stare2data) begin
state_n = DATA;
end
else begin
state_n = state_c;
end
DATA : if (data2check) begin
state_n = CHECK;
end
else if (data2stop) begin
state_n = STOP;
end
else begin
state_n = state_c;
end
CHECK : if (check2stop) begin
state_n = STOP;
end
else begin
state_n = state_c;
end
STOP : if (stop2idle) begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
default : state_n = state_c;
endcase
end
assign idle2stare = state_c == IDLE && tx_data_vld ;
assign stare2data = state_c == STARE && end_bit_cnt ;
assign data2check = state_c == DATA && end_bit_cnt && CHECK_BIT != "None";
assign data2stop = state_c == DATA && end_bit_cnt && CHECK_BIT == "None";
assign check2stop = state_c == CHECK && end_bit_cnt ;
assign stop2idle = state_c == STOP && end_bit_cnt ;
//****************************************************************
//--波特计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bps_cnt <= 'd0;
end
else if(add_bps_cnt)begin
if(end_bps_cnt)begin
bps_cnt <= 'd0;
end
else begin
bps_cnt <= bps_cnt + 1'b1;
end
end
end
assign add_bps_cnt = state_c != IDLE;
assign end_bps_cnt = add_bps_cnt && bps_cnt == BPS_MAX - 1;
//****************************************************************
//--比特计数器
//****************************************************************
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
bit_cnt <= 'd0;
end
else if(add_bit_cnt)begin
if(end_bit_cnt)begin
bit_cnt <= 'd0;
end
else begin
bit_cnt <= bit_cnt + 1'b1;
end
end
end
assign add_bit_cnt = end_bps_cnt;
assign end_bit_cnt = add_bit_cnt && bit_cnt == bit_max - 1;
always @(*)
case(state_c )
IDLE : bit_max = 1 ;
STARE : bit_max = 1 ;
DATA : bit_max = 8 ;
CHECK : bit_max = 1 ;
STOP : bit_max = 1 ;
default : bit_max = 1 ;
endcase
/****************************************************************
数据校验
****************************************************************/
//奇校验缩位同或
/* assign check_vld = ~^tx_data_r; */
//偶校验缩位异或
/* assign check_vld = ^tx_data_r; */
assign check_vld = (CHECK_BIT == "Odd")?~^tx_data_r:^tx_data_r;
//****************************************************************
//--输入数据寄存
//****************************************************************
always @(posedge clk or negedge rst_n)
if(!rst_n)begin
tx_data_r <= 0;
end
else if(tx_data_vld)
tx_data_r <= tx_data;
/****************************************************************
串口时序
****************************************************************/
always @(*)
case(state_c )
IDLE : tx = 1 ;
STARE : tx = 0 ;
DATA : if(tx_data_r[bit_cnt])
tx = 1;
else
tx = 0;
CHECK : tx <= check_vld ;
STOP : tx = 1 ;
default : tx = 1 ;
endcase
assign ready = state_c == IDLE;
endmodule
六、仿真波形图
状态机上电进入WAIT状态,等待40ms后进入初始化状态,(如图一所示),进入初始化状态,发送 00011 接口状态机的跳转条件,然后发送70,e1.08.00,对 AHT10进行初始化操作,完成后进入空闲状态。空闲状态直接进入到RD_REQ状态测量指令发送,cmd 的数据为 00011,发送的数据为70,AC,33.00,这是(仿真波形图二的第二个区域),发送完测量指令以后等待 80ms测量的完成,然后进入数据的读取,发送 71 指今,并读取 6 个 8bit 的数据,完成以后状态机进入 DEALY状态,延时 2S 以后进入下一次的温度数据的读取,串口显示为2s更新一次数据。
七、板级验证效果
串口每隔2s接受到一组温湿度数据,板级验证结果与实验要求一致