Skip to content

Instantly share code, notes, and snippets.

@jasonmhite
Created August 20, 2018 17:04
Show Gist options
  • Save jasonmhite/f92c3e6a341312e178f2288ac2a8543a to your computer and use it in GitHub Desktop.
Save jasonmhite/f92c3e6a341312e178f2288ac2a8543a to your computer and use it in GitHub Desktop.
BMP280 pyFTDI
import pyftdi as F
from pyftdi import i2c
from ctypes import c_short
from time import sleep
import atexit
### Some utility functions
# These both assume the second byte is the higher bits.
# Concat two adacent bytes to a signed short
def getShort(data, index):
return c_short((data[index+1] << 8) + data[index]).value
# Concat two adacent bytes to an unsigned short
def getUShort(data, index):
return (data[index+1] << 8) + data[index]
### Important register addresses
# See the datasheet.
# i2c adress.
BME_ADDR = 0x76
# Register that contains raw measurement values (ro).
REG_DATA = 0xF7
# Controls for oversampling and power options (rw).
REG_CONTROL = 0xF4
# Controls sampling rate, filter coefficients and interface (rw, but
# writes may be ignored unless the device is in sleep mode).
# I will be leaving this as the default settings, it's just here for reference.
REG_CONFIG = 0xF5
# Calibration data registers (ro)
REG_CAL1 = 0x88
REG_CAL2 = 0xA1 # Not used (is for the BME280)
# Reset register, writing the value 0xB6 will trigger a soft reset.
REG_RESET = 0xE0
### Calculate the values for configuration.
# I will use forced mode, where it takes one measurement and then goes
# to sleep. You can also put it in free-run mode, where it constantly
# takes measurements, but forced mode is more appropriate here.
#
# You need to write the control register every time
# you want a new measurement in this mode.
OVERSAMPLE_TEMP = 2 # 2x oversampling (0b010)
OVERSAMPLE_PRES = 2 # 2x oversampling (0b010)
MODE = 1 # Forced mode, take one measurement then sleep (0b1)
control = OVERSAMPLE_TEMP << 5 | OVERSAMPLE_PRES << 2 | MODE
# 0b01000000 0b00001000 0b00000001
# --------------------------------------------------------
# = 0b01001001
### Set up i2c.
# Same deal as the SPI example
ctrl = i2c.I2cController()
ctrl.configure('ftdi://ftdi:232h/1')
atexit.register(ctrl.terminate)
# Port here automatically prepends the i2c slave address to all transactions.
port = ctrl.get_port(BME_ADDR)
### Now let's start talking to the chip!
# Force a reset to start fresh. Device will start in sleep mode (not taking
# measurements.
port.write_to(REG_RESET, [0xB6])
# Read the chip id, you can use this to distinguish between different chips
# in the line. All the chips have the same interface, but some have more
# sensors. E.g. the BMP280 is pressure and temperature, BME280 is the same thing
# but also humidity. Reading pressure and temperature is exactly the same
# between the two.
# If you adapt this code for the BME280, you need to change the i2c address
# and add code to also read the humidity.
chip_id = port.read_from(0xD0, 1) # Should be 0x58 == 88 for production BMP280.
# Set oversampling and activate forced mode.
port.write_to(REG_CONTROL, [control])
### Read values and do the conversion
# The following procedure just comes from the datasheet, we're just reading
# out register values and converting them.
# Read calibration. This is fixed and only needs to be done once.
cal1 = port.read_from(REG_CAL1, 24)
cal2 = port.read_from(REG_CAL2, 1) # Not used, will be zero
# Convert the raw bytes to the calibration coefficients. Ditto, only
# needs to be done once.
dig_T1 = getUShort(cal1, 0)
dig_T2 = getShort(cal1, 2)
dig_T3 = getShort(cal1, 4)
dig_P1 = getUShort(cal1, 6)
dig_P2 = getShort(cal1, 8)
dig_P3 = getShort(cal1, 10)
dig_P4 = getShort(cal1, 12)
dig_P5 = getShort(cal1, 14)
dig_P6 = getShort(cal1, 16)
dig_P7 = getShort(cal1, 18)
dig_P8 = getShort(cal1, 20)
dig_P9 = getShort(cal1, 22)
# Sleep long enough for a measurement to complete. You can calculate
# the minimum wait time using a formula in the datasheet or you can also
# look at the status register if you want. Here I'm just sleeping for
# a relatively long time.
# The actual sampling time depends on oversampling settings.
sleep(0.100)
# Read the raw measurement data from the device. These are in the form
# of raw ADC readings, which must be converted using the device-specific
# calibration and the procedure in the datasheet.
data = port.read_from(REG_DATA, 8)
# Cat together the bytes to get the raw integer pressure and temp readings.
pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
# Formulas... first calculate the temperature.
var1 = ((((temp_raw >> 3) - (dig_T1 << 1))) * (dig_T2)) >> 11
var2 = (((((temp_raw >> 4) - (dig_T1)) * ((temp_raw >> 4) - (dig_T1))) >> 12) * (dig_T3)) >> 14
t_fine = var1 + var2
temperature = float(((t_fine * 5) + 128) >> 8);
# Now calculate some values that are used to compensate the pressure reading
# for temperature.
var1 = t_fine / 2.0 - 64000.0
var2 = var1 * var1 * dig_P6 / 32768.0
var2 = var2 + var1 * dig_P5 * 2.0
var2 = var2 / 4.0 + dig_P4 * 65536.0
var1 = (dig_P3 * var1 * var1 / 524288.0 + dig_P2 * var1) / 524288.0
var1 = (1.0 + var1 / 32768.0) * dig_P1
# Use the above values to compensate the base pressure reading for
# temperature.
if var1 == 0:
pressure = 0 # An error occurred with the reading so skip this.
else:
pressure = 1048576.0 - pres_raw
pressure = ((pressure - var2 / 4096.0) * 6250.0) / var1
var1 = dig_P9 * pressure * pressure / 2147483648.0
var2 = pressure * dig_P8 / 32768.0
pressure = pressure + (var1 + var2 + dig_P7) / 16.0
# Print out the readings.
print("id: 0x{:x}".format(chip_id[0]))
print("temp: {0:.2f}C".format(temperature / 100.0))
print("pressure: {0:.2f}hPa".format(pressure / 100.0))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment