Pimoroni LSM303D with pico/micropython: integration issues with I2Cdevice lib

I try to use Pimoroni’s 6-axis LSM303D with Pi pico (Thonny micropython).
The module comes with drivers for RP 2,3,3+,zero etc. but not micropython driver.
Works well with arduino nano as well.
I tried to rewrite the i2cdevice lib to match machine.I2C but it does not seem that easy.
Would appreciate any help.
Thanks! :)

It looks like there is Circuit Python support, if that helps?
LSM303D (Accelerometer & Compass) · Issue #17 · pimoroni/BreakoutGarden (github.com)

I use “pimoroni-pico-v0.2.2-micropython-v1.15.uf2” because my project utilizes both Pimoroni display and Pimoroni LSM303D. I assume the suggested circuit python by adafruit requires another uf2 file that would not fit the display.
if I misunderstand something about this env, you are welcome to shed some light

Yes, if you use the Adafruit Circuit Python uf2, you will have to find Circuit Python libraries for your other hardware. It’s what I had to do with my Pico RGB Keypad Base. The Adafruit Circuit Python has USB HID support but Micro Python doesn’t.

You could also try our MicroPython version with the Blinka compatibility layer baked in, that may let you use the LSM303D CircuitPython drivers from within MicroPython?

1 Like

Well, I put some effort into it but obviously I’m missing something.
Using Thonny, I uploaded PlatformDetect and Blinka onto my pico, clearing some unused directories from blinka. then got some error about missing adafruit_pioasm.
I have adafruit_pioasm.mpy file, but apparently this env requires .py and not .mpy

There looks to be a .py version of adafruit_pioasm in the .py driver bundle at Libraries ?

It’s possible that not all breakouts will work using the Blinka route though, I think it depends on how the hardware is addressed.

1 Like

I managed to control the accelarator without a library driver.
My original goal was to use Pimoroni display with LSM303D together.
I was trying to combine micropython and circuit python altogether with blinka and adafruit lib and pimoroni uf2, and everything got mixed up.
Eventually I took the direct approach and opened the LSM303D datasheet. Enabling the accelerator requires only one I2C command.
It works.
The below example is like a levelling app. you can stick the accelerator behind Pimoroni display, connect LSM303D I2C to pico pins 1-2. (not sure I use I2C most efficiently)
you get a leveler bubble moving on display as you tilt it.

from machine import Pin, I2C
import time
import picodisplay as display

# Initialise display with a bytearray display buffer
xx = display.get_width()
yy = display.get_height()
buf = bytearray(xx * (yy+10) * 2) ## add 10 lines to avoid bottom line corruption
display.init(buf)
display.set_backlight(0.5)

## SET I2C bus - using pico pins 1-2
sda=machine.Pin(0) 
scl=machine.Pin(1)
i2c=machine.I2C(0,sda=sda, scl=scl, freq=100000)

## Enable LSM303D accelerator at I2C addr 0x1D
config=bytearray(1)    
config[0]=39+8
## Reg Control1: (0x20) 50Hz+enable (0x57) + block update
i2c.writeto_mem(29, 32, config)

def get_axis(reg): ## 40 - X , 42 - Y , 44 - Z
    high=bytearray(1)
    low=bytearray(1)
    i2c.readfrom_mem_into(29, reg, low)
    i2c.readfrom_mem_into(29, reg+1, high)
    res = high[0] * 256 + low[0]
    if (res<16384):
        result = res/16384.0
    elif (res>=16384 and res<49152):
        result = (32768-res)/16384.0
    else:
        result = (res-65536)/16384.0
    return result

while True:
    display.set_pen(160, 160, 25)
    display.clear()
    x = ( get_axis(40) + 1) * xx / 2.0
    y = (-get_axis(42) + 1) * yy / 2.0
    display.set_pen(60,60,65)
    display.circle(int(x), int(y), 12)
    display.set_pen(180,180,200)
    display.circle(int(x), int(y), 9)
    display.update()
    time.sleep(0.05)

1 Like

@Yos this is great - I’ve got it working (most of the time - had to put a delay in)

Can you explain this?

    if (res<16384):
        result = res/16384.0
    elif (res>=16384 and res<49152):
        result = (32768-res)/16384.0
    else:
        result = (res-65536)/16384.0

