development of a module for the bmp180 pressure sensor

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Turbinenreiter
Posts: 288
Joined: Sun May 04, 2014 8:54 am

development of a module for the bmp180 pressure sensor

Post by Turbinenreiter » Tue Jul 01, 2014 9:48 am

[This is not a micropython speficic question, but a general one.]

So, I'm trying to write a module for the BMP180. It's connected via i2c and I figured that out already, I think.

However, I have to read some values from the sensors EEPROM for calibration, and I can't figure out how to get integers from the Bytes. (edit: changed Bits to Bytes)

MSB: \x1b
LSB: \x1b

According to the data sheet, it's a short and it should be somwhere around 400.

Using struct.unpack() gives me results that are not really what I'd expect.
i.E.
>>>struct.unpack('>h', '\x1b\x1b')
(6939,)

>>>struct.unpack('>h', '\x1b')
(6912,)

>>>struct.unpack('>h', '1b')
(12642,)

>>>struct.unpack('>h', '1b1b')
(12642,)

I don't get it. At all. I know hex 1b is 27. Hex 1b1b should be 443. And that the MSB comes first, then the LSB, or the other way round, depending on big/little endian.
Last edited by Turbinenreiter on Wed Jul 02, 2014 12:53 pm, edited 2 times in total.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: I2C, MSB, LSB, endianness and I'm in over my head

Post by dhylands » Tue Jul 01, 2014 3:23 pm

0x1b1b = 6939 not 443. 443 = 0x01bb

Lets look at a better example.

Code: Select all

Micro Python v1.1.1-60-g8993fb6-dirty on 2014-06-30; UNIX version
>>> import struct
>>> struct.unpack('<h', b'\x00\x01')
(256,)
>>> struct.unpack('>h', b'\x00\x01')
(1,)
<h says to interpret the bytes as little-endian, so the '\x00' will the the LSB and the '\x01' will be the MSB. Thus wee get 0x0100 or 256.

>h says to interpret the bytes as big-endian, to the '\x00' will be the MSB and the '\x01' will be the LSB. Thus we get 0x0001 or 1

443 = 0x01BB not 0x1B1B

And here's how to explain your examples:

Code: Select all

>>>struct.unpack('>h', '\x1b\x1b')
(6939,)
0x1b1b is equal to 6939.

Code: Select all

>>>struct.unpack('>h', '\x1b')
(6912,)
Here you've essentally not provided the second byte, so its the same as '\x1b\x00' The greater says big endian. 0x1b00 is 6912

Code: Select all

>>>struct.unpack('>h', '1b')
(12642,)
Here, you've provided character constants. '1b' is the same as b'\x31\x62' and 0x3162 = 12642

Code: Select all

>>>struct.unpack('>h', '1b1b')
(12642,)
This just used the first 2 bytes of the 4 byte string you provided so same answer as before.

443 = 0x01bb

Code: Select all

>>> struct.unpack('>h', b'\x01\xbb')
(443,)

pfalcon
Posts: 1155
Joined: Fri Feb 28, 2014 2:05 pm

Re: I2C, MSB, LSB, endianness and I'm in over my head

Post by pfalcon » Tue Jul 01, 2014 5:34 pm

It also should be noted that bits and bytes should not be mixed up. Term "endianness" usually applies to how bytes are laid out in words. Terms "LSB" and "MSB" usually (perhaps less universally) apply to bits. Of course, someone may use these terms in different meanings, but then one targets for confusion.

Going next, when dealing with serial protocols, there's a question which bit is transferred first - LSB or MSB. Term "endianness" doesn't occur on the level of hardware serial protocols at all. What's being transferred is just a stream of bytes, each byte either LSB or MSB first, that's all. Interpretation of those bytes occur on another level, that's where endianness questions may arise.
Awesome MicroPython list
Pycopy - A better MicroPython https://github.com/pfalcon/micropython
MicroPython standard library for all ports and forks - https://github.com/pfalcon/micropython-lib
More up to date docs - http://pycopy.readthedocs.io/

Turbinenreiter
Posts: 288
Joined: Sun May 04, 2014 8:54 am

Re: I2C, MSB, LSB, endianness and I'm in over my head

Post by Turbinenreiter » Tue Jul 01, 2014 6:39 pm

Thanks a lot for the examples!
So struct.unpack needs to be passed a string (b'' > bytestring?) with two Bytes in it.
I somehow treated it as function that converts hex to int.

