Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPI readings corrupted on Raspberry Pi #57

Closed
jerryneedell opened this issue Nov 30, 2018 · 17 comments
Closed

SPI readings corrupted on Raspberry Pi #57

jerryneedell opened this issue Nov 30, 2018 · 17 comments

Comments

@jerryneedell
Copy link

jerryneedell commented Nov 30, 2018

We ran into an interesting issue when using an lsm9ds1 and an rfm9x on a RaspBerry Pi
Both boards worked fine individually but when run together, the first sesor reading from the lsm9ds1 AFTER any communication with the rfm9x was corrupted.

There is a lengthy forum discussion
https://forums.adafruit.com/viewtopic.php?f=50&t=143689&start=30#p71264

I also ran the same test on a feather_m4 express and did not see the same behavior

I wonder if it is related to the difference in SPI mode (polarity and phase) being used on the rfm9x vs lsm9ds1.
Is there some problem with the transition between the modes. It only appears to impact the lsm9ds1, the rfm9x appears to be operating normally, but there is a lot more traffic to the rfm9x and it is possible that a bad command may not be immediately apparent.

The issue is not present if the I2C interface to the lsm9ds1 is used.

I'll try to get some cleaner examples of the issue but wanted to post this so you are aware of it.

@ladyada
Copy link
Member

ladyada commented Nov 30, 2018

its probably due to the weird way we use the SPI peripheral locking & CS lines. i can fix if i get some example code

@jerryneedell
Copy link
Author

jerryneedell commented Nov 30, 2018

Do you just want the code that fails -- here is an example -- If you leave the first temperature reading comment out, it will fails and the temperature will be corrupted -- If you read it twice - the second reading is OK.

import board
import busio
import digitalio
import adafruit_rfm9x
import adafruit_lsm9ds1
from digitalio import DigitalInOut, Direction
import time

RADIO_FREQ_MHZ   = 915.0

CS    = digitalio.DigitalInOut(board.D24)
RESET = digitalio.DigitalInOut(board.D17)

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

csag = DigitalInOut(board.D21)
csm = DigitalInOut(board.D20)

rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
sensor = adafruit_lsm9ds1.LSM9DS1_SPI(spi,csag,csm)

rfm9x.tx_power = 23

while True:
# read twice -- first reading is corrupted
    temp = sensor.temperature
    temp = sensor.temperature
    gyro_x, gyro_y, gyro_z = sensor.gyro
    accel_x, accel_y, accel_z = sensor.acceleration
    mag_x, mag_y, mag_z = sensor.magnetic

    stri = '{0:0.3f},{1:0.3f},{2:0.3f},{3:0.3f},{4:0.3f},{5:0.3f},{6:0.3f},{7:0.3f},{8:0.3f},{9:0.3f}'.format(accel_x, accel_y, accel_z,mag_x, mag_y, mag_z,gyro_x, gyro_y, gyro_z,temp)
    print(stri)
    
    packet = rfm9x.receive(with_header=True,rx_filter=0xff)
    # you can remove everything below here if you don't want to see the radio packets
    # Optionally change the receive timeout from its default of 0.5 seconds:
    #packet = rfm9x.receive(timeout=5.0)
    # If no packet was received during the timeout then None is returned.
    if packet is None:
        continue
    else:
        # Received a packet!
        # Print out the raw bytes of the packet:
        print('Received (raw bytes): {0}'.format(packet))
        print([hex(x) for x in packet])
        # And decode to ASCII text and print it too.  Note that you always
        # receive raw bytes and need to convert to a text format like ASCII
        # if you intend to do string processing on your data.  Make sure the
        # sending side is sending ASCII data before you try to decode!
        packet_text = str(packet[4:], 'ascii')
        print('Received (ASCII): {0}'.format(packet_text))
        # Also read the RSSI (signal strength) of the last received message and
        # print it.
        rssi = rfm9x.rssi
        print('Received signal strength: {0} dB'.format(rssi))
    

@jerryneedell
Copy link
Author

I can try to come up with a better minimal case using a different SPI device instead of the LoRa

@jerryneedell
Copy link
Author

but for now this is a bit more minimal

