跳到主要内容

11.4 实验原理

11.4.1 BMP280模块介绍

BMP280是一款大气压力传感器,采用8pin的2.0mm x 2.5mm贴片LGA封装,数字I2C总线接口,管脚功能描述如下:

alt text
管脚功能描述

BMP280接口可以支持I2C和SPI总线,气I2C接口典型电路如下:

alt text
典型电路连接

11.4.2 BMP280模块连接

STEP BaseBoard V4.0底板上的气压传感器BMP280模块电路图如下(上拉电阻未显示):

alt text
BMP280模块电路

上图为气压传感器BMP280模块电路,与FPGA硬件接口有I2C总线(SCL、SDA)。SDO引脚不能悬空,用来确定I2C器件的地址,可以拉高(0x77)或者拉低(0x76),对于BMP280来说地址是一样的,都可以作为I2C寻址。

11.4.3.BMP280模块驱动设计

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

alt text
BMP280时序图
alt text
BMP280时序参数

通过BMP280时序参数了解,BMP280可以支持I2C高速模式。

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

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

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

BMP280测量时会先测量温度,然后测量压力值,然后看设置需不需要进行滤波计算,最后结果存到寄存器。进行驱动时首先软件复位,然后设置配置寄存器,然后读取数据。

alt text
配置寄存器

本实验涉及软件复位、温度和气压测量、读取配置参数以及读取数据。

软件复位:

I2C写操作时序:

alt text

软件复位操作寄存器是0xE0,往寄存器写数值0xB6就能完成器件的复位。同样道理0xF4、0xF5寄存器也是需要进行配置的寄存器,具体参数可以参考数据手册。

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

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

补偿参数读取:

芯片内部有NVM非易事性内存存储了一系列用于补偿计算的参数,这些参数出厂时就写入了不能修改,用于计算测量的温度和气压指。首先我们要读取出这些参数:

alt text

I2C读操作:

alt text

读取参数用连续两次读操作完成,程序实现如下:

