discovery icon indicating copy to clipboard operation
discovery copied to clipboard

Newer revisions use LSM303AGR instead of LSM303DLHC

Open aditsachde opened this issue 3 years ago • 16 comments

Newer revisions of the discovery board use the LSM303AGR instead of the LSM303DLHC. The I2C address of the LSM303AGR chip is still the same but the registers to read are different. This makes it very confusing, as the code doesn't end up panicking, only returning zeros or garbage data.

aditsachde avatar Oct 31 '20 06:10 aditsachde

Thanks for noticing. I have a WIP driver for the LSM303AGR so you can at least play around with that. It is actually the same accelerometer as the micro:bit board has, for which this book is being rewritten for (a deprecation/removal of the current book version is still uncertain since the F3 disco board seems available for purchase again)

eldruin avatar Nov 18 '20 17:11 eldruin

That's great news! Even with the discrepancies the book has, I found this book extremely helpful with dipping my toes into the embedded rust world, and updating the book will help countless more people.

aditsachde avatar Nov 18 '20 22:11 aditsachde

Additionally, to make the LSM303AGR works like in the section 14.5 of the discovery book, you have to change its system mode from default idle to continuous mode by modifying the first two bits of the register CFG_REG_A_M via the I2C as well. Otherwise, your (x, y, z) value would probably stuck at some values! Hope this help someone.

Winnaries avatar Dec 12 '20 13:12 Winnaries

This is for anyone working through the book and you're stuck at the I2C section because the returned value from the IRA_REG_M register is 0.

Here is the link for the LSM303AGR data sheet. As noted above, newer board revisions use this IC instead of the LSM303DLHC.

For a quick test the WHO_AM_I_M register (address 0x4F) of the magnetometer can be used to quickly query the device via I2C.

If successfully you should see 64 or 0b01000000

So the code from the book becomes:

const MAGNETOMETER: u8 = 0b001_1110;
// Addresses of the magnetometer's registers.
const WHO_AM_I_M: u8 = 0x4F;

#[entry]
fn main() -> ! {
    let (i2c1, _delay, mut itm) = aux14::init();

    // Stage 1: Send the address of the register we want to read to the
    // magnetometer
    {
        // Broadcast START
        // Broadcast the MAGNETOMETER address with the R/W bit set to Write
        i2c1.cr2.write(|w| {
            w.start().set_bit();
            w.sadd1().bits(MAGNETOMETER);
            w.rd_wrn().clear_bit();
            w.nbytes().bits(1);
            w.autoend().clear_bit()
        });

        // Wait until we can send more data
        while i2c1.isr.read().txis().bit_is_clear() {}

        // Send the address of the register that we want to read: WHO_AM_I_M
        i2c1.txdr.write(|w| w.txdata().bits(WHO_AM_I_M));

        // Wait until the previous byte has been transmitted
        while i2c1.isr.read().tc().bit_is_clear() {}
    }

    // Stage 2: Receive the contents of the register we asked for
    let byte = {
        // Broadcast RESTART
        // Broadcast the MAGNETOMETER address with the R/W bit set to Read.
        i2c1.cr2.modify(|_, w| {
            w.start().set_bit();
            w.nbytes().bits(1);
            w.rd_wrn().set_bit();
            w.autoend().set_bit()
        });

        // Wait until we have received the contents of the register
        while i2c1.isr.read().rxne().bit_is_clear() {}

        // Broadcast STOP (automatic because of `AUTOEND = 1`)

        i2c1.rxdr.read().rxdata().bits()
    };

    // Expected output:  0b01000000(64)
    iprintln!(&mut itm.stim[0], "0x{:02X} - 0b{:08b}", WHO_AM_I_M, byte);

    loop {}
}

wbuck avatar Dec 25 '20 18:12 wbuck

Until there is a rewrite, is it possible to flag this divergence (more?) prominently in the book?

Akin to how the missing solder bridge is nice and big and hard to miss (which I still managed to miss on first read).

waalge avatar Feb 10 '21 17:02 waalge

@waalge Sure thing. We would be glad about PRs doing that.

eldruin avatar Feb 11 '21 07:02 eldruin

Happy to do more with instruction/ pointers.

When does the book get rebuilt so that changes appear?

waalge avatar Feb 11 '21 09:02 waalge

The one hosted at https://rust-embedded.github.io/discovery should be rebuilt shortly after each PR merge. The one hosted at https://docs.rust-embedded.org/discovery is a bit outdated because our CI for it is currently broken. We recently had some discussion about solving this and only publishing one version.

