Class: Denko::Sensor::QMP6988

Inherits:
Object
  • Object
show all
Includes:
Behaviors::Lifecycle, Behaviors::Poller, I2C::Peripheral, PressureHelper, TemperatureHelper
Defined in:
lib/denko/sensor/qmp6988.rb

Constant Summary collapse

I2C_ADDRESS =
0x70
UPDATE_TIME =
0.020
RESET_REGISTER =
0xE0
RESET_COMMAND =
0xE6
CTRL_MEAS_REGISTER =
0xF4
STANDBY_TIME_REGISTER =
0xF5
IIR_REGISTER =
0xF1
CONFIG_LENGTH =
5
DATA_REGISTER =
0xF7
DATA_LENGTH =
6
CALIBRATION_REGISTER =
0xA0
CALIBRATION_LENGTH =
25
CHIP_ID_REGISTER =
0xD1
CHIP_ID_LENGTH =
1
FORCED_MODE =
0b01
NORMAL_MODE =
0b11
STANDBY_TIMES =

Standby Times for Normal (Continuous) Mode in milliseconds

{
  1    => 0b000,
  5    => 0b001,
  50   => 0b010,
  250  => 0b011,
  500  => 0b100,
  1000 => 0b101,
  2000 => 0b110,
  4000 => 0b111,
}
OVERSAMPLE_FACTORS =

Oversample Setting Values Note: Each sensor has a separate oversample setting.

General formula:

2 ** (n-1), where n is the decimal value of the bits, up to 16x max oversampling.
{
  # 0  =>  0b000, # Sensor skipped. Value will be 0x800000.
  1  =>  0b001,
  2  =>  0b010,
  4  =>  0b011,
  8  =>  0b100,
  16 =>  0b101,
  32 =>  0b110,
  64 =>  0b111,
}
TEMPERATURE_SAMPLE_TIME =

Single sample times (in milliseconds) for each sensor, derived from datasheet examples.

0.9
PRESSURE_SAMPLE_TIME =
0.85
IIR_COEFFICIENTS =

IIR Filter Coefficients

{
  0  =>  0b000,
  2  =>  0b001,
  4  =>  0b010,
  8  =>  0b011,
  16 =>  0b100,
  32 =>  0b101, # 0b110 and 0b111 are also valid for 16.
}
CONVERSION_FACTORS =
{
  a1:  { A: -6.3e-03,   S: 4.3e-04 },
  a2:  { A: -1.9e-11,   S: 1.2e-10 },
  bt1: { A:  1.0e-01,   S: 9.1e-02 },
  bt2: { A:  1.2e-08,   S: 1.2e-06 },
  bp1: { A:  3.3e-02,   S: 1.9e-02 },
  b11: { A:  2.1e-07,   S: 1.4e-07 },
  bp2: { A: -6.3e-10,   S: 3.5e-10 },
  b12: { A:  2.9e-13,   S: 7.6e-13 },
  b21: { A:  2.1e-15,   S: 1.2e-14 },
  bp3: { A:  1.3e-16,   S: 7.9e-17 },
  a0:  16.0,
  b00: 16.0,
}

Constants included from Behaviors::Reader

Behaviors::Reader::READ_WAIT_TIME

Constants included from Behaviors::Lifecycle

Behaviors::Lifecycle::CALLBACK_METHODS

Constants included from I2C::Peripheral

I2C::Peripheral::I2C_FREQUENCY, I2C::Peripheral::I2C_REPEATED_START

Instance Attribute Summary collapse

Attributes included from Behaviors::Threaded

#interrupts_enabled, #thread

Attributes included from I2C::Peripheral

#i2c_frequency, #i2c_repeated_start

Attributes included from Behaviors::Component

#board, #params

Instance Method Summary collapse

Methods included from PressureHelper

#pressure, #pressure_atm, #pressure_bar

Methods included from TemperatureHelper

#temperature, #temperature_f, #temperature_k

Methods included from Behaviors::Poller

#poll, #poll_using, #stop

Methods included from Behaviors::Threaded

#enable_interrupts, included, #mruby_thread_check, #stop, #stop_thread, #threaded, #threaded_loop

Methods included from Behaviors::Reader

#read, #read_busy?, #read_nb, #read_raw, #read_using, #update

