GY-87 micropython library
-
Hi there,
I wondering is there someone who tried to use GY-87 sensor pack, with lopy4(+pytrack).
I would like to add some redundancy to my project. (Like elevation can come from gps and pressure sensor also).- Can you recommend me a library what I can use?
- I think I also need some help with the wiring. An SI7021 is already connected to the pytrack, But as far as I know it's possible to use I2C devices serial. So maybe I am good to go? But this has some extra pin, over the tipical 4 wires, what need fo I2C.
Thanks!
-
@tttadam Updated code. The name of the methods are changed to reflect PEP8. SO
readRaw() -> read_raw
and a new
read_scaled(), which returns the values as Gauss and °C.
The constructor has an optional second argument for the temperature offset in °C .# # initial code by Sebastian Folz, M.Sc. at # http://nobytes.blogspot.com/2018/03/qmc5883-magnetic-field-sensor.html # which, I assume, was itself ported from C-Code # See also https://github.com/RigacciOrg/py-qmc5883l # # Changes and Additions: # - port to micropython's I2C methods # - add method read() for scaled values # - reading register values into a static buffer # - parsed register values in one single struct call # - changed method names according to PEP8 # - apply PyLint and fixed bugs & warnings reported by it # import time import struct from machine import idle class QMC5883: #Default I2C address ADDR = 0x0D #QMC5883 Register numbers X_LSB = 0 X_MSB = 1 Y_LSB = 2 Y_MSB = 3 Z_LSB = 4 Z_MSB = 5 STATUS = 6 T_LSB = 7 T_MSB = 8 CONFIG = 9 CONFIG2 = 10 RESET = 11 STATUS2 = 12 CHIP_ID = 13 #Bit values for the STATUS register STATUS_DRDY = 1 STATUS_OVL = 2 STATUS_DOR = 4 #Oversampling values for the CONFIG register CONFIG_OS512 = 0b00000000 CONFIG_OS256 = 0b01000000 CONFIG_OS128 = 0b10000000 CONFIG_OS64 = 0b11000000 #Range values for the CONFIG register CONFIG_2GAUSS = 0b00000000 CONFIG_8GAUSS = 0b00010000 #Rate values for the CONFIG register CONFIG_10HZ = 0b00000000 CONFIG_50HZ = 0b00000100 CONFIG_100HZ = 0b00001000 CONFIG_200HZ = 0b00001100 #Mode values for the CONFIG register CONFIG_STANDBY = 0b00000000 CONFIG_CONT = 0b00000001 # Mode values for the CONFIG2 register CONFIG2_INT_DISABLE = 0b00000001 CONFIG2_ROL_PTR = 0b01000000 CONFIG2_SOFT_RST = 0b10000000 def __init__(self, i2c, offset=50.0): self.i2c = i2c self.temp_offset = offset self.oversampling = QMC5883.CONFIG_OS64 self.range = QMC5883.CONFIG_2GAUSS self.rate = QMC5883.CONFIG_100HZ self.mode = QMC5883.CONFIG_CONT self.register = bytearray(9) self.reset() def reset(self): self.i2c.writeto_mem(QMC5883.ADDR, QMC5883.RESET, 0x01) time.sleep(0.1) self.reconfig() def reconfig(self): # print("{0:b}".format(self.oversampling | self.range | self.rate | self.mode)) self.i2c.writeto_mem(QMC5883.ADDR, QMC5883.CONFIG, self.oversampling | self.range | self.rate | self.mode) time.sleep(0.01) self.i2c.writeto_mem(QMC5883.ADDR, QMC5883.CONFIG2, QMC5883.CONFIG2_INT_DISABLE) time.sleep(0.01) def set_oversampling(self, x): self.oversampling = x self.reconfig() def set_range(self, x): self.range = x self.reconfig() def set_sampling_rate(self, x): self.rate = x self.reconfig() def ready(self): status = self.i2c.readfrom_mem(QMC5883.ADDR, QMC5883.STATUS, 1)[0] # prevent hanging up here. # Happens when reading less bytes then then all 3 axis and will end up in a loop. # So, return any data but avoid the loop. if status == QMC5883.STATUS_DOR: print("Incomplete read") return QMC5883.STATUS_DRDY return status & QMC5883.STATUS_DRDY def read_raw(self): try: while not self.ready(): idle() self.i2c.readfrom_mem_into(QMC5883.ADDR, QMC5883.X_LSB, self.register) except OSError as e: print ("OSError", e) pass # just silently re-use the old values # Convert the axis values to signed Short before returning x, y, z, _, t = struct.unpack('<hhhBh', self.register) return (x, y, z, t) def read_scaled(self): x, y, z, t = self.read_raw() scale = 12000 if self.range == QMC5883.CONFIG_2GAUSS else 3000 return (x / scale, y / scale , z / scale, (t / 100 + self.temp_offset))
-
@tttadam The first three are the x,y,z values for the magnetic field, the last one is the temperature. All numbers a raw values. I have prepared a version which returns proper scaled gauss values for the magnetic field and °C for temperature. I did not test it yet, so I will send it later.
The temperature value has an offset. According to the data sheet, it is scaled properly, so differences are fine. But the offset for the absolute value has to be determined. For my device it is about 5000. So if you local temperature is about 30°C, then it's about the same. The scaling is 100LSB/°C. So if you divide the value by 100, you get °C (with offset).
-
@robert-hh I found the issue.
GY-87 is on breadboard and and from the breadboard connected to the pytrack via dupont cables.
It was a contact issue. Sorry for the false alarm.
Can you help me out what are those 4 numbers mean, or where do I find a some info about that? My guess is that the last one is related to the temperature somehow but I am not sure.(-925, -2192, -8705, -2071)
-
@tttadam
Thanks for the error log. But that only happens if you catch the error, print a message and then continue. The the symbol status is not defined, raising a new exception. So you would either add:
status = 0
or
return 0
after the print statements.Did you used this library for porting?
No. I found Python code, which in turn might have been ported from that web side. See http://nobytes.blogspot.com/2018/03/qmc5883-magnetic-field-sensor.html with a link to another interesting port, of which I might carry over the calibration part.
I did intentionally not include that I2C magic, because that is only relevant for this GY87 board.
-
@robert-hh Okay, So I try-catch block for OSError for
status = self.i2c.readfrom_mem(QMC5883.ADDR, QMC5883.STATUS, 1)[0]
and
return QMC5883.STATUS_DRDY
lines. Looks like it's not recovering.
Something went wrongI2C bus error Traceback (most recent call last): File "main2.py", line 17, in <module> File "/flash/lib/qmc5883.py", line 110, in readRaw File "/flash/lib/qmc5883.py", line 97, in ready NameError: local variable referenced before assignment.
Did you used this library for porting?
Because it's mention something about readraw blocking.
Where did you find the desription about these four values? What are does mean?The whole code looks like this, but it is the same, only thing I added, that I moved a i2c magic into the init method from main:
# # ported from http://nobytes.blogspot.com/2018/03/qmc5883-magnetic-field-sensor.html # import time import struct from machine import idle class QMC5883: #Default I2C address ADDR = 0x0D #QMC5883 = Register = numbers X_LSB = 0 X_MSB = 1 Y_LSB = 2 Y_MSB = 3 Z_LSB = 4 Z_MSB = 5 STATUS = 6 T_LSB = 7 T_MSB = 8 CONFIG = 9 CONFIG2 = 10 RESET = 11 CHIP_ID = 13 #Bit values for the STATUS register STATUS_DRDY = 1 STATUS_OVL = 2 STATUS_DOR = 4 #Oversampling values for the CONFIG register MC5883L_CONFIG_OS512 = 0b00000000 CONFIG_OS256 = 0b01000000 CONFIG_OS128 = 0b10000000 CONFIG_OS64 = 0b11000000 #Range values for the CONFIG register CONFIG_2GAUSS = 0b00000000 CONFIG_8GAUSS = 0b00010000 #Rate values for the CONFIG register CONFIG_10HZ = 0b00000000 CONFIG_50HZ = 0b00000100 CONFIG_100HZ = 0b00001000 CONFIG_200HZ = 0b00001100 #Mode values for the CONFIG register CONFIG_STANDBY = 0b00000000 CONFIG_CONT = 0b00000001 def __init__(self, i2c): self.i2c = i2c self.i2c.writeto_mem(104, 107, 128) # reset MPU6050 self.i2c.writeto_mem(104, 107, 0) # disable sleep self.i2c.writeto_mem(104, 55, 2) # enable I2C passthru self.oversampling = QMC5883.CONFIG_OS64 self.range = QMC5883.CONFIG_2GAUSS self.rate = QMC5883.CONFIG_100HZ self.mode = QMC5883.CONFIG_CONT self.reset() def reconfig(self): print("{0:b}".format(self.oversampling | self.range | self.rate | self.mode)) self.i2c.writeto_mem(QMC5883.ADDR, QMC5883.CONFIG, self.oversampling | self.range | self.rate | self.mode) def reset(self): self.i2c.writeto_mem(QMC5883.ADDR, QMC5883.RESET, 0x01) time.sleep(0.1) self.reconfig() time.sleep(0.01) def setOversampling(self, x): self.oversampling = x self.reconfig() def setRange(x): self.range = x self.reconfig() def setSamplingRate(self, x): self.rate = x self.reconfig() def ready(self): try: status = self.i2c.readfrom_mem(QMC5883.ADDR, QMC5883.STATUS, 1)[0] except OSError as e: print("Something went wrong"+str(e)) #TODO: Tune it a little # prevent hanging up here. # Happens when reading less bytes then then all 3 axis and will end up in a loop. # So, return any data but avoid the loop. if (status == QMC5883.STATUS_DOR): print("fail") try: return QMC5883.STATUS_DRDY except OSError as e: print("Something went wrong"+str(e)) #TODO: Tune it a little return status & QMC5883.STATUS_DRDY def readRaw(self): while not self.ready(): idle() pass # Python performs a wrong casting at read_i2c_block_data. # The filled buffer has to be onverted afterwards by mpdule Struct register = self.i2c.readfrom_mem(QMC5883.ADDR, QMC5883.X_LSB, 9) # Convert the axis values to signed Short before returning x, y, z, status, t = struct.unpack('<hhhBh', register) return (x, y, z, t)
-
@tttadam No, the ; is not needed (probably a left-over from an initial C-Code file), but it also does not hurt. There is another one in reconfig(). The OSError is probably caused by a I2C communication problem to the device. I did not let it run for longer than about 20 minutes or 1000 measurement. If you enclose that into a try/except clause, does the device recover?
The temperature reading has an offset. That is also told in the data sheet. If I add 4900 to the value, it is almost OK. The value seems to be scaled as °C*100.
-
This post is deleted!
-
This is the error what i get now. Maybe tomorrow with fresh eye I will see something :)
[13, 64, 104, 119] 11001001 Traceback (most recent call last): File "main2.py", line 17, in <module> File "/flash/lib/qmc5883.py", line 95, in readRaw File "/flash/lib/qmc5883.py", line 81, in ready OSError: I2C bus error
-
@robert-hh thank you, it's looks useful, but after some time it's produce an OSError. Line 17 in my code is this : print(qmc.readRaw())
and the line 99 is this. register = self.i2c.readfrom_mem(QMC5883.ADDR, QMC5883.X_LSB, 9)
I think there were an ; which is not needed.
But something else is still not alright. I didn't find it yet.(6967, -4882, -7620, -1943) (4742, -7337, -7950, -1923) Traceback (most recent call last): File "main2.py", line 17, in <module> File "/flash/lib/qmc5883.py", line 99, in readRaw OSError: I2C bus error
-
@tttadam I found a RasPi driver for the QMC5883 and adapted it to pycom MP:
# # ported from http://nobytes.blogspot.com/2018/03/qmc5883-magnetic-field-sensor.html # import time import struct from machine import idle class QMC5883: #Default I2C address ADDR = 0x0D #QMC5883 = Register = numbers X_LSB = 0 X_MSB = 1 Y_LSB = 2 Y_MSB = 3 Z_LSB = 4 Z_MSB = 5 STATUS = 6 T_LSB = 7 T_MSB = 8 CONFIG = 9 CONFIG2 = 10 RESET = 11 CHIP_ID = 13 #Bit values for the STATUS register STATUS_DRDY = 1 STATUS_OVL = 2 STATUS_DOR = 4 #Oversampling values for the CONFIG register MC5883L_CONFIG_OS512 = 0b00000000 CONFIG_OS256 = 0b01000000 CONFIG_OS128 = 0b10000000 CONFIG_OS64 = 0b11000000 #Range values for the CONFIG register CONFIG_2GAUSS = 0b00000000 CONFIG_8GAUSS = 0b00010000 #Rate values for the CONFIG register CONFIG_10HZ = 0b00000000 CONFIG_50HZ = 0b00000100 CONFIG_100HZ = 0b00001000 CONFIG_200HZ = 0b00001100 #Mode values for the CONFIG register CONFIG_STANDBY = 0b00000000 CONFIG_CONT = 0b00000001 def __init__(self, i2c): self.i2c = i2c self.oversampling = QMC5883.CONFIG_OS64 self.range = QMC5883.CONFIG_2GAUSS self.rate = QMC5883.CONFIG_100HZ self.mode = QMC5883.CONFIG_CONT self.reset() def reconfig(self): print("{0:b}".format(self.oversampling | self.range | self.rate | self.mode)) self.i2c.writeto_mem(QMC5883.ADDR, QMC5883.CONFIG, self.oversampling | self.range | self.rate | self.mode); def reset(self): self.i2c.writeto_mem(QMC5883.ADDR, QMC5883.RESET, 0x01) time.sleep(0.1) self.reconfig() time.sleep(0.01) def setOversampling(self, x): self.oversampling = x self.reconfig() def setRange(x): self.range = x self.reconfig() def setSamplingRate(self, x): self.rate = x self.reconfig() def ready(self): status = self.i2c.readfrom_mem(QMC5883.ADDR, QMC5883.STATUS, 1)[0] # prevent hanging up here. # Happens when reading less bytes then then all 3 axis and will end up in a loop. # So, return any data but avoid the loop. if (status == QMC5883.STATUS_DOR): print("fail") return QMC5883.STATUS_DRDY return status & QMC5883.STATUS_DRDY def readRaw(self): while not self.ready(): idle() pass # Python performs a wrong casting at read_i2c_block_data. # The filled buffer has to be onverted afterwards by mpdule Struct register = self.i2c.readfrom_mem(QMC5883.ADDR, QMC5883.X_LSB, 9); # Convert the axis values to signed Short before returning x, y, z, status, t = struct.unpack('<hhhBh', register) return (x, y, z, t)
and a short test code:
import time from qmc5883 import QMC5883 from machine import I2C i2c=I2C() i2c.writeto_mem(104, 107, 128) # reset MPU6050 i2c.writeto_mem(104, 107, 0) # disable sleep i2c.writeto_mem(104, 55, 2) # enable I2C passthru print(i2c.scan()) time.sleep(1) qmc = QMC5883(i2c) while True: print(qmc.readRaw()) time.sleep(1)
-
@tttadam A short google search gave an answer:
The chip in the board is not a HMC5883, but a QMC5883. See https://github.com/nodemcu/nodemcu-firmware/issues/2187
The QMC5883 server for the same purpose, but has different programming interface.
-
@tttadam So let's see if that is a bug or an intended address trabslation (but why?)
-
This is what I see. So same for me.
>>> from machine import I2C >>> i2c = I2C(1) >>> i2c.scan() [64, 104, 119] >>> i2c.writeto_mem(104, 107, 128) # reset MPU6050 >>> i2c.writeto_mem(104, 107, 0) # disable sleep >>> i2c.writeto_mem(104, 55, 2) # enable I2C passthru >>> i2c.scan() [13, 64, 104, 119] >>>
-
@tttadam When you do the i2c.scan(), which address is reported for the HMC5883? It puzzles me that my device reports 13 instead of 30, which is the address in all examples.
Besides that, I have the impression that in normal operation the HMC5883 is controlled by the MCU6050 for its own purpose. So you might get the information you need through the MCU6050.
If you ask me about what I did to find that way: I looked at the circuit, read the data sheets and looked at some code examples for the MCU6050.
-
@robert-hh It's working for me too! :)
Awsome! :)
Now I just need to find a correct library for it.
I will start with thisCan you give me some explonation what happend?
What your overall expression of the board?
-
@tttadam So I found a way to talk to the compass, with the following sequence:
from machine import I2C i2c=I2C() i2c.writeto_mem(104, 107, 128) # reset MPU6050 i2c.writeto_mem(104, 107, 0) # disable sleep i2c.writeto_mem(104, 55, 2) # enable I2C passthru i2c.scan()
The scan should show you another I2C address. In my case it is 13, which deviates from the HMV5883 data sheet.
-
@tttadam The altitude is calculated from the actual pressure and what you enter as sea level pressure, roughly 8.5m/hPa. You can see it in the python script.
The compass chip should be available when the I2C-bus is set to pass-through. I'm trying it at the moment.
-
@robert-hh said in GY-87 micropython library:
BMP180
I just checked the bmp18. It's seems something off with the altitude...
(1028 is the current QNH in Budapest)>>> bmp.sealevel = 1028 >>> bmp.altitude 51.46274 >>> bmp.pressure 1021.34 >>> bmp.temperature 24.2 >>> si7021.readTemp() 22.92749 >>> my_gps.altitude 120.3
So you have any guess how Can I get the compass data?
-
@tttadam So, looking at the device and the wiring it seems clear why the HMC5883 is not visible in the scan: it is connected to the MBU6050 slave interface. Still, according to the MPU6050 data sheet, it should be visible, because at start-up the MPU6050 connects the main I2C interface to the aux interface.
The I2C signals are fine.However, on the board I have the BMP180 is broken. The temperature sensor always returns 229.3°C. At least that helped me to find a signed/unsigned bug in my driver.