import board
import busio
import digitalio
import adafruit_rfm9x
import adafruit_lsm9ds1
import time

RADIO_FREQ_MHZ   = 915.0

CS    = digitalio.DigitalInOut(board.D24)
RESET = digitalio.DigitalInOut(board.D17)

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)

csag = DigitalInOut(board.D21)
csm = DigitalInOut(board.D20)

rfm9x = adafruit_rfm9x.RFM9x(spi, CS, RESET, RADIO_FREQ_MHZ)
sensor = adafruit_lsm9ds1.LSM9DS1_SPI(spi,csag,csm)

rfm9x.tx_power = 23

while True:
# read twice -- first reading is corrupted
    temp = sensor.temperature
    temp = sensor.temperature
    gyro_x, gyro_y, gyro_z = sensor.gyro
    accel_x, accel_y, accel_z = sensor.acceleration
    mag_x, mag_y, mag_z = sensor.magnetic

    stri = '{0:0.3f},{1:0.3f},{2:0.3f},{3:0.3f},{4:0.3f},{5:0.3f},{6:0.3f},{7:0.3f},{8:0.3f},{9:0.3f}'.format(accel_x, accel_y, accel_z,mag_x, mag_y, mag_z,gyro_x, gyro_y, gyro_z,temp)
    print(stri)
    
    packet = rfm9x.receive()

the line before the while() loop to set the tx power -- causes the sensor reading to be corrupted - if you comment it out, then the first reading is OK and subsequent reading fail. If you comment out the last line then only th firs reading is bad and it reads normally after that since there is no communication to the rfm9x in the loop.

@jerryneedell
Copy link
Author

jerryneedell commented Nov 30, 2018

here is a simpler example using the lsm9ds1 and a bmp280 - same issue
here I read the accel sensor twice -- the first is corrupted, the second is OK

import board
import busio
import digitalio
import adafruit_bmp280
import adafruit_lsm9ds1
from digitalio import DigitalInOut, Direction
import time

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
bmp_cs = digitalio.DigitalInOut(board.D22)
bmp280 = adafruit_bmp280.Adafruit_BMP280_SPI(spi, bmp_cs)

# change this to match the location's pressure (hPa) at sea level
bmp280.sea_level_pressure = 1013.25

csag = DigitalInOut(board.D21)
csm = DigitalInOut(board.D20)

sensor = adafruit_lsm9ds1.LSM9DS1_SPI(spi,csag,csm)


while True:
    bmp_temp=bmp280.temperature
    accel_x, accel_y, accel_z = sensor.acceleration
    accel_x, accel_y, accel_z = sensor.acceleration
    temp = sensor.temperature
    gyro_x, gyro_y, gyro_z = sensor.gyro
    mag_x, mag_y, mag_z = sensor.magnetic

    stri = '{0:0.3f},{1:0.3f},{2:0.3f},{3:0.3f},{4:0.3f},{5:0.3f},{6:0.3f},{7:0.3f},{8:0.3f},{9:0.3f},{10:0.3f}'.format(accel_x, accel_y, accel_z,mag_x, mag_y, mag_z,gyro_x, gyro_y, gyro_z,temp,bmp_temp)
    print(stri)

@jerryneedell
Copy link
Author

even simpler test case

import board
import busio
import digitalio
import adafruit_bmp280
import adafruit_lsm9ds1
from digitalio import DigitalInOut, Direction
import time

spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
bmp_cs = digitalio.DigitalInOut(board.D22)
bmp280 = adafruit_bmp280.Adafruit_BMP280_SPI(spi, bmp_cs)

# change this to match the location's pressure (hPa) at sea level
bmp280.sea_level_pressure = 1013.25

csag = DigitalInOut(board.D21)
csm = DigitalInOut(board.D20)

sensor = adafruit_lsm9ds1.LSM9DS1_SPI(spi,csag,csm)


while True:
    bmp_temp=bmp280.temperature
    accel_x, accel_y, accel_z = sensor.read_accel_raw()
    accel_x2, accel_y2, accel_z2 = sensor.read_accel_raw()
    print(accel_x, accel_y,accel_z, accel_x2, accel_y2, accel_z2)

and output