eldruin avatar Feb 11 '21 10:02 eldruin

Additionally, to make the LSM303AGR works like in the section 14.5 of the discovery book, you have to change its system mode from default idle to continuous mode by modifying the first two bits of the register CFG_REG_A_M via the I2C as well. Otherwise, your (x, y, z) value would probably stuck at some values! Hope this help someone.

Indeed this helped me get to a solution for the LSM303AGR .

Here's my implementation for reading the magnetometer continuously.

#![deny(unsafe_code)]
#![no_main]
#![no_std]

#[allow(unused_imports)]
use aux14::{entry, iprint, iprintln, prelude::*};
use aux14::i2c1::RegisterBlock;

// Slave address
const MAGNETOMETER: u8 = 0b001_1110;

// Addresses of the magnetometer's registers
const OUT_X_H_M: u8 = 0x03;
const IRA_REG_M: u8 = 0x0A;


const WHO_AM_I_M: u8 = 0x4F;

const CFG_REG_A_M: u8 = 0x60;
const OUTX_L_REG_M: u8 = 0x068;

const OUT_X_ACC: u8 = 0x28;

fn set_mode_continuous__LSM303AGR(i2c1: &RegisterBlock) -> (u8) {
    {
        // Broadcast START
        // Broadcast the MAGNETOMETER address with the R/W bit set to Write
        i2c1.cr2.write(|w| {
            w.start().set_bit();
            w.sadd1().bits(MAGNETOMETER);
            w.rd_wrn().clear_bit();
            w.nbytes().bits(2);
            w.autoend().clear_bit()
        });

        // Wait until we can send more data
        while i2c1.isr.read().txis().bit_is_clear() {}

        // Send the address of the register that we want to read: WHO_AM_I_M
        i2c1.txdr.write(|w| w.txdata().bits(CFG_REG_A_M));
        i2c1.txdr.write(|w| w.txdata().bits(0x0));

        // Wait until the previous byte has been transmitted
        while i2c1.isr.read().tc().bit_is_clear() {}
    }

    let cfg_reg_a_m_byte = {
        // Broadcast RESTART
        // Broadcast the MAGNETOMETER address with the R/W bit set to Read.
        i2c1.cr2.modify(|_, w| {
            w.start().set_bit();
            w.nbytes().bits(1);
            w.rd_wrn().set_bit();
            w.autoend().set_bit()
        });

        // Wait until we have received the contents of the register
        while i2c1.isr.read().rxne().bit_is_clear() {}

        // Broadcast STOP (automatic because of `AUTOEND = 1`)

        i2c1.rxdr.read().rxdata().bits()
    };

    cfg_reg_a_m_byte
}

fn who_am_i__LSM303AGR(i2c1: &RegisterBlock) -> u8 {

    // Stage 1: Send the address of the register we want to read to the
    // magnetometer
    {
        // Broadcast START
        // Broadcast the MAGNETOMETER address with the R/W bit set to Write
        i2c1.cr2.write(|w| {
            w.start().set_bit();
            w.sadd1().bits(MAGNETOMETER);
            w.rd_wrn().clear_bit();
            w.nbytes().bits(1);
            w.autoend().clear_bit()
        });

        // Wait until we can send more data
        while i2c1.isr.read().txis().bit_is_clear() {}

        // Send the address of the register that we want to read: WHO_AM_I_M
        i2c1.txdr.write(|w| w.txdata().bits(WHO_AM_I_M));

        // Wait until the previous byte has been transmitted
        while i2c1.isr.read().tc().bit_is_clear() {}
    }

    // Stage 2: Receive the contents of the register we asked for
    let byte = {
        // Broadcast RESTART
        // Broadcast the MAGNETOMETER address with the R/W bit set to Read.
        i2c1.cr2.modify(|_, w| {
            w.start().set_bit();
            w.nbytes().bits(1);
            w.rd_wrn().set_bit();
            w.autoend().set_bit()
        });

        // Wait until we have received the contents of the register
        while i2c1.isr.read().rxne().bit_is_clear() {}

        // Broadcast STOP (automatic because of `AUTOEND = 1`)

        i2c1.rxdr.read().rxdata().bits()
    };

    byte
}

