1

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.

Jonas
  • 121,568
  • 97
  • 310
  • 388
  • Your handling of chip-select looks a bit odd. It should always be a push-pull output. You seem to be configuring as an input (with a pull resistor?) – pmacfarlane Jun 01 '23 at 14:42
  • @pmacfarlane you are right. I have updated my code. That was one of my misunderstandings, but it still does not work. – Jonas Jun 01 '23 at 14:46

1 Answers1

3

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.

Finomnis
  • 18,094
  • 1
  • 20
  • 27
  • I tried with `transfer` now, but I got `[255, 255]` as value. I am expecting `0x58`. – Jonas Jun 01 '23 at 14:34
  • @Jonas Well, I tried. I sadly cannot help you any further as I don't have a BMP280 on hand. Next steps I could think of: 1) read the sensor documentation to see if you need to do something to enable the SPI interface of the sensor first. 2) look at the traffic with a logic analyzer or an oscilloscope, to see what is happening on the bus, and whether your code has a problem or the device isn't answering. I personally usually use a Saleae Logic 8 (because I found one cheap online and like the interface) – Finomnis Jun 01 '23 at 14:52
  • Ok, I got `[255, 88]` after adding code to manage `spi_cs` before and after the `transfer`. – Jonas Jun 01 '23 at 14:52
  • @Jonas Happy to hear :) Yes, my next step would have been to look at the bus with a logic analyzer, and then one would probably have realized that the `CS` connection doesn't trigger properly. Glad to hear it's working now :) – Finomnis Jun 01 '23 at 14:54
  • 1
    I think the key info here was the use of `block!()` otherwise this code looks similar to the one I provided. – Jonas Jun 01 '23 at 14:55
  • @Jonas Partially; I think the two things were `block!()` and the fact that you have to `send(0)` before calling `read()`. You were only reading the byte during the previous `send` and were never actually giving the device the chance to answer. – Finomnis Jun 01 '23 at 14:57