pi@gjnpi3p-1:~/projects/blinka/lsm9ds1 $ python3.6 raw_bmp.py 
SPI(): __init()
spiPorts: ((0, 11, 10, 9), (1, 21, 20, 19))
for:
0 11 10 9
Line 91
<class 'adafruit_blinka.microcontroller.generic_linux.spi.SPI'>
<adafruit_blinka.microcontroller.generic_linux.spi.SPI object at 0x768d7e30>
30977 -28399 6692 31 -40 12453
30977 -28399 6692 -147 33 14302
30977 -28399 6692 -495 49 19184
30977 -28399 6692 -244 54 15883
30977 -28399 6692 254 178 12700
30977 -28399 6692 -325 -7 14864
30977 -28399 6692 79 -142 13965
30977 -28399 6692 -658 29 14777
30977 -28399 6692 -3 -176 14946
30977 -28399 6692 147 166 16666

@ladyada
Copy link
Member

ladyada commented Nov 30, 2018

nice - thank you

@jerryneedell
Copy link
Author

jerryneedell commented Nov 30, 2018

Is this significant? -- I set up my Sales for the SPI polarity/phase of the LSM9DS1 and it shows the two reads from the sensor and rejects the first. Note how the LSM CS goes low before the clock polarity switches. Is that causing the problem?

screenshot

@ladyada
Copy link
Member

ladyada commented Nov 30, 2018

could be - but 99% of sensors use MODE 0 so i dont think thats all of it! i mean, i know where the problem probably is, its how we configure the SPI device when we send data, something amiss there

@jerryneedell
Copy link
Author

OK -- I'll go back to other projects for awhile -- Let me know if you need anything else.

@ladyada
Copy link
Member

ladyada commented Nov 30, 2018

npnp
thank you for the help!

@jerryneedell
Copy link
Author

jerryneedell commented Nov 30, 2018

just for comparison -- here is a logic analyzer capture of the same code running on a feather_m4_express -- note how the CS goes low after the SCK has changed polarity for the first transaction - both reads are successful.

feather_m4

@caternuson
Copy link
Contributor

Just throwing in my 2c FWIW:

The SPIDevice context manager notionally takes care of configuring the port each time:
https://github.com/adafruit/Adafruit_CircuitPython_BusDevice/blob/master/adafruit_bus_device/spi_device.py#L82
so in theory, should be able to switch between two devices with different modes. And it works on the Feather (see @jerryneedell 's trace above), so that's good.

On the RPi, this is also notionally happening. The blinka configure:
https://github.com/adafruit/Adafruit_Blinka/blob/master/src/busio.py#L97
calls init:
https://github.com/adafruit/Adafruit_Blinka/blob/master/src/adafruit_blinka/microcontroller/generic_linux/spi.py#L18
which sets mode:
https://github.com/adafruit/Adafruit_Blinka/blob/master/src/adafruit_blinka/microcontroller/generic_linux/spi.py#L30
But it appears, and can be seen in the traces above, that the actual changes to SCK do not occur until a write or read operation is done. Which corrupts the first attempt.

The python spidev module is just doing ioctl under the hood to the linux spidev kernel module.
https://github.com/doceme/py-spidev/blob/master/spidev_module.c#L864
So maybe this is just a "feature"?

@ladyada
Copy link
Member

ladyada commented Dec 2, 2018

ok did a little more lookin yeah this is due to us sharing an SPI peripheral amongst multiple devices. and yeah it only happens with mixed modes
i dont have a good solution for now. sorry :( we dont have many mode=1 devices, looks like lsm9ds1 is one of the only ones. we can start a Blinka Troubleshooting page for now, but i dont think i can get into this quite yet, or solve it :/

@ladyada
Copy link
Member

ladyada commented Dec 2, 2018

https://learn.adafruit.com/circuitpython-on-raspberrypi-linux/faq-troubleshooting

lol the LSM9DS1 is the only board that has this issue, nice work finding the one incompatibility, isn't that how it always is with programming :D

@jerryneedell
Copy link
Author

Thanks for digging into it. There's always one troublemaker ;-)

@ladyada
Copy link
Member

ladyada commented Apr 21, 2020

closing as this is now documented

@ladyada ladyada closed this as completed Apr 21, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants