Class: Denko::Sensor::QMP6988

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

Constant Summary collapse

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,
}

Instance Attribute Summary collapse

Attributes included from Behaviors::Threaded

#interrupts_enabled, #thread

Attributes included from Behaviors::Callbacks

#callback_mutex

Attributes included from I2C::Peripheral

#i2c_frequency, #i2c_repeated_start

Attributes included from Behaviors::BusPeripheral

#address

Attributes included from Behaviors::Component

#board

Instance Method Summary collapse

Methods included from Behaviors::Poller

#poll, #poll_using, #stop

Methods included from Behaviors::Threaded

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

Methods included from Behaviors::Reader

#read, #read_using, #wait_for_read

Methods included from Behaviors::Callbacks

#add_callback, #callbacks, #initialize, #remove_callback, #update

Methods included from Behaviors::State

#initialize, #state

Methods included from I2C::Peripheral

#i2c_read, #i2c_write

Methods included from Behaviors::BusPeripheral

#atomically

Methods included from Behaviors::Component

#initialize, #micro_delay

Instance Attribute Details

#calibration_data_loadedObject (readonly)

Calibration



245
246
247
# File 'lib/denko/sensor/qmp6988.rb', line 245

def calibration_data_loaded
  @calibration_data_loaded
end

#iir_coefficientObject

Returns the value of attribute iir_coefficient.



111
112
113
# File 'lib/denko/sensor/qmp6988.rb', line 111

def iir_coefficient
  @iir_coefficient
end

#pressure_samplesObject

Returns the value of attribute pressure_samples.



140
141
142
# File 'lib/denko/sensor/qmp6988.rb', line 140

def pressure_samples
  @pressure_samples
end

#registersObject (readonly)

Returns the value of attribute registers.



240
241
242
# File 'lib/denko/sensor/qmp6988.rb', line 240

def registers
  @registers
end

#standby_timeObject

Returns the value of attribute standby_time.



120
121
122
# File 'lib/denko/sensor/qmp6988.rb', line 120

def standby_time
  @standby_time
end

#temperature_samplesObject

Returns the value of attribute temperature_samples.



130
131
132
# File 'lib/denko/sensor/qmp6988.rb', line 130

def temperature_samples
  @temperature_samples
end

Instance Method Details

#_readObject

Reading & Processing



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

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_REGISTER, DATA_LENGTH)
end

#after_initialize(options = {}) ⇒ Object



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/denko/sensor/qmp6988.rb', line 72

def after_initialize(options={})
  super(options)

  # Avoid repeated memory allocation for callback data and state.
  @reading     = { temperature: nil, pressure: nil }
  self.state   = { temperature: nil, pressure: nil }

  reset
  
  # Get 5 config registers. Copy 0xF4 to modify it for control.
  get_config_registers
  @ctrl_meas_register = @registers[:f4].dup

  # Default settings
  self.iir_coefficient = 0
  self.temperature_samples = 1
  self.pressure_samples = 1
  self.forced_mode

  # self.forced mode triggered an initial measurement so IIR works properly if enabled.
  # Wait for those values to enter the data registers, but don't read them back.
  sleep @measurement_time

  get_calibration_data
end

#before_initialize(options = {}) ⇒ Object



67
68
69
70
# File 'lib/denko/sensor/qmp6988.rb', line 67

def before_initialize(options={})
  @i2c_address = 0x70
  super(options)
end

#calculate_measurement_timeObject



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

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



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

def chip_id
  return @chip_id if @chip_id
  i2c_read(CHIP_ID_REGISTER, 1)
  sleep 0.001 while !@chip_id
  @chip_id
end

#continuous_modeObject



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

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



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

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



262
263
264
# File 'lib/denko/sensor/qmp6988.rb', line 262

def get_calibration_data
  i2c_read(CALIBRATION_REGISTER, CALIBRATION_LENGTH)
end

#get_config_registersObject



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

def get_config_registers
  @registers = {}
  i2c_read(IIR_REGISTER, CONFIG_LENGTH)
  sleep 0.001 while @registers.empty?
  @registers
end

#pre_callback_filter(bytes) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/denko/sensor/qmp6988.rb', line 184

def pre_callback_filter(bytes)
  if bytes.length == DATA_LENGTH
    return process_reading(bytes)
  elsif bytes.length == CONFIG_LENGTH
    process_config(bytes)
  elsif bytes.length == CALIBRATION_LENGTH
    process_calibration(bytes)
  elsif bytes.length == CHIP_ID_LENGTH
    @chip_id = bytes[0]
  end
  return nil
end

#process_calibration(bytes) ⇒ Object



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
292
293
294
295
296
297
298
299
300
301
302
303
304
305
# File 'lib/denko/sensor/qmp6988.rb', line 266

def process_calibration(bytes)
  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

#process_config(bytes) ⇒ Object



237
238
239
# File 'lib/denko/sensor/qmp6988.rb', line 237

def process_config(bytes)
  @registers = { f1: bytes[0], f2: bytes[1], f3: bytes[2], f4: bytes[3], f5: bytes[4] }
end

#process_reading(bytes) ⇒ Object



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/denko/sensor/qmp6988.rb', line 197

def process_reading(bytes)
  # 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

#resetObject

Configuration Methods



101
102
103
104
# File 'lib/denko/sensor/qmp6988.rb', line 101

def reset
  i2c_write [RESET_REGISTER, RESET_COMMAND]
  sleep UPDATE_TIME
end

#update_state(reading) ⇒ Object



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

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