Im having trouble reproducing the solution in processing.org , as well trying to access the magnetometer data(to figure out direction/angle) but im keep getting None as a result does anyone has an idea?

from machine import UART, Pin, I2C
import time
import sys 

TOF_length = 16
TOF_header=(87,0,255)
TOF_system_time = 0
TOF_distance = 0
TOF_status = 0
TOF_signal = 0
TOF_check = 0

uart0 = UART(0, baudrate=921600 , tx=Pin(16), rx=Pin(17)) # 921600 speed if errors appear
## SET I2C bus - using pico pins 1-2 | 1, scl=machine.Pin(3), sda=machine.Pin(2)
sda=machine.Pin(2) 
scl=machine.Pin(3)
i2c=machine.I2C(1,sda=sda, scl=scl, freq=100000)

## Enable LSM303D accelerator at I2C addr 0x1D
config=bytearray(1)    
config[0]=39+8
## Reg Control1: (0x20) 50Hz+enable (0x57) + block update
i2c.writeto_mem(29, 32, config)

config=bytearray(1)   
config[0]=7+8
# LSM303DLHC Mag address, 0x1E(30)
# Select MR register, 0x02(02)
# 0x00(00) Continous conversion mode
i2c.writeto_mem(29, 34, config)

def get_axis(reg): ## 40 - X , 42 - Y , 44 - Z
    high=bytearray(1)
    low=bytearray(1)
    i2c.readfrom_mem_into(29, reg, low)
    i2c.readfrom_mem_into(29, reg+1, high)
    res = high[0] * 256 + low[0]
    if (res<16384):
        result = res/16384.0
    elif (res>=16384 & res<49152):
        result = (32768-res)/16384.0
    else: 
        result = (res-65536)/16384.0
    return result

def verifyCheckSum(data, len):
    #print(data)
    TOF_check = 0
    for k in range(0,len-1):
        TOF_check += data[k]
    TOF_check=TOF_check%256
    if(TOF_check == data[len-1]):
        ##print("TOF data is ok!")
        return 1    
    else:
        ##print("TOF data is error!")
        return 0

def getMag(reg):
    high=bytearray(1)
    low=bytearray(1)
    # LSM303DLHC Mag address, 0x1E(30)
    # Read data back from 0x03(03), 2 bytes
    # X-Axis Mag MSB, X-Axis Mag LSB
    ##data0 = bus.read_byte_data(0x1E, 0x03)
    ##data1 = bus.read_byte_data(0x1E, 0x04)
    i2c.readfrom_mem_into(29, reg, low)
    i2c.readfrom_mem_into(29, reg+1, high)

    # Convert the data
    xMag = high[0] * 256 + low[0]
    #xMag = data0 * 256 + data1
    if xMag > 32767 :
        xMag -= 65536

    # LSM303DLHC Mag address, 0x1E(30)
    # Read data back from 0x05(05), 2 bytes
    # Y-Axis Mag MSB, Y-Axis Mag LSB
    ##data0 = bus.read_byte_data(0x1E, 0x07)
    ##data1 = bus.read_byte_data(0x1E, 0x08)
    i2c.readfrom_mem_into(29, reg, low)
    i2c.readfrom_mem_into(29, reg+1, high)
    
    # Convert the data
    yMag = high[0] * 256 + low[0]
    if yMag > 32767 :
        yMag -= 65536

    # LSM303DLHC Mag address, 0x1E(30)
    # Read data back from 0x07(07), 2 bytes
    # Z-Axis Mag MSB, Z-Axis Mag LSB
    ##data0 = bus.read_byte_data(0x1E, 0x05)
    ##data1 = bus.read_byte_data(0x1E, 0x06)
    i2c.readfrom_mem_into(29, reg, low)
    i2c.readfrom_mem_into(29, reg+1, high)

    # Convert the data
    #zMag = data0 * 256 + data1
    zMag = high[0] * 256 + low[0]
    if zMag > 32767 :
        zMag -= 65536

