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

Newer revisions use LSM303AGR instead of LSM303DLHC #274

Open
aditsachde opened this issue Oct 31, 2020 · 16 comments
Open

Newer revisions use LSM303AGR instead of LSM303DLHC #274

aditsachde opened this issue Oct 31, 2020 · 16 comments

Comments

@aditsachde
Copy link

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.

@eldruin
Copy link
Member

eldruin commented Nov 18, 2020

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)

@aditsachde
Copy link
Author

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.

@Winnaries
Copy link

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.

@wbuck
Copy link

wbuck commented Dec 25, 2020

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 {}
}

@waalge
Copy link
Contributor

waalge commented Feb 10, 2021

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).

@eldruin
Copy link
Member

eldruin commented Feb 11, 2021

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

bors bot added a commit that referenced this issue Feb 11, 2021
300: add two notes flagging lsm303agr r=eldruin a=waalge

In response to  #274 (comment)

Please edit at leisure / suggest anything

Co-authored-by: waalge <[email protected]>
@waalge
Copy link
Contributor

waalge commented Feb 11, 2021

Happy to do more with instruction/ pointers.

When does the book get rebuilt so that changes appear?

@eldruin
Copy link
Member

eldruin commented Feb 11, 2021

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.

@nargetdev
Copy link

nargetdev commented Feb 15, 2021

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);
    }
}

@folknology
Copy link

folknology commented Feb 25, 2021

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;

@pfesenmeier
Copy link

@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.

@al-jshen
Copy link
Contributor

al-jshen commented Aug 24, 2021

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),
        }
    }
}

@davemo88
Copy link

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.

@VasanthakumarV
Copy link

@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());

@gmg137
Copy link

gmg137 commented Dec 15, 2021

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
}

@wldmr
Copy link

wldmr commented Jan 1, 2022

@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). 🤷

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