Reading/Writing to LSM6DSOX via SPI from Raspberry Pi - raspberry-pi

I'm having trouble reading and writing to my Adafruit LSM6DSOX IMU from my Raspberry Pi 4 running Ubuntu 20.04. I need to do it via SPI since I require the bandwidth, but I can only seem to read the WHO_AM_I register successfully. Reading/writing to any other register only returns 0x00. I have verified that I can read data off the IMU from an Arduino via SPI, but if I try to read a register other than 0x0F (the IMU_ID) I get 0x0 as a response. Any insight/ideas what could be causing this would be greatly appreciated!
EDIT: It turns out I can read the following registers:
0x0f : 0x6c
0x13 : 0x1c
0x33 : 0x1c
0x53 : 0x1c
0x73 : 0x1c
These are all random registers however, and the value 0x1C doesn't seem to correspond with anything.
This is my main.py:
import LSM6DSOX
def main():
imu=LSM6DSOX.LSM6DSOX()
imu.initSPI()
whoamI=imu.read_reg(0x0F)
while(whoamI != imu.LSM6DSOX_ID):
imu.ms_sleep(200)
print('searching for IMU')
whoamI=imu.get_id()
print(hex(whoamI))
print('found lsm6dsox IMU')
imu.spi.close()
imu.spi = None
if __name__=="__main__":
main()
This is an excerpt from my LSM6DSOX.py:
def initSPI(self):
# Setup communication SPI
self.spi = spidev.SpiDev()
self.spi.open(0, 0)
self.spi.mode=0b11 #mode 3, (mode 0 is also fine)
self.spi.max_speed_hz = 500000
return self.spi
def read_reg(self, reg, len=1):
# Set up message
buf = bytearray(len+1)
buf[0] = 0b10000000 | reg # MSB bit must be 1 to indicate a read operation. this is OR'd with the register address you want to read
resp =self.spi.xfer2(buf) #send (and recieve) data to the imu
if len==1:
return resp[1]
else:
return resp[1:] #display recieved data
def write_reg(self, reg, data, len=1):
# Set up message
buf = bytearray(len+1)
buf[0] = 0b00000000 | reg # MSB bit must be 0 to indicate a read operation. this is OR'd with the register address you want to read
buf[1:] =bytes(data)
resp =self.spi.xfer2(buf) #send (and recieve) data to the imu
return resp[1:] #display recieved data

Related

I2C: difference between I2C Tools and python SMBus2

I am trying to understand the i2c protocol and it's implementation through the python SMBus library and I2C Tools for Linux.
Let's look at the following I2C Sequences out of a datasheet: chapter 4.2 for the SFM3003:
I can read out the data, if I first initialise a continuous measurement with $i2cset -y 0x28 0x36 0x08 i
and then run my python script:
from smbus2 import SMBus, i2c_msg
import time
OFFSET_FLOW = -24576 # [-]
SCALEFACTOR_FLOW = 170 # [slm^-1]
OFFSET_TEMP = 0 # [-]
SCALEFACTOR_TEMP = 200 # [°C^-1]
#bus.write_byte_data(0x28, 0x36, 0x08)
msg = i2c_msg.read(0x28, 9)
bus = SMBus(1)
while True:
bus.i2c_rdwr(msg)
data = list(msg)
# JUST SOME CONVERSION FROM HERE ON
flow_bytes = data[0:2]
flow_bytes_converted = bytes(flow_bytes)
flow_raw = int.from_bytes(flow_bytes_converted, byteorder='big', signed=True)
print("flow_raw: ", flow_raw)
flow = (flow_raw-OFFSET_FLOW)/SCALEFACTOR_FLOW
print("flow: ", flow)
temp_bytes = data[3:5]
temp_bytes_converted = bytes(temp_bytes)
temp_raw = int.from_bytes(temp_bytes_converted, byteorder='big', signed=True)
print("temp_raw: ", temp_raw)
temp = (temp_raw-OFFSET_TEMP)/SCALEFACTOR_TEMP
print("temp: ", temp)
time.sleep(0.1)
If I uncomment the line bus.write_byte_data(0x28, 0x36, 0x08) and try to run the python script without initialisation from the command line($i2cset -y 0x28 0x36 0x08 i), I get an Remote I/O Error and see a NACK on my bus after writing 0x08 to the register 0x36.
So how is that command different to what I do with the I2C Linux Tools?

How do I synchronize clocks on two Raspberry Pi Picos?