#[entry]
fn main() -> ! {
    let (i2c1, _delay, mut itm) = aux14::init();

    let cfg_reg_a_m_byte: u8 = set_mode_continuous__LSM303AGR(i2c1);
    // Expected output:  0x60 - 0b00000000
    iprintln!(&mut itm.stim[0], "0x{:02X} - 0b{:08b}", CFG_REG_A_M, cfg_reg_a_m_byte);

    let whoami: u8 = who_am_i__LSM303AGR(i2c1);
    // Expected output:  0x4F - 0b01000000
    iprintln!(&mut itm.stim[0], "0x{:02X} - 0b{:08b}", WHO_AM_I_M, whoami);

    // sample magnetometer
    loop{
        // ask for an array of 6 register values starting at OUTX_L_REG_M (0x68)
        {
            // Broadcast START
            // Broadcast the MAGNETOMETER address with the R/W bit set to Write
            i2c1.cr2.write(|w| {
                w.start().set_bit();
                w.sadd1().bits(MAGNETOMETER);
                w.rd_wrn().clear_bit();
                w.nbytes().bits(1);
                w.autoend().clear_bit()
            });

            // Wait until we can send more data
            while i2c1.isr.read().txis().bit_is_clear() {}

            // Send the address of the register that we want to read: WHO_AM_I_M
            i2c1.txdr.write(|w| w.txdata().bits(OUTX_L_REG_M));

            // Wait until the previous byte has been transmitted
            while i2c1.isr.read().tc().bit_is_clear() {}
        }

        i2c1.cr2.modify(|_, w| {
            w.start().set_bit();
            w.nbytes().bits(6);
            w.rd_wrn().set_bit();
            w.autoend().set_bit()
        });

        let mut buffer = [0u8; 6];
        for byte in &mut buffer {
            // Wait until we have received something
            while i2c1.isr.read().rxne().bit_is_clear() {}

            *byte = i2c1.rxdr.read().rxdata().bits();
        }
        // Broadcast STOP (automatic because of `AUTOEND = 1`)

        iprintln!(&mut itm.stim[0], "{:?}", buffer);
    }
}

nargetdev avatar Feb 15 '21 05:02 nargetdev

Also note that the code to convert from the 6 register readings to 3 x i16 have the LSB/MSB order mixed up it should be LSB first :

    let x_l = u16::from(buffer[0]);
    let x_h = u16::from(buffer[1]);
    let z_l = u16::from(buffer[2]);
    let z_h = u16::from(buffer[3]);
    let y_l = u16::from(buffer[4]);
    let y_h = u16::from(buffer[5]);

    let x = ((x_h << 8) + x_l) as i16;
    let y = ((y_h << 8) + y_l) as i16;
    let z = ((z_h << 8) + z_l) as i16;

folknology avatar Feb 25 '21 16:02 folknology

@folknology thanks!

p. 45 of AGR datasheet image

I'm seeing that the registers are in order x, then y, then z. This is giving me the results I'm getting from using the lsm303agr crate in ch 15.

pfesenmeier avatar Aug 05 '21 22:08 pfesenmeier

Hi, I suspect that I have a board with LSM303AGR. I copy-pasted the 14.4 solution and am still getting panics in itmdump, and it is related to Lsm303dlhc::new (error message below).

panicked at 'called `Result::unwrap()` on an `Err` value: Arbitration', src/14-i2c/auxiliary/src/lib.rs:37:26

The book points to this thread but there don't seem to be any solutions to this problem here. What should be modified? Looking at the lsm303agr crate it looks like there will need to be pretty big changes? Sorry, I have no experience with embedded programming so I am pretty clueless. Thanks.

Edit: Ok, took a while but I managed to figure something out. Not sure if this is the appropriate place to post this, but hopefully it helps someone. I was using the lsm303agr crate but that gave Comm(Arbitration) errors. I rewrote everything and am now able to get data from the magnetometer.

In Cargo.toml:

[dependencies]
cortex-m = "0.7"
cortex-m-rt = { version = "0.7", features = ["device"] }
panic-itm = "0.4"
stm32f3xx-hal = { version = "0.8", features = ["ld", "rt", "stm32f303xc"] }
lsm303agr = "0.2"

In src/main.rs:

#![deny(unsafe_code)]
#![no_std]
#![no_main]

use core::convert::TryInto;
use cortex_m::{asm, iprintln};
use cortex_m_rt::entry;
use lsm303agr::Lsm303agr;
use panic_itm as _;
use stm32f3xx_hal::{self as hal, pac, prelude::*};