Methods included from Behaviors::Callbacks

#add_callback, #callbacks, #remove_callback, #update

Methods included from Behaviors::Lifecycle

included

Methods included from I2C::Peripheral

#address, #i2c_default, #i2c_read, #i2c_read_raw, #i2c_write

Methods included from Behaviors::BusPeripheralAddressed

#address

Methods included from Behaviors::BusPeripheral

#atomically

Methods included from Behaviors::Component

#initialize, #micro_delay

Instance Attribute Details

#calibration_data_loadedObject (readonly)

Calibration



233
234
235
# File 'lib/denko/sensor/qmp6988.rb', line 233

def calibration_data_loaded
  @calibration_data_loaded
end

#iir_coefficientObject

Returns the value of attribute iir_coefficient.



113
114
115
# File 'lib/denko/sensor/qmp6988.rb', line 113

def iir_coefficient
  @iir_coefficient
end

#pressure_samplesObject

Returns the value of attribute pressure_samples.



142
143
144
# File 'lib/denko/sensor/qmp6988.rb', line 142

def pressure_samples
  @pressure_samples
end

#registersObject (readonly)

Returns the value of attribute registers.



228
229
230
# File 'lib/denko/sensor/qmp6988.rb', line 228

def registers
  @registers
end

#standby_timeObject

Returns the value of attribute standby_time.



122
123
124
# File 'lib/denko/sensor/qmp6988.rb', line 122

def standby_time
  @standby_time
end

#temperature_samplesObject

Returns the value of attribute temperature_samples.



132
133
134
# File 'lib/denko/sensor/qmp6988.rb', line 132

def temperature_samples
  @temperature_samples
end

Instance Method Details

#_readObject

Reading & Processing



175
176
177
178
179
180
181
182
183
184
# File 'lib/denko/sensor/qmp6988.rb', line 175

def _read
  if @forced_mode
    # Write CTRL_MEAS register to trigger reading, then wait for measurement.
    i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
    sleep @measurement_time
  end

  # Read the data bytes.
  i2c_read(DATA_LENGTH, register: DATA_REGISTER)
end

#calculate_measurement_timeObject



144
145
146
147
148
149
# File 'lib/denko/sensor/qmp6988.rb', line 144

def calculate_measurement_time
  @measurement_time = (@temperature_samples.to_i * TEMPERATURE_SAMPLE_TIME) +
                     (@pressure_samples.to_i * PRESSURE_SAMPLE_TIME)
  # Add 5ms for safety and convert to seconds.
  @measurement_time = (@measurement_time + 5) * 0.001
end

#chip_idObject



165
166
167
168
169
170
# File 'lib/denko/sensor/qmp6988.rb', line 165

def chip_id
  return @chip_id if @chip_id
  bytes = i2c_read_raw(1, register: CHIP_ID_REGISTER)
  @chip_id = bytes[0] if bytes
  @chip_id
end

#continuous_modeObject



158
159
160
161
162
163
# File 'lib/denko/sensor/qmp6988.rb', line 158

def continuous_mode
  @ctrl_meas_register = (@ctrl_meas_register & 0b11111100) | NORMAL_MODE
  i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
  @forced_mode = false
  sleep UPDATE_TIME
end

#forced_modeObject



151
152
153
154
155
156
# File 'lib/denko/sensor/qmp6988.rb', line 151

def forced_mode
  @ctrl_meas_register = (@ctrl_meas_register & 0b11111100) | FORCED_MODE
  i2c_write [CTRL_MEAS_REGISTER, @ctrl_meas_register]
  @forced_mode = true
  sleep UPDATE_TIME
end

#get_calibration_dataObject



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/denko/sensor/qmp6988.rb', line 250