while True:
    time.sleep(0.1) 
    #magx = getMag(10) 
    #print(magx) 
    x = ( get_axis(40) + 1) #xx is displayscreen * xx / 2.0
    y = (-get_axis(42) + 1) #yy is displayscreen * yy / 2.0
    z = (-get_axis(44) + 1)
    ##print("accel:",f'{x:.3f}',",",f'{y:.3f}',",",f'{z:.3f}')
    TOF_data=()
    if(uart0.any()>=32):
        try:
            for i in range(0,16):
                TOF_data=TOF_data+(ord(uart0.read(1)),ord(uart0.read(1)))
            #print(TOF_data)
            for j in range(0,16):
                if((TOF_data[j]==TOF_header[0] and TOF_data[j+1]==TOF_header[1] and TOF_data[j+2]==TOF_header[2]) and (verifyCheckSum(TOF_data[j:TOF_length],TOF_length))):
                    if(((TOF_data[j+12]) | (TOF_data[j+13]<<8) )==0):
                        print("Out of range!")
                    else:
                        #print("TOF id is: "+ str(TOF_data[j+3]))
                        TOF_system_time = TOF_data[j+4] | TOF_data[j+5]<<8 | TOF_data[j+6]<<16 | TOF_data[j+7]<<24;
                        #print("TOF system time is: "+str(TOF_system_time)+'ms')
                        TOF_distance = (TOF_data[j+8]) | (TOF_data[j+9]<<8) | (TOF_data[j+10]<<16);
                        ##print("TOF distance is: "+str(TOF_distance)+'mm')
                        TOF_status = TOF_data[j+11];
                        #print("TOF status is: "+str(TOF_status))
                        TOF_signal = TOF_data[j+12] | TOF_data[j+13]<<8;
                        #print("TOF signal is: "+str(TOF_signal))                
                    break
            print("(",str(TOF_distance),",",f'{x:.3f}',",",f'{y:.3f}',",",f'{z:.3f}',")")
        except:
            print("TOF fail")        
##output is (distanceinMM,x,y,z) xyz accel