MODE2:begin //两次读操作
 if(cnt_mode2 >= 4'd10) cnt_mode2 <= 1'b0;  
 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; state <= WRITE; end   //设备地址
     4'd2:   begin data_wr <= reg_addr; state <= WRITE; end  //寄存器地址
     4'd3:   begin state <= START; end   //I2C通信时序中的START
     4'd4:   begin data_wr <= (dev_addr<<1)|8'h01; state <= WRITE; end   //设备地址
   4'd5:   begin ack <= ACK; state <= READ; end    //读寄存器数据
   4'd6:   begin dat_l <= data_r; end
   4'd7:   begin ack <= NACK; state <= READ; end   //读寄存器数据
   4'd8:   begin dat_h <= data_r; end
   4'd9:   begin state <= STOP; end    //I2C通信时序中的STOP
   4'd10:  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

测量数据读取:

BMP280的温度和气压测量数据都是三个字节存储,读取数据时参数用连续三次次读操作完成,程序实现如下:

MODE3:begin //三次读操作
   if(cnt_mode3 >= 4'd12) cnt_mode3 <= 1'b0;   //对START中的子状态执行控制cnt_start
   else cnt_mode3 <= cnt_mode3 + 1'b1;
   state_back <= MODE3;
   case(cnt_mode3)
     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; state <= WRITE; end  //寄存器地址
     4'd3:   begin state <= START; end   //I2C通信时序中的START
     4'd4:   begin data_wr <= (dev_addr<<1)|8'h01; state <= WRITE; end   //设备地址
     4'd5:   begin ack <= ACK; state <= READ; end    //读寄存器数据
     4'd6:   begin dat_h <= data_r; end
     4'd7:   begin ack <= ACK; state <= READ; end    //读寄存器数据
     4'd8:   begin dat_l <= data_r; end
     4'd9:   begin ack <= NACK; state <= READ; end   //读寄存器数据
     4'd10:  begin dat_xl <= data_r; end
     4'd11:  begin state <= STOP; end    //I2C通信时序中的STOP
     4'd12:  begin state <= MAIN; end    //返回MAIN
     default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
   endcase
end

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

MAIN:begin
 if(cnt_main >= 8'd34) cnt_main <= 8'd28;    //写完控制指令后循环读数据
 else cnt_main <= cnt_main + 1'b1;  
 case(cnt_main)
   8'd0:   begin dev_addr <= 7'h76; reg_addr <= 8'hE0; reg_data <= 8'hb6; state <= MODE1; end  //写入配置
   8'd1:   begin state <= DELAY; end   //200ms延时
   8'd2:   begin dev_addr <= 7'h76; reg_addr <= 8'hf4; reg_data <= 8'h55; state <= MODE1; end  //写入配置
   8'd3:   begin dev_addr <= 7'h76; reg_addr <= 8'hf5; reg_data <= 8'h00; state <= MODE1; end  //写入配置
   8'd4:   begin dev_addr <= 7'h76; reg_addr <= 8'h88;  state <= MODE2; end    //读取配置
   8'd5:   begin dig_t1 <= {dat_h,dat_l}; end  //读取数据
   8'd6:   begin dev_addr <= 7'h76; reg_addr <= 8'h8a;  state <= MODE2; end    //读取配置
   8'd7:   begin dig_t2 <= {dat_h,dat_l}; end  //读取数据
   8'd8:   begin dev_addr <= 7'h76; reg_addr <= 8'h8c;  state <= MODE2; end    //读取配置
   8'd9: begin dig_t3 <= {dat_h,dat_l}; end    //读取数据
                            8'd10:  begin dev_addr <= 7'h76; reg_addr <= 8'h8e;  state <= MODE2; end    //读取配置
   8'd11:  begin dig_p1 <= {dat_h,dat_l}; end  //读取数据
   8'd12:  begin dev_addr <= 7'h76; reg_addr <= 8'h90;  state <= MODE2; end    //读取配置
   8'd13:  begin dig_p2 <= {dat_h,dat_l}; end  //读取数据
   8'd14:  begin dev_addr <= 7'h76; reg_addr <= 8'h92;  state <= MODE2; end    //读取配置
   8'd15:  begin dig_p3 <= {dat_h,dat_l}; end  //读取数据
   8'd16:  begin dev_addr <= 7'h76; reg_addr <= 8'h94;  state <= MODE2; end    //读取配置
   8'd17:  begin dig_p4 <= {dat_h,dat_l}; end  //读取数据
   8'd18:  begin dev_addr <= 7'h76; reg_addr <= 8'h96;  state <= MODE2; end    //读取配置
   8'd19:  begin dig_p5 <= {dat_h,dat_l}; end  //读取数据
   8'd20:  begin dev_addr <= 7'h76; reg_addr <= 8'h98;  state <= MODE2; end    //读取配置
   8'd21:  begin dig_p6 <= {dat_h,dat_l}; end  //读取数据
   8'd22:  begin dev_addr <= 7'h76; reg_addr <= 8'h9a;  state <= MODE2; end    //读取配置
   8'd23:  begin dig_p7 <= {dat_h,dat_l}; end  //读取数据
   8'd24:  begin dev_addr <= 7'h76; reg_addr <= 8'h9c;  state <= MODE2; end    //读取配置
   8'd25:  begin dig_p8 <= {dat_h,dat_l}; end  //读取数据
   8'd26:  begin dev_addr <= 7'h76; reg_addr <= 8'h9e;  state <= MODE2; end    //读取配置
   8'd27:  begin dig_p9 <= {dat_h,dat_l}; end  //读取数据
   8'd28:  begin dev_addr <= 7'h76; reg_addr <= 8'hf4; reg_data <= 8'h55; state <= MODE1; end  //写入配置
   8'd29:  begin state <= DELAY; dat_valid <= 1'b0; end    //200ms延时
   8'd30:  begin dev_addr <= 7'h76; reg_addr <= 8'hf7;  state <= MODE3; end    //读取配置
   8'd31:  begin adc_p <= {dat_h,dat_l,dat_xl[7:4]}; end   //读取数据
   8'd32:  begin dev_addr <= 7'h76; reg_addr <= 8'hfa;  state <= MODE3; end    //读取配置
   8'd33:  begin adc_t <= {dat_h,dat_l,dat_xl[7:4]}; end   //读取数据
   8'd34:  begin dat_valid <= 1'b1; end    //读取数据
   default: state <= IDLE; //如果程序失控,进入IDLE自复位状态
 endcase
end

11.4.4 系统总体实现

BMP280驱动模块得到的是温度和气压的编码值,还需要进行补偿运算的到温度和气压值,运算后的数据是二进制数,想要显示在数码管上还需要BCD转码。

alt text

alt text

可以看到,公式中有大量的乘法运算,为了使板卡资源得到更好的利用,一般不直接使用“*”号,而是根据实际实际情况使用合适的乘法器。在这里我们使用的乘法器如下所示:

module multiply(              // 乘法器
    input         clk,        // 时钟
    input         mult_begin, // 乘法开始信号
    input  [31:0] mult_op1,   // 乘法源操作数1
    input  [31:0] mult_op2,   // 乘法源操作数2
    output [63:0] product,    // 乘积
    output        mult_end   // 乘法结束信号
);
    //乘法正在运算信号和结束信号
   
    reg mult_valid;
      //加载乘数,运算时每次右移一位,相当于y
    reg  [31:0] multiplier;

    assign mult_end = mult_valid & ~(|multiplier); //乘法结束信号:乘数全0
    always @(posedge clk)   //①
    begin
        if (!mult_begin || mult_end)    //如果没有开始或者已经结束了
        begin
            mult_valid <= 1'b0;     //mult_valid 赋值成0,说明现在没有进行有效的乘法运算
        end
        else
        begin
            mult_valid <= 1'b1;
       //     test <= 1'b1;
        end
    end

    //两个源操作取绝对值,正数的绝对值为其本身,负数的绝对值为取反加1
    wire        op1_sign;      //操作数1的符号位
    wire        op2_sign;      //操作数2的符号位
    wire [31:0] op1_absolute;  //操作数1的绝对值
    wire [31:0] op2_absolute;  //操作数2的绝对值
    assign op1_sign = mult_op1[31];
    assign op2_sign = mult_op2[31];
    assign op1_absolute = op1_sign ? (~mult_op1+1) : mult_op1;
    assign op2_absolute = op2_sign ? (~mult_op2+1) : mult_op2;
    //加载被乘数,运算时每次左移一位
    reg  [63:0] multiplicand;
    always @ (posedge clk)  //②
    begin
        if (mult_valid)
        begin    // 如果正在进行乘法,则被乘数每时钟左移一位
            multiplicand <= {multiplicand[62:0],1'b0};  //被乘数x每次左移一位。
        end
        else if (mult_begin)
        begin   // 乘法开始,加载被乘数,为乘数1的绝对值
            multiplicand <= {32'd0,op1_absolute};
        end
    end
   
    always @ (posedge clk)  //③
    begin
    if(mult_valid)
    begin       //如果正在进行乘法,则乘数每时钟右移一位
         multiplier <= {1'b0,multiplier[31:1]}; //相当于乘数y右移一位
    end
    else if(mult_begin)
    begin   //乘法开始,加载乘数,为乘数2的绝对值
        multiplier <= op2_absolute;
        end
    end
    // 部分积:乘数末位为1,由被乘数左移得到;乘数末位为0,部分积为0
    wire [63:0] partial_product;
    assign partial_product = multiplier[0] ? multiplicand:64'd0;        //若此时y的最低位为1,则把x赋值给部分积partial_product,否则把0赋值给partial_product
   
    //累加器
    reg [63:0] product_temp;        //临时结果
    always @ (posedge clk)  //④//clk信号从0变为1时,激发此段语句的执行,但语句的执行需要时间
    begin
        if (mult_valid)
        begin
            product_temp <= product_temp + partial_product;
        end      
        else if (mult_begin)
        begin
        product_temp <= 64'd0;
        end
     end
     
    //乘法结果的符号位和乘法结果
    reg product_sign;   //乘积结果的符号
    always @ (posedge clk)  // 乘积⑤
    begin
        if (mult_valid)
        begin
              product_sign <= op1_sign ^ op2_sign;
        end
    end
    //若乘法结果为负数,则需要对结果取反+1
   
    assign product = product_sign ? (~product_temp+1) : product_temp;
endmodule

同样的,对于公式中的除法我们也会使用如下的除法器进行运算

module div32(
    input clk,rst_n,
    input start,
    input [31:0] a,
    input [31:0] b,
    output done,
    output [31:0] yshang,
    output [31:0] yyushu
);
reg[63:0] temp_a;
reg[63:0] temp_b;
reg[5:0] cnt;
reg done_r;

reg start_last;
wire start_flag_w;

reg [3:0]   state_c;
parameter IDLE = 4'b1111;
parameter INIT = 4'b0001;
parameter CAL1 = 4'b0010;
parameter CAL2 = 4'b0100;
parameter DONE = 4'b1000;
 
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        start_last <= 1'b0;
    end
    else    begin
        start_last <= start;
    end
end

assign start_flag_w = (~start_last)&(start);


always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        state_c <= IDLE;
    end
    else    begin
        case (state_c)
            IDLE :  state_c <= (start_flag_w)?INIT:IDLE;
            INIT :  state_c <= CAL1;
            CAL1 :  state_c <= CAL2;
            CAL2 :  state_c <= (cnt == 6'd31)?DONE:CAL1;
            DONE :  state_c <= IDLE;
            default: state_c <= IDLE;
        endcase
    end
end
//------------------------------------------------
always @(posedge clk or negedge rst_n)begin
    if(!rst_n) cnt <= 6'd0;
    else if (state_c == INIT) cnt <= 6'd0;
    else if (state_c == CAL2) cnt <= cnt + 1;
    else    cnt <= cnt;
end
//------------------------------------------------    
assign done = (state_c == DONE)?1'b1:1'b0;
//------------------------------------------------
always @ (posedge clk or negedge rst_n)begin
    if(!rst_n) begin
        temp_a <= 64'h0;
        temp_b <= 64'h0;
    end
    else begin
        case (state_c)
            IDLE:  begin
                temp_a <= 64'h0;
                temp_b <= 64'h0;
            end
            INIT:   begin
                temp_a <= {32'h00000000,a};
                temp_b <= {b,32'h00000000};
            end
            CAL1:   begin
                temp_a <= temp_a << 1;
            end
            CAL2:   begin
                if (temp_a[63:32] > temp_b[63:32]) begin
                    temp_a <= temp_a -temp_b + 1;
                end
                else temp_a <= temp_a;
            end
            default: begin
                temp_a <= temp_a;
                temp_b <= temp_b;
            end
        endcase
    end
end
assign yshang = temp_a[31:0];
assign yyushu = temp_a[63:32];
endmodule

计算计数器,用来确定计算的步骤,因加减移位运算与乘法除法所使用的时间不同,所以应根据计算的模式确定计数器是否加一。

reg     [5:0]       cnt_cal;    //公式计数器
reg     [1:0]       cal_mode;//00 sim_cal,   01 mult;    11 div;
wire                mult_end;//乘法结束信号;
wire                div_done_w;//除法结束信号;
always@(posedge clk or negedge rst_n)
begin
    if(!rst_n)  begin
        cnt_cal <= 6'b0;
    end
    else if (cnt_cal == 51) begin
        cnt_cal <= 'd0;
    end
    else begin
        case (cal_mode)
            MUL:begin
                if (mult_end) begin
                    cnt_cal <= cnt_cal + 1'b1;
                end
                else begin
                    cnt_cal <= cnt_cal;
                end
            end
            DIV:begin
                if (div_done_w) begin
                    cnt_cal <= cnt_cal + 1'b1;
                end
                else begin
                    cnt_cal <= cnt_cal;
                end
            end
            SIM:begin
                cnt_cal <= (cnt_sync)? cnt_cal + 1'b1:cnt_cal;
            end
            default: cnt_cal <= cnt_cal;
        endcase
    end
end

获取BMP280补偿数据进行计算

reg signed  [15:0]  digP1,digP2,digP3,digP4,digP5,digP6,digP7,digP8,digP9;
reg signed  [15:0]  digp;
always@(posedge dat_valid)begin
    digP1 <= dig_p1;
    digP2 <= dig_p2;
    digP3 <= dig_p3;
    digP4 <= dig_p4;
    digP5 <= dig_p5;
    digP6 <= dig_p6;
    digP7 <= dig_p7;
    digP8 <= dig_p8;
    digP9 <= dig_p9;
end
always@(posedge clk)begin
    case (cnt_cal)
        'd13: digp <= (digP6[15]) ? (~digP6 + 1'b1) : digP6;
        'd15: digp <= (digP5[15]) ? (~digP5 + 1'b1) : digP5;
        'd17: digp <= (digP4[15]) ? (~digP4 + 1'b1) : digP4;
        'd20: digp <= (digP3[15]) ? (~digP3 + 1'b1) : digP3;
        'd22: digp <= (digP2[15]) ? (~digP2 + 1'b1) : digP2;
        'd25: digp <= digP1;
        'd28: adcp <= adc_p;
        'd40: digp <= (digP9[15]) ? (~digP9 + 1'b1) : digP9;
        'd43: digp <= (digP8[15]) ? (~digP8 + 1'b1) : digP8;
        'd45: digp <= (digP7[15]) ? (~digP7 + 1'b1) : digP7;
        default: begin
            adcp <= adcp;
            digp <= digp;
        end
    endcase
end

这样就可以根据计算公式,计算得出气压的值,以 var2 = (((var1>>2) * (var1>>2)) >> 11) * ((BMP280_S32_t)dig_P6)为例

'd12      :begin
            cal_mode <= MUL;
            mult_op1 <= var1 >>> 2;
            mult_op2 <= var1 >>> 2;
        end
        'd13    :begin
            cal_mode <= SIM;
            var2 <= (cnt_sync)?var2:(product >>> 11);
        end
        'd14    :begin
            cal_mode <= MUL;
            mult_op1 <= var2;
            mult_op2 <= {16'd0,digp};
        end
        'd15    :begin
            cal_mode <= SIM;
            var2 <= (cnt_sync)?var2:product[31:0];
        end

BCD转码在前面电压器实验中介绍过,可以直接例化。

综合后的设计框图如下:

alt text