Read a single register
Let's put all that theory into practice!
First things first we need to know the target addresses of both the accelerometer and the magnetometer inside the chip, these can be found in the LSM303AGR's datasheet on page 39 and are:
- 0011001 for the accelerometer
- 0011110 for the magnetometer
NOTE Remember that these are only the 7 leading bits of the address, the 8th bit is going to be the bit that determines whether we are performing a read or write.
Next up we'll need a register to read from. Lots of I2C chips out there will
provide some sort of device identification register for their controllers to read.
This is done since considering the thousands (or even millions) of I2C chips
out there it is highly likely that at some point two chips with the same address
will end up being built (after all the address is "only" 7 bit wide). With
this device ID register a driver could then make sure that it is indeed talking
to a LSM303AGR and not some other chip that just happens to have the same address.
As you can read in the LSM303AGR's datasheet (specifically on page 46 and 61)
it does provide two registers called WHO_AM_I_A
at address 0x0f
and WHO_AM_I_M
at address 0x4f
which contain some bit patterns that are unique to the device
(The A is as in accelerometer and the M is as in magnetometer).
The only thing missing now is the software part, i.e. which API of the microbit
/the HAL
crates we should use for this. However, if you read through the datasheet of the nRF chip
you are using you will soon find out that they don't actually have an I2C peripheral.
Luckily for us though, they have I2C-compatible ones called TWI (Two Wire Interface)
and TWIM (depending on which chip you use, just like UART and UARTE).
Now if we put the documentation of the twi(m)
module from the microbit
crate
together with all the other information we have gathered so far we'll end up with this
piece of code to read out and print the two device IDs:
#![deny(unsafe_code)] #![no_main] #![no_std] use cortex_m_rt::entry; use rtt_target::{rtt_init_print, rprintln}; use panic_rtt_target as _; use microbit::hal::prelude::*; #[cfg(feature = "v1")] use microbit::{ hal::twi, pac::twi0::frequency::FREQUENCY_A, }; #[cfg(feature = "v2")] use microbit::{ hal::twim, pac::twim0::frequency::FREQUENCY_A, }; const ACCELEROMETER_ADDR: u8 = 0b0011001; const MAGNETOMETER_ADDR: u8 = 0b0011110; const ACCELEROMETER_ID_REG: u8 = 0x0f; const MAGNETOMETER_ID_REG: u8 = 0x4f; #[entry] fn main() -> ! { rtt_init_print!(); let board = microbit::Board::take().unwrap(); #[cfg(feature = "v1")] let mut i2c = { twi::Twi::new(board.TWI0, board.i2c.into(), FREQUENCY_A::K100) }; #[cfg(feature = "v2")] let mut i2c = { twim::Twim::new(board.TWIM0, board.i2c_internal.into(), FREQUENCY_A::K100) }; let mut acc = [0]; let mut mag = [0]; // First write the address + register onto the bus, then read the chip's responses i2c.write_read(ACCELEROMETER_ADDR, &[ACCELEROMETER_ID_REG], &mut acc).unwrap(); i2c.write_read(MAGNETOMETER_ADDR, &[MAGNETOMETER_ID_REG], &mut mag).unwrap(); rprintln!("The accelerometer chip's id is: {:#b}", acc[0]); rprintln!("The magnetometer chip's id is: {:#b}", mag[0]); loop {} }
Apart from the initialization, this piece of code should be straight forward if you
understood the I2C protocol as described before. The initialization here works similarly
to the one from the UART chapter.
We pass the peripheral as well as the pins that are used to communicate with the chip to the constructor; and then the frequency we wish the bus to operate on, in this case 100 kHz (K100
).
Testing it
As always you have to modify Embed.toml
to fit your MCU and can then use:
# For micro:bit v2
$ cargo embed --features v2 --target thumbv7em-none-eabihf
# For micro:bit v1
$ cargo embed --features v1 --target thumbv6m-none-eabi
in order to test our little example program.