I'm sending bits as LED blinks from one Pi Pico, and using another Pico to receive voltage from a photodiode and read the bits.
When sending/receiving from the same PiPico, it works accurately at 60us a bit. However, when sending from one pico and receiving from a second pico, I can only send/receive bits accurately at 0.1s a bit. If I move to sending/receiving at 0.01s, I start losing information. This leads me to believe my issue is with the clocks or sampling rates on the two pipicos, that they are slightly different.
Is there a way to synchronize two pipico clocks using Thonny/Micropython?
Code for Pico #1 sending blinks/bits:
led=Pin(13,Pin.OUT) #Led to blink
SaqVoltage = machine.ADC(28) #receive an input signal, when over a certain voltage, send the time using bits
#Ommitted code here to grab first low voltage values from SaqVoltage signal and use to determine if there is a high voltage received, when high voltage is received, send "hi" in led bits
#More omitted calibration code here for led/photodiode
hi = "0110100001101001"
while True:
SaqVoltage_value = SaqVoltage.read_u16()*3.3 / 65536 #read input signal to see if high voltage is sent
finalBitString = ""
if SaqVoltage_value > saQCutOff: #high voltage found, send "hi" sequence
finalBitString = #hi plus some start/stop sequences
for i in range (0, len(finalBitString)):
if finalBitString[i]=="1":
led(1)
elif finalBitString[i]=="0":
led(0)
utime.sleep(.01)
Code for Pico#2 receiving bits:
SaqDiode = machine.ADC(28) #read photodiode
#ommitted code here to calibrate leds/photodiodes
startSeqSaq = []
wordBits=[]
while True:
utime.sleep(.01) #sample diode every 0.01 seconds
SaqDiode_value = SaqDiode.read_u16()*3.3 / 65536
if (saqDiodeHi - saqCutOff <= SaqDiode_value): #read saq diode and determine 1 or 0
bit=1
elif (SaqDiode_value <= saqDiodeLo + saqCutOff):
bit=0
if len(startSeqSaq)==10: #record last 10 received bits to check if start sequence
startSeqSaq.pop(0)
startSeqSaq.append(bit)
elif len(startSeqSaq)<10:
startSeqSaq.append(bit)
if startSeqSaq == startSeq: #found start sequence, start reading bits
while True:
utime.sleep(.01)
SaqDiode_value = SaqDiode.read_u16()*3.3 / 65536
if (saqDiodeHi - saqCutOff <= SaqDiode_value):
bit=1
wordBits.append(bit)
elif (SaqDiode_value < saqDiodeLo + saqCutOff):
bit=0
wordBits.append(bit)
if len(wordBits)>10: #check for stop sequence
last14=wordBits[-10:]
else:
last14 = []
if last14==endSeq:
char = frombits(wordBits[:-10])
wordBits=[]
print("Function Generator Reset Signal Time in ms from Start: ", char)
break

Grove I2C display not working on Raspberry Pi 3B