#[entry]
fn main() -> ! {
    let cp = cortex_m::Peripherals::take().unwrap();
    let mut itm = cp.ITM;

    let dp = pac::Peripherals::take().unwrap();

    let mut flash = dp.FLASH.constrain();
    let mut rcc = dp.RCC.constrain();
    let clocks = rcc.cfgr.freeze(&mut flash.acr);

    let mut gpiob = dp.GPIOB.split(&mut rcc.ahb);

    let mut scl =
        gpiob
            .pb6
            .into_af4_open_drain(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl);
    let mut sda =
        gpiob
            .pb7
            .into_af4_open_drain(&mut gpiob.moder, &mut gpiob.otyper, &mut gpiob.afrl);
    scl.internal_pull_up(&mut gpiob.pupdr, true);
    sda.internal_pull_up(&mut gpiob.pupdr, true);

    let i2c = hal::i2c::I2c::new(
        dp.I2C1,
        (scl, sda),
        400.kHz().try_into().unwrap(),
        clocks,
        &mut rcc.apb1,
    );

    let mut sensor = Lsm303agr::new_with_i2c(i2c);
    sensor
        .set_mag_odr(lsm303agr::MagOutputDataRate::Hz10)
        .unwrap();

    loop {
        match sensor.mag_data() {
            Ok(val) => iprintln!(&mut itm.stim[0], "{:?}", val),
            Err(_) => asm::delay(1_000_000),
        }
    }
}

al-jshen avatar Aug 24 '21 22:08 al-jshen

Hello. I was working through this and got stuck here. I followed @wbuck's example above but there isn't a sadd1 anymore, just a sadd and its bits method takes a u16. I tried changing the MAGNETOMETER to a u16 but it just hangs when I run and do continue in gdb.

davemo88 avatar Sep 17 '21 01:09 davemo88

@davemo88 From f3's reference manual, page: 87

Screenshot 2021-10-15 at 1 23 04 PM
// For 7 bit address, we populate bits 1 through 7 of SADD
// This gives me output that matches `lsm303agr` crate
w.sadd().bits((MAGNETOMETER << 1).into());

VasanthakumarV avatar Oct 15 '21 08:10 VasanthakumarV

In the latest version the magnetometer data can be obtained correctly using the following code.

#![deny(unsafe_code)]
#![no_main]
#![no_std]

use aux14::i2c1::RegisterBlock;
#[allow(unused_imports)]
use aux14::{entry, iprint, iprintln, prelude::*};

// Slave address
const MAGNETOMETER: u16 = 0b0011_1100;

// Addresses of the magnetometer's registers
const WHO_AM_I_M: u8 = 0x4F;

const CFG_REG_A_M: u8 = 0x60;
const OUTX_L_REG_M: u8 = 0x068;

#[entry]
fn main() -> ! {
    let (i2c1, mut delay, mut itm) = aux14::init();

    let cfg_reg_a_m_byte: u8 = set_mode_continuous__LSM303AGR(i2c1);
    // Expected output:  0x60 - 0b00000000
    iprintln!(
        &mut itm.stim[0],
        "0x{:02X} - 0b{:08b}",
        CFG_REG_A_M,
        cfg_reg_a_m_byte
    );

    let whoami: u8 = who_am_i__LSM303AGR(i2c1);
    // Expected output:  0x4F - 0b01000000
    iprintln!(&mut itm.stim[0], "0x{:02X} - 0b{:08b}", WHO_AM_I_M, whoami);

    loop {
        // ask for an array of 6 register values starting at OUTX_L_REG_M (0x68)
        {
            // Broadcast START
            // Broadcast the MAGNETOMETER address with the R/W bit set to Write
            i2c1.cr2.write(|w| {
                w.start().set_bit();
                w.sadd().bits(MAGNETOMETER);
                w.rd_wrn().clear_bit();
                w.nbytes().bits(1);
                w.autoend().clear_bit()
            });

            // Wait until we can send more data
            while i2c1.isr.read().txis().bit_is_clear() {}

            // Send the address of the register that we want to read: WHO_AM_I_M
            i2c1.txdr.write(|w| w.txdata().bits(OUTX_L_REG_M));

            // Wait until the previous byte has been transmitted
            while i2c1.isr.read().tc().bit_is_clear() {}
        }

        i2c1.cr2.modify(|_, w| {
            w.start().set_bit();
            w.nbytes().bits(6);
            w.rd_wrn().set_bit();
            w.autoend().set_bit()
        });

        let mut buffer = [0u8; 6];
        for byte in &mut buffer {
            // Wait until we have received something
            while i2c1.isr.read().rxne().bit_is_clear() {}

            *byte = i2c1.rxdr.read().rxdata().bits();
        }
        // Broadcast STOP (automatic because of `AUTOEND = 1`)

        iprintln!(&mut itm.stim[0], "{:?}", buffer);

        let x_l = u16::from(buffer[0]);
        let x_h = u16::from(buffer[1]);
        let y_l = u16::from(buffer[2]);
        let y_h = u16::from(buffer[3]);
        let z_l = u16::from(buffer[4]);
        let z_h = u16::from(buffer[5]);

        let x = ((x_h << 8) + x_l) as i16;
        let y = ((y_h << 8) + y_l) as i16;
        let z = ((z_h << 8) + z_l) as i16;

        iprintln!(&mut itm.stim[0], "{:?}", (x, y, z));

        delay.delay_ms(1_000_u16);
    }
}

