跳到主要内容

10.4 实验原理

10.4.1 SHT-30模块介绍

SHT-30是一款集成温度和湿度感测于一体的传感器芯片,采用2.5mm x 2.5mm贴片DFN封装,数字I2C总线接口,管脚功能描述如下:

alt text
管脚功能描述

SHT-20芯片典型电路连接如下:

alt text
典型电路连接

SHT-30芯片默认状态温度和湿度分辨率分别为0.01℃和0.01%RH。

10.4.2 SHT-30模块连接

STEP BaseBoard V4.0底板上的温湿度传感器SHT-30模块电路图如下(上拉电阻未显示):

alt text
SHT-20模块电路

上图为温湿度传感器SHT-30模块电路,与FPGA硬件接口有I2C总线(SCL、SDA)。

10.4.3 SHT-30模块驱动设计

智能接近系统设计实验中我们已经讲述学习过I2C总线驱动的设计,本实验可以上原来的基础上调整,首先来了解SHT-30时序中的参数要点。

alt text

SHT-30时序图

alt text
SHT-30时序参数

通过SHT-30时序参数了解,SHT-30支持I2C通信最大1MHz模式,

  • 分频得到400KHz的时钟,程序实现同智能接近系统设计实验。

I2C时序基本单元(启动、停止、发送、接收、发应答、读应答)协议里统一的,所以所以基本单元状态的设计也是不需要调整的。

  • 启动时序状态设计程序实现同智能接近系统设计实验。
  • 发送单元和读应答单元合并,时序状态设计程序实现同智能接近系统设计实验。
  • 接收单元和写应答单元合并,时序状态设计程序实现同智能接近系统设计实验。
  • 停止时序状态设计程序实现同智能接近系统设计实验。

SHT-30驱动的流程,手册上看到SHT-30芯片有很多指令,指令列表可查看数据手册

本实验涉及软件复位、温度测量和湿度测量三个操作分别查看其时序流程。

软件复位:

软件复位操作时序流程如下:

alt text

我们将这种操作设计成一个一个状态,程序实现如下:

