Let's write an embassy project: I2C IMU
In this project you will learn how to use the I2C bus to communicate with an IMU (Inertial Measurement Unit) sensor. You will also learn how to find the correct driver library for the sensor you are using and read sensor data with it.
You then can then for example calculate the current orientation of the device, or let an LED blink, if the rotational speed is above a certain threshold.
But that's up to you :)
Setup
Setup
esp-generate --chip esp32c3 --headless -o probe-rs -o defmt -o embassy -o unstable-hal i2c_imu
If you use an ESP32s3:
esp-generate --chip esp32s3 --headless -o probe-rs -o defmt -o embassy -o unstable-hal i2c_imu
Install Xtensa toolchain
espup install --targets=esp32s3
Source the toolchain into your environment
source ~/export-esp.sh
Build and flashing
cargo run --release
What is I2C?
Serial Protocol to transfer data. Needs two signals: SDA
and SCL
(Serial Data and Serial Clock). Together they build and transfer data between a start signal (S) and a stop signal (P)
To show the data transfer, we can use a diagram.
Image source: Wikipedia
Within the start (S) and stop (P) signal we can transfer data bits.
Interesting to know is that i2c is a so called master-slave protocol. Meaning, that the master initiates the communication - and the slave(s) respond to the master's requests.
In this case our MCU is the master (that also generates the clock signal) and the peripheral is the slave.
The speeds we can achieve are:
- 100kHz (Standard Mode, 100kbit/s)
- 400kHz (Fast Mode, 400kbit/s)
- 1MHz (Fast Mode Plus, 1Mbit/s)
- 3.4MHz (High Speed Mode, 1.7-3.4 Mbit/s)
More info to i2c you can find at Wikipedia.
How to wire i2c with the ESP32
Note: The two pull-up registers in this diagram are very likely redundant since almost all I2C breakout board include them already. But if you put resistors like in the diagram, use 4.7k Ohms resistors, to make the I2C work.
Finding a driver for given IMU
If you are not familiar with cargo crates, you can have a look at crates.io and type in mpu6050
, as this is the sensor we are using.
As we also use the quite new embedded-hal
crate - it is beneficial, if you filter the found crates for Recent Updates
.
You then add the crate with cargo
cargo add <crate-you-found>
Try to implement it yourself
You will find plenty of examples in the crates you chose to for this project.
Note: If happen to have an IMU like shown in the image below - those hang up, if you calibrate them. Just leave the calibration out, reading values works.
Netherless: here are some hints, if you get stuck somewhere:
What to do/try:
This should be your steps:
- Find a crate that supports the MPU6050 sensor.
- Look at the examples they provide - maybe you get a hang of how this works
- Initialize an i2c connection using the crate.
- Read the sensor data using the crate.
The crate to use:
We found those two crates useful:
Note: The mpu6050 crate is a bit dated. If you want to use it, make sure you directly
use the master branch of it. Atleast this version complies to embedded-hal
>= 1.
We recommed to go for the mpu6050-dmp crate, as it provides a more complete examples and
also supports asynchronous operations.
E.g. Installing the mpu6050-dmp
crate
cargo add mpu6050-dmp -F async -F defmt-03
Initializing an i2c connection:
#![allow(unused)] fn main() { let i2c_config = Config::default().with_frequency(Rate::from_khz(400)); let i2c = I2c::new(peripherals.I2C0, i2c_config) .expect("Failed to initialize I2C") .with_sda(peripherals.GPIO5) .with_scl(peripherals.GPIO6) .into_async(); }
If you get totally lost, we provided you a possible solution at code/i2c_imu_s3
(esp32s3) or code/i2c_imu_c3
(esp32c3), where we calibrate the IMU and read the sensor data. You can also use this one, if you want to continue building something bigger on it.
You can also ask us and others for help!