fn who_am_i__LSM303AGR(i2c1: &RegisterBlock) -> u8 {
    // Stage 1: Send the address of the register we want to read to the
    // magnetometer
    {
        // Broadcast START
        // Broadcast the MAGNETOMETER address with the R/W bit set to Write
        i2c1.cr2.write(|w| {
            w.start().set_bit();
            w.sadd().bits(MAGNETOMETER);
            w.rd_wrn().clear_bit();
            w.nbytes().bits(1);
            w.autoend().clear_bit()
        });

        // Wait until we can send more data
        while i2c1.isr.read().txis().bit_is_clear() {}

        // Send the address of the register that we want to read: WHO_AM_I_M
        i2c1.txdr.write(|w| w.txdata().bits(WHO_AM_I_M));

        // Wait until the previous byte has been transmitted
        while i2c1.isr.read().tc().bit_is_clear() {}
    }

    // Stage 2: Receive the contents of the register we asked for
    let byte = {
        // Broadcast RESTART
        // Broadcast the MAGNETOMETER address with the R/W bit set to Read.
        i2c1.cr2.modify(|_, w| {
            w.start().set_bit();
            w.nbytes().bits(1);
            w.rd_wrn().set_bit();
            w.autoend().set_bit()
        });

        // Wait until we have received the contents of the register
        while i2c1.isr.read().rxne().bit_is_clear() {}

        // Broadcast STOP (automatic because of `AUTOEND = 1`)

        i2c1.rxdr.read().rxdata().bits()
    };

    byte
}

fn set_mode_continuous__LSM303AGR(i2c1: &RegisterBlock) -> (u8) {
    {
        // Broadcast START
        // Broadcast the MAGNETOMETER address with the R/W bit set to Write
        i2c1.cr2.write(|w| {
            w.start().set_bit();
            w.sadd().bits(MAGNETOMETER);
            w.rd_wrn().clear_bit();
            w.nbytes().bits(2);
            w.autoend().clear_bit()
        });

        // Wait until we can send more data
        while i2c1.isr.read().txis().bit_is_clear() {}

        // Send the address of the register that we want to read: WHO_AM_I_M
        i2c1.txdr.write(|w| w.txdata().bits(CFG_REG_A_M));
        i2c1.txdr.write(|w| w.txdata().bits(0x0));

        // Wait until the previous byte has been transmitted
        while i2c1.isr.read().tc().bit_is_clear() {}
    }

    let cfg_reg_a_m_byte = {
        // Broadcast RESTART
        // Broadcast the MAGNETOMETER address with the R/W bit set to Read.
        i2c1.cr2.modify(|_, w| {
            w.start().set_bit();
            w.nbytes().bits(1);
            w.rd_wrn().set_bit();
            w.autoend().set_bit()
        });

        // Wait until we have received the contents of the register
        while i2c1.isr.read().rxne().bit_is_clear() {}

        // Broadcast STOP (automatic because of `AUTOEND = 1`)

        i2c1.rxdr.read().rxdata().bits()
    };

    cfg_reg_a_m_byte
}

gmg137 avatar Dec 15 '21 09:12 gmg137

@al-jshen Just another datapoint: I had the same problem that you describe, but for me it was fixed simply by restarting everything (board & openocd). 🤷

wldmr avatar Jan 01 '22 16:01 wldmr