def get_calibration_data
  bytes = i2c_read_raw(CALIBRATION_LENGTH, register: CALIBRATION_REGISTER)

  if bytes
    # These 2 values are 20-bit instead of 16-bit, so can't combine them with #pack.
    a0_unsigned  = (bytes[18] << 12) + (bytes[19] << 4) + (bytes[24] & 0b00001111)
    b00_unsigned = (bytes[0] << 12) + (bytes[1] << 4) + ((bytes[24] & 0b11110000) >> 4)

    # Cast the raw bytes as big-endian signed.
    @calibration_raw = {
      # Shift these to 32-bit before converting to signed, then reverse the shift after.
      a0: [(a0_unsigned << 12)].pack('L>').unpack('l>')[0] >> 12,
      b00: [(b00_unsigned << 12)].pack('L>').unpack('l>')[0] >> 12,

      a1: bytes[20..21].pack('C*').unpack('s>')[0],
      a2: bytes[22..23].pack('C*').unpack('s>')[0],

      b11: bytes[8..9].pack('C*').unpack('s>')[0],
      b12: bytes[12..13].pack('C*').unpack('s>')[0],
      b21: bytes[14..15].pack('C*').unpack('s>')[0],

      bp1: bytes[6..7].pack('C*').unpack('s>')[0],
      bp2: bytes[10..11].pack('C*').unpack('s>')[0],
      bp3: bytes[16..17].pack('C*').unpack('s>')[0],

      bt1: bytes[2..3].pack('C*').unpack('s>')[0],
      bt2: bytes[4..5].pack('C*').unpack('s>')[0],
    }

    # Use conversion formulae to calculate compensation coefficients, all as floats.
    @calibration = {}
    @calibration_raw.keys.each do |key|
      if CONVERSION_FACTORS[key].class == Float
        @calibration[key] = @calibration_raw[key] / CONVERSION_FACTORS[key]
      else
        @calibration[key] = CONVERSION_FACTORS[key][:A] + (CONVERSION_FACTORS[key][:S] * @calibration_raw[key] / 32767.0)
      end
    end

    @calibration_data_loaded = true
  end
end

#get_config_registersObject



222
223
224
225
226
227
# File 'lib/denko/sensor/qmp6988.rb', line 222

def get_config_registers
  bytes = i2c_read_raw(CONFIG_LENGTH, register: IIR_REGISTER)
  if bytes
    @registers = { f1: bytes[0], f2: bytes[1], f3: bytes[2], f4: bytes[3], f5: bytes[4] }
  end
end

#pre_callback_filter(bytes) ⇒ Object



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/denko/sensor/qmp6988.rb', line 186

def pre_callback_filter(bytes)
  return nil unless bytes.length == DATA_LENGTH

  # Temperature and pressure are 24-bits long each, and need 2^23 subtracted.
  dt = ((bytes[3] << 16) + (bytes[4] << 8) + bytes[5]) - (0b1 << 23)
  dp = ((bytes[0] << 16) + (bytes[1] << 8) + bytes[2]) - (0b1 << 23)

  # Compensated temperature calculated in 1/256 of a degree Celsius.
  tr =  @calibration[:a0] +
        @calibration[:a1] * dt +
        @calibration[:a2] * (dt ** 2)
  reading[:temperature] = tr / 256.0

  # Compensated pressure calculated in Pascals.
  reading[:pressure] =  @calibration[:b00] +
                        @calibration[:bt1] * tr +
                        @calibration[:bp1] * dp +
                        @calibration[:b11] * (tr * dp)  +
                        @calibration[:bt2] * (tr ** 2) +
                        @calibration[:bp2] * (dp ** 2) +
                        @calibration[:b12] * (dp * (tr ** 2)) +
                        @calibration[:b21] * ((dp ** 2) * tr) +
                        @calibration[:bp3] * (dp ** 3)

  # Return reading for callbacks.
  reading
end

#readingObject



96
97
98
# File 'lib/denko/sensor/qmp6988.rb', line 96

def reading
  @reading ||= { temperature: nil, pressure: nil }
end

#resetObject

Configuration Methods



103
104
105
106
# File 'lib/denko/sensor/qmp6988.rb', line 103

def reset
  i2c_write [RESET_REGISTER, RESET_COMMAND]
  sleep UPDATE_TIME
end

#stateObject



92
93
94
# File 'lib/denko/sensor/qmp6988.rb', line 92

def state
  @state ||= { temperature: nil, pressure: nil }
end

#update_state(reading) ⇒ Object



214
215
216
217
218
219
220
# File 'lib/denko/sensor/qmp6988.rb', line 214

def update_state(reading)
  @state_mutex.lock
  @state[:temperature] = reading[:temperature]
  @state[:pressure]    = reading[:pressure]
  @state_mutex.unlock
  @state
end