I am trying to read a value from a sensor, BMP280 over SPI on a Raspberry Pi Pico. But I am getting an unexpected value.
I created a new repo based on the rp2040-project-template and modified it to add SPI functionality.
I added these imports:
use embedded_hal::prelude::_embedded_hal_spi_FullDuplex;
use rp_pico::hal::spi;
use rp_pico::hal::gpio;
use fugit::RateExtU32;
Then I setup SPI in the bottom of main function:
let _spi_sclk = pins.gpio2.into_mode::<gpio::FunctionSpi>();
let _spi_mosi = pins.gpio3.into_mode::<gpio::FunctionSpi>();
let _spi_miso = pins.gpio4.into_mode::<gpio::FunctionSpi>();
let mut spi_cs = pins.gpio5.into_push_pull_output_in_state(PinState::Low); // initial pull down, for SPI
let spi = spi::Spi::<_, _, 8>::new(pac.SPI0);
let mut spi = spi.init(
&mut pac.RESETS,
clocks.peripheral_clock.freq(),
10.MHz(), // bmp280 has 10MHz as maximum
&embedded_hal::spi::MODE_0,
);
spi_cs.set_high().unwrap(); // pull up, set as inactive after init
delay.delay_ms(200); // some delay for testing
Then I try to read the ID registry
spi_cs.set_low().unwrap();
let res_w = spi.send(0xd0 as u8); // 0xd0 is address for ID, with msb 1
let res_r = spi.read();
spi_cs.set_high().unwrap();
// check results
match res_w {
Ok(_) => info!("write worked"),
Err(_) => info!("failed to write")
}
match res_r {
Ok(v) => info!("read value from SPI: {}", v),
Err(_) => info!("failed to read SPI")
}
With this code, the SPI read fails. Why is that?
Perhaps it is necessary to set a mode on the sensor, before reading the ID. I can add this code above the read, to set forced mode.
spi_cs.set_low().unwrap();
spi.send(0xf4-128 as u8).expect("failed to send first byte"); // registry 0xf4 with msb 0
spi.send(0x1 as u8).expect("failed to send second byte");
spi_cs.set_high().unwrap();
Now the read of ID registry works, but I get value 255
and not the expected 0x58
.
What am I doing wrong?
I have also tried with transfer
using this code:
let mut data: [u8; 2] = [0xd0, 0x0];
let transfer_success = spi.transfer(&mut data);
match transfer_success {
Ok(v) => info!("read data {}", v),
Err(_) => info!("failed to read")
}
But I read the values as [255, 255]
with this code, not the expected 0x58
.
read()
is probably not the function you want to use here; it doesn't acutally perform any bus action but only gives you the byte that was read during the last send()
.
The function you actually want to use is transfer()
. On a full-duplex SPI bus, a "read" action is always also a "write" action, and transfer
performs both. Be aware that if you only want to read, you need to write the same amount of zeros, because only the bus master can provide the clock to do anything.
So if you want to write 0xd0
, followed by reading a single byte, you need to transfer()
the values [0xd0, 0x00]
. The same array that you use to put your sent data into transfer()
will then contain the received data; most likely [0x00, <data>]
(or [0xff, <data>]
, not sure. Probably 0xff
, as you already mentioned that you read a 255
).
The implementation of transfer
shows how read()
is actually supposed to be used:
fn transfer<'w>(&mut self, words: &'w mut [W]) -> Result<&'w [W], S::Error> {
for word in words.iter_mut() {
block!(self.send(word.clone()))?;
*word = block!(self.read())?;
}
Ok(words)
}
Note the block!()
here - in embedded, asynchronous calls usually return an error indicating that the operation would block, until it is finished. The block!()
macro converts an asynchronous call to a blocking call, which is most likely where your error comes from.
Either way, I would recommend deriving your code from the official example, those are usually pretty good at demonstrating the intended way an object should be used.