MODE1:begin //16位寄存器写操作
if(cnt_mode1 >= 4'd5) cnt_mode1 <= 1'b0;    //对START中的子状态执行控制cnt_start
  else cnt_mode1 <= cnt_mode1 + 1'b1;
       state_back <= MODE1;
       case(cnt_mode1)
         4'd0:   begin state <= START; end   //I2C通信时序中的START
         4'd1:   begin data_wr <= dev_addr<<1; state <= WRITE; end   //设备地址
         4'd2:   begin data_wr <= reg_addr[15:8]; state <= WRITE; end    //寄存器地址
         4'd3: begin data_wr <= reg_addr[7:0]; state <= WRITE; end   //寄存器地址
         4'd4:   begin state <= STOP; end    //I2C通信时序中的STOP
         4'd5:   begin state <= MAIN; end    //返回MAIN
         default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
       endcase
end

温湿度测量:

温度测量分为两种模式:Single shot和period data acquisition,两种模式都可用但时序不同,本实验我们使用signle shot,其操作时序流程如下:

alt text

根据温湿度测量的时序流程,我们分为两部分,写指令部分和读数据部分,程序实现如下:

WRITE:begin //I2C通信时序中的写操作WRITE和相应判断操作ACK
 if(cnt <= 3'd6) begin   //共需要发送8bit的数据,这里控制循环的次数
   if(cnt_write >= 3'd3)
begin cnt_write <= 1'b0; cnt <= cnt + 1'b1; end
   else
begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
   end
else
begin
         if(cnt_write >= 3'd7)
begin cnt_write <= 1'b0; cnt <= 1'b0; end //两个变量都恢复初值
else
begin cnt_write <= cnt_write + 1'b1; cnt <= cnt; end
       end
       case(cnt_write)                       //按照I2C的时序传输数据
         3'd0:   begin scl <= 1'b0; sda <= data_wr[7-cnt]; end   //SCL拉低,并控制SDA输出对应的位
         3'd1:   begin scl <= 1'b1; end  //SCL拉高,保持4.0us以上
         3'd2:   begin scl <= 1'b1; end  //clk_400khz每个周期2.5us,需要两个周期
         3'd3:   begin scl <= 1'b0; end  //SCL拉低,准备发送下1bit的数据
                            //获取从设备的响应信号并判断
         3'd4:   begin sda <= 1'bz; end  //释放SDA线,准备接收从设备的响应信号
         3'd5:   begin scl <= 1'b1; end  //SCL拉高,保持4.0us以上
         3'd6:   begin ack_flag <= i2c_sda; end  //获取从设备的响应信号并判断
         3'd7:   begin scl <= 1'b0; if(ack_flag)state <= state; else state <= state_back; end //SCL拉低,如果不应答循环写
         default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
        endcase
end
MODE2:begin //两次读操作
   if(cnt_mode2 >= 4'd15) cnt_mode2 <= 4'd0;   //对START中的子状态执行控制cnt_start
   else cnt_mode2 <= cnt_mode2 + 1'b1;
   state_back <= MODE2;
   case(cnt_mode2)
     4'd0:   begin state <= START; end   //I2C通信时序中的START
     4'd1:   begin data_wr <= (dev_addr<<1)|8'h01; state <= WRITE; end   //设备地址
     4'd2:   begin ack <= ACK; state <= READ; end    //读寄存器数据
     4'd3:   begin tmdata_h <= data_r; end
     4'd4:   begin ack <= ACK; state <= READ; end    //读寄存器数据
     4'd5:   begin tmdata_l <= data_r; end
     4'd6:   begin ack <= ACK; state <= READ; end    //读寄存器数据
     4'd7:   begin tmdata_crc <= data_r; end
     4'd8:   begin ack <= ACK; state <= READ; end    //读寄存器数据
     4'd9:   begin hmdata_h <= data_r; end
     4'd10:begin ack <= ACK; state <= READ; end  //读寄存器数据
     4'd11:begin hmdata_l <= data_r; end
     4'd12:begin ack <= NACK; state <= READ; end //读寄存器数据
     4'd13:begin hmdata_crc <= data_r; end
     4'd14:begin state <= STOP; end  //I2C通信时序中的STOP
     4'd15:begin state <= MAIN; end  //返回MAIN
     default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
   endcase
end

最后我们编程控制状态机按照驱动例程代码中流程运行,程序实现如下:

MAIN:begin
     if(cnt_main >= 4'd4) cnt_main <= 4'd2;      //写完控制指令后循环读数据
     else cnt_main <= cnt_main + 1'b1;  
     case(cnt_main)
       4'd0:   begin dev_addr <= 7'h44; reg_addr <= 16'h30a2; state <= MODE1; end  //软件复位
       4'd1:   begin num_delay <= 24'd600; state <= DELAY; end //1.5ms延时
       4'd2:   begin dev_addr <= 7'h44; reg_addr <= 16'h2c06; state <= MODE1; end  //写入配置
       4'd3:   begin num_delay <= 24'd6000; state <= DELAY; end    //15ms延时
       4'd4:   begin dev_addr <= 7'h44; state <= MODE2; end    //读取配置
      default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
   endcase
end

10.4.4.系统总体实现

SHT-30驱动模块得到的是温度和湿度的编码值,想要得到℃和%RH的温度和湿度的数据还需要运算,运算后的数据是二进制数,想要显示在数码管上还需要BCD转码。先考虑运算:

alt text

这里我们以温度的运算为例,如下:

T = -45 + 175 * T_code / 2^16

程序实现如下:

wire [31:0] a = T_code * 16'd17500;
wire [31:0] b = a >> 16; //除以2^16取商
wire [31:0] c = (b>=32'd4500)? (b - 32'd4500):(32'd4500 - b); //温度有正负,取绝对值
wire [15:0] T_data_bin = c[15:0];

上面程序中没有除以100的运算,没有集成专用除法器的FPGA实现除法运算非常麻烦,需要大量的逻辑资源且性能不佳,通常我们不在FPGA中直接做除法运算,上面程序中两个除法,⑴除以2^16可以通过右移16位方式解决 ⑵除以100在二进制数中不好解决,而在BCD码的十进制数据很好处理,相当于小数点左移两位(十进制位),所以等完成BCD码后再来处理。

BCD转码在前面电压器实验中介绍过,这里直接例化,程序实现如下:

//进行BCD转码处理
//小数点在BCD码基础上左移2位,完成除以100的操作
//T_data_bcd[19:16]百位,[15:12]十位,[11:8]个位,[7:0]两个小数位
wire [19:0] T_data_bcd;
bin_to_bcd u1
(
.rst_n              (rst_n      ),  //系统复位,低有效
.bin_code           (T_data_bin ),  //需要进行BCD转码的二进制数据
.bcd_code           (T_data_bcd )   //转码后的BCD码型数据输出
);

//要显示的数据,保留1位小数
//若温度为负,将T_data_bcd[19:16]百位数据用数字A替换,同时把数码管A的字库显示负号
assign T_data = (b>=32'd4500)? T_data_bcd[19:4]:{4'ha,T_data_bcd[15:4]};

//数据显示使能,高位消零
assign dat_en[7] = |T_data[15:12]; //自或
assign dat_en[6] = (b>=32'd4500)?(|T_data[15:8]):(|T_data[11:8]);
assign dat_en[5:4] = 2'b11;

//小数点显示使能
assign dot_en[7:4] = 4'b0010;

综合后的设计框图如下:

alt text