I only have my RPi 3B and a I²C display, I don't own any GrovePi hat and I want to show some text to the said display. Is there a way to make it work?
My display.
i2cdetect -y 1 shows this result, meaning the RPi is detecting the I²C display and it should work. But nothing seemed to be showing on the display, except for the full block on the first row.
I've tried literally everything on the internet, some library worked (as in throwing no errors) but the display still stays the same, full block on the first row.
I've installed python3, smbus, smbus2 and i2c-tools. I've changed the address to 3e.
My most recent *.py file. It does write 'LCD printing' yet nothing else works.
I don't find a way to change the contrast of the LCD (no potentiometer or whatsoever)
#!/usr/bin/python3
import smbus2 as smbus
import time
# Define some device parameters
I2C_ADDR = 0x3e # I2C device address, if any error, change this address to 0x3f
LCD_WIDTH = 16 # Maximum characters per line
# Define some device constants
LCD_CHR = 1 # Mode - Sending data
LCD_CMD = 0 # Mode - Sending command
LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line
LCD_BACKLIGHT = 0x08 # On
ENABLE = 0b00000100 # Enable bit
# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005
# Open I2C interface
# bus = smbus.SMBus(0) # Rev 1 Pi uses 0
bus = smbus.SMBus(1) # Rev 2 Pi uses 1
def lcd_init():
# Initialise display
lcd_byte(0x33, LCD_CMD) # 110011 Initialise
lcd_byte(0x32, LCD_CMD) # 110010 Initialise
lcd_byte(0x06, LCD_CMD) # 000110 Cursor move direction
lcd_byte(0x0C, LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
lcd_byte(0x28, LCD_CMD) # 101000 Data length, number of lines, font size
lcd_byte(0x01, LCD_CMD) # 000001 Clear display
time.sleep(E_DELAY)
def lcd_byte(bits, mode):
# Send byte to data pins
# bits = the data
# mode = 1 for data
# 0 for command
bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
bits_low = mode | ((bits << 4) & 0xF0) | LCD_BACKLIGHT
# High bits
bus.write_byte(I2C_ADDR, bits_high)
lcd_toggle_enable(bits_high)
# Low bits
bus.write_byte(I2C_ADDR, bits_low)
lcd_toggle_enable(bits_low)
def lcd_toggle_enable(bits):
# Toggle enable
time.sleep(E_DELAY)
bus.write_byte(I2C_ADDR, (bits | ENABLE))
time.sleep(E_PULSE)
bus.write_byte(I2C_ADDR, (bits & ~ENABLE))
time.sleep(E_DELAY)
def lcd_string(message, line):
# Send string to display
message = message.ljust(LCD_WIDTH, " ")
lcd_byte(line, LCD_CMD)
for i in range(LCD_WIDTH):
lcd_byte(ord(message[i]), LCD_CHR)
if __name__ == '__main__':
lcd_init()
while True:
# Send some test
lcd_string("Hello ", LCD_LINE_1)
lcd_string(" World", LCD_LINE_2)
print('LCD printing!')
time.sleep(3)

PCI configuration space registers - write values

I am developing a network driver (RTL8139) for a selfmade operating system and have problems in writing values to the PCI configuration space registers.
I want to change the value of the interrupt line (offset 0x3c) to get another IRQ number and enable bus master (set bit 2) of the command register (offset 0x04).
When I read back the values I see that my values are correctly written. But instead of using the new IRQ number (in my case 6) it uses the old value (11).
Also bus master for DMA is not working (my packets I would like to send have the correct size (set by IO-Port) but they have no content (all values just 0, transceive buffer is on physical memory and has non zero values). This always worked with my code as expected after I double checked the physical address. I need this to let the network controller access to my physical memory where my buffers for receive/transceive are. (Bus mastering needed for RTL8139)
Do I have to do something else to confirm my changes to the PCI-device?
As emulator I use qemu.
For reading/writing I wrote the following functions:
uint16_t pci_read_word(uint16_t bus, uint16_t slot, uint16_t func, uint16_t offset)
{
uint64_t address;
uint64_t lbus = (uint64_t)bus;
uint64_t lslot = (uint64_t)slot;
uint64_t lfunc = (uint64_t)func;
uint16_t tmp = 0;
address = (uint64_t)((lbus << 16) | (lslot << 11) |
(lfunc << 8) | (offset & 0xfc) | ((uint32_t)0x80000000));
outportl (0xCF8, address);
tmp = (uint16_t)((inportl (0xCFC) >> ((offset & 2) * 8)) & 0xffff);
return (tmp);
}
uint16_t pci_write_word(uint16_t bus, uint16_t slot, uint16_t func, uint16_t offset, uint16_t data)
{
uint64_t address;
uint64_t lbus = (uint64_t)bus;
uint64_t lslot = (uint64_t)slot;
uint64_t lfunc = (uint64_t)func;
uint32_t tmp = 0;
address = (uint64_t)((lbus << 16) | (lslot << 11) |
(lfunc << 8) | (offset & 0xfc) | ((uint32_t)0x80000000));
outportl (0xCF8, address);
tmp = (inportl (0xCFC));
tmp &= ~(0xFFFF << ((offset & 0x2)*8)); // reset the word at the offset
tmp |= data << ((offset & 0x2)*8); // write the data at the offset
outportl (0xCF8, address); // set address again just to be sure
outportl(0xCFC,tmp); // write data
return pci_read_word(bus,slot,func,offset); // read back data;
}
I hope somebody can help me.
The "interrupt line" field (at offset 0x03C in PCI configuration space) literally does nothing.
The Full Story
PCI cards could use up to 4 "PCI IRQs" at the PCI slot; and use them in order (so if you have ten PCI cards that all have one IRQ, then they'll all use the first PCI IRQ at the slot).
There's a tricky "barber pole" arrangement to connect "PCI IRQ at the slot" to "PCI IRQ at the host controller" that is designed to reduce IRQ sharing. If you have ten PCI cards that all have one IRQ, then they'll all use the first PCI IRQ at the slot, but the first IRQ at each PCI slot will be connected to a different PCI IRQ at the host controller. All of this is hard-wired and can not be changed by software.
To complicate things more; to connect PCI IRQs (from the PCI host controller) to the legacy PIC chips, a special "PCI IRQ router" was added. In theory the configuration of the "PCI IRQ router" can be changed by software (if you can find the documentation that describes a table that describes the location, capabilities and restrictions of the "PCI IRQ router").
Without the firmware's help it'd be impossible for an OS to figure out which PIC chip input a PCI device actually uses. For that reason, the firmware figures it out during boot and then stores "PIC chip input number" somewhere for the OS to find. That is what the "interrupt line" register is - it's just an 8-bit register that can store anything you like (that the BIOS/firmware uses to store "PIC chip input number").

STM32F4 : EEPROM 25LC256 management through SPI

I am trying to drive a EEPROM Chip 25LC256 with a STM32F469I-DISCO but can't achieve it.
I have tried to make my own function with HAL API bases but apparently something is wrong : I don't know if I write datas on the chip since I can't read it. Let me explain more.
So my chip is a DIP 25LC256 (DS is above is you wish). PINs HOLD and WP of EEPROM are tied to VCC (3.3V). PIN CS is connected to PH6 (ARD_D10 on board) and is managed by the software. PIN SI and PIN SO are respectively connected to PB15 (ARD_D11) and PB14 (ARD_D12) with the right alternate function (GPIO_AF5_SPI2). PIN SCK is also connected to PD3 (ADR_D13).
Here is my SPI configuration code :
EEPROM_StatusTypeDef ConfigurationSPI2(SPI_HandleTypeDef *spi2Handle){
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
GPIO_InitTypeDef gpioInit;
//// SCK [PD3]
gpioInit.Pin = GPIO_PIN_3;
gpioInit.Mode = GPIO_MODE_AF_PP;
gpioInit.Pull = GPIO_PULLDOWN;
gpioInit.Speed = GPIO_SPEED_FREQ_HIGH;
gpioInit.Alternate = GPIO_AF5_SPI2;
HAL_GPIO_Init(GPIOD, &gpioInit);
//// MOSI [PB15]
gpioInit.Pin = GPIO_PIN_15;
gpioInit.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &gpioInit);
//// MISO [PB14]
gpioInit.Pin = GPIO_PIN_14;
gpioInit.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &gpioInit);
//// CS [PH6]
gpioInit.Pin = GPIO_PIN_6;
gpioInit.Mode = GPIO_MODE_OUTPUT_PP;
gpioInit.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOH, &gpioInit);
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, GPIO_PIN_SET);
//// SPI2
__HAL_RCC_SPI2_CLK_ENABLE();
spi2Handle->Instance = SPI2;
spi2Handle->Init.Mode = SPI_MODE_MASTER;
spi2Handle->Init.Direction = SPI_DIRECTION_2LINES;
spi2Handle->Init.DataSize = SPI_DATASIZE_8BIT;
spi2Handle->Init.CLKPolarity = SPI_POLARITY_LOW;
spi2Handle->Init.CLKPhase = SPI_PHASE_1EDGE;
spi2Handle->Init.NSS = SPI_NSS_SOFT;
spi2Handle->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
spi2Handle->Init.FirstBit = SPI_FIRSTBIT_MSB;
spi2Handle->Init.TIMode = SPI_TIMODE_DISABLE;
spi2Handle->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE ;
spi2Handle->Init.CRCPolynomial = 7;
if(HAL_SPI_Init(spi2Handle) != HAL_OK){
return EEPROM_ERROR;
}
return EEPROM_OK;
}
And two functions allowing respectively (and theorically) to WRITE and READ into the the chip :
Write Function :
EEPROM_StatusTypeDef WriteEEPROM(SPI_HandleTypeDef *spi2Handle, uint8_t *txBuffer, uint16_t size, uint16_t addr){
uint8_t addrLow = addr & 0xFF;
uint8_t addrHigh = (addr >> 8);
uint8_t wrenInstruction = WREN_EEPROM; // Value : 0x06
uint8_t buffer[32] = {WRITE_EEPROM, addrHigh, addrLow}; //Value : 0x02
for(uint i = 0 ; i < size ; i++){
buffer[3+i] = txBuffer[i];
}
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, RESET);
if(HAL_SPI_Transmit(spi2Handle, &wrenInstruction, 1, TIMEOUT_EEPROM) != HAL_OK){
return EEPROM_ERROR;;
}
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, SET);
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, RESET);
if(HAL_SPI_Transmit(spi2Handle, buffer, (size + 3), TIMEOUT_EEPROM) != HAL_OK){
return EEPROM_ERROR;
}
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, SET);
return EEPROM_OK;
}
Read Function :
EEPROM_StatusTypeDef ReadEEPROM(SPI_HandleTypeDef *spi2Handle, uint8_t *rxBuffer, uint16_t size, uint16_t addr){
uint8_t addrLow = addr & 0xFF;
uint8_t addrHigh = (addr >> 8);
uint8_t txBuffer[3] = {READ_EEPROM, addrHigh, addrLow};
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, RESET);
HAL_SPI_Transmit(spi2Handle, txBuffer, 3, TIMEOUT_EEPROM);
HAL_SPI_Receive(spi2Handle, rxBuffer, size, TIMEOUT_EEPROM);
HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, SET);
return EEPROM_OK;
}
I know my function are not very "beautiful" but it was a first attempt. In my main, I have tried in the first place to write into the chip the data "0x05" at the 0x01 adress then to read this data back :
uint8_t bufferEEPROM[1] = {5};
uint8_t bufferEEPROM2[1] = {1};
WriteEEPROM(&spi2Handle, bufferEEPROM, 1, 0x01);
ReadEEPROM(&spi2Handle, bufferEEPROM2, 1, 0x01);
I have an oscilloscope so since it didn't work (monitoring with STM Studio) I visualized the CLK and SI PINs then CLK and SO PINs (can only see two channel at the same time) :
As you can see, with the first picture that shows CLK (yellow) and SI (or MOSI) in blue, I have all the data expected : The WRite ENable instruction then the WRITE instruction. Following the ADDRESS, then the DATA.
After that, the Read Function starts. First the READ instruction and the ADDRESS where I want to fetch the data. The last 8 bits are supposed to be the data stored at the address (0x01 in this case). Something happens on SI PIN but I guess this is because the HAL_SPI_Receive() function actually calls HAL_SPI_TransmitReceive() with my array bufferEEPROM2 as parameter (that's why we can se 0b00000001). And so it is because of my SPI configuration parameter (Full-duplex).
Anyway, theorically I am supposed to see 0b00000101 on SO PIN but as you can see in the second picture.... nothing.
I have tried to change gpioInit.Pull for SO PIN on PULLUP and PULLDOWN but nothing changed. NOPULL is because that's the last thing I have tried.
The thing is I don't know where to start. My transmission seems to work (but is it actually ?). Is there anything wrong with my initialization ? Acutally my main question would be : why I don't receive any data from my EEPROM ?
Many thanks !
Write operations need some time to complete (your datasheet says 5 ms on page 4), during that time no operation other than read status is possible. Try polling the status register with the RDSR (0x05) opcode to find out when it becomes ready (bit 0). You could also check the status (bit 1) before and after issuing WREN to see if it was successful.
So the problem is now solved. Here are the improvements :
There was actually two issues. The first one and certainly the most important is, as berendi stated, a timing issue. In my WRITE function I didn't let the time for the EEPROM to complete its write cycle (5 ms on datasheet). I added the following code line at the end of all my WRITE functions :
HAL_Delay(10); //10 ms wait for the EEPROM to complete the write cycle
The delay value could be less I think if time is preicous (theorically 5ms). I didn't test below 10 ms though. An other thing. With the oscilloscope I also saw that my Chip Select used to went HIGH in the middle of my last clock edge. I could not say if this could also imply some issues since that's a thing I solved in the first place by adding a code line before HAl_Delay(10). All my SPI transmission functions finishes this way now :
while(HAL_GPIO_ReadPin(CLK_PORT, CLK_PIN) == GPIO_PIN_SET){
}
HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET);
HAL_Delay(10);
This way I have the proper pattern and I can write in the EEPROM and read back what I wrote.
NB : A last thing that made me goes deeper into my misunderstanding of the events : since my write functions didn't work, I focused on STATUS REGISTER write and read function (in order to solve this step by step). The write function didn't work either and in fact it was because the WRENbit wasn't set. I though (wrong one) that the fact to write into the STATUS REGISTER didn't ask also to set WREN like the WRITE functions into the memory ask to. Actually, it is also necessary.
Thanks for the help !