Could you explain the math behind '0x1b1b is equal to 6939'?
When I take 0x1b1b as a hex, I would expect 443.
64 32 16 1 (edit: <- this is wrong, it goes: 4096 256 16 1)
1 b 1 b
1x64 + 11*32 + 1*16 + 1*11 = 443

Obviously, that's not how MSB/LSB works.

Also, despite being wrong, 443 would better fit to what's in the datasheet^^
http://www.adafruit.com/datasheets/BST- ... 000-09.pdf page 15
It also should be noted that bits and bytes should not be mixed up.
True! It was a typo in the OP, I meant Bytes.

Thanks for taking the time. Somehow Wikipedia and stackoverflow don't make sense to me on this.
Last edited by Turbinenreiter on Tue Jul 01, 2014 7:14 pm, edited 1 time in total.

Turbinenreiter
Posts: 288
Joined: Sun May 04, 2014 8:54 am

Re: I2C, MSB, LSB, endianness and I'm in over my head

Post by Turbinenreiter » Tue Jul 01, 2014 6:48 pm

Thin I got it.

(MSB -> hex to int) * 256 + (LSB -> hex to int)

b'\x1b\x1b'
\x1b -> 27
27 * 256 + 27 = 6939

... but why 256?

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: I2C, MSB, LSB, endianness and I'm in over my head

Post by dhylands » Tue Jul 01, 2014 7:03 pm

Turbinenreiter wrote:Thin I got it.

(MSB -> hex to int) * 256 + (LSB -> hex to int)

b'\x1b\x1b'
\x1b -> 27
27 * 256 + 27 = 6939

... but why 256?
There are 8 bits in a byte. 2 to the power 8 = 256.

Turbinenreiter
Posts: 288
Joined: Sun May 04, 2014 8:54 am

Re: I2C, MSB, LSB, endianness and I'm in over my head

Post by Turbinenreiter » Tue Jul 01, 2014 7:09 pm

... yeah.

Mixed up my hex and my binary tables.

head = spinning

this is it:
4096 256 16 1
1 b 1 b
1x4096 + 11*256 + 1*16 + 1*11 = 6939

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: I2C, MSB, LSB, endianness and I'm in over my head

Post by dhylands » Tue Jul 01, 2014 7:41 pm

Looking at the datahseet, the calibration is stored at addresses 0xAA thru 0xBF.

Did you get the 0x1b1b from the correct location?