Took me a while to figure it out posting it here for others
edited: here is a clean version https://github.com/staberas/LSM303D-Pico-Micropython
you only need the i2c initialisation (under # LSM303D Mag address) and the functions getMagX , getMagY , getMagZ , and the heading variable calculation at the end of the script

from machine import UART, Pin, I2C
import time
import sys
from math import atan2, pi, asin, cos, sin

TOF_length = 16
TOF_header=(87,0,255)
TOF_system_time = 0
TOF_distance = 0
TOF_status = 0
TOF_signal = 0
TOF_check = 0

xMag = 0
yMag = 0
zMag = 0
heading=0

uart0 = UART(0, baudrate=921600 , tx=Pin(16), rx=Pin(17)) # 921600 speed if errors appear
## SET I2C bus - using pico pins 1-2 | 1, scl=machine.Pin(3), sda=machine.Pin(2)
sda=machine.Pin(2)
scl=machine.Pin(3)
i2c=machine.I2C(1,sda=sda, scl=scl, freq=100000)

## Enable LSM303D accelerator at I2C addr 0x1D => 0x29
config=bytearray(1)
config[0]=39+8
## Reg Control1: (0x20) 50Hz+enable (0x57) + block update
i2c.writeto_mem(29, 32, config)

config=bytearray(1)   
config[0]=0
# LSM303D Mag address, 
# 0x00(00) Continous conversion mode
config[0]=0
i2c.writeto_mem(29, 34, config) 
config[0]=0
i2c.writeto_mem(29, 35, config) 
config[0]=100
i2c.writeto_mem(29, 36, config) 
config[0]=32
i2c.writeto_mem(29, 37, config)
config[0]=0
i2c.writeto_mem(29, 38, config) 


def get_axis(reg): ## 40 - X , 42 - Y , 44 - Z
    high=bytearray(1)
    low=bytearray(1)
    i2c.readfrom_mem_into(29, reg, low)
    i2c.readfrom_mem_into(29, reg+1, high)
    res = high[0] * 256 + low[0]
    if (res<16384):
        result = res/16384.0
    elif (res>=16384 & res<49152):
        result = (32768-res)/16384.0
    else: 
        result = (res-65536)/16384.0
    return result

def verifyCheckSum(data, len):
    #print(data)
    TOF_check = 0
    for k in range(0,len-1):
        TOF_check += data[k]
    TOF_check=TOF_check%256
    if(TOF_check == data[len-1]):
        ##print("TOF data is ok!")
        return 1    
    else:
        ##print("TOF data is error!")
        return 0

def getMagX():
    high=bytearray(1)
    low=bytearray(1)
    # LSM303DLHC Mag address, 0x1E(30)
    # Read data back from 0x03(03), 2 bytes
    # X-Axis Mag MSB, X-Axis Mag LSB
    i2c.readfrom_mem_into(29, 8, low)
    i2c.readfrom_mem_into(29, 9, high)

    # Convert the data
    xMag = high[0] * 256 + low[0]
    #xMag = data0 * 256 + data1
    if xMag > 32767 :
        xMag -= 65536
    return xMag


def getMagY():
    high=bytearray(1)
    low=bytearray(1)
    # LSM303DLHC Mag address, 0x1E(30)
    # Read data back from 0x05(05), 2 bytes
    # Y-Axis Mag MSB, Y-Axis Mag LSB
    i2c.readfrom_mem_into(29, 10, low)
    i2c.readfrom_mem_into(29, 11, high)
    
    # Convert the data
    yMag = high[0] * 256 + low[0]
    if yMag > 32767 :
        yMag -= 65536
    return yMag


def getMagZ():
    high=bytearray(1)
    low=bytearray(1)
    # LSM303DLHC Mag address, 0x1E(30)
    # Read data back from 0x07(07), 2 bytes
    # Z-Axis Mag MSB, Z-Axis Mag LSB
    i2c.readfrom_mem_into(29, 12, low)
    i2c.readfrom_mem_into(29, 13, high)

    # Convert the data
    #zMag = data0 * 256 + data1
    zMag = high[0] * 256 + low[0]
    if zMag > 32767 :
        zMag -= 65536
    return zMag


while True:
    time.sleep(0.1) 
    xMag = getMagX()
    yMag = getMagY()
    zMag = getMagZ()
    x = ( get_axis(40) + 1) #xx is displayscreen * xx / 2.0
    y = (-get_axis(42) + 1) #yy is displayscreen * yy / 2.0
    z = (-get_axis(44) + 1)
    TOF_data=()
    if(uart0.any()>=32):
        try:
            for i in range(0,16):
                TOF_data=TOF_data+(ord(uart0.read(1)),ord(uart0.read(1)))
            #print(TOF_data)
            for j in range(0,16):
                if((TOF_data[j]==TOF_header[0] and TOF_data[j+1]==TOF_header[1] and TOF_data[j+2]==TOF_header[2]) and (verifyCheckSum(TOF_data[j:TOF_length],TOF_length))):
                    if(((TOF_data[j+12]) | (TOF_data[j+13]<<8) )==0):
                        print("Out of range!")
                    else:
                        #print("TOF id is: "+ str(TOF_data[j+3]))
                        TOF_system_time = TOF_data[j+4] | TOF_data[j+5]<<8 | TOF_data[j+6]<<16 | TOF_data[j+7]<<24;
                        #print("TOF system time is: "+str(TOF_system_time)+'ms')
                        TOF_distance = (TOF_data[j+8]) | (TOF_data[j+9]<<8) | (TOF_data[j+10]<<16);
                        ##print("TOF distance is: "+str(TOF_distance)+'mm')
                        TOF_status = TOF_data[j+11];
                        #print("TOF status is: "+str(TOF_status))
                        TOF_signal = TOF_data[j+12] | TOF_data[j+13]<<8;
                        #print("TOF signal is: "+str(TOF_signal))                
                    break
            #heading
            heading = 180*atan2(yMag,xMag)/pi #assume pitch, roll are 0  
            if(heading < 0):
                heading += 360
            #head Tilt calculates a tilt-compensated heading.
            #A float between 0 and 360 degrees is returned. You need
            #to pass this function both a magneto and acceleration array.
            #float pitch = asin(-accelValue[X]);
            #float roll = asin(accelValue[Y] / cos(pitch));
    
            print(" ",str(TOF_distance),",",f'{x:.3f}',",",f'{y:.3f}',",",f'{z:.3f}',",",xMag,",",yMag,",",zMag,",",heading," ")
        except:
            print("TOF fail")