It looks like the chip-id of 0x55 is stored at address 0xD0 (so you can use that to verify that you're reading what you think you should be reading.

Turbinenreiter
Posts: 288
Joined: Sun May 04, 2014 8:54 am

Re: I2C, MSB, LSB, endianness and I'm in over my head

Post by Turbinenreiter » Wed Jul 02, 2014 12:51 pm

[I will repurpose this thread as 'BMP180 module development']

@dhylands: I'm quite sure it's from the rigth location. Reading 0xD0 also gives me the correct chip-id of 0x55 (altough it was printed out as b'U' - which took me a while to figure out^^)

I now have a (rough) implementation of a class for the BMP180. The values are off, but they react correctly, as in if I put my finger on the sensor the temperature rises and if I blow on it the pressure does to. I guess it's just some errors in the equations, there are quite some and long also to calculate the true values from the raw ones. But that's just bugs to find.

But it leads me to another question:
Right now I have implemented everything in a class.
That means when I use it as a module, it looks like this:

Code: Select all

import bmp180

sensor = bmp180.bmp180()    # but this seems ugly ...

print(sensor.get_altitude())
... but the sensor = bmp180.bmp180() part seems rather ugly ... .
Is there a better way to do this?
I would like it to look something like that:

Code: Select all

import bmp180

print(bmp180.get_altitude())
... without having to create an Obkect from the class, but rather have already an object imported.

Here is the whole source of the module. Don't go hunting for bugs, but comments on the code are appreciated! I will also have to rethink which variables need to be self. and which not.

Code: Select all

import struct
import pyb

class bmp180():

    def __init__(self):

        self.oss = 3

        self.bmp = pyb.I2C(2, pyb.I2C.MASTER)
        self.bmp_addr = self.bmp.scan()[0]

        self.chip_id = self.bmp.mem_read(2, self.bmp_addr, 0xAA)

        self.AC1 = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xAA))[0]
        self.AC2 = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xAC))[0]
        self.AC3 = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xAE))[0]
        self.AC4 = struct.unpack('>H', self.bmp.mem_read(2, self.bmp_addr, 0xB0))[0]
        self.AC5 = struct.unpack('>H', self.bmp.mem_read(2, self.bmp_addr, 0xB2))[0]
        self.AC6 = struct.unpack('>H', self.bmp.mem_read(2, self.bmp_addr, 0xB4))[0]
        self.B1 = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xB6))[0]
        self.B2 = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xB8))[0]
        self.MB = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xBA))[0]
        self.MC = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xBC))[0]
        self.MD = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xBE))[0]

    def read_uncomp_temp(self):

        self.bmp.mem_write(0x2E, self.bmp_addr, 0xF4)
        pyb.delay(5)
        self.UT = struct.unpack('>h', self.bmp.mem_read(2, self.bmp_addr, 0xF6))[0]
        return self.UT

    def read_uncomp_pressure(self, oss):

        self.bmp.mem_write((0x34+(self.oss<<6)), self.bmp_addr, 0xF4)
        pyb.delay((self.oss+1)*7)
        self.MSB = struct.unpack('<h', self.bmp.mem_read(1, self.bmp_addr, 0xF6))[0]
        self.LSB = struct.unpack('<h', self.bmp.mem_read(1, self.bmp_addr, 0xF7))[0]
        self.XLSB = struct.unpack('<h', self.bmp.mem_read(1, self.bmp_addr, 0xF8))[0]
        self.UP = ((self.MSB<<16)+(self.LSB<<8)+self.XLSB)>>(8-self.oss)
        return self.UP

    def calc_temp(self, UT):

        self.X1 = (self.UT-self.AC6)*self.AC5/2**15
        self.X2 = self.MC*2**11/(self.X1+self.MD)
        self.B5 = self.X1+self.X2
        self.T = (self.B5+8)/2**4
        return self.T, self.B5

    def calc_pressure(self, UP, oss):

        self.B5 = self.calc_temp(self.read_uncomp_temp())[1]
        self.B6 = self.B5-4000
        self.X1 = (self.B2*(self.B6*self.B6/2**12))/2**11
        self.X2 = self.AC2*self.B6/2**11
        self.X3 = self.X1+self.X2
        self.B3 = ((int((self.AC1*4+self.X3))<<self.oss)+2)/4
        self.X1 = self.AC3*self.B6/2**13
        self.X2 = (self.B1*(self.B6*self.B6/2**12))/2**16
        self.X3 = ((self.X1+self.X2)+2)/2**2
        self.B4 = self.AC4*(self.X3+32768)/2**15
        self.B7 = (self.UP-self.B3)*(5000>>self.oss)
        if self.B7 < 0x80000000: self.p = (self.B7*2)/self.B4
        else:               self.p = (self.B7/self.B4)*2
        self.X1 = (self.p/2**8)**2
        self.X1 = (self.X1*3038)/2**16
        self.X2 = (-7357*self.p)/2**16
        self.p = self.p+(self.X1+self.X2+3791)/2**4
        return self.p

    def get_temp(self):

        return self.calc_temp(self.read_uncomp_temp())[0]

    def get_pressure(self, oss):

        return self.calc_pressure(self.read_uncomp_pressure(self.oss), self.oss)

    def get_temp_and_pressure(self, oss):
        return self.get_temp(), self.get_pressure(self.oss)

    def get_altitude(self):
        self.h = 44330*(1-(self.get_pressure(3)/1013.25)**(1/5.255))
        return self.h
edit: also: is class.__dict__ not implemented yet (I'm still on the version the board shipped with, need to update that)?

fma
Posts: 164
Joined: Wed Jan 01, 2014 5:38 pm
Location: France

Re: development of a module for the bmp180 pressure sensor

Post by fma » Wed Jul 02, 2014 1:25 pm

You can do:

Code: Select all

import struct
import pyb

class BMP180():

    def __init__(self):
...

bmp180 = BMP180()
and:

Code: Select all

from bmp180 import bmp180

bmp180.get_altitude()
But I don't think it is a good way to do; you should let the main module instanciate the object.

Code: Select all

from bmp180 import BMP180

bmp180 = BMP180()
Frédéric

Post Reply