From 2767c291d16154fe63dff6935cc272e52edaea87 Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Wed, 19 Nov 2025 00:49:41 +0200 Subject: [PATCH 01/19] solved and added some of the comments --- Cargo.lock | 107 ++++++++- Cargo.toml | 2 +- codebook.toml | 6 + lab05/.cargo/config.toml | 8 + lab05/Cargo.toml | 32 +++ lab05/build.rs | 46 ++++ lab05/src/bin/ex2.rs | 108 +++++++++ lab05/src/bin/ex3.rs | 318 +++++++++++++++++++++++++++ lab05/src/bin/ex4_v1.rs | 92 ++++++++ lab05/src/bin/ex4_v2.rs | 164 ++++++++++++++ lab05/src/bin/ex4_v3_bus.rs | 62 ++++++ lab05/src/bin/ex4_v3_device.rs | 65 ++++++ lab05/src/bin/ex5.rs | 123 +++++++++++ lab05/src/lib.rs | 3 + lab05/src/mpu6500/bus.rs | 110 +++++++++ lab05/src/mpu6500/device.rs | 71 ++++++ lab05/src/mpu6500/device_blocking.rs | 71 ++++++ lab05/src/mpu6500/mod.rs | 68 ++++++ 18 files changed, 1443 insertions(+), 13 deletions(-) create mode 100644 lab05/.cargo/config.toml create mode 100644 lab05/Cargo.toml create mode 100644 lab05/build.rs create mode 100644 lab05/src/bin/ex2.rs create mode 100644 lab05/src/bin/ex3.rs create mode 100644 lab05/src/bin/ex4_v1.rs create mode 100644 lab05/src/bin/ex4_v2.rs create mode 100644 lab05/src/bin/ex4_v3_bus.rs create mode 100644 lab05/src/bin/ex4_v3_device.rs create mode 100644 lab05/src/bin/ex5.rs create mode 100644 lab05/src/lib.rs create mode 100644 lab05/src/mpu6500/bus.rs create mode 100644 lab05/src/mpu6500/device.rs create mode 100644 lab05/src/mpu6500/device_blocking.rs create mode 100644 lab05/src/mpu6500/mod.rs diff --git a/Cargo.lock b/Cargo.lock index e7191f0..f6e3047 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "bare-metal" version = "0.2.5" @@ -241,7 +247,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "554e3e840696f54b4c9afcf28a0f24da431c927f4151040020416e7393d6d0d8" dependencies = [ "defmt 1.0.1", - "embassy-futures 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "embassy-futures", "embassy-hal-internal", "embassy-sync", "embassy-time", @@ -291,11 +297,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc2d050bdc5c21e0862a89256ed8029ae6c290a93aecefc73084b3002cdebb01" -[[package]] -name = "embassy-futures" -version = "0.1.2" -source = "git+https://github.com/embassy-rs/embassy?rev=3e8d8fe#3e8d8fec15286eb25b8bba7d103c8fc279544551" - [[package]] name = "embassy-hal-internal" version = "0.3.0" @@ -334,7 +335,7 @@ dependencies = [ "defmt 1.0.1", "document-features", "embassy-embedded-hal", - "embassy-futures 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "embassy-futures", "embassy-hal-internal", "embassy-net-driver", "embassy-sync", @@ -378,7 +379,7 @@ dependencies = [ "embedded-io-async", "futures-core", "futures-sink", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -414,7 +415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "80e2ee86063bd028a420a5fb5898c18c87a8898026da1d4c852af2c443d0a454" dependencies = [ "embassy-executor-timer-queue", - "heapless", + "heapless 0.8.0", ] [[package]] @@ -448,6 +449,29 @@ dependencies = [ "nb 1.1.0", ] +[[package]] +name = "embedded-graphics" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0649998afacf6d575d126d83e68b78c0ab0e00ca2ac7e9b3db11b4cbe8274ef0" +dependencies = [ + "az", + "byteorder", + "embedded-graphics-core", + "float-cmp", + "micromath", +] + +[[package]] +name = "embedded-graphics-core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba9ecd261f991856250d2207f6d8376946cd9f412a2165d3b75bc87a0bc7a044" +dependencies = [ + "az", + "byteorder", +] + [[package]] name = "embedded-hal" version = "0.2.7" @@ -517,6 +541,15 @@ dependencies = [ "embedded-storage", ] +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "fnv" version = "1.0.7" @@ -572,6 +605,16 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heapless" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af2455f757db2b292a9b1768c4b70186d443bcb3b316252d6b540aec1cd89ed" +dependencies = [ + "hash32", + "stable_deref_trait", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -600,7 +643,7 @@ dependencies = [ "defmt-rtt", "embassy-embedded-hal", "embassy-executor", - "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy?rev=3e8d8fe)", + "embassy-futures", "embassy-stm32", "embassy-sync", "embassy-time", @@ -617,7 +660,7 @@ dependencies = [ "defmt-rtt", "embassy-embedded-hal", "embassy-executor", - "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy?rev=3e8d8fe)", + "embassy-futures", "embassy-stm32", "embassy-sync", "embassy-time", @@ -635,11 +678,33 @@ dependencies = [ "defmt-rtt", "embassy-embedded-hal", "embassy-executor", - "embassy-futures 0.1.2 (git+https://github.com/embassy-rs/embassy?rev=3e8d8fe)", + "embassy-futures", + "embassy-stm32", + "embassy-sync", + "embassy-time", + "embedded-hal-async", + "panic-probe", +] + +[[package]] +name = "lab05" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt 1.0.1", + "defmt-rtt", + "embassy-embedded-hal", + "embassy-executor", + "embassy-futures", "embassy-stm32", "embassy-sync", "embassy-time", + "embedded-graphics", + "embedded-hal 1.0.0", "embedded-hal-async", + "heapless 0.9.2", + "mipidsi", "panic-probe", ] @@ -649,6 +714,24 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" +[[package]] +name = "micromath" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815" + +[[package]] +name = "mipidsi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ba34dcbf61182ffa6992b5a4d9b566d5a99df127fd93f6d314213347329e92f" +dependencies = [ + "embedded-graphics-core", + "embedded-hal 1.0.0", + "heapless 0.8.0", + "nb 1.1.0", +] + [[package]] name = "nb" version = "0.1.3" diff --git a/Cargo.toml b/Cargo.toml index 51c6dd4..92958b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["lab01", "lab02", "lab03", "lab04"] +members = ["lab01", "lab02", "lab03", "lab04", "lab05"] [workspace.dependencies] # Low level access to Cortex-M processors diff --git a/codebook.toml b/codebook.toml index ebbc5a2..3bcdb6f 100644 --- a/codebook.toml +++ b/codebook.toml @@ -1,16 +1,21 @@ words = [ "boostrap", "chrono", + "clk", "debouncer", "defmt", + "dma", "dyn", "eabihf", "exti", + "gpdma", "ish", "linker's", "mcu", "mille", "moder", + "mosi", + "mpu", "nmagic", "pwm", "pwm's", @@ -21,4 +26,5 @@ words = [ "tlink", "uptime", "vcc", + "xout", ] diff --git a/lab05/.cargo/config.toml b/lab05/.cargo/config.toml new file mode 100644 index 0000000..2399423 --- /dev/null +++ b/lab05/.cargo/config.toml @@ -0,0 +1,8 @@ +[build] +target = "thumbv8m.main-none-eabihf" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-rs run --chip STM32U545RETxQ" + +[env] +DEFMT_LOG = "debug" diff --git a/lab05/Cargo.toml b/lab05/Cargo.toml new file mode 100644 index 0000000..33e8e97 --- /dev/null +++ b/lab05/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "lab05" +version = "0.1.0" +edition = "2024" + +[dependencies] +# Low level access to Cortex-M processors +cortex-m.workspace = true +# Boostrap crate for Cortex-M Processors +cortex-m-rt.workspace = true +# Defmt support +defmt.workspace = true +defmt-rtt.workspace = true +# Embedded HAL utilities +embassy-embedded-hal.workspace = true +# Async/await executor +embassy-executor.workspace = true +# Utilities for working with futures, compatible with no_std and not using alloc +embassy-futures.workspace = true +# STM32 HAL Implementation +embassy-stm32.workspace = true +# Synchronization primitives and data structures with async support +embassy-sync.workspace = true +# Timekeeping, delays and timeouts +embassy-time.workspace = true +embedded-graphics = "0.8.1" +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" +heapless = "0.9.2" +mipidsi = "0.9.0" +# Panic handler that exits `probe-run` with an error code +panic-probe.workspace = true diff --git a/lab05/build.rs b/lab05/build.rs new file mode 100644 index 0000000..f5ee10f --- /dev/null +++ b/lab05/build.rs @@ -0,0 +1,46 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +// use std::env; +// use std::fs::File; +// use std::io::Write; +// use std::path::PathBuf; + +fn main() { + // This section is not needed as the `embassy-stm32` crate + // provides `memory.x` and adds it to the linker's path. + // + // If using a crate that does not provide it, please + // uncomment the this section and the related imported + // items. + + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + // let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + // File::create(out.join("memory.x")) + // .unwrap() + // .write_all(include_bytes!("memory.x")) + // .unwrap(); + // println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + // println!("cargo:rerun-if-changed=memory.x"); + + // Prevent the linker from page aligning segments + println!("cargo:rustc-link-arg-bins=--nmagic"); + + // Required for `cortex-m` + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + // Required for `defmt` + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); +} diff --git a/lab05/src/bin/ex2.rs b/lab05/src/bin/ex2.rs new file mode 100644 index 0000000..422f12b --- /dev/null +++ b/lab05/src/bin/ex2.rs @@ -0,0 +1,108 @@ +#![no_std] +#![no_main] + +use defmt::{debug, info, warn}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{Level, Output, Speed}, + spi::{self, Spi}, + time::Hertz, +}; +use panic_probe as _; + +/// WHO_AM_I Register Address +const WHO_AM_I: u8 = 0x75; +/// WHO_AM_I Register Value for the MPU6500 sensor +const WHO_AM_I_VALUE: u8 = 0x70; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + // Create the SPI bus configuration + let mut config = spi::Config::default(); + // Set the SPI frequency to 1 MHz + config.frequency = Hertz(1_000_000); + + // SPI1 is exposed by the Arduino header using pins: + // - MISO - D12 (PA6) + // - MOSI - D11 (PA7) + // - CLK - D13 (PA5) + // + // We use the asynchronous API an we need two free + // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 + let mut spi = Spi::new( + peripherals.SPI1, + peripherals.PA5, + peripherals.PA7, + peripherals.PA6, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + config, + ); + + // We use the D7 (PA8) pin as CS + let mut mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the first bit: + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the WHO_AM_I register address 0x0111_0101 + // and obtain 0b1111_0101. + // + // We add another 0 that will be ignored by the sensor, but + // is required as the sensor will send us the WHO_AM_I register's + // value while we transmit this 0. + let command = [(1 << 7) | WHO_AM_I, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // The second byte is the value of the WHO_AM_I register. + let mut rx = [0u8; 2]; + + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by one zero byte) + // - receive in the rx buffer (random byte followed by the value of the WHO_AM_I register) + let res = spi.transfer(&mut rx, &command).await; + // End the SPI transmission by setting the CS line HIGH. + mpu6500_cs_pin.set_high(); + + // SPI transmissions might return errors. We check if the `spi.transfer` function + // has returned an error. + if let Err(error) = res { + // If we have an error, display it. + warn!("Failed to connect to the sensor: {}", error); + } else { + // If there is no transmission error, we check the returned value. + + // The rx buffer received two bytes: + // - the first byte is random data as it was received while we transmitted + // the command + // - the second byte is the register's value that was received while + // we transmitted the 0 value + let who_am_i_value = rx[1]; + debug!("Sensor's WHO_AM_I register is {:x}", who_am_i_value); + + // If the register's value is the one expected, + // we confirm that the MPU 6500 is connected to + // the SPI. + if who_am_i_value == WHO_AM_I_VALUE { + info!("Sensor is MPU6500"); + } else { + // If the value is incorrect, the connection is faulty or + // there might be another sensor connected. + warn!("This is not an MPU6500 sensor"); + } + } +} diff --git a/lab05/src/bin/ex3.rs b/lab05/src/bin/ex3.rs new file mode 100644 index 0000000..c5123cd --- /dev/null +++ b/lab05/src/bin/ex3.rs @@ -0,0 +1,318 @@ +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{Level, Output, Speed}, + spi::{self, Spi}, + time::Hertz, +}; +use embassy_time::Timer; +use panic_probe as _; + +/// WHO_AM_I Register Address +const WHO_AM_I: u8 = 0x75; +/// GYRO_CONFIG Register Address +const GYRO_CONFIG: u8 = 0x1b; +/// ACCEL_CONFIG Register Address +const ACCEL_CONFIG: u8 = 0x1c; + +/// ACCEL_XOUT_H Register Address +/// +/// This register stores the high value (most significant 8 bits +/// of the acceleration). +const ACCEL_XOUT_H: u8 = 0x3b; + +/// GYRO_XOUT_H Register Address +/// +/// This register stores the high value (most significant 8 bits +/// of the gyro). +const GYRO_XOUT_H: u8 = 0x43; + +/// WHO_AM_I Register Value for the MPU6500 sensor +const WHO_AM_I_VALUE: u8 = 0x70; + +/// The acceleration scale value for ±2g +const ACCEL_SCALE_2G: u8 = 0b00; + +/// The gyre scale value for ±1000 deg / s +const GYRO_SCALE_1000: u8 = 0b10; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + // Create the SPI bus configuration + let mut config = spi::Config::default(); + // Set the SPI frequency to 1 MHz + config.frequency = Hertz(1_000_000); + + // SPI1 is exposed by the Arduino header using pins: + // - MISO - D12 (PA6) + // - MOSI - D11 (PA7) + // - CLK - D13 (PA5) + // + // We use the asynchronous API an we need two free + // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 + let mut spi = Spi::new( + peripherals.SPI1, + peripherals.PA5, + peripherals.PA7, + peripherals.PA6, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + config, + ); + + // We use the D7 (PA8) pin as CS + let mut mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the first bit: + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the WHO_AM_I register address 0x0111_0101 + // and obtain 0b1111_0101. + // + // We add another 0 that will be ignored by the sensor, but + // is required as the sensor will send us the WHO_AM_I register's + // value while we transmit this 0. + let command = [(1 << 7) | WHO_AM_I, 0]; + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // The second byte is the value of the WHO_AM_I register. + let mut rx = [0u8; 2]; + + // Verify that the MPU 6500 sensor is connected. + + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by one zero byte) + // - receive in the rx buffer (random byte followed by the value of the WHO_AM_I register) + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. + spi.transfer(&mut rx, &command).await.unwrap(); + // End the SPI transmission by setting the CS line HIGH. + mpu6500_cs_pin.set_high(); + // Panic if the WHO_AM_I register value if wrong + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. + assert_eq!(rx[1], WHO_AM_I_VALUE); + + // Set the acceleration scale configuration + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the first bit: + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the ACCEL_CONFIG register address 0x0001_1011 + // and obtain 0x0001_1011. + // + // This was actually not required as the ACCEL_CONFIG's + // most significant bit was already 0. + // + // The second position of the command buffer is the value that + // we want to write to the ACCEL_CONFIG register. In our case + // we are only interested in the ACCEL_FS_SEL field which + // is 2 bits long starting at bit position 3. + // + // As all the other fields of the ACCEL_CONFIG register are 0, + // all that we have to do is to shift the + // ACCEL_SCALE_2G value 3 positions to the left. + let command = [!(1 << 7) & ACCEL_CONFIG, ACCEL_SCALE_2G << 3]; + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. + let mut rx = [0u8; 2]; + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. + spi.transfer(&mut rx, &command).await.unwrap(); + // End the SPI transmission by setting the CS line HIGH. + mpu6500_cs_pin.set_high(); + + // Set the gyro scale configuration + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | bytes that we want to write | + // - R/W is the the first bit: + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the GYRO_CONFIG register address 0x0001_1100 + // and obtain 0x0001_1100. + // + // This was actually not required as the GYRO_CONFIG's + // most significant bit was already 0. + // + // The second position of the command buffer is the value that + // we want to write to the GYRO_CONFIG register. In our case + // we are only interested in the GYRO_FS_SEL field which + // is 2 bits long starting at bit position 3. + // + // As all the other fields of the GYRO_CONFIG register are 0, + // all that we have to do is to shift the + // GYRO_SCALE_1000 value 3 positions to the left. + let command = [GYRO_CONFIG, GYRO_SCALE_1000 << 3]; + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. + let mut rx = [0u8; 2]; + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. + spi.transfer(&mut rx, &command).await.unwrap(); + // End the SPI transmission by setting the CS line HIGH. + mpu6500_cs_pin.set_high(); + + loop { + // Read the acceleration + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the first bit: + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the ACCEL_XOUT_H register address 0x0011_1011 + // and obtain 0b1011_1011. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the ACCEL_XOUT_H register's + // value followed by the values of the next 5 registers: + // - ACCEL_XOUT_L at 0x3c + // - ACCEL_YOUT_H at 0x3d + // - ACCEL_YOUT_L at 0x3e + // - ACCEL_ZOUT_H at 0x3f + // - ACCEL_ZOUT_L at 0x40 + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. + let command = [(1 << 7) | ACCEL_XOUT_H, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the ACCEL_XOUT_H, ACCEL_YOUT_H, ACCEL_YOUT_L, + // ACCEL_ZOUT_H and ACCEL_ZOUT_L registers. + let mut rx = [0u8; 7]; + + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the ACCEL_XOUT_H, ACCEL_YOUT_H, ACCEL_YOUT_L, + // ACCEL_ZOUT_H and ACCEL_ZOUT_L registers). + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. + spi.transfer(&mut rx, &command).await.unwrap(); + + // End the SPI transmission by setting the CS line HIGH. + mpu6500_cs_pin.set_high(); + info!("Acceleration Values: {:?}", &rx[1..]); + + // Read the gyro + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the first bit: + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the GYRO_XOUT_H register address 0x0100_0011 + // and obtain 0b1100_0011. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the GYRO_XOUT_H register's + // value followed by the values of the next 5 registers: + // - GYROL_XOUT_L at 0x44 + // - GYROL_YOUT_H at 0x45 + // - GYROL_YOUT_L at 0x46 + // - GYROL_ZOUT_H at 0x47 + // - GYROL_ZOUT_L at 0x48 + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. + let command = [(1 << 7) | GYRO_XOUT_H, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the GYRO_XOUT_H, GYRO_YOUT_H, GYRO_YOUT_L, + // GYRO_ZOUT_H and GYRO_ZOUT_L registers. + let mut rx = [0u8; 7]; + + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the GYRO_XOUT_H, GYRO_YOUT_H, GYRO_YOUT_L, + // GYRO_ZOUT_H and GYRO_ZOUT_L registers). + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. + spi.transfer(&mut rx, &command).await.unwrap(); + + // End the SPI transmission by setting the CS line HIGH. + mpu6500_cs_pin.set_high(); + info!("Gyro Values: {:?}", &rx[1..]); + + Timer::after_millis(100).await; + } +} diff --git a/lab05/src/bin/ex4_v1.rs b/lab05/src/bin/ex4_v1.rs new file mode 100644 index 0000000..4b9a36d --- /dev/null +++ b/lab05/src/bin/ex4_v1.rs @@ -0,0 +1,92 @@ +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{Level, Output, Speed}, + spi::{self, Spi}, + time::Hertz, +}; +use embassy_time::Timer; +use panic_probe as _; + +const WHO_AM_I: u8 = 0x75; +const GYRO_CONFIG: u8 = 0x1b; +const ACCEL_CONFIG: u8 = 0x1c; + +const ACCEL_XOUT_H: u8 = 0x3b; +const GYRO_XOUT_H: u8 = 0x43; + +const WHO_AM_I_VALUE: u8 = 0x70; +const ACCEL_SCALE_2G: u8 = 0b00; +const GYRO_SCALE_1000: u8 = 0b10; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + let mut config = spi::Config::default(); + config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new( + peripherals.SPI1, + peripherals.PA5, + peripherals.PA7, + peripherals.PA6, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + config, + ); + + let mut mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + + let command = [(1 << 7) | WHO_AM_I, 0]; + let mut rx = [0u8; 2]; + mpu6500_cs_pin.set_low(); + spi.transfer(&mut rx, &command).await.unwrap(); + mpu6500_cs_pin.set_high(); + assert_eq!(rx[1], WHO_AM_I_VALUE); + + mpu6500_cs_pin.set_low(); + let command = [ACCEL_CONFIG, ACCEL_SCALE_2G << 3]; + let mut rx = [0u8; 2]; + spi.transfer(&mut rx, &command).await.unwrap(); + mpu6500_cs_pin.set_high(); + + mpu6500_cs_pin.set_low(); + let command = [GYRO_CONFIG, GYRO_SCALE_1000 << 3]; + let mut rx = [0u8; 2]; + spi.transfer(&mut rx, &command).await.unwrap(); + mpu6500_cs_pin.set_high(); + + loop { + mpu6500_cs_pin.set_low(); + let command = [(1 << 7) | ACCEL_XOUT_H, 0, 0, 0, 0, 0, 0]; + let mut rx = [0u8; 7]; + spi.transfer(&mut rx, &command).await.unwrap(); + mpu6500_cs_pin.set_high(); + info!( + "Acceleration: X {}, Y {}, Z {}", + i16::from_le_bytes([rx[1], rx[2]]), + i16::from_le_bytes([rx[3], rx[4]]), + i16::from_le_bytes([rx[5], rx[6]]) + ); + + mpu6500_cs_pin.set_low(); + let command = [(1 << 7) | GYRO_XOUT_H, 0, 0, 0, 0, 0, 0]; + let mut rx = [0u8; 7]; + spi.transfer(&mut rx, &command).await.unwrap(); + mpu6500_cs_pin.set_high(); + info!( + "Gyro: X {}, Y {}, Z {}", + i16::from_le_bytes([rx[1], rx[2]]), + i16::from_le_bytes([rx[3], rx[4]]), + i16::from_le_bytes([rx[5], rx[6]]) + ); + + Timer::after_millis(100).await; + } +} diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs new file mode 100644 index 0000000..6f12cf6 --- /dev/null +++ b/lab05/src/bin/ex4_v2.rs @@ -0,0 +1,164 @@ +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{Level, Output, Speed}, + mode::Async, + spi::{self, Error, Spi}, + time::Hertz, +}; +use embassy_time::Timer; +use panic_probe as _; + +const WHO_AM_I: u8 = 0x75; +const GYRO_CONFIG: u8 = 0x1b; +const ACCEL_CONFIG: u8 = 0x1c; + +const ACCEL_XOUT_H: u8 = 0x3b; +const GYRO_XOUT_H: u8 = 0x43; + +const WHO_AM_I_VALUE: u8 = 0x70; + +#[repr(u8)] +#[allow(dead_code)] +enum GyroScale { + Gs250 = 0b00, + Gs500 = 0b01, + Gs1000 = 0b10, + Gs2000 = 0b11, +} + +#[repr(u8)] +#[allow(dead_code)] +enum AccelScale { + G2 = 0b00, + G4 = 0b01, + G8 = 0b10, + G16 = 0b11, +} + +struct Acceleration { + x: i16, + y: i16, + z: i16, +} + +struct Gyro { + x: i16, + y: i16, + z: i16, +} + +async fn set_gyro_scale( + spi: &mut Spi<'_, Async>, + cs: &mut Output<'_>, + scale: GyroScale, +) -> Result<(), Error> { + cs.set_low(); + let command = [(1 << 7) | GYRO_CONFIG, (scale as u8) << 3]; + let mut rx = [0u8; 2]; + let res = spi.transfer(&mut rx, &command).await; + cs.set_high(); + res +} + +async fn set_accel_scale( + spi: &mut Spi<'_, Async>, + cs: &mut Output<'_>, + scale: AccelScale, +) -> Result<(), Error> { + cs.set_low(); + let command = [(1 << 7) | ACCEL_CONFIG, (scale as u8) << 3]; + let mut rx = [0u8; 2]; + let res = spi.transfer(&mut rx, &command).await; + cs.set_high(); + res +} + +async fn read_acceleration( + spi: &mut Spi<'_, Async>, + cs: &mut Output<'_>, +) -> Result { + cs.set_low(); + let command = [(1 << 7) | ACCEL_XOUT_H, 0, 0, 0, 0, 0, 0]; + let mut rx = [0u8; 7]; + let res = spi.transfer(&mut rx, &command).await; + cs.set_high(); + match res { + Ok(()) => Ok(Acceleration { + x: i16::from_le_bytes([rx[1], rx[2]]), + y: i16::from_le_bytes([rx[3], rx[4]]), + z: i16::from_le_bytes([rx[5], rx[6]]), + }), + Err(error) => Err(error), + } +} + +async fn read_gyro(spi: &mut Spi<'_, Async>, cs: &mut Output<'_>) -> Result { + cs.set_low(); + let command = [(1 << 7) | GYRO_XOUT_H, 0, 0, 0, 0, 0, 0]; + let mut rx = [0u8; 7]; + let res = spi.transfer(&mut rx, &command).await; + cs.set_high(); + match res { + Ok(()) => Ok(Gyro { + x: i16::from_le_bytes([rx[1], rx[2]]), + y: i16::from_le_bytes([rx[3], rx[4]]), + z: i16::from_le_bytes([rx[5], rx[6]]), + }), + Err(error) => Err(error), + } +} + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + let mut config = spi::Config::default(); + config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new( + peripherals.SPI1, + peripherals.PA5, + peripherals.PA7, + peripherals.PA6, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + config, + ); + + let mut mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + + let command = [(1 << 7) | WHO_AM_I, 0]; + let mut rx = [0u8; 2]; + mpu6500_cs_pin.set_low(); + spi.transfer(&mut rx, &command).await.unwrap(); + mpu6500_cs_pin.set_high(); + assert_eq!(rx[1], WHO_AM_I_VALUE); + + set_accel_scale(&mut spi, &mut mpu6500_cs_pin, AccelScale::G2) + .await + .expect("Failed to set the acceleration scale"); + set_gyro_scale(&mut spi, &mut mpu6500_cs_pin, GyroScale::Gs1000) + .await + .expect("Failed to set the gyro scale"); + + loop { + let acceleration = read_acceleration(&mut spi, &mut mpu6500_cs_pin) + .await + .unwrap(); + info!( + "Acceleration: X {}, Y {}, Z {}", + acceleration.x, acceleration.y, acceleration.z + ); + + let gyro = read_gyro(&mut spi, &mut mpu6500_cs_pin).await.unwrap(); + info!("Gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z); + + Timer::after_millis(100).await; + } +} diff --git a/lab05/src/bin/ex4_v3_bus.rs b/lab05/src/bin/ex4_v3_bus.rs new file mode 100644 index 0000000..aface79 --- /dev/null +++ b/lab05/src/bin/ex4_v3_bus.rs @@ -0,0 +1,62 @@ +#![no_std] +#![no_main] + +use defmt::{info, warn}; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{Level, Output, Speed}, + spi::{self, Spi}, + time::Hertz, +}; +use embassy_time::Timer; +use lab05::mpu6500::{AccelScale, GyroScale, bus::Mpu6500}; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + let mut config = spi::Config::default(); + config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new( + peripherals.SPI1, + peripherals.PA5, + peripherals.PA7, + peripherals.PA6, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + config, + ); + + let mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + + let mut mpu6500 = Mpu6500::new(&mut spi, mpu6500_cs_pin); + + if mpu6500.is_connected().await { + mpu6500 + .set_accel_scale(AccelScale::G2) + .await + .expect("Failed to set the acceleration scale"); + mpu6500 + .set_gyro_scale(GyroScale::Gs1000) + .await + .expect("Failed to set the gyro scale"); + + loop { + let acceleration = mpu6500.read_acceleration().await.unwrap(); + info!( + "acceleration: X {}, Y {}, Z {}", + acceleration.x, acceleration.y, acceleration.z + ); + + let gyro = mpu6500.read_gyro().await.unwrap(); + info!("gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z); + Timer::after_millis(100).await; + } + } else { + warn!("MPU6500 sensor is not connected."); + } +} diff --git a/lab05/src/bin/ex4_v3_device.rs b/lab05/src/bin/ex4_v3_device.rs new file mode 100644 index 0000000..4c47e82 --- /dev/null +++ b/lab05/src/bin/ex4_v3_device.rs @@ -0,0 +1,65 @@ +#![no_std] +#![no_main] + +use defmt::{info, warn}; +use defmt_rtt as _; +use embassy_embedded_hal::shared_bus::asynch::spi::SpiDevice; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{Level, Output, Speed}, + spi::{self, Spi}, + time::Hertz, +}; +use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, mutex::Mutex}; +use embassy_time::Timer; +use lab05::mpu6500::{AccelScale, GyroScale, device::Mpu6500}; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + let mut config = spi::Config::default(); + config.frequency = Hertz(1_000_000); + + let spi = Spi::new( + peripherals.SPI1, + peripherals.PA5, + peripherals.PA7, + peripherals.PA6, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + config, + ); + let mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + let spi_mutex = Mutex::::new(spi); + let mut spi_device = SpiDevice::new(&spi_mutex, mpu6500_cs_pin); + + let mut mpu6500 = Mpu6500::new(&mut spi_device); + + if mpu6500.is_connected().await { + mpu6500 + .set_accel_scale(AccelScale::G2) + .await + .expect("Failed to set the acceleration scale"); + mpu6500 + .set_gyro_scale(GyroScale::Gs1000) + .await + .expect("Failed to set the gyro scale"); + + loop { + let acceleration = mpu6500.read_acceleration().await.unwrap(); + info!( + "Acceleration: X {}, Y {}, Z {}", + acceleration.x, acceleration.y, acceleration.z + ); + + let gyro = mpu6500.read_gyro().await.unwrap(); + info!("Gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z); + Timer::after_millis(100).await; + } + } else { + warn!("MPU6500 sensor is not connected."); + } +} diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs new file mode 100644 index 0000000..159fa46 --- /dev/null +++ b/lab05/src/bin/ex5.rs @@ -0,0 +1,123 @@ +#![no_std] +#![no_main] + +use core::{cell::RefCell, fmt::Write}; + +use defmt::{debug, info, warn}; +use defmt_rtt as _; +use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; +use embassy_executor::Spawner; +use embassy_stm32::{ + gpio::{Level, Output, Speed}, + spi::{self, Spi}, + time::Hertz, +}; +use embassy_sync::blocking_mutex::{Mutex, raw::NoopRawMutex}; +use embassy_time::{Delay, Timer}; +use embedded_graphics::{ + Drawable, + draw_target::DrawTarget, + mono_font::{MonoTextStyle, ascii::FONT_6X10}, + pixelcolor::Rgb565, + prelude::{Point, RgbColor}, + text::Text, +}; +use lab05::mpu6500::{AccelScale, GyroScale, device_blocking::Mpu6500}; +use mipidsi::{ + interface::SpiInterface, + models::ST7735s, + options::{Orientation, Rotation}, +}; +use panic_probe as _; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + let screen_rst = Output::new(peripherals.PA1, Level::Low, Speed::Low); + let screen_dc = Output::new(peripherals.PA2, Level::Low, Speed::Low); + let screen_cs = Output::new(peripherals.PA3, Level::High, Speed::Low); + + let spi = Spi::new_blocking( + peripherals.SPI1, + peripherals.PA5, + peripherals.PA7, + peripherals.PA6, + spi::Config::default(), + ); + let spi_bus_mutex: Mutex = Mutex::new(RefCell::new(spi)); + + let mut screen_spi_config = spi::Config::default(); + screen_spi_config.frequency = Hertz(3_000_000); + + let display_spi = SpiDeviceWithConfig::new(&spi_bus_mutex, screen_cs, screen_spi_config); + + let mut screen_buffer = [0; 4096]; + + let di = SpiInterface::new(display_spi, screen_dc, &mut screen_buffer); + let mut screen = mipidsi::Builder::new(ST7735s, di) + .reset_pin(screen_rst) + .orientation(Orientation::new().rotate(Rotation::Deg180)) + .init(&mut Delay) + .unwrap(); + + let mut mpu6500_spi_config = spi::Config::default(); + mpu6500_spi_config.frequency = Hertz(1_000_000); + + let mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + + let mut mpu6500_spi_device = + SpiDeviceWithConfig::new(&spi_bus_mutex, mpu6500_cs_pin, mpu6500_spi_config); + + let mut mpu6500 = Mpu6500::new(&mut mpu6500_spi_device); + + screen.clear(Rgb565::BLACK).unwrap(); + let style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + + if mpu6500.is_connected() { + mpu6500 + .set_accel_scale(AccelScale::G2) + .expect("Failed to set the acceleration scale"); + mpu6500 + .set_gyro_scale(GyroScale::Gs1000) + .expect("Failed to set the gyro scale"); + + loop { + let acceleration = mpu6500.read_acceleration().unwrap(); + + let mut buf = heapless::String::<20>::new(); + core::write!( + &mut buf, + "Acceleration: X {}, Y {}, Z {}", + acceleration.x, + acceleration.y, + acceleration.z + ) + .unwrap(); + + Text::new(&buf, Point::new(10, 20), style) + .draw(&mut screen) + .unwrap(); + + debug!( + "Acceleration: X {}, Y {}, Z {}", + acceleration.x, acceleration.y, acceleration.z + ); + + let gyro = mpu6500.read_gyro().unwrap(); + + let mut buf = heapless::String::<20>::new(); + core::write!(&mut buf, "Gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z).unwrap(); + + Text::new(&buf, Point::new(10, 50), style) + .draw(&mut screen) + .unwrap(); + + info!("Gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z); + Timer::after_millis(100).await; + } + } else { + warn!("MPU6500 sensor is not connected."); + } +} diff --git a/lab05/src/lib.rs b/lab05/src/lib.rs new file mode 100644 index 0000000..9238769 --- /dev/null +++ b/lab05/src/lib.rs @@ -0,0 +1,3 @@ +#![no_std] + +pub mod mpu6500; diff --git a/lab05/src/mpu6500/bus.rs b/lab05/src/mpu6500/bus.rs new file mode 100644 index 0000000..6191976 --- /dev/null +++ b/lab05/src/mpu6500/bus.rs @@ -0,0 +1,110 @@ +//! MPU 6500 SPI driver that uses the SPI Bus. +//! +//! This driver requires the SPI bus, meaning that the +//! while SPI bus is not available for other drivers while this +//! driver uses it. +//! +//! The driver receives the SPI bus and CS pin and is responsible +//! for actuating the CS pin to enable the SPI device. + +use embassy_stm32::gpio::Output; + +/// The `embedded_hal_async` crate exports standard Hardware Abstraction +/// Layer (HAL) traits that libraries like `embassy` implement. Drivers +/// use these traits instead of the actual implementation of the HALs. +/// +/// This allows drivers to function with any type of bus implementation +/// library that implements these traits. In our case, we use `embassy`s +/// implementation of the SPI bus, but the driver could be used with +/// any other library. +use embedded_hal_async::spi::SpiBus; + +use crate::mpu6500::{ + ACCEL_CONFIG, AccelScale, Acceleration, GYRO_CONFIG, Gyro, GyroScale, ValueRegister, WHO_AM_I, + WHO_AM_I_VALUE, +}; + +/// MPU 6500 SPI Bus driver +pub struct Mpu6500<'a, S: SpiBus> { + spi: &'a mut S, + cs: Output<'a>, +} + +/// Public API +/// +/// The function defined here are exported by the driver. +/// +/// The type `S` used by the driver is defined as *any type that +/// implements the `SpiBus` trait. +impl<'a, S: SpiBus> Mpu6500<'a, S> { + pub fn new(spi: &'a mut S, cs: Output<'a>) -> Mpu6500<'a, S> { + Mpu6500 { spi, cs } + } + + pub async fn is_connected(&mut self) -> bool { + let command = [(1 << 7) | WHO_AM_I, 0]; + let mut rx = [0u8; 2]; + + self.cs.set_low(); + let res = self.spi.transfer(&mut rx, &command).await; + self.cs.set_high(); + match res { + Ok(()) => rx[1] == WHO_AM_I_VALUE, + Err(_error) => false, + } + } + + pub async fn set_gyro_scale(&mut self, scale: GyroScale) -> Result<(), S::Error> { + self.cs.set_low(); + let command = [GYRO_CONFIG, (scale as u8) << 3]; + let mut rx = [0u8; 2]; + let res = self.spi.transfer(&mut rx, &command).await; + self.cs.set_high(); + res + } + + pub async fn set_accel_scale(&mut self, scale: AccelScale) -> Result<(), S::Error> { + self.cs.set_low(); + let command = [ACCEL_CONFIG, (scale as u8) << 3]; + let mut rx = [0u8; 2]; + let res = self.spi.transfer(&mut rx, &command).await; + self.cs.set_high(); + res + } + + pub async fn read_acceleration(&mut self) -> Result { + let rx = self.read_value(ValueRegister::AccelXOutH).await?; + Ok(Acceleration { + x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + }) + } + + pub async fn read_gyro(&mut self) -> Result { + let rx = self.read_value(ValueRegister::GyroXOutH).await?; + Ok(Gyro { + x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + }) + } +} + +/// Private API +/// +/// The function defined here are not exported by the driver and +/// are only used by the driver itself. +impl<'a, S: SpiBus> Mpu6500<'a, S> { + async fn read_value(&mut self, value_register: ValueRegister) -> Result<[u8; 6], S::Error> { + self.cs.set_low(); + let command = [(1 << 7) | value_register as u8, 0, 0, 0, 0, 0, 0]; + let mut rx = [0u8; 7]; + let res = self.spi.transfer(&mut rx, &command).await; + self.cs.set_high(); + match res { + Ok(()) => Ok((&command[1..]).try_into().unwrap()), + Err(error) => Err(error), + } + } +} diff --git a/lab05/src/mpu6500/device.rs b/lab05/src/mpu6500/device.rs new file mode 100644 index 0000000..aea811b --- /dev/null +++ b/lab05/src/mpu6500/device.rs @@ -0,0 +1,71 @@ +use embedded_hal_async::spi::SpiDevice; + +use crate::mpu6500::{ + ACCEL_CONFIG, AccelScale, Acceleration, GYRO_CONFIG, Gyro, GyroScale, ValueRegister, WHO_AM_I, + WHO_AM_I_VALUE, +}; + +pub struct Mpu6500<'a, S: SpiDevice> { + spi: &'a mut S, +} + +/// Public API +impl<'a, S: SpiDevice> Mpu6500<'a, S> { + pub fn new(spi: &'a mut S) -> Mpu6500<'a, S> { + Mpu6500 { spi } + } + + pub async fn is_connected(&mut self) -> bool { + let command = [(1 << 7) | WHO_AM_I, 0]; + let mut rx = [0u8; 2]; + + let res = self.spi.transfer(&mut rx, &command).await; + match res { + Ok(()) => rx[1] == WHO_AM_I_VALUE, + Err(_error) => false, + } + } + + pub async fn set_gyro_scale(&mut self, scale: GyroScale) -> Result<(), S::Error> { + let command = [GYRO_CONFIG, (scale as u8) << 3]; + let mut rx = [0u8; 2]; + self.spi.transfer(&mut rx, &command).await + } + + pub async fn set_accel_scale(&mut self, scale: AccelScale) -> Result<(), S::Error> { + let command = [ACCEL_CONFIG, (scale as u8) << 3]; + let mut rx = [0u8; 2]; + self.spi.transfer(&mut rx, &command).await + } + + pub async fn read_acceleration(&mut self) -> Result { + let rx = self.read_value(ValueRegister::AccelXOutH).await?; + Ok(Acceleration { + x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + }) + } + + pub async fn read_gyro(&mut self) -> Result { + let rx = self.read_value(ValueRegister::GyroXOutH).await?; + Ok(Gyro { + x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + }) + } +} + +/// Private API +impl<'a, S: SpiDevice> Mpu6500<'a, S> { + async fn read_value(&mut self, value_register: ValueRegister) -> Result<[u8; 6], S::Error> { + let command = [(1 << 7) | value_register as u8, 0, 0, 0, 0, 0, 0]; + let mut rx = [0u8; 7]; + let res = self.spi.transfer(&mut rx, &command).await; + match res { + Ok(()) => Ok((&command[1..]).try_into().unwrap()), + Err(error) => Err(error), + } + } +} diff --git a/lab05/src/mpu6500/device_blocking.rs b/lab05/src/mpu6500/device_blocking.rs new file mode 100644 index 0000000..b8aed1c --- /dev/null +++ b/lab05/src/mpu6500/device_blocking.rs @@ -0,0 +1,71 @@ +use embedded_hal::spi::SpiDevice; + +use crate::mpu6500::{ + ACCEL_CONFIG, AccelScale, Acceleration, GYRO_CONFIG, Gyro, GyroScale, ValueRegister, WHO_AM_I, + WHO_AM_I_VALUE, +}; + +pub struct Mpu6500<'a, S: SpiDevice> { + spi: &'a mut S, +} + +/// Public API +impl<'a, S: SpiDevice> Mpu6500<'a, S> { + pub fn new(spi: &'a mut S) -> Mpu6500<'a, S> { + Mpu6500 { spi } + } + + pub fn is_connected(&mut self) -> bool { + let command = [(1 << 7) | WHO_AM_I, 0]; + let mut rx = [0u8; 2]; + + let res = self.spi.transfer(&mut rx, &command); + match res { + Ok(()) => rx[1] == WHO_AM_I_VALUE, + Err(_error) => false, + } + } + + pub fn set_gyro_scale(&mut self, scale: GyroScale) -> Result<(), S::Error> { + let command = [GYRO_CONFIG, (scale as u8) << 3]; + let mut rx = [0u8; 2]; + self.spi.transfer(&mut rx, &command) + } + + pub fn set_accel_scale(&mut self, scale: AccelScale) -> Result<(), S::Error> { + let command = [ACCEL_CONFIG, (scale as u8) << 3]; + let mut rx = [0u8; 2]; + self.spi.transfer(&mut rx, &command) + } + + pub fn read_acceleration(&mut self) -> Result { + let rx = self.read_value(ValueRegister::AccelXOutH)?; + Ok(Acceleration { + x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + }) + } + + pub fn read_gyro(&mut self) -> Result { + let rx = self.read_value(ValueRegister::GyroXOutH)?; + Ok(Gyro { + x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + }) + } +} + +/// Private API +impl<'a, S: SpiDevice> Mpu6500<'a, S> { + fn read_value(&mut self, value_register: ValueRegister) -> Result<[u8; 6], S::Error> { + let command = [(1 << 7) | value_register as u8, 0, 0, 0, 0, 0, 0]; + let mut rx = [0u8; 7]; + let res = self.spi.transfer(&mut rx, &command); + match res { + Ok(()) => Ok((&command[1..]).try_into().unwrap()), + Err(error) => Err(error), + } + } +} diff --git a/lab05/src/mpu6500/mod.rs b/lab05/src/mpu6500/mod.rs new file mode 100644 index 0000000..0e68c7b --- /dev/null +++ b/lab05/src/mpu6500/mod.rs @@ -0,0 +1,68 @@ +pub mod bus; +pub mod device; +pub mod device_blocking; + +/// WHO_AM_I Register Address +const WHO_AM_I: u8 = 0x75; +/// GYRO_CONFIG Register Address +const GYRO_CONFIG: u8 = 0x1b; +/// ACCEL_CONFIG Register Address +const ACCEL_CONFIG: u8 = 0x1c; + +/// WHO_AM_I Register Value for the MPU6500 sensor +const WHO_AM_I_VALUE: u8 = 0x70; + +/// The register address that the [`Mpu6500::read_value`] +/// function should read +/// +/// This is represented as a `u8` so that it can be cast +/// to a `u8` using the `as` keyword +/// +/// [`Copy`] and [`Clone`] are derived so that the value +/// can be copied when sent as a parameter to a function. +#[repr(u8)] +#[derive(Copy, Clone)] +pub enum ValueRegister { + AccelXOutH = 0x3b, + GyroXOutH = 0x43, +} + +/// The possible values for the `GYRO_FS_SEL` field of +/// the `GYRO_CONFIG` register. +/// +/// This is represented as a `u8` so that it can be cast +/// to a `u8` using the `as` keyword +#[repr(u8)] +pub enum GyroScale { + Gs250 = 0b00, + Gs500 = 0b01, + Gs1000 = 0b10, + Gs2000 = 0b11, +} + +/// The possible values for the `ACCEL_FS_SEL` field of +/// the `ACCEL_CONFIG` register. +/// +/// This is represented as a `u8` so that it can be cast +/// to a `u8` using the `as` keyword +#[repr(u8)] +pub enum AccelScale { + G2 = 0b00, + G4 = 0b01, + G8 = 0b10, + G16 = 0b11, +} + +/// Stores the acceleration on all the three axes. +pub struct Acceleration { + pub x: i16, + pub y: i16, + pub z: i16, +} + +/// Stores the gyro values on all the three axes. +pub struct Gyro { + pub x: i16, + pub y: i16, + pub z: i16, +} From 88f4bdc12515515ac1548367dad64391c9dcd154 Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Wed, 19 Nov 2025 01:28:18 +0200 Subject: [PATCH 02/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/bin/ex3.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/bin/ex3.rs b/lab05/src/bin/ex3.rs index c5123cd..5474fa7 100644 --- a/lab05/src/bin/ex3.rs +++ b/lab05/src/bin/ex3.rs @@ -37,7 +37,7 @@ const WHO_AM_I_VALUE: u8 = 0x70; /// The acceleration scale value for ±2g const ACCEL_SCALE_2G: u8 = 0b00; -/// The gyre scale value for ±1000 deg / s +/// The gyro scale value for ±1000 deg / s const GYRO_SCALE_1000: u8 = 0b10; #[embassy_executor::main] From a367eafb3633db2b6f99c64f0aaef3ed6840a973 Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Wed, 19 Nov 2025 16:09:01 +0200 Subject: [PATCH 03/19] fixed solution display --- lab05/src/bin/ex4_v1.rs | 12 +++--- lab05/src/bin/ex4_v2.rs | 12 +++--- lab05/src/bin/ex5.rs | 59 ++++++++++++++++++++-------- lab05/src/mpu6500/bus.rs | 38 +++++++++--------- lab05/src/mpu6500/device.rs | 34 ++++++++++++---- lab05/src/mpu6500/device_blocking.rs | 34 ++++++++++++---- lab05/src/mpu6500/mod.rs | 2 +- 7 files changed, 130 insertions(+), 61 deletions(-) diff --git a/lab05/src/bin/ex4_v1.rs b/lab05/src/bin/ex4_v1.rs index 4b9a36d..7f1b8c8 100644 --- a/lab05/src/bin/ex4_v1.rs +++ b/lab05/src/bin/ex4_v1.rs @@ -70,9 +70,9 @@ async fn main(_spawner: Spawner) { mpu6500_cs_pin.set_high(); info!( "Acceleration: X {}, Y {}, Z {}", - i16::from_le_bytes([rx[1], rx[2]]), - i16::from_le_bytes([rx[3], rx[4]]), - i16::from_le_bytes([rx[5], rx[6]]) + i16::from_be_bytes([rx[1], rx[2]]), + i16::from_be_bytes([rx[3], rx[4]]), + i16::from_be_bytes([rx[5], rx[6]]) ); mpu6500_cs_pin.set_low(); @@ -82,9 +82,9 @@ async fn main(_spawner: Spawner) { mpu6500_cs_pin.set_high(); info!( "Gyro: X {}, Y {}, Z {}", - i16::from_le_bytes([rx[1], rx[2]]), - i16::from_le_bytes([rx[3], rx[4]]), - i16::from_le_bytes([rx[5], rx[6]]) + i16::from_be_bytes([rx[1], rx[2]]), + i16::from_be_bytes([rx[3], rx[4]]), + i16::from_be_bytes([rx[5], rx[6]]) ); Timer::after_millis(100).await; diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs index 6f12cf6..90cfe68 100644 --- a/lab05/src/bin/ex4_v2.rs +++ b/lab05/src/bin/ex4_v2.rs @@ -89,9 +89,9 @@ async fn read_acceleration( cs.set_high(); match res { Ok(()) => Ok(Acceleration { - x: i16::from_le_bytes([rx[1], rx[2]]), - y: i16::from_le_bytes([rx[3], rx[4]]), - z: i16::from_le_bytes([rx[5], rx[6]]), + x: i16::from_be_bytes([rx[1], rx[2]]), + y: i16::from_be_bytes([rx[3], rx[4]]), + z: i16::from_be_bytes([rx[5], rx[6]]), }), Err(error) => Err(error), } @@ -105,9 +105,9 @@ async fn read_gyro(spi: &mut Spi<'_, Async>, cs: &mut Output<'_>) -> Result Ok(Gyro { - x: i16::from_le_bytes([rx[1], rx[2]]), - y: i16::from_le_bytes([rx[3], rx[4]]), - z: i16::from_le_bytes([rx[5], rx[6]]), + x: i16::from_be_bytes([rx[1], rx[2]]), + y: i16::from_be_bytes([rx[3], rx[4]]), + z: i16::from_be_bytes([rx[5], rx[6]]), }), Err(error) => Err(error), } diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs index 159fa46..0eef002 100644 --- a/lab05/src/bin/ex5.rs +++ b/lab05/src/bin/ex5.rs @@ -8,7 +8,9 @@ use defmt_rtt as _; use embassy_embedded_hal::shared_bus::blocking::spi::SpiDeviceWithConfig; use embassy_executor::Spawner; use embassy_stm32::{ + Config, gpio::{Level, Output, Speed}, + rcc::{Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk, VoltageScale, mux}, spi::{self, Spi}, time::Hertz, }; @@ -20,7 +22,7 @@ use embedded_graphics::{ mono_font::{MonoTextStyle, ascii::FONT_6X10}, pixelcolor::Rgb565, prelude::{Point, RgbColor}, - text::Text, + text::{Text, renderer::CharacterStyle}, }; use lab05::mpu6500::{AccelScale, GyroScale, device_blocking::Mpu6500}; use mipidsi::{ @@ -32,12 +34,29 @@ use panic_probe as _; #[embassy_executor::main] async fn main(_spawner: Spawner) { - let peripherals = embassy_stm32::init(Default::default()); + let mut config = Config::default(); + config.rcc.hsi = true; + config.rcc.pll1 = Some(Pll { + source: PllSource::HSI, // 16 MHz + prediv: PllPreDiv::DIV1, + mul: PllMul::MUL10, + divp: None, + divq: None, + divr: Some(PllDiv::DIV1), // 160 MHz + }); + config.rcc.sys = Sysclk::PLL1_R; + config.rcc.voltage_range = VoltageScale::RANGE1; + config.rcc.mux.iclksel = mux::Iclksel::HSI48; // USB uses ICLK + + let peripherals = embassy_stm32::init(config); info!("Device started"); - let screen_rst = Output::new(peripherals.PA1, Level::Low, Speed::Low); - let screen_dc = Output::new(peripherals.PA2, Level::Low, Speed::Low); - let screen_cs = Output::new(peripherals.PA3, Level::High, Speed::Low); + // screen reset is D2 (PC8) + let screen_rst = Output::new(peripherals.PC8, Level::Low, Speed::Low); + // screen dc is D3 (PB3) + let screen_dc = Output::new(peripherals.PB3, Level::Low, Speed::Low); + // screen cs is D4(PB5) + let screen_cs = Output::new(peripherals.PB5, Level::High, Speed::Low); let spi = Spi::new_blocking( peripherals.SPI1, @@ -53,7 +72,7 @@ async fn main(_spawner: Spawner) { let display_spi = SpiDeviceWithConfig::new(&spi_bus_mutex, screen_cs, screen_spi_config); - let mut screen_buffer = [0; 4096]; + let mut screen_buffer = [0; 10 * 4096]; let di = SpiInterface::new(display_spi, screen_dc, &mut screen_buffer); let mut screen = mipidsi::Builder::new(ST7735s, di) @@ -73,7 +92,9 @@ async fn main(_spawner: Spawner) { let mut mpu6500 = Mpu6500::new(&mut mpu6500_spi_device); screen.clear(Rgb565::BLACK).unwrap(); - let style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + let mut style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + style.set_background_color(Some(Rgb565::BLACK)); + // let style_black = MonoTextStyle::new(&FONT_6X10, Rgb565::BLACK); if mpu6500.is_connected() { mpu6500 @@ -85,18 +106,19 @@ async fn main(_spawner: Spawner) { loop { let acceleration = mpu6500.read_acceleration().unwrap(); + let gyro = mpu6500.read_gyro().unwrap(); - let mut buf = heapless::String::<20>::new(); + let mut acceleration_buf = heapless::String::<100>::new(); core::write!( - &mut buf, - "Acceleration: X {}, Y {}, Z {}", + &mut acceleration_buf, + "Acceleration:\n X {} \n Y {} \n Z {} ", acceleration.x, acceleration.y, acceleration.z ) .unwrap(); - Text::new(&buf, Point::new(10, 20), style) + Text::new(&acceleration_buf, Point::new(0, 20), style) .draw(&mut screen) .unwrap(); @@ -105,12 +127,17 @@ async fn main(_spawner: Spawner) { acceleration.x, acceleration.y, acceleration.z ); - let gyro = mpu6500.read_gyro().unwrap(); - - let mut buf = heapless::String::<20>::new(); - core::write!(&mut buf, "Gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z).unwrap(); + let mut gyro_buf = heapless::String::<100>::new(); + core::write!( + &mut gyro_buf, + "Gyro:\n X {} \n Y {} \n Z {} ", + gyro.x, + gyro.y, + gyro.z + ) + .unwrap(); - Text::new(&buf, Point::new(10, 50), style) + Text::new(&gyro_buf, Point::new(0, 120), style) .draw(&mut screen) .unwrap(); diff --git a/lab05/src/mpu6500/bus.rs b/lab05/src/mpu6500/bus.rs index 6191976..783bd5e 100644 --- a/lab05/src/mpu6500/bus.rs +++ b/lab05/src/mpu6500/bus.rs @@ -1,22 +1,24 @@ -//! MPU 6500 SPI driver that uses the SPI Bus. +//! MPU 6500 SPI async driver that uses the SPI Bus. //! //! This driver requires the SPI bus, meaning that the -//! while SPI bus is not available for other drivers while this -//! driver uses it. +//! SPI bus is not available for other drivers while this +//! driver is available and has not ben dropped. The SPI +//! bus is not available to other drivers even if this +//! driver is not transferring anything. //! //! The driver receives the SPI bus and CS pin and is responsible //! for actuating the CS pin to enable the SPI device. use embassy_stm32::gpio::Output; -/// The `embedded_hal_async` crate exports standard Hardware Abstraction -/// Layer (HAL) traits that libraries like `embassy` implement. Drivers -/// use these traits instead of the actual implementation of the HALs. -/// -/// This allows drivers to function with any type of bus implementation -/// library that implements these traits. In our case, we use `embassy`s -/// implementation of the SPI bus, but the driver could be used with -/// any other library. +// The `embedded_hal_async` crate exports standard async Hardware Abstraction +// Layer (HAL) traits that libraries like `embassy` implement. Drivers +// use these traits instead of the actual implementation of the HALs. + +// This allows drivers to function with any type of bus implementation +// library that implements these traits. In our case, we use `embassy`s +// implementation of the SPI bus, but the driver could be used with +// any other library. use embedded_hal_async::spi::SpiBus; use crate::mpu6500::{ @@ -75,18 +77,18 @@ impl<'a, S: SpiBus> Mpu6500<'a, S> { pub async fn read_acceleration(&mut self) -> Result { let rx = self.read_value(ValueRegister::AccelXOutH).await?; Ok(Acceleration { - x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), }) } pub async fn read_gyro(&mut self) -> Result { let rx = self.read_value(ValueRegister::GyroXOutH).await?; Ok(Gyro { - x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), }) } } @@ -103,7 +105,7 @@ impl<'a, S: SpiBus> Mpu6500<'a, S> { let res = self.spi.transfer(&mut rx, &command).await; self.cs.set_high(); match res { - Ok(()) => Ok((&command[1..]).try_into().unwrap()), + Ok(()) => Ok((&rx[1..]).try_into().unwrap()), Err(error) => Err(error), } } diff --git a/lab05/src/mpu6500/device.rs b/lab05/src/mpu6500/device.rs index aea811b..09f75c5 100644 --- a/lab05/src/mpu6500/device.rs +++ b/lab05/src/mpu6500/device.rs @@ -1,3 +1,23 @@ +//! MPU 6500 SPI async driver that uses an SPI Device of +//! the SPI Bus. +//! +//! This driver requires an SPI device, meaning that the +//! SPI bus is available for other drivers while this +//! driver is not transferring. +//! +//! The driver receives the SPI device that includes the CS +//! pi and is not responsible for actuating the CS pin +//! to enable the SPI device. The [`SpiDevice::transfer`] +//! takes care of activation the CS pin of the device. + +// The `embedded_hal_async` crate exports standard async Hardware Abstraction +// Layer (HAL) traits that libraries like `embassy` implement. Drivers +// use these traits instead of the actual implementation of the HALs. + +// This allows drivers to function with any type of bus implementation +// library that implements these traits. In our case, we use `embassy`s +// implementation of the SPI bus, but the driver could be used with +// any other library. use embedded_hal_async::spi::SpiDevice; use crate::mpu6500::{ @@ -41,18 +61,18 @@ impl<'a, S: SpiDevice> Mpu6500<'a, S> { pub async fn read_acceleration(&mut self) -> Result { let rx = self.read_value(ValueRegister::AccelXOutH).await?; Ok(Acceleration { - x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), }) } pub async fn read_gyro(&mut self) -> Result { let rx = self.read_value(ValueRegister::GyroXOutH).await?; Ok(Gyro { - x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), }) } } @@ -64,7 +84,7 @@ impl<'a, S: SpiDevice> Mpu6500<'a, S> { let mut rx = [0u8; 7]; let res = self.spi.transfer(&mut rx, &command).await; match res { - Ok(()) => Ok((&command[1..]).try_into().unwrap()), + Ok(()) => Ok((&rx[1..]).try_into().unwrap()), Err(error) => Err(error), } } diff --git a/lab05/src/mpu6500/device_blocking.rs b/lab05/src/mpu6500/device_blocking.rs index b8aed1c..97ab322 100644 --- a/lab05/src/mpu6500/device_blocking.rs +++ b/lab05/src/mpu6500/device_blocking.rs @@ -1,3 +1,23 @@ +//! MPU 6500 SPI blocking driver that uses an SPI Device of +//! the SPI Bus. +//! +//! This driver requires an SPI device, meaning that the +//! SPI bus is available for other drivers while this +//! driver is not transferring. +//! +//! The driver receives the SPI device that includes the CS +//! pi and is not responsible for actuating the CS pin +//! to enable the SPI device. The [`SpiDevice::transfer`] +//! takes care of activation the CS pin of the device. + +// The `embedded_hal` crate exports standard Hardware Abstraction +// Layer (HAL) traits that libraries like `embassy` implement. Drivers +// use these traits instead of the actual implementation of the HALs. + +// This allows drivers to function with any type of bus implementation +// library that implements these traits. In our case, we use `embassy`s +// implementation of the SPI bus, but the driver could be used with +// any other library. use embedded_hal::spi::SpiDevice; use crate::mpu6500::{ @@ -41,18 +61,18 @@ impl<'a, S: SpiDevice> Mpu6500<'a, S> { pub fn read_acceleration(&mut self) -> Result { let rx = self.read_value(ValueRegister::AccelXOutH)?; Ok(Acceleration { - x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), }) } pub fn read_gyro(&mut self) -> Result { let rx = self.read_value(ValueRegister::GyroXOutH)?; Ok(Gyro { - x: i16::from_le_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_le_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_le_bytes((&rx[4..6]).try_into().unwrap()), + x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), + y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), + z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), }) } } @@ -64,7 +84,7 @@ impl<'a, S: SpiDevice> Mpu6500<'a, S> { let mut rx = [0u8; 7]; let res = self.spi.transfer(&mut rx, &command); match res { - Ok(()) => Ok((&command[1..]).try_into().unwrap()), + Ok(()) => Ok((&rx[1..]).try_into().unwrap()), Err(error) => Err(error), } } diff --git a/lab05/src/mpu6500/mod.rs b/lab05/src/mpu6500/mod.rs index 0e68c7b..4610868 100644 --- a/lab05/src/mpu6500/mod.rs +++ b/lab05/src/mpu6500/mod.rs @@ -12,7 +12,7 @@ const ACCEL_CONFIG: u8 = 0x1c; /// WHO_AM_I Register Value for the MPU6500 sensor const WHO_AM_I_VALUE: u8 = 0x70; -/// The register address that the [`Mpu6500::read_value`] +/// The register address that the [`bus::Mpu6500::read_value`] /// function should read /// /// This is represented as a `u8` so that it can be cast From a7083608fd64f52b5e17ce10c47cfd02b55b273a Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Wed, 19 Nov 2025 16:09:47 +0200 Subject: [PATCH 04/19] delete unused line --- lab05/src/bin/ex5.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs index 0eef002..a73540f 100644 --- a/lab05/src/bin/ex5.rs +++ b/lab05/src/bin/ex5.rs @@ -94,7 +94,6 @@ async fn main(_spawner: Spawner) { screen.clear(Rgb565::BLACK).unwrap(); let mut style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); style.set_background_color(Some(Rgb565::BLACK)); - // let style_black = MonoTextStyle::new(&FONT_6X10, Rgb565::BLACK); if mpu6500.is_connected() { mpu6500 From 9e1f79b8953902ff302dc30a54849de4d597bfd0 Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Thu, 20 Nov 2025 16:46:36 +0200 Subject: [PATCH 05/19] add comments --- codebook.toml | 1 + lab05/src/bin/ex2.rs | 2 +- lab05/src/bin/ex3.rs | 14 +- lab05/src/bin/ex4_v1.rs | 251 +++++++++++++++++++++++++- lab05/src/bin/ex4_v2.rs | 314 +++++++++++++++++++++++++++++---- lab05/src/bin/ex4_v3_bus.rs | 32 +++- lab05/src/bin/ex4_v3_device.rs | 39 +++- lab05/src/bin/ex5.rs | 42 ++++- lab05/src/mpu6500/mod.rs | 8 + 9 files changed, 652 insertions(+), 51 deletions(-) diff --git a/codebook.toml b/codebook.toml index 3bcdb6f..49f2001 100644 --- a/codebook.toml +++ b/codebook.toml @@ -12,6 +12,7 @@ words = [ "ish", "linker's", "mcu", + "microcontroller", "mille", "moder", "mosi", diff --git a/lab05/src/bin/ex2.rs b/lab05/src/bin/ex2.rs index 422f12b..553d8cb 100644 --- a/lab05/src/bin/ex2.rs +++ b/lab05/src/bin/ex2.rs @@ -48,7 +48,7 @@ async fn main(_spawner: Spawner) { // This is the buffer that is sent to the sensor. The format is: // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | - // - R/W is the the first bit: + // - R/W is the the most significant bit (first bit): // - 1 - read the register's value from the sensor // - 0 - write a value to the sensor's register // diff --git a/lab05/src/bin/ex3.rs b/lab05/src/bin/ex3.rs index 5474fa7..b2f451d 100644 --- a/lab05/src/bin/ex3.rs +++ b/lab05/src/bin/ex3.rs @@ -72,7 +72,7 @@ async fn main(_spawner: Spawner) { // This is the buffer that is sent to the sensor. The format is: // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | - // - R/W is the the first bit: + // - R/W is the the most significant bit (first bit): // - 1 - read the register's value from the sensor // - 0 - write a value to the sensor's register // @@ -117,7 +117,7 @@ async fn main(_spawner: Spawner) { // This is the buffer that is sent to the sensor. The format is: // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | - // - R/W is the the first bit: + // - R/W is the the most significant bit (first bit): // - 1 - read the register's value from the sensor // - 0 - write a value to the sensor's register // @@ -139,12 +139,14 @@ async fn main(_spawner: Spawner) { // all that we have to do is to shift the // ACCEL_SCALE_2G value 3 positions to the left. let command = [!(1 << 7) & ACCEL_CONFIG, ACCEL_SCALE_2G << 3]; + // Even though we do not read any values form the sensor, we have to // supply an rx buffer with the same length as the command buffer. // The sensor will send us random data, but we use DMA and // DMA will want to transfer some data to us, regardless if it // is useful data or not. let mut rx = [0u8; 2]; + // Start the SPI transmission by setting the CS line LOW. mpu6500_cs_pin.set_low(); // Transfer the data: @@ -156,6 +158,7 @@ async fn main(_spawner: Spawner) { // but works for our example. If this happens in production, the firmware // should gracefully fail. spi.transfer(&mut rx, &command).await.unwrap(); + // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); @@ -163,7 +166,7 @@ async fn main(_spawner: Spawner) { // This is the buffer that is sent to the sensor. The format is: // | R/W REGISTER_ADDRESS | bytes that we want to write | - // - R/W is the the first bit: + // - R/W is the the most significant bit (first bit): // - 1 - read the register's value from the sensor // - 0 - write a value to the sensor's register // @@ -210,7 +213,7 @@ async fn main(_spawner: Spawner) { // This is the buffer that is sent to the sensor. The format is: // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | - // - R/W is the the first bit: + // - R/W is the the most significant bit (first bit): // - 1 - read the register's value from the sensor // - 0 - write a value to the sensor's register // @@ -243,6 +246,7 @@ async fn main(_spawner: Spawner) { // Start the SPI transmission by setting the CS line LOW. mpu6500_cs_pin.set_low(); + // Transfer the data: // - send the command buffer (command followed by six zero bytes) // - receive in the rx buffer (random byte followed by the values of @@ -263,7 +267,7 @@ async fn main(_spawner: Spawner) { // This is the buffer that is sent to the sensor. The format is: // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | - // - R/W is the the first bit: + // - R/W is the the most significant bit (first bit): // - 1 - read the register's value from the sensor // - 0 - write a value to the sensor's register // diff --git a/lab05/src/bin/ex4_v1.rs b/lab05/src/bin/ex4_v1.rs index 7f1b8c8..43e9ee5 100644 --- a/lab05/src/bin/ex4_v1.rs +++ b/lab05/src/bin/ex4_v1.rs @@ -12,15 +12,32 @@ use embassy_stm32::{ use embassy_time::Timer; use panic_probe as _; +/// WHO_AM_I Register Address const WHO_AM_I: u8 = 0x75; +/// GYRO_CONFIG Register Address const GYRO_CONFIG: u8 = 0x1b; +/// ACCEL_CONFIG Register Address const ACCEL_CONFIG: u8 = 0x1c; +/// ACCEL_XOUT_H Register Address +/// +/// This register stores the high value (most significant 8 bits +/// of the acceleration). const ACCEL_XOUT_H: u8 = 0x3b; + +/// GYRO_XOUT_H Register Address +/// +/// This register stores the high value (most significant 8 bits +/// of the gyro). const GYRO_XOUT_H: u8 = 0x43; +/// WHO_AM_I Register Value for the MPU6500 sensor const WHO_AM_I_VALUE: u8 = 0x70; + +/// The acceleration scale value for ±2g const ACCEL_SCALE_2G: u8 = 0b00; + +/// The gyro scale value for ±1000 deg / s const GYRO_SCALE_1000: u8 = 0b10; #[embassy_executor::main] @@ -28,9 +45,18 @@ async fn main(_spawner: Spawner) { let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); + // Create the SPI bus configuration let mut config = spi::Config::default(); + // Set the SPI frequency to 1 MHz config.frequency = Hertz(1_000_000); + // SPI1 is exposed by the Arduino header using pins: + // - MISO - D12 (PA6) + // - MOSI - D11 (PA7) + // - CLK - D13 (PA5) + // + // We use the asynchronous API an we need two free + // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let mut spi = Spi::new( peripherals.SPI1, peripherals.PA5, @@ -41,33 +67,199 @@ async fn main(_spawner: Spawner) { config, ); + // We use the D7 (PA8) pin as CS let mut mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the WHO_AM_I register address 0x0111_0101 + // and obtain 0b1111_0101. + // + // We add another 0 that will be ignored by the sensor, but + // is required as the sensor will send us the WHO_AM_I register's + // value while we transmit this 0. let command = [(1 << 7) | WHO_AM_I, 0]; + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // The second byte is the value of the WHO_AM_I register. let mut rx = [0u8; 2]; + + // Verify that the MPU 6500 sensor is connected. + + // Start the SPI transmission by setting the CS line LOW. mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by one zero byte) + // - receive in the rx buffer (random byte followed by the value of the WHO_AM_I register) + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. spi.transfer(&mut rx, &command).await.unwrap(); + // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); + // Panic if the WHO_AM_I register value if wrong + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. assert_eq!(rx[1], WHO_AM_I_VALUE); - mpu6500_cs_pin.set_low(); - let command = [ACCEL_CONFIG, ACCEL_SCALE_2G << 3]; + // Set the acceleration scale configuration + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the ACCEL_CONFIG register address 0x0001_1011 + // and obtain 0x0001_1011. + // + // This was actually not required as the ACCEL_CONFIG's + // most significant bit was already 0. + // + // The second position of the command buffer is the value that + // we want to write to the ACCEL_CONFIG register. In our case + // we are only interested in the ACCEL_FS_SEL field which + // is 2 bits long starting at bit position 3. + // + // As all the other fields of the ACCEL_CONFIG register are 0, + // all that we have to do is to shift the + // ACCEL_SCALE_2G value 3 positions to the left. + let command = [!(1 << 7) & ACCEL_CONFIG, ACCEL_SCALE_2G << 3]; + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. let mut rx = [0u8; 2]; + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. spi.transfer(&mut rx, &command).await.unwrap(); + // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); - mpu6500_cs_pin.set_low(); + // Set the gyro scale configuration + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | bytes that we want to write | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the GYRO_CONFIG register address 0x0001_1100 + // and obtain 0x0001_1100. + // + // This was actually not required as the GYRO_CONFIG's + // most significant bit was already 0. + // + // The second position of the command buffer is the value that + // we want to write to the GYRO_CONFIG register. In our case + // we are only interested in the GYRO_FS_SEL field which + // is 2 bits long starting at bit position 3. + // + // As all the other fields of the GYRO_CONFIG register are 0, + // all that we have to do is to shift the + // GYRO_SCALE_1000 value 3 positions to the left. let command = [GYRO_CONFIG, GYRO_SCALE_1000 << 3]; + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. let mut rx = [0u8; 2]; + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. spi.transfer(&mut rx, &command).await.unwrap(); + // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); loop { - mpu6500_cs_pin.set_low(); + // Read the acceleration + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the ACCEL_XOUT_H register address 0x0011_1011 + // and obtain 0b1011_1011. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the ACCEL_XOUT_H register's + // value followed by the values of the next 5 registers: + // - ACCEL_XOUT_L at 0x3c + // - ACCEL_YOUT_H at 0x3d + // - ACCEL_YOUT_L at 0x3e + // - ACCEL_ZOUT_H at 0x3f + // - ACCEL_ZOUT_L at 0x40 + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. let command = [(1 << 7) | ACCEL_XOUT_H, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the ACCEL_XOUT_H, ACCEL_YOUT_H, ACCEL_YOUT_L, + // ACCEL_ZOUT_H and ACCEL_ZOUT_L registers. let mut rx = [0u8; 7]; + + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); // Transfer the data: + + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the ACCEL_XOUT_H, ACCEL_YOUT_H, ACCEL_YOUT_L, + // ACCEL_ZOUT_H and ACCEL_ZOUT_L registers). + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. spi.transfer(&mut rx, &command).await.unwrap(); + + // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); + + // Convert the raw values into `i16` m/s^2 info!( "Acceleration: X {}, Y {}, Z {}", i16::from_be_bytes([rx[1], rx[2]]), @@ -75,11 +267,60 @@ async fn main(_spawner: Spawner) { i16::from_be_bytes([rx[5], rx[6]]) ); - mpu6500_cs_pin.set_low(); + // Read the gyro + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the GYRO_XOUT_H register address 0x0100_0011 + // and obtain 0b1100_0011. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the GYRO_XOUT_H register's + // value followed by the values of the next 5 registers: + // - GYROL_XOUT_L at 0x44 + // - GYROL_YOUT_H at 0x45 + // - GYROL_YOUT_L at 0x46 + // - GYROL_ZOUT_H at 0x47 + // - GYROL_ZOUT_L at 0x48 + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. let command = [(1 << 7) | GYRO_XOUT_H, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the GYRO_XOUT_H, GYRO_YOUT_H, GYRO_YOUT_L, + // GYRO_ZOUT_H and GYRO_ZOUT_L registers. let mut rx = [0u8; 7]; + + // Start the SPI transmission by setting the CS line LOW. + mpu6500_cs_pin.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the GYRO_XOUT_H, GYRO_YOUT_H, GYRO_YOUT_L, + // GYRO_ZOUT_H and GYRO_ZOUT_L registers). + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. spi.transfer(&mut rx, &command).await.unwrap(); + + // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); + + // Convert the raw values into `i16` deg/s info!( "Gyro: X {}, Y {}, Z {}", i16::from_be_bytes([rx[1], rx[2]]), diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs index 90cfe68..e68ac3d 100644 --- a/lab05/src/bin/ex4_v2.rs +++ b/lab05/src/bin/ex4_v2.rs @@ -13,55 +13,90 @@ use embassy_stm32::{ use embassy_time::Timer; use panic_probe as _; +// Import the `AccelScale`, `Acceleration`, `Gyro` and `GyroScale` from a common module +// as they are used by several source code files. +use lab05::mpu6500::{AccelScale, Acceleration, Gyro, GyroScale}; + +/// WHO_AM_I Register Address const WHO_AM_I: u8 = 0x75; +/// GYRO_CONFIG Register Address const GYRO_CONFIG: u8 = 0x1b; +/// ACCEL_CONFIG Register Address const ACCEL_CONFIG: u8 = 0x1c; +/// ACCEL_XOUT_H Register Address +/// +/// This register stores the high value (most significant 8 bits +/// of the acceleration). const ACCEL_XOUT_H: u8 = 0x3b; + +/// GYRO_XOUT_H Register Address +/// +/// This register stores the high value (most significant 8 bits +/// of the gyro). const GYRO_XOUT_H: u8 = 0x43; +/// WHO_AM_I Register Value for the MPU6500 sensor const WHO_AM_I_VALUE: u8 = 0x70; -#[repr(u8)] -#[allow(dead_code)] -enum GyroScale { - Gs250 = 0b00, - Gs500 = 0b01, - Gs1000 = 0b10, - Gs2000 = 0b11, -} - -#[repr(u8)] -#[allow(dead_code)] -enum AccelScale { - G2 = 0b00, - G4 = 0b01, - G8 = 0b10, - G16 = 0b11, -} - -struct Acceleration { - x: i16, - y: i16, - z: i16, -} - -struct Gyro { - x: i16, - y: i16, - z: i16, -} - +/// Set the gyro scale +/// +/// The function receives: +/// - a reference to the SPI bus +/// - a reference tp the CS pin +/// - the gyro scale value async fn set_gyro_scale( spi: &mut Spi<'_, Async>, cs: &mut Output<'_>, scale: GyroScale, ) -> Result<(), Error> { - cs.set_low(); + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | bytes that we want to write | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the GYRO_CONFIG register address 0x0001_1100 + // and obtain 0x0001_1100. + // + // This was actually not required as the GYRO_CONFIG's + // most significant bit was already 0. + // + // The second position of the command buffer is the value that + // we want to write to the GYRO_CONFIG register. In our case + // we are only interested in the GYRO_FS_SEL field which + // is 2 bits long starting at bit position 3. + // + // As all the other fields of the GYRO_CONFIG register are 0, + // all that we have to do is to shift the + // GYRO_SCALE_1000 value 3 positions to the left. let command = [(1 << 7) | GYRO_CONFIG, (scale as u8) << 3]; + + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. let mut rx = [0u8; 2]; + + // Start the SPI transmission by setting the CS line LOW. + cs.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // return it to the caller at the end of the function. let res = spi.transfer(&mut rx, &command).await; + + // End the SPI transmission by setting the CS line HIGH. cs.set_high(); + + // Return the transmission result (either OK(()) or Err(error)) res } @@ -70,45 +105,195 @@ async fn set_accel_scale( cs: &mut Output<'_>, scale: AccelScale, ) -> Result<(), Error> { - cs.set_low(); + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the ACCEL_CONFIG register address 0x0001_1011 + // and obtain 0x0001_1011. + // + // This was actually not required as the ACCEL_CONFIG's + // most significant bit was already 0. + // + // The second position of the command buffer is the value that + // we want to write to the ACCEL_CONFIG register. In our case + // we are only interested in the ACCEL_FS_SEL field which + // is 2 bits long starting at bit position 3. + // + // As all the other fields of the ACCEL_CONFIG register are 0, + // all that we have to do is to shift the + // ACCEL_SCALE_2G value 3 positions to the left. let command = [(1 << 7) | ACCEL_CONFIG, (scale as u8) << 3]; + + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. let mut rx = [0u8; 2]; + + // Start the SPI transmission by setting the CS line LOW. + cs.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // return it to the caller at the end of the function. let res = spi.transfer(&mut rx, &command).await; + + // End the SPI transmission by setting the CS line HIGH. cs.set_high(); + + // Return the transmission result (either OK(()) or Err(error)) res } +/// Read the acceleration +/// +/// The function receives: +/// - a reference to the SPI bus +/// - a reference tp the CS pin +/// The function returns either the acceleration value or an error async fn read_acceleration( spi: &mut Spi<'_, Async>, cs: &mut Output<'_>, ) -> Result { - cs.set_low(); + // Read the acceleration + + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the ACCEL_XOUT_H register address 0x0011_1011 + // and obtain 0b1011_1011. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the ACCEL_XOUT_H register's + // value followed by the values of the next 5 registers: + // - ACCEL_XOUT_L at 0x3c + // - ACCEL_YOUT_H at 0x3d + // - ACCEL_YOUT_L at 0x3e + // - ACCEL_ZOUT_H at 0x3f + // - ACCEL_ZOUT_L at 0x40 + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. let command = [(1 << 7) | ACCEL_XOUT_H, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the ACCEL_XOUT_H, ACCEL_YOUT_H, ACCEL_YOUT_L, + // ACCEL_ZOUT_H and ACCEL_ZOUT_L registers. let mut rx = [0u8; 7]; + + // Start the SPI transmission by setting the CS line LOW. + cs.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the ACCEL_XOUT_H, ACCEL_YOUT_H, ACCEL_YOUT_L, + // ACCEL_ZOUT_H and ACCEL_ZOUT_L registers). + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // use it to verify if the transmission was successful. let res = spi.transfer(&mut rx, &command).await; + + // End the SPI transmission by setting the CS line HIGH. cs.set_high(); + + // Verify if the transmission was successful match res { + // The transmission was successful, extract and return the acceleration Ok(()) => Ok(Acceleration { x: i16::from_be_bytes([rx[1], rx[2]]), y: i16::from_be_bytes([rx[3], rx[4]]), z: i16::from_be_bytes([rx[5], rx[6]]), }), + // The transmission was not successful, return the error Err(error) => Err(error), } } +/// Read the gyro +/// +/// The function receives: +/// - a reference to the SPI bus +/// - a reference tp the CS pin +/// The function returns either the acceleration value or an error async fn read_gyro(spi: &mut Spi<'_, Async>, cs: &mut Output<'_>) -> Result { - cs.set_low(); + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the GYRO_XOUT_H register address 0x0100_0011 + // and obtain 0b1100_0011. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the GYRO_XOUT_H register's + // value followed by the values of the next 5 registers: + // - GYROL_XOUT_L at 0x44 + // - GYROL_YOUT_H at 0x45 + // - GYROL_YOUT_L at 0x46 + // - GYROL_ZOUT_H at 0x47 + // - GYROL_ZOUT_L at 0x48 + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. let command = [(1 << 7) | GYRO_XOUT_H, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the GYRO_XOUT_H, GYRO_YOUT_H, GYRO_YOUT_L, + // GYRO_ZOUT_H and GYRO_ZOUT_L registers. let mut rx = [0u8; 7]; + + // Start the SPI transmission by setting the CS line LOW. + cs.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the GYRO_XOUT_H, GYRO_YOUT_H, GYRO_YOUT_L, + // GYRO_ZOUT_H and GYRO_ZOUT_L registers). + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // use it to verify if the transmission was successful. let res = spi.transfer(&mut rx, &command).await; + + // End the SPI transmission by setting the CS line HIGH. cs.set_high(); + + // Verify if the transmission was successful match res { + // The transmission was successful, extract and return the gyro Ok(()) => Ok(Gyro { x: i16::from_be_bytes([rx[1], rx[2]]), y: i16::from_be_bytes([rx[3], rx[4]]), z: i16::from_be_bytes([rx[5], rx[6]]), }), + // The transmission was not successful, return the error Err(error) => Err(error), } } @@ -118,9 +303,18 @@ async fn main(_spawner: Spawner) { let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); + // Create the SPI bus configuration let mut config = spi::Config::default(); + // Set the SPI frequency to 1 MHz config.frequency = Hertz(1_000_000); + // SPI1 is exposed by the Arduino header using pins: + // - MISO - D12 (PA6) + // - MOSI - D11 (PA7) + // - CLK - D13 (PA5) + // + // We use the asynchronous API an we need two free + // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let mut spi = Spi::new( peripherals.SPI1, peripherals.PA5, @@ -131,23 +325,69 @@ async fn main(_spawner: Spawner) { config, ); + // We use the D7 (PA8) pin as CS let mut mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the WHO_AM_I register address 0x0111_0101 + // and obtain 0b1111_0101. + // + // We add another 0 that will be ignored by the sensor, but + // is required as the sensor will send us the WHO_AM_I register's + // value while we transmit this 0. let command = [(1 << 7) | WHO_AM_I, 0]; + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // The second byte is the value of the WHO_AM_I register. let mut rx = [0u8; 2]; + + // Verify that the MPU 6500 sensor is connected. + + // Start the SPI transmission by setting the CS line LOW. mpu6500_cs_pin.set_low(); + // Transfer the data: + // - send the command buffer (command followed by one zero byte) + // - receive in the rx buffer (random byte followed by the value of the WHO_AM_I register) + // + // Using the `unwrap` function will generate a panic if the `transfer` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. spi.transfer(&mut rx, &command).await.unwrap(); + // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); + // Panic if the WHO_AM_I register value if wrong + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. assert_eq!(rx[1], WHO_AM_I_VALUE); + // Set the acceleration scale set_accel_scale(&mut spi, &mut mpu6500_cs_pin, AccelScale::G2) .await .expect("Failed to set the acceleration scale"); + + // Set the gyro scale set_gyro_scale(&mut spi, &mut mpu6500_cs_pin, GyroScale::Gs1000) .await .expect("Failed to set the gyro scale"); loop { + // Read the acceleration + // + // Using the `unwrap` function will generate a panic if the `read_acceleration` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. let acceleration = read_acceleration(&mut spi, &mut mpu6500_cs_pin) .await .unwrap(); @@ -156,6 +396,12 @@ async fn main(_spawner: Spawner) { acceleration.x, acceleration.y, acceleration.z ); + // Read the gyro + // + // Using the `unwrap` function will generate a panic if the `read_gyro` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. let gyro = read_gyro(&mut spi, &mut mpu6500_cs_pin).await.unwrap(); info!("Gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z); diff --git a/lab05/src/bin/ex4_v3_bus.rs b/lab05/src/bin/ex4_v3_bus.rs index aface79..889c0a2 100644 --- a/lab05/src/bin/ex4_v3_bus.rs +++ b/lab05/src/bin/ex4_v3_bus.rs @@ -10,17 +10,28 @@ use embassy_stm32::{ time::Hertz, }; use embassy_time::Timer; -use lab05::mpu6500::{AccelScale, GyroScale, bus::Mpu6500}; use panic_probe as _; +// We use the MPU6500 driver that requires the whole SPI bus +use lab05::mpu6500::{AccelScale, GyroScale, bus::Mpu6500}; + #[embassy_executor::main] async fn main(_spawner: Spawner) { let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); + // Create the SPI bus configuration let mut config = spi::Config::default(); + // Set the SPI frequency to 1 MHz config.frequency = Hertz(1_000_000); + // SPI1 is exposed by the Arduino header using pins: + // - MISO - D12 (PA6) + // - MOSI - D11 (PA7) + // - CLK - D13 (PA5) + // + // We use the asynchronous API an we need two free + // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let mut spi = Spi::new( peripherals.SPI1, peripherals.PA5, @@ -31,29 +42,48 @@ async fn main(_spawner: Spawner) { config, ); + // We use the D7 (PA8) pin as CS let mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + // Create an instance of the MPU6500 driver let mut mpu6500 = Mpu6500::new(&mut spi, mpu6500_cs_pin); + // Verify that the MPU 6500 sensor is connected. if mpu6500.is_connected().await { + // Set the acceleration scale mpu6500 .set_accel_scale(AccelScale::G2) .await .expect("Failed to set the acceleration scale"); + + // Set the gyro scale mpu6500 .set_gyro_scale(GyroScale::Gs1000) .await .expect("Failed to set the gyro scale"); loop { + // Read the acceleration + // + // Using the `unwrap` function will generate a panic if the `read_acceleration` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. let acceleration = mpu6500.read_acceleration().await.unwrap(); info!( "acceleration: X {}, Y {}, Z {}", acceleration.x, acceleration.y, acceleration.z ); + // Read the gyro + // + // Using the `unwrap` function will generate a panic if the `read_gyro` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. let gyro = mpu6500.read_gyro().await.unwrap(); info!("gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z); + Timer::after_millis(100).await; } } else { diff --git a/lab05/src/bin/ex4_v3_device.rs b/lab05/src/bin/ex4_v3_device.rs index 4c47e82..1ea973e 100644 --- a/lab05/src/bin/ex4_v3_device.rs +++ b/lab05/src/bin/ex4_v3_device.rs @@ -12,17 +12,28 @@ use embassy_stm32::{ }; use embassy_sync::{blocking_mutex::raw::ThreadModeRawMutex, mutex::Mutex}; use embassy_time::Timer; -use lab05::mpu6500::{AccelScale, GyroScale, device::Mpu6500}; use panic_probe as _; +// We use the MPU6500 driver that requires a SPI device +use lab05::mpu6500::{AccelScale, GyroScale, device::Mpu6500}; + #[embassy_executor::main] async fn main(_spawner: Spawner) { let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); + // Create the SPI bus configuration let mut config = spi::Config::default(); + // Set the SPI frequency to 1 MHz config.frequency = Hertz(1_000_000); + // SPI1 is exposed by the Arduino header using pins: + // - MISO - D12 (PA6) + // - MOSI - D11 (PA7) + // - CLK - D13 (PA5) + // + // We use the asynchronous API an we need two free + // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let spi = Spi::new( peripherals.SPI1, peripherals.PA5, @@ -32,31 +43,57 @@ async fn main(_spawner: Spawner) { peripherals.GPDMA1_CH1, config, ); + + // We use the D7 (PA8) pin as CS let mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + + // Create a Mutex that so that we can safely share the SPI bus between devices. + // + // Due to the Mutex, only one device will have access to the bus at a time. let spi_mutex = Mutex::::new(spi); + + // Create a SPI device for the MPU6500 driver. let mut spi_device = SpiDevice::new(&spi_mutex, mpu6500_cs_pin); + // Create an instance of the MPU6500 driver let mut mpu6500 = Mpu6500::new(&mut spi_device); + // Verify that the MPU 6500 sensor is connected. if mpu6500.is_connected().await { + // Set the acceleration scale mpu6500 .set_accel_scale(AccelScale::G2) .await .expect("Failed to set the acceleration scale"); + + // Set the gyro scale mpu6500 .set_gyro_scale(GyroScale::Gs1000) .await .expect("Failed to set the gyro scale"); loop { + // Read the acceleration + // + // Using the `unwrap` function will generate a panic if the `read_acceleration` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. let acceleration = mpu6500.read_acceleration().await.unwrap(); info!( "Acceleration: X {}, Y {}, Z {}", acceleration.x, acceleration.y, acceleration.z ); + // Read the gyro + // + // Using the `unwrap` function will generate a panic if the `read_gyro` function fails. + // This is a quick and dirty trick that is not recommended in production firmware, + // but works for our example. If this happens in production, the firmware + // should gracefully fail. let gyro = mpu6500.read_gyro().await.unwrap(); info!("Gyro: X {}, Y {}, Z {}", gyro.x, gyro.y, gyro.z); + Timer::after_millis(100).await; } } else { diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs index a73540f..f69e73a 100644 --- a/lab05/src/bin/ex5.rs +++ b/lab05/src/bin/ex5.rs @@ -24,7 +24,6 @@ use embedded_graphics::{ prelude::{Point, RgbColor}, text::{Text, renderer::CharacterStyle}, }; -use lab05::mpu6500::{AccelScale, GyroScale, device_blocking::Mpu6500}; use mipidsi::{ interface::SpiInterface, models::ST7735s, @@ -32,8 +31,16 @@ use mipidsi::{ }; use panic_probe as _; +// We use the MPU6500 driver that requires a blocking SPI device +use lab05::mpu6500::{AccelScale, GyroScale, device_blocking::Mpu6500}; + #[embassy_executor::main] async fn main(_spawner: Spawner) { + // Using displays means transferring a lot of data. While this + // works with the default clock settings, it is very slow. + // We use these lines of code to setup the external oscillator and + // increase the frequency of the microcontroller to make the + // display transfer faster. let mut config = Config::default(); config.rcc.hsi = true; config.rcc.pll1 = Some(Pll { @@ -55,9 +62,13 @@ async fn main(_spawner: Spawner) { let screen_rst = Output::new(peripherals.PC8, Level::Low, Speed::Low); // screen dc is D3 (PB3) let screen_dc = Output::new(peripherals.PB3, Level::Low, Speed::Low); - // screen cs is D4(PB5) - let screen_cs = Output::new(peripherals.PB5, Level::High, Speed::Low); + // SPI1 is exposed by the Arduino header using pins: + // - MISO - D12 (PA6) + // - MOSI - D11 (PA7) + // - CLK - D13 (PA5) + // + // We need a blocking SPI as the `mididsi` display drivers require a blocking SPI device. let spi = Spi::new_blocking( peripherals.SPI1, peripherals.PA5, @@ -65,30 +76,53 @@ async fn main(_spawner: Spawner) { peripherals.PA6, spi::Config::default(), ); + + // Create a Mutex that so that we can safely share the SPI bus between devices. + // + // Due to the Mutex, only one device will have access to the bus at a time. let spi_bus_mutex: Mutex = Mutex::new(RefCell::new(spi)); + // Set up the configuration for the display SPI device. let mut screen_spi_config = spi::Config::default(); screen_spi_config.frequency = Hertz(3_000_000); + // Create a SPI device for the mididsi display driver. + // + // The display requires a higher frequency than the MPU6500 sensor. let display_spi = SpiDeviceWithConfig::new(&spi_bus_mutex, screen_cs, screen_spi_config); - let mut screen_buffer = [0; 10 * 4096]; + // Use the D4(PB5) pin as the CS for the display. + let screen_cs = Output::new(peripherals.PB5, Level::High, Speed::Low); + + // Allocate a buffer for the display data transfer. + let mut screen_buffer = [0; 4096]; + // Display work in several interfaces, like SPI or I2C. Display drivers are usually written in a way + // in which they work on top of these interfaces. They accept a `DI` type which abstract the + // display interface. let di = SpiInterface::new(display_spi, screen_dc, &mut screen_buffer); + + // Create an instance of the display driver. let mut screen = mipidsi::Builder::new(ST7735s, di) .reset_pin(screen_rst) .orientation(Orientation::new().rotate(Rotation::Deg180)) .init(&mut Delay) .unwrap(); + // Create a SPI device for the MPU6500 sensor driver. + // + // The MPU6500 sensor requires a lower frequency than the display. let mut mpu6500_spi_config = spi::Config::default(); mpu6500_spi_config.frequency = Hertz(1_000_000); + // We use the D7 (PA8) pin as CS for the MPU6500 sensor. let mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + // Create a SPI device for the MPU6500 driver. let mut mpu6500_spi_device = SpiDeviceWithConfig::new(&spi_bus_mutex, mpu6500_cs_pin, mpu6500_spi_config); + // Create an instance of the MPU6500 driver let mut mpu6500 = Mpu6500::new(&mut mpu6500_spi_device); screen.clear(Rgb565::BLACK).unwrap(); diff --git a/lab05/src/mpu6500/mod.rs b/lab05/src/mpu6500/mod.rs index 4610868..19d9144 100644 --- a/lab05/src/mpu6500/mod.rs +++ b/lab05/src/mpu6500/mod.rs @@ -30,6 +30,10 @@ pub enum ValueRegister { /// The possible values for the `GYRO_FS_SEL` field of /// the `GYRO_CONFIG` register. /// +/// Instead of using numbers we defined this as an enum to +/// make sure that users cannot use any other values +/// than the these. +/// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword #[repr(u8)] @@ -43,6 +47,10 @@ pub enum GyroScale { /// The possible values for the `ACCEL_FS_SEL` field of /// the `ACCEL_CONFIG` register. /// +/// Instead of using numbers we defined this as an enum to +/// make sure that users cannot use any other values +/// than the these. +/// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword #[repr(u8)] From 595f879d9da444d683d8d6e66c8889cc9b64e044 Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Thu, 20 Nov 2025 19:10:57 +0200 Subject: [PATCH 06/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/bin/ex5.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs index f69e73a..dff729b 100644 --- a/lab05/src/bin/ex5.rs +++ b/lab05/src/bin/ex5.rs @@ -86,7 +86,7 @@ async fn main(_spawner: Spawner) { let mut screen_spi_config = spi::Config::default(); screen_spi_config.frequency = Hertz(3_000_000); - // Create a SPI device for the mididsi display driver. + // Create a SPI device for the mipidsi display driver. // // The display requires a higher frequency than the MPU6500 sensor. let display_spi = SpiDeviceWithConfig::new(&spi_bus_mutex, screen_cs, screen_spi_config); From 9293544847b0d5b17d85eb320773ab615d9ea478 Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Thu, 20 Nov 2025 19:11:06 +0200 Subject: [PATCH 07/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/bin/ex4_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs index e68ac3d..f367939 100644 --- a/lab05/src/bin/ex4_v2.rs +++ b/lab05/src/bin/ex4_v2.rs @@ -159,7 +159,7 @@ async fn set_accel_scale( /// /// The function receives: /// - a reference to the SPI bus -/// - a reference tp the CS pin +/// - a reference to the CS pin /// The function returns either the acceleration value or an error async fn read_acceleration( spi: &mut Spi<'_, Async>, From 75e3d7c990edddbf6a66f193a70883a2d8c7f1fe Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Thu, 20 Nov 2025 19:11:16 +0200 Subject: [PATCH 08/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/bin/ex4_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs index f367939..2ede018 100644 --- a/lab05/src/bin/ex4_v2.rs +++ b/lab05/src/bin/ex4_v2.rs @@ -233,7 +233,7 @@ async fn read_acceleration( /// /// The function receives: /// - a reference to the SPI bus -/// - a reference tp the CS pin +/// - a reference to the CS pin /// The function returns either the acceleration value or an error async fn read_gyro(spi: &mut Spi<'_, Async>, cs: &mut Output<'_>) -> Result { // This is the buffer that is sent to the sensor. The format is: From f84979674757a99d4022e4913bff5811ed94e56e Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Thu, 20 Nov 2025 19:17:15 +0200 Subject: [PATCH 09/19] fix build --- lab05/src/bin/ex4_v2.rs | 2 ++ lab05/src/bin/ex5.rs | 10 +++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs index 2ede018..ca60201 100644 --- a/lab05/src/bin/ex4_v2.rs +++ b/lab05/src/bin/ex4_v2.rs @@ -160,6 +160,7 @@ async fn set_accel_scale( /// The function receives: /// - a reference to the SPI bus /// - a reference to the CS pin +/// /// The function returns either the acceleration value or an error async fn read_acceleration( spi: &mut Spi<'_, Async>, @@ -234,6 +235,7 @@ async fn read_acceleration( /// The function receives: /// - a reference to the SPI bus /// - a reference to the CS pin +/// /// The function returns either the acceleration value or an error async fn read_gyro(spi: &mut Spi<'_, Async>, cs: &mut Output<'_>) -> Result { // This is the buffer that is sent to the sensor. The format is: diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs index dff729b..7fc2fe5 100644 --- a/lab05/src/bin/ex5.rs +++ b/lab05/src/bin/ex5.rs @@ -86,14 +86,18 @@ async fn main(_spawner: Spawner) { let mut screen_spi_config = spi::Config::default(); screen_spi_config.frequency = Hertz(3_000_000); +<<<<<<< HEAD // Create a SPI device for the mipidsi display driver. +======= + // Use the D4(PB5) pin as the CS for the display. + let screen_cs = Output::new(peripherals.PB5, Level::High, Speed::Low); + + // Create a SPI device for the mididsi display driver. +>>>>>>> 97ecb18 (fix build) // // The display requires a higher frequency than the MPU6500 sensor. let display_spi = SpiDeviceWithConfig::new(&spi_bus_mutex, screen_cs, screen_spi_config); - // Use the D4(PB5) pin as the CS for the display. - let screen_cs = Output::new(peripherals.PB5, Level::High, Speed::Low); - // Allocate a buffer for the display data transfer. let mut screen_buffer = [0; 4096]; From 32298c27be462044d149784bcfd856480d46eb8f Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Thu, 20 Nov 2025 19:18:16 +0200 Subject: [PATCH 10/19] fix error --- lab05/src/bin/ex5.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs index 7fc2fe5..2439130 100644 --- a/lab05/src/bin/ex5.rs +++ b/lab05/src/bin/ex5.rs @@ -86,14 +86,10 @@ async fn main(_spawner: Spawner) { let mut screen_spi_config = spi::Config::default(); screen_spi_config.frequency = Hertz(3_000_000); -<<<<<<< HEAD - // Create a SPI device for the mipidsi display driver. -======= // Use the D4(PB5) pin as the CS for the display. let screen_cs = Output::new(peripherals.PB5, Level::High, Speed::Low); - // Create a SPI device for the mididsi display driver. ->>>>>>> 97ecb18 (fix build) + // Create a SPI device for the mipidsi display driver. // // The display requires a higher frequency than the MPU6500 sensor. let display_spi = SpiDeviceWithConfig::new(&spi_bus_mutex, screen_cs, screen_spi_config); From 1fd5a5edae9885301d5eb4f0e0a26033957e60de Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Thu, 20 Nov 2025 19:18:49 +0200 Subject: [PATCH 11/19] fix typo --- lab05/src/bin/ex4_v2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs index ca60201..9e33e6a 100644 --- a/lab05/src/bin/ex4_v2.rs +++ b/lab05/src/bin/ex4_v2.rs @@ -43,7 +43,7 @@ const WHO_AM_I_VALUE: u8 = 0x70; /// /// The function receives: /// - a reference to the SPI bus -/// - a reference tp the CS pin +/// - a reference to the CS pin /// - the gyro scale value async fn set_gyro_scale( spi: &mut Spi<'_, Async>, From 1e62fb65c1d08c6d7be407525a687fd7870ea7a4 Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Thu, 20 Nov 2025 20:42:01 +0200 Subject: [PATCH 12/19] added comments and used the scale values --- codebook.toml | 1 + lab05/src/bin/ex3.rs | 2 +- lab05/src/bin/ex4_v1.rs | 39 +++- lab05/src/bin/ex4_v2.rs | 51 +++++- lab05/src/mpu6500/bus.rs | 263 ++++++++++++++++++++++++--- lab05/src/mpu6500/device.rs | 224 +++++++++++++++++++++-- lab05/src/mpu6500/device_blocking.rs | 217 ++++++++++++++++++++-- lab05/src/mpu6500/mod.rs | 85 ++++++++- 8 files changed, 799 insertions(+), 83 deletions(-) diff --git a/codebook.toml b/codebook.toml index 49f2001..4c7604d 100644 --- a/codebook.toml +++ b/codebook.toml @@ -14,6 +14,7 @@ words = [ "mcu", "microcontroller", "mille", + "mipidsi", "moder", "mosi", "mpu", diff --git a/lab05/src/bin/ex3.rs b/lab05/src/bin/ex3.rs index b2f451d..1f9dfc1 100644 --- a/lab05/src/bin/ex3.rs +++ b/lab05/src/bin/ex3.rs @@ -37,7 +37,7 @@ const WHO_AM_I_VALUE: u8 = 0x70; /// The acceleration scale value for ±2g const ACCEL_SCALE_2G: u8 = 0b00; -/// The gyro scale value for ±1000 deg / s +/// The gyro scale value for ±1000 deg/s const GYRO_SCALE_1000: u8 = 0b10; #[embassy_executor::main] diff --git a/lab05/src/bin/ex4_v1.rs b/lab05/src/bin/ex4_v1.rs index 43e9ee5..0648588 100644 --- a/lab05/src/bin/ex4_v1.rs +++ b/lab05/src/bin/ex4_v1.rs @@ -10,6 +10,7 @@ use embassy_stm32::{ time::Hertz, }; use embassy_time::Timer; +use lab05::mpu6500::{AccelScale, GyroScale}; use panic_probe as _; /// WHO_AM_I Register Address @@ -37,9 +38,29 @@ const WHO_AM_I_VALUE: u8 = 0x70; /// The acceleration scale value for ±2g const ACCEL_SCALE_2G: u8 = 0b00; -/// The gyro scale value for ±1000 deg / s +/// The gyro scale value for ±1000 deg/s const GYRO_SCALE_1000: u8 = 0b10; +/// Converts the `u16` acceleration value to m/s^2 using +/// the configured acceleration scale. +fn convert_to_g(value: i16, scale: AccelScale) -> f32 { + // i16::MAX ...... scale.value() (2, 4, 8 or 16 x g) + // value ......... acceleration + // + // acceleration = (value x scale.value()) / i16::MAX + (value as f32 * scale.value()) / i16::MAX as f32 +} + +/// Converts the `u16` acceleration value to deg/s using +/// the configured gyro scale. +fn convert_to_deg_s(value: i16, scale: GyroScale) -> f32 { + // i16::MAX ...... scale.value() (250, 500, 1000 or 2000 deg/s) + // value ......... gyro + // + // acceleration = (value x scale.value()) / i16::MAX + (value as f32 * scale.value()) / i16::MAX as f32 +} + #[embassy_executor::main] async fn main(_spawner: Spawner) { let peripherals = embassy_stm32::init(Default::default()); @@ -260,11 +281,13 @@ async fn main(_spawner: Spawner) { mpu6500_cs_pin.set_high(); // Convert the raw values into `i16` m/s^2 + // + // We know that we have set the acceleration scale to 2G info!( "Acceleration: X {}, Y {}, Z {}", - i16::from_be_bytes([rx[1], rx[2]]), - i16::from_be_bytes([rx[3], rx[4]]), - i16::from_be_bytes([rx[5], rx[6]]) + convert_to_g(i16::from_be_bytes([rx[1], rx[2]]), AccelScale::G2), + convert_to_g(i16::from_be_bytes([rx[3], rx[4]]), AccelScale::G2), + convert_to_g(i16::from_be_bytes([rx[5], rx[6]]), AccelScale::G2) ); // Read the gyro @@ -321,11 +344,13 @@ async fn main(_spawner: Spawner) { mpu6500_cs_pin.set_high(); // Convert the raw values into `i16` deg/s + // + // We know that we have set the gyro scale to 1000 deg/s info!( "Gyro: X {}, Y {}, Z {}", - i16::from_be_bytes([rx[1], rx[2]]), - i16::from_be_bytes([rx[3], rx[4]]), - i16::from_be_bytes([rx[5], rx[6]]) + convert_to_deg_s(i16::from_be_bytes([rx[1], rx[2]]), GyroScale::Gs1000), + convert_to_deg_s(i16::from_be_bytes([rx[3], rx[4]]), GyroScale::Gs1000), + convert_to_deg_s(i16::from_be_bytes([rx[5], rx[6]]), GyroScale::Gs1000) ); Timer::after_millis(100).await; diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs index 9e33e6a..94e730c 100644 --- a/lab05/src/bin/ex4_v2.rs +++ b/lab05/src/bin/ex4_v2.rs @@ -39,6 +39,26 @@ const GYRO_XOUT_H: u8 = 0x43; /// WHO_AM_I Register Value for the MPU6500 sensor const WHO_AM_I_VALUE: u8 = 0x70; +/// Converts the `u16` acceleration value to m/s^2 using +/// the configured acceleration scale. +fn convert_to_g(value: i16, scale: AccelScale) -> f32 { + // i16::MAX ...... scale.value() (2, 4, 8 or 16 x g) + // value ......... acceleration + // + // acceleration = (value x scale.value()) / i16::MAX + (value as f32 * scale.value()) / i16::MAX as f32 +} + +/// Converts the `u16` acceleration value to deg/s using +/// the configured gyro scale. +fn convert_to_deg_s(value: i16, scale: GyroScale) -> f32 { + // i16::MAX ...... scale.value() (250, 500, 1000 or 2000 deg/s) + // value ......... gyro + // + // acceleration = (value x scale.value()) / i16::MAX + (value as f32 * scale.value()) / i16::MAX as f32 +} + /// Set the gyro scale /// /// The function receives: @@ -73,7 +93,7 @@ async fn set_gyro_scale( // As all the other fields of the GYRO_CONFIG register are 0, // all that we have to do is to shift the // GYRO_SCALE_1000 value 3 positions to the left. - let command = [(1 << 7) | GYRO_CONFIG, (scale as u8) << 3]; + let command = [!(1 << 7) & GYRO_CONFIG, (scale as u8) << 3]; // Even though we do not read any values form the sensor, we have to // supply an rx buffer with the same length as the command buffer. @@ -100,6 +120,12 @@ async fn set_gyro_scale( res } +/// Set the acceleration scale +/// +/// The function receives: +/// - a reference to the SPI bus +/// - a reference to the CS pin +/// - the acceleration scale value async fn set_accel_scale( spi: &mut Spi<'_, Async>, cs: &mut Output<'_>, @@ -111,12 +137,11 @@ async fn set_accel_scale( // - 1 - read the register's value from the sensor // - 0 - write a value to the sensor's register // - // We shift 1 with 7 positions obtaining 0b1000_0000 and + // We should shift 1 with 7 positions obtaining 0b1000_0000 and // negate it to obtain 0b0111_1111. We need to make sure // that bit 7 is 0 as we are performing a write. We // AND this value with the ACCEL_CONFIG register address 0x0001_1011 // and obtain 0x0001_1011. - // // This was actually not required as the ACCEL_CONFIG's // most significant bit was already 0. // @@ -128,7 +153,7 @@ async fn set_accel_scale( // As all the other fields of the ACCEL_CONFIG register are 0, // all that we have to do is to shift the // ACCEL_SCALE_2G value 3 positions to the left. - let command = [(1 << 7) | ACCEL_CONFIG, (scale as u8) << 3]; + let command = [ACCEL_CONFIG, (scale as u8) << 3]; // Even though we do not read any values form the sensor, we have to // supply an rx buffer with the same length as the command buffer. @@ -220,10 +245,13 @@ async fn read_acceleration( // Verify if the transmission was successful match res { // The transmission was successful, extract and return the acceleration + // + // We have to convert the `i16` value to m/s^2. We know that we have set the + // acceleration scale to AccelScale::G2. Ok(()) => Ok(Acceleration { - x: i16::from_be_bytes([rx[1], rx[2]]), - y: i16::from_be_bytes([rx[3], rx[4]]), - z: i16::from_be_bytes([rx[5], rx[6]]), + x: convert_to_g(i16::from_be_bytes([rx[1], rx[2]]), AccelScale::G2), + y: convert_to_g(i16::from_be_bytes([rx[3], rx[4]]), AccelScale::G2), + z: convert_to_g(i16::from_be_bytes([rx[5], rx[6]]), AccelScale::G2), }), // The transmission was not successful, return the error Err(error) => Err(error), @@ -288,12 +316,15 @@ async fn read_gyro(spi: &mut Spi<'_, Async>, cs: &mut Output<'_>) -> Result Ok(Gyro { - x: i16::from_be_bytes([rx[1], rx[2]]), - y: i16::from_be_bytes([rx[3], rx[4]]), - z: i16::from_be_bytes([rx[5], rx[6]]), + x: convert_to_deg_s(i16::from_be_bytes([rx[1], rx[2]]), GyroScale::Gs1000), + y: convert_to_deg_s(i16::from_be_bytes([rx[3], rx[4]]), GyroScale::Gs1000), + z: convert_to_deg_s(i16::from_be_bytes([rx[5], rx[6]]), GyroScale::Gs1000), }), // The transmission was not successful, return the error Err(error) => Err(error), diff --git a/lab05/src/mpu6500/bus.rs b/lab05/src/mpu6500/bus.rs index 783bd5e..f7d2ce2 100644 --- a/lab05/src/mpu6500/bus.rs +++ b/lab05/src/mpu6500/bus.rs @@ -8,6 +8,15 @@ //! //! The driver receives the SPI bus and CS pin and is responsible //! for actuating the CS pin to enable the SPI device. +//! +//! ## Advantages +//! - we do not have to use a Mutex +//! - we can perform back to back transmissions, as there we control the CS pin +//! +//! ## Disadvantages: +//! - we cannot use the SPI bus for other devices when this driver +//! does not transfer any data +//! - we can forget the CS pin low and the transmission will not end use embassy_stm32::gpio::Output; @@ -22,91 +31,295 @@ use embassy_stm32::gpio::Output; use embedded_hal_async::spi::SpiBus; use crate::mpu6500::{ - ACCEL_CONFIG, AccelScale, Acceleration, GYRO_CONFIG, Gyro, GyroScale, ValueRegister, WHO_AM_I, + AccelScale, Acceleration, ConfigRegister, Gyro, GyroScale, ValueRegister, WHO_AM_I, WHO_AM_I_VALUE, }; /// MPU 6500 SPI Bus driver pub struct Mpu6500<'a, S: SpiBus> { + /// The SPI bus spi: &'a mut S, + + /// The CS pin cs: Output<'a>, + + /// The configured acceleration scale + accel_scale: AccelScale, + + /// The configured gyro scale + gyro_scale: GyroScale, } /// Public API /// -/// The function defined here are exported by the driver. +/// The functions defined here are exported by the driver. /// /// The type `S` used by the driver is defined as *any type that -/// implements the `SpiBus` trait. +/// implements the `SpiBus` trait*. impl<'a, S: SpiBus> Mpu6500<'a, S> { + /// Create a new MPU6500 SPI bus driver instance pub fn new(spi: &'a mut S, cs: Output<'a>) -> Mpu6500<'a, S> { - Mpu6500 { spi, cs } + Mpu6500 { + spi, + cs, + // The default value for the acceleration scale for + // MPU6500 is 2G + accel_scale: AccelScale::G2, + // The default value for the gyro scale for + // MPU6500 is 250 deg / 2 + gyro_scale: GyroScale::Gs250, + } } + /// Verifies if the MPU6500 sensor is connected to the bus pub async fn is_connected(&mut self) -> bool { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the WHO_AM_I register address 0x0111_0101 + // and obtain 0b1111_0101. + // + // We add another 0 that will be ignored by the sensor, but + // is required as the sensor will send us the WHO_AM_I register's + // value while we transmit this 0. let command = [(1 << 7) | WHO_AM_I, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // The second byte is the value of the WHO_AM_I register. let mut rx = [0u8; 2]; + // Start the SPI transmission by setting the CS line LOW. self.cs.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by one zero byte) + // - receive in the rx buffer (random byte followed by the value of the WHO_AM_I register) + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // use it to verify if the transmission was successful. let res = self.spi.transfer(&mut rx, &command).await; + + // End the SPI transmission by setting the CS line HIGH. self.cs.set_high(); + + // Verify if the transmission was successful match res { - Ok(()) => rx[1] == WHO_AM_I_VALUE, + // The transmission was successful, verify if the read WHO_AM_I value is correct + Ok(()) => { + // If the register's value is the one expected, + // we confirm that the MPU 6500 is connected to + // the SPI by returning `true` other wise we + // return `false`. + rx[1] == WHO_AM_I_VALUE + } + // The transmission was not successful, return the error Err(_error) => false, } } + /// Set the gyro scale pub async fn set_gyro_scale(&mut self, scale: GyroScale) -> Result<(), S::Error> { - self.cs.set_low(); - let command = [GYRO_CONFIG, (scale as u8) << 3]; - let mut rx = [0u8; 2]; - let res = self.spi.transfer(&mut rx, &command).await; - self.cs.set_high(); - res + let res = self + .write_config(ConfigRegister::Gyro, (scale as u8) << 3) + .await; + // Verify if the transmission was successful + match res { + // The transmission was successful, store the new gyro_scale value and return + Ok(()) => { + self.gyro_scale = scale; + Ok(()) + } + // The transmission was not successful, return the error + Err(error) => Err(error), + } } + /// Set the acceleration scale pub async fn set_accel_scale(&mut self, scale: AccelScale) -> Result<(), S::Error> { - self.cs.set_low(); - let command = [ACCEL_CONFIG, (scale as u8) << 3]; - let mut rx = [0u8; 2]; - let res = self.spi.transfer(&mut rx, &command).await; - self.cs.set_high(); - res + let res = self + .write_config(ConfigRegister::Accel, (scale as u8) << 3) + .await; + // Verify if the transmission was successful + match res { + // The transmission was successful, store the new gyro_scale value and return + Ok(()) => { + self.accel_scale = scale; + Ok(()) + } + // The transmission was not successful, return the error + Err(error) => Err(error), + } } + /// Read the acceleration + /// + /// The function returns either the acceleration value or an error pub async fn read_acceleration(&mut self) -> Result { let rx = self.read_value(ValueRegister::AccelXOutH).await?; Ok(Acceleration { - x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), + x: self.convert_to_g(i16::from_be_bytes((&rx[0..2]).try_into().unwrap())), + y: self.convert_to_g(i16::from_be_bytes((&rx[2..4]).try_into().unwrap())), + z: self.convert_to_g(i16::from_be_bytes((&rx[4..6]).try_into().unwrap())), }) } + /// Read the gyro + /// + /// The function returns either the gyro value or an error pub async fn read_gyro(&mut self) -> Result { let rx = self.read_value(ValueRegister::GyroXOutH).await?; Ok(Gyro { - x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), + x: self.convert_to_deg_s(i16::from_be_bytes((&rx[0..2]).try_into().unwrap())), + y: self.convert_to_deg_s(i16::from_be_bytes((&rx[2..4]).try_into().unwrap())), + z: self.convert_to_deg_s(i16::from_be_bytes((&rx[4..6]).try_into().unwrap())), }) } } /// Private API /// -/// The function defined here are not exported by the driver and +/// The functions defined here are not exported by the driver and /// are only used by the driver itself. impl<'a, S: SpiBus> Mpu6500<'a, S> { - async fn read_value(&mut self, value_register: ValueRegister) -> Result<[u8; 6], S::Error> { + /// Internal function that sets the value of a config register. + /// + /// Writing the acceleration and gyro config use the same SPI transfer code, + /// the only difference being the register address and the value. + /// + /// This function is used by `Mpu6500::set_accel_scale` and `Mpu6500::set_gyro_scale`. + /// + /// This function returns success or an error. + async fn write_config( + &mut self, + config_register: ConfigRegister, + value: u8, + ) -> Result<(), S::Error> { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the register's address. + // + // The second position of the command buffer is the value that + // we want to write to the config register. + let command = [!(1 << 7) & config_register as u8, value]; + + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. + let mut rx = [0u8; 2]; + + // Start the SPI transmission by setting the CS line LOW. self.cs.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // return it to the caller at the end of the function. + let res = self.spi.transfer(&mut rx, &command).await; + + // End the SPI transmission by setting the CS line HIGH. + self.cs.set_high(); + + // Return the transmission result (either OK(()) or Err(error)) + res + } + + /// Internal function that reads six vales from the sensor starting from + /// the address of the `value_register` provided. + /// + /// Reading the acceleration or the gyro uses the same SPI transfer code, + /// the only difference being the register address where the read + /// starts. + /// + /// This function is used by `Mpu6500::read_acceleration` and `Mpu6500::read_gyro`. + /// + /// This function returns the raw array of six value or an error. + async fn read_value(&mut self, value_register: ValueRegister) -> Result<[u8; 6], S::Error> { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the register address. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the ACCEL_XOUT_H register's + // value followed by the values of the next 5 registers. + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. let command = [(1 << 7) | value_register as u8, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the read registers. let mut rx = [0u8; 7]; + + // Start the SPI transmission by setting the CS line LOW. + self.cs.set_low(); + + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the read registers). + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // use it to verify if the transmission was successful. let res = self.spi.transfer(&mut rx, &command).await; + + // End the SPI transmission by setting the CS line HIGH. self.cs.set_high(); + + // Verify if the transmission was successful match res { + // The transmission was successful, extract and return the raw values Ok(()) => Ok((&rx[1..]).try_into().unwrap()), + // The transmission was not successful, return the error Err(error) => Err(error), } } + + /// Converts the `u16` acceleration value to m/s^2 using + /// the configured acceleration scale. + fn convert_to_g(&self, value: i16) -> f32 { + // i16::MAX ...... self.accel_scale.value() (2, 4, 8 or 16 x g) + // value ......... acceleration + // + // acceleration = (value x self.accel_scale.value()) / i16::MAX + (value as f32 * self.accel_scale.value()) / i16::MAX as f32 + } + + /// Converts the `u16` acceleration value to deg/s using + /// the configured gyro scale. + fn convert_to_deg_s(&self, value: i16) -> f32 { + // i16::MAX ...... self.gyro_scale.value() (250, 500, 1000 or 2000 deg/s) + // value ......... gyro + // + // acceleration = (value x self.gyro_scale.value()) / i16::MAX + (value as f32 * self.gyro_scale.value()) / i16::MAX as f32 + } } diff --git a/lab05/src/mpu6500/device.rs b/lab05/src/mpu6500/device.rs index 09f75c5..cc8e974 100644 --- a/lab05/src/mpu6500/device.rs +++ b/lab05/src/mpu6500/device.rs @@ -9,6 +9,18 @@ //! pi and is not responsible for actuating the CS pin //! to enable the SPI device. The [`SpiDevice::transfer`] //! takes care of activation the CS pin of the device. +//! +//! ## Advantages: +//! - we can use the SPI bus for other devices when this driver +//! does not transfer any data +//! - we cannot forget the CS pin low and we are sure that the +//! transmission ends +//! +//! ## Disadvantages +//! - we have to use a Mutex +//! - we cannot perform back to back transmissions, as another device +//! might get a hold of the SPI bus's Mutex in between +//! the transactions // The `embedded_hal_async` crate exports standard async Hardware Abstraction // Layer (HAL) traits that libraries like `embassy` implement. Drivers @@ -21,71 +33,253 @@ use embedded_hal_async::spi::SpiDevice; use crate::mpu6500::{ - ACCEL_CONFIG, AccelScale, Acceleration, GYRO_CONFIG, Gyro, GyroScale, ValueRegister, WHO_AM_I, + AccelScale, Acceleration, ConfigRegister, Gyro, GyroScale, ValueRegister, WHO_AM_I, WHO_AM_I_VALUE, }; +/// MPU 6500 SPI Device driver pub struct Mpu6500<'a, S: SpiDevice> { + /// The SPI device + /// spi: &'a mut S, + /// The configured acceleration scale + accel_scale: AccelScale, + + /// The configured gyro scale + gyro_scale: GyroScale, } /// Public API +/// +/// The functions defined here are exported by the driver. +/// +/// The type `S` used by the driver is defined as *any type that +/// implements the `SpiBus` trait*. impl<'a, S: SpiDevice> Mpu6500<'a, S> { + /// Create a new MPU6500 SPI device driver instance pub fn new(spi: &'a mut S) -> Mpu6500<'a, S> { - Mpu6500 { spi } + Mpu6500 { + spi, + // The default value for the acceleration scale for + // MPU6500 is 2G + accel_scale: AccelScale::G2, + // The default value for the gyro scale for + // MPU6500 is 250 deg / 2 + gyro_scale: GyroScale::Gs250, + } } + /// Verifies if the MPU6500 sensor is connected to the bus pub async fn is_connected(&mut self) -> bool { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the WHO_AM_I register address 0x0111_0101 + // and obtain 0b1111_0101. + // + // We add another 0 that will be ignored by the sensor, but + // is required as the sensor will send us the WHO_AM_I register's + // value while we transmit this 0. let command = [(1 << 7) | WHO_AM_I, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // The second byte is the value of the WHO_AM_I register. let mut rx = [0u8; 2]; + // Transfer the data: + // - send the command buffer (command followed by one zero byte) + // - receive in the rx buffer (random byte followed by the value of the WHO_AM_I register) + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // use it to verify if the transmission was successful. let res = self.spi.transfer(&mut rx, &command).await; + + // Verify if the transmission was successful match res { - Ok(()) => rx[1] == WHO_AM_I_VALUE, + // The transmission was successful, verify if the read WHO_AM_I value is correct + Ok(()) => { + // If the register's value is the one expected, + // we confirm that the MPU 6500 is connected to + // the SPI by returning `true` other wise we + // return `false`. + rx[1] == WHO_AM_I_VALUE + } + // The transmission was not successful, return the error Err(_error) => false, } } + /// Set the gyro scale pub async fn set_gyro_scale(&mut self, scale: GyroScale) -> Result<(), S::Error> { - let command = [GYRO_CONFIG, (scale as u8) << 3]; - let mut rx = [0u8; 2]; - self.spi.transfer(&mut rx, &command).await + self.write_config(ConfigRegister::Gyro, (scale as u8) << 3) + .await } + /// Set the acceleration scale pub async fn set_accel_scale(&mut self, scale: AccelScale) -> Result<(), S::Error> { - let command = [ACCEL_CONFIG, (scale as u8) << 3]; - let mut rx = [0u8; 2]; - self.spi.transfer(&mut rx, &command).await + let res = self + .write_config(ConfigRegister::Accel, (scale as u8) << 3) + .await; + match res { + Ok(()) => { + self.accel_scale = scale; + Ok(()) + } + Err(error) => Err(error), + } } + /// Read the acceleration + /// + /// The function returns either the acceleration value or an error pub async fn read_acceleration(&mut self) -> Result { let rx = self.read_value(ValueRegister::AccelXOutH).await?; Ok(Acceleration { - x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), + x: self.convert_to_g(i16::from_be_bytes((&rx[0..2]).try_into().unwrap())), + y: self.convert_to_g(i16::from_be_bytes((&rx[2..4]).try_into().unwrap())), + z: self.convert_to_g(i16::from_be_bytes((&rx[4..6]).try_into().unwrap())), }) } + /// Read the gyro + /// + /// The function returns either the gyro value or an error pub async fn read_gyro(&mut self) -> Result { let rx = self.read_value(ValueRegister::GyroXOutH).await?; Ok(Gyro { - x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), + x: self.convert_to_deg_s(i16::from_be_bytes((&rx[0..2]).try_into().unwrap())), + y: self.convert_to_deg_s(i16::from_be_bytes((&rx[2..4]).try_into().unwrap())), + z: self.convert_to_deg_s(i16::from_be_bytes((&rx[4..6]).try_into().unwrap())), }) } } /// Private API +/// +/// The functions defined here are not exported by the driver and +/// are only used by the driver itself. impl<'a, S: SpiDevice> Mpu6500<'a, S> { + /// Internal function that sets the value of a config register. + /// + /// Writing the acceleration and gyro config use the same SPI transfer code, + /// the only difference being the register address and the value. + /// + /// This function is used by `Mpu6500::set_accel_scale` and `Mpu6500::set_gyro_scale`. + /// + /// This function returns success or an error. + async fn write_config( + &mut self, + config_register: ConfigRegister, + value: u8, + ) -> Result<(), S::Error> { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the register's address. + // + // The second position of the command buffer is the value that + // we want to write to the config register. + let command = [!(1 << 7) & config_register as u8, value]; + + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. + let mut rx = [0u8; 2]; + + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + self.spi.transfer(&mut rx, &command).await + } + + /// Internal function that reads six vales from the sensor starting from + /// the address of the `value_register` provided. + /// + /// Reading the acceleration or the gyro uses the same SPI transfer code, + /// the only difference being the register address where the read + /// starts. + /// + /// This function is used by `Mpu6500::read_acceleration` and `Mpu6500::read_gyro`. + /// + /// This function returns the raw array of six value or an error. async fn read_value(&mut self, value_register: ValueRegister) -> Result<[u8; 6], S::Error> { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the register address. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the ACCEL_XOUT_H register's + // value followed by the values of the next 5 registers. + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. let command = [(1 << 7) | value_register as u8, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the read registers. let mut rx = [0u8; 7]; + + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the read registers). + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // use it to verify if the transmission was successful. let res = self.spi.transfer(&mut rx, &command).await; + + // Verify if the transmission was successful match res { + // The transmission was successful, extract and return the raw values Ok(()) => Ok((&rx[1..]).try_into().unwrap()), + // The transmission was not successful, return the error Err(error) => Err(error), } } + + /// Converts the `u16` acceleration value to m/s^2 using + /// the configured acceleration scale. + fn convert_to_g(&self, value: i16) -> f32 { + // i16::MAX ...... self.accel_scale.value() (2, 4, 8 or 16 x g) + // value ......... acceleration + // + // acceleration = (value x self.accel_scale.value()) / i16::MAX + (value as f32 * self.accel_scale.value()) / i16::MAX as f32 + } + + /// Converts the `u16` acceleration value to deg/s using + /// the configured gyro scale. + fn convert_to_deg_s(&self, value: i16) -> f32 { + // i16::MAX ...... self.gyro_scale.value() (250, 500, 1000 or 2000 deg/s) + // value ......... gyro + // + // acceleration = (value x self.gyro_scale.value()) / i16::MAX + (value as f32 * self.gyro_scale.value()) / i16::MAX as f32 + } } diff --git a/lab05/src/mpu6500/device_blocking.rs b/lab05/src/mpu6500/device_blocking.rs index 97ab322..2aa6834 100644 --- a/lab05/src/mpu6500/device_blocking.rs +++ b/lab05/src/mpu6500/device_blocking.rs @@ -9,6 +9,18 @@ //! pi and is not responsible for actuating the CS pin //! to enable the SPI device. The [`SpiDevice::transfer`] //! takes care of activation the CS pin of the device. +//! +//! ## Advantages: +//! - we can use the SPI bus for other devices when this driver +//! does not transfer any data +//! - we cannot forget the CS pin low and we are sure that the +//! transmission ends +//! +//! ## Disadvantages +//! - we have to use a Mutex +//! - we cannot perform back to back transmissions, as another device +//! might get a hold of the SPI bus's Mutex in between +//! the transactions // The `embedded_hal` crate exports standard Hardware Abstraction // Layer (HAL) traits that libraries like `embassy` implement. Drivers @@ -21,71 +33,246 @@ use embedded_hal::spi::SpiDevice; use crate::mpu6500::{ - ACCEL_CONFIG, AccelScale, Acceleration, GYRO_CONFIG, Gyro, GyroScale, ValueRegister, WHO_AM_I, + AccelScale, Acceleration, ConfigRegister, Gyro, GyroScale, ValueRegister, WHO_AM_I, WHO_AM_I_VALUE, }; +/// MPU 6500 SPI Device blocking driver pub struct Mpu6500<'a, S: SpiDevice> { + /// The SPI device spi: &'a mut S, + + /// The configured acceleration scale + accel_scale: AccelScale, + + /// The configured gyro scale + gyro_scale: GyroScale, } /// Public API +/// +/// The functions defined here are exported by the driver. +/// +/// The type `S` used by the driver is defined as *any type that +/// implements the `SpiBus` trait*. impl<'a, S: SpiDevice> Mpu6500<'a, S> { + /// Create a new MPU6500 SPI device driver instance pub fn new(spi: &'a mut S) -> Mpu6500<'a, S> { - Mpu6500 { spi } + Mpu6500 { + spi, + // The default value for the acceleration scale for + // MPU6500 is 2G + accel_scale: AccelScale::G2, + // The default value for the gyro scale for + // MPU6500 is 250 deg / 2 + gyro_scale: GyroScale::Gs250, + } } + /// Verifies if the MPU6500 sensor is connected to the bus pub fn is_connected(&mut self) -> bool { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the WHO_AM_I register address 0x0111_0101 + // and obtain 0b1111_0101. + // + // We add another 0 that will be ignored by the sensor, but + // is required as the sensor will send us the WHO_AM_I register's + // value while we transmit this 0. let command = [(1 << 7) | WHO_AM_I, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // The second byte is the value of the WHO_AM_I register. let mut rx = [0u8; 2]; + // Transfer the data: + // - send the command buffer (command followed by one zero byte) + // - receive in the rx buffer (random byte followed by the value of the WHO_AM_I register) + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // use it to verify if the transmission was successful. let res = self.spi.transfer(&mut rx, &command); + + // Verify if the transmission was successful match res { - Ok(()) => rx[1] == WHO_AM_I_VALUE, + // The transmission was successful, verify if the read WHO_AM_I value is correct + Ok(()) => { + // If the register's value is the one expected, + // we confirm that the MPU 6500 is connected to + // the SPI by returning `true` other wise we + // return `false`. + rx[1] == WHO_AM_I_VALUE + } + // The transmission was not successful, return the error Err(_error) => false, } } + /// Set the gyro scale pub fn set_gyro_scale(&mut self, scale: GyroScale) -> Result<(), S::Error> { - let command = [GYRO_CONFIG, (scale as u8) << 3]; - let mut rx = [0u8; 2]; - self.spi.transfer(&mut rx, &command) + self.write_config(ConfigRegister::Gyro, (scale as u8) << 3) } + /// Set the acceleration scale pub fn set_accel_scale(&mut self, scale: AccelScale) -> Result<(), S::Error> { - let command = [ACCEL_CONFIG, (scale as u8) << 3]; - let mut rx = [0u8; 2]; - self.spi.transfer(&mut rx, &command) + let res = self.write_config(ConfigRegister::Accel, (scale as u8) << 3); + match res { + Ok(()) => { + self.accel_scale = scale; + Ok(()) + } + Err(error) => Err(error), + } } + /// Read the acceleration + /// + /// The function returns either the acceleration value or an error pub fn read_acceleration(&mut self) -> Result { let rx = self.read_value(ValueRegister::AccelXOutH)?; Ok(Acceleration { - x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), + x: self.convert_to_g(i16::from_be_bytes((&rx[0..2]).try_into().unwrap())), + y: self.convert_to_g(i16::from_be_bytes((&rx[2..4]).try_into().unwrap())), + z: self.convert_to_g(i16::from_be_bytes((&rx[4..6]).try_into().unwrap())), }) } + /// Read the gyro + /// + /// The function returns either the gyro value or an error pub fn read_gyro(&mut self) -> Result { let rx = self.read_value(ValueRegister::GyroXOutH)?; Ok(Gyro { - x: i16::from_be_bytes((&rx[0..2]).try_into().unwrap()), - y: i16::from_be_bytes((&rx[2..4]).try_into().unwrap()), - z: i16::from_be_bytes((&rx[4..6]).try_into().unwrap()), + x: self.convert_to_deg_s(i16::from_be_bytes((&rx[0..2]).try_into().unwrap())), + y: self.convert_to_deg_s(i16::from_be_bytes((&rx[2..4]).try_into().unwrap())), + z: self.convert_to_deg_s(i16::from_be_bytes((&rx[4..6]).try_into().unwrap())), }) } } /// Private API +/// +/// The functions defined here are not exported by the driver and +/// are only used by the driver itself. impl<'a, S: SpiDevice> Mpu6500<'a, S> { + /// Internal function that sets the value of a config register. + /// + /// Writing the acceleration and gyro config use the same SPI transfer code, + /// the only difference being the register address and the value. + /// + /// This function is used by `Mpu6500::set_accel_scale` and `Mpu6500::set_gyro_scale`. + /// + /// This function returns success or an error. + fn write_config(&mut self, config_register: ConfigRegister, value: u8) -> Result<(), S::Error> { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // negate it to obtain 0b0111_1111. We need to make sure + // that bit 7 is 0 as we are performing a write. We + // AND this value with the register's address. + // + // The second position of the command buffer is the value that + // we want to write to the config register. + let command = [!(1 << 7) & config_register as u8, value]; + + // Even though we do not read any values form the sensor, we have to + // supply an rx buffer with the same length as the command buffer. + // The sensor will send us random data, but we use DMA and + // DMA will want to transfer some data to us, regardless if it + // is useful data or not. + let mut rx = [0u8; 2]; + + // Transfer the data: + // - send the command buffer (command followed by the register's new value) + // - receive in the rx buffer random bytes + self.spi.transfer(&mut rx, &command) + } + + /// Internal function that reads six vales from the sensor starting from + /// the address of the `value_register` provided. + /// + /// Reading the acceleration or the gyro uses the same SPI transfer code, + /// the only difference being the register address where the read + /// starts. + /// + /// This function is used by `Mpu6500::read_acceleration` and `Mpu6500::read_gyro`. + /// + /// This function returns the raw array of six value or an error. fn read_value(&mut self, value_register: ValueRegister) -> Result<[u8; 6], S::Error> { + // This is the buffer that is sent to the sensor. The format is: + // | R/W REGISTER_ADDRESS | as many zeros as many data bytes we want to read | + // - R/W is the the most significant bit (first bit): + // - 1 - read the register's value from the sensor + // - 0 - write a value to the sensor's register + // + // We shift 1 with 7 positions obtaining 0b1000_0000 and + // OR it with the register address. + // + // We add six 0s that will be ignored by the sensor, but + // are required as the sensor will send us the ACCEL_XOUT_H register's + // value followed by the values of the next 5 registers. + // + // Most sensors work like this. When reading or writing, the register + // in the command is the first register. Every other value that is read + // or written is to or from the following registers. let command = [(1 << 7) | value_register as u8, 0, 0, 0, 0, 0, 0]; + + // This is the receive buffer. It is used to store bytes that the + // sensor sends. We will ignore the first byte, as that byte is transmitted + // by the sensor while we transmit the read command. The sensor sends random + // data in the first byte. + // + // Bytes 1..6 store the values of the read registers. let mut rx = [0u8; 7]; + + // Transfer the data: + // - send the command buffer (command followed by six zero bytes) + // - receive in the rx buffer (random byte followed by the values of + // the read registers). + // + // We do store the result of the transmission (either OK(()) or Err(error)) and + // use it to verify if the transmission was successful. let res = self.spi.transfer(&mut rx, &command); + + // Verify if the transmission was successful match res { + // The transmission was successful, extract and return the raw values Ok(()) => Ok((&rx[1..]).try_into().unwrap()), + // The transmission was not successful, return the error Err(error) => Err(error), } } + + /// Converts the `u16` acceleration value to m/s^2 using + /// the configured acceleration scale. + fn convert_to_g(&self, value: i16) -> f32 { + // i16::MAX ...... self.accel_scale.value() (2, 4, 8 or 16 x g) + // value ......... acceleration + // + // acceleration = (value x self.accel_scale.value()) / i16::MAX + (value as f32 * self.accel_scale.value()) / i16::MAX as f32 + } + + /// Converts the `u16` acceleration value to deg/s using + /// the configured gyro scale. + fn convert_to_deg_s(&self, value: i16) -> f32 { + // i16::MAX ...... self.gyro_scale.value() (250, 500, 1000 or 2000 deg/s) + // value ......... gyro + // + // acceleration = (value x self.gyro_scale.value()) / i16::MAX + (value as f32 * self.gyro_scale.value()) / i16::MAX as f32 + } } diff --git a/lab05/src/mpu6500/mod.rs b/lab05/src/mpu6500/mod.rs index 19d9144..7c03c87 100644 --- a/lab05/src/mpu6500/mod.rs +++ b/lab05/src/mpu6500/mod.rs @@ -1,20 +1,51 @@ +//! MPU 6500 SPI driver that uses the SPI Bus. +//! +//! This module exports three drivers +//! - MPU6500 async SPI Bus driver +//! - MPU6500 async SPI Device driver +//! - MPU6500 blocking SPI Device driver +//! +//! It defines several data structures used by all the drivers. + pub mod bus; pub mod device; pub mod device_blocking; /// WHO_AM_I Register Address const WHO_AM_I: u8 = 0x75; -/// GYRO_CONFIG Register Address -const GYRO_CONFIG: u8 = 0x1b; -/// ACCEL_CONFIG Register Address -const ACCEL_CONFIG: u8 = 0x1c; /// WHO_AM_I Register Value for the MPU6500 sensor const WHO_AM_I_VALUE: u8 = 0x70; +/// The gravitational acceleration +const G: f32 = 9.80665; + +/// The register address that the [`bus::Mpu6500::write_config`] +/// function should read +/// +/// Instead of using numbers we defined this as an enum to +/// make sure that users cannot use any other values +/// than the these. +/// +/// This is represented as a `u8` so that it can be cast +/// to a `u8` using the `as` keyword +/// +/// [`Copy`] and [`Clone`] are derived so that the value +/// can be copied when sent as a parameter to a function. +#[repr(u8)] +#[derive(Copy, Clone)] +pub enum ConfigRegister { + Gyro = 0x1b, + Accel = 0x1c, +} + /// The register address that the [`bus::Mpu6500::read_value`] /// function should read /// +/// Instead of using numbers we defined this as an enum to +/// make sure that users cannot use any other values +/// than the these. +/// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword /// @@ -36,7 +67,11 @@ pub enum ValueRegister { /// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword +/// +/// [`Copy`] and [`Clone`] are derived so that the value +/// can be copied when sent as a parameter to a function. #[repr(u8)] +#[derive(Copy, Clone)] pub enum GyroScale { Gs250 = 0b00, Gs500 = 0b01, @@ -44,6 +79,19 @@ pub enum GyroScale { Gs2000 = 0b11, } +impl GyroScale { + /// Returns the absolute maximum value in deg/s for + /// each scale value. + pub fn value(&self) -> f32 { + match self { + GyroScale::Gs250 => 250f32, + GyroScale::Gs500 => 500f32, + GyroScale::Gs1000 => 1000f32, + GyroScale::Gs2000 => 2000f32, + } + } +} + /// The possible values for the `ACCEL_FS_SEL` field of /// the `ACCEL_CONFIG` register. /// @@ -53,7 +101,11 @@ pub enum GyroScale { /// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword +/// +/// [`Copy`] and [`Clone`] are derived so that the value +/// can be copied when sent as a parameter to a function. #[repr(u8)] +#[derive(Copy, Clone)] pub enum AccelScale { G2 = 0b00, G4 = 0b01, @@ -61,16 +113,29 @@ pub enum AccelScale { G16 = 0b11, } +impl AccelScale { + /// Returns the absolute maximum value in m/s^2 for + /// each scale value. + pub fn value(&self) -> f32 { + match self { + AccelScale::G2 => 2f32 * G, + AccelScale::G4 => 4f32 * G, + AccelScale::G8 => 8f32 * G, + AccelScale::G16 => 16f32 * G, + } + } +} + /// Stores the acceleration on all the three axes. pub struct Acceleration { - pub x: i16, - pub y: i16, - pub z: i16, + pub x: f32, + pub y: f32, + pub z: f32, } /// Stores the gyro values on all the three axes. pub struct Gyro { - pub x: i16, - pub y: i16, - pub z: i16, + pub x: f32, + pub y: f32, + pub z: f32, } From d61cddc1af856099f783e83f373c055ea5b7ddc4 Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Thu, 20 Nov 2025 20:48:47 +0200 Subject: [PATCH 13/19] added a comment --- lab05/src/bin/ex5.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs index 2439130..09e26ec 100644 --- a/lab05/src/bin/ex5.rs +++ b/lab05/src/bin/ex5.rs @@ -127,6 +127,10 @@ async fn main(_spawner: Spawner) { screen.clear(Rgb565::BLACK).unwrap(); let mut style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + + // We set the background color to print the characters with a background, so that we can + // fully overwrite the previous text. Unless we use a background color, the text will + // superpose the letters and digits on the old text. style.set_background_color(Some(Rgb565::BLACK)); if mpu6500.is_connected() { From abbb739c1ce540ed0ad553455b89cc05e4457f42 Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Thu, 20 Nov 2025 22:00:39 +0200 Subject: [PATCH 14/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/mpu6500/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/mpu6500/mod.rs b/lab05/src/mpu6500/mod.rs index 7c03c87..35d1124 100644 --- a/lab05/src/mpu6500/mod.rs +++ b/lab05/src/mpu6500/mod.rs @@ -97,7 +97,7 @@ impl GyroScale { /// /// Instead of using numbers we defined this as an enum to /// make sure that users cannot use any other values -/// than the these. +/// than these. /// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword From 90477f6e2c1ce3aa9437038b09f792bf6bf19181 Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Thu, 20 Nov 2025 22:00:50 +0200 Subject: [PATCH 15/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/mpu6500/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/mpu6500/mod.rs b/lab05/src/mpu6500/mod.rs index 35d1124..5ba1558 100644 --- a/lab05/src/mpu6500/mod.rs +++ b/lab05/src/mpu6500/mod.rs @@ -63,7 +63,7 @@ pub enum ValueRegister { /// /// Instead of using numbers we defined this as an enum to /// make sure that users cannot use any other values -/// than the these. +/// than these. /// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword From 6596e0f5b6df130f8824a21b6c004f586f10661c Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Thu, 20 Nov 2025 22:01:03 +0200 Subject: [PATCH 16/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/bin/ex2.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/bin/ex2.rs b/lab05/src/bin/ex2.rs index 553d8cb..371c6a9 100644 --- a/lab05/src/bin/ex2.rs +++ b/lab05/src/bin/ex2.rs @@ -31,7 +31,7 @@ async fn main(_spawner: Spawner) { // - MOSI - D11 (PA7) // - CLK - D13 (PA5) // - // We use the asynchronous API an we need two free + // We use the asynchronous API and we need two free // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let mut spi = Spi::new( peripherals.SPI1, From 9c6151af5ddb6755d0730219431ea99408507490 Mon Sep 17 00:00:00 2001 From: Alexandru RADOVICI Date: Thu, 20 Nov 2025 22:05:09 +0200 Subject: [PATCH 17/19] typo --- .gitignore | 25 +++++++++++++++++++++++++ lab05/src/bin/ex3.rs | 4 ++-- lab05/src/bin/ex4_v1.rs | 4 ++-- lab05/src/bin/ex4_v2.rs | 4 ++-- lab05/src/bin/ex4_v3_bus.rs | 2 +- lab05/src/bin/ex4_v3_device.rs | 4 ++-- lab05/src/bin/ex5.rs | 10 +++++----- lab05/src/mpu6500/bus.rs | 4 ++-- lab05/src/mpu6500/device.rs | 2 +- lab05/src/mpu6500/device_blocking.rs | 2 +- 10 files changed, 43 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index ad67955..b283815 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,28 @@ target # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# MacOS +# +## Folder view configuration files +.DS_Store +Desktop.ini + +# Thumbnail cache files +._* +Thumbs.db + +# Files that might appear on external disks +.Spotlight-V100 +.Trashes + +# Compiled Python files +*.pyc + +# Compiled C++ files +*.out + +# Application specific files +venv +node_modules +.sass-cache diff --git a/lab05/src/bin/ex3.rs b/lab05/src/bin/ex3.rs index 1f9dfc1..04609ed 100644 --- a/lab05/src/bin/ex3.rs +++ b/lab05/src/bin/ex3.rs @@ -55,7 +55,7 @@ async fn main(_spawner: Spawner) { // - MOSI - D11 (PA7) // - CLK - D13 (PA5) // - // We use the asynchronous API an we need two free + // We use the asynchronous API and we need two free // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let mut spi = Spi::new( peripherals.SPI1, @@ -107,7 +107,7 @@ async fn main(_spawner: Spawner) { spi.transfer(&mut rx, &command).await.unwrap(); // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); - // Panic if the WHO_AM_I register value if wrong + // Panic if the WHO_AM_I register value is wrong // This is a quick and dirty trick that is not recommended in production firmware, // but works for our example. If this happens in production, the firmware // should gracefully fail. diff --git a/lab05/src/bin/ex4_v1.rs b/lab05/src/bin/ex4_v1.rs index 0648588..b41a64a 100644 --- a/lab05/src/bin/ex4_v1.rs +++ b/lab05/src/bin/ex4_v1.rs @@ -76,7 +76,7 @@ async fn main(_spawner: Spawner) { // - MOSI - D11 (PA7) // - CLK - D13 (PA5) // - // We use the asynchronous API an we need two free + // We use the asynchronous API and we need two free // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let mut spi = Spi::new( peripherals.SPI1, @@ -128,7 +128,7 @@ async fn main(_spawner: Spawner) { spi.transfer(&mut rx, &command).await.unwrap(); // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); - // Panic if the WHO_AM_I register value if wrong + // Panic if the WHO_AM_I register value is wrong // This is a quick and dirty trick that is not recommended in production firmware, // but works for our example. If this happens in production, the firmware // should gracefully fail. diff --git a/lab05/src/bin/ex4_v2.rs b/lab05/src/bin/ex4_v2.rs index 94e730c..939c588 100644 --- a/lab05/src/bin/ex4_v2.rs +++ b/lab05/src/bin/ex4_v2.rs @@ -346,7 +346,7 @@ async fn main(_spawner: Spawner) { // - MOSI - D11 (PA7) // - CLK - D13 (PA5) // - // We use the asynchronous API an we need two free + // We use the asynchronous API and we need two free // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let mut spi = Spi::new( peripherals.SPI1, @@ -398,7 +398,7 @@ async fn main(_spawner: Spawner) { spi.transfer(&mut rx, &command).await.unwrap(); // End the SPI transmission by setting the CS line HIGH. mpu6500_cs_pin.set_high(); - // Panic if the WHO_AM_I register value if wrong + // Panic if the WHO_AM_I register value is wrong // This is a quick and dirty trick that is not recommended in production firmware, // but works for our example. If this happens in production, the firmware // should gracefully fail. diff --git a/lab05/src/bin/ex4_v3_bus.rs b/lab05/src/bin/ex4_v3_bus.rs index 889c0a2..a2ed298 100644 --- a/lab05/src/bin/ex4_v3_bus.rs +++ b/lab05/src/bin/ex4_v3_bus.rs @@ -30,7 +30,7 @@ async fn main(_spawner: Spawner) { // - MOSI - D11 (PA7) // - CLK - D13 (PA5) // - // We use the asynchronous API an we need two free + // We use the asynchronous API and we need two free // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let mut spi = Spi::new( peripherals.SPI1, diff --git a/lab05/src/bin/ex4_v3_device.rs b/lab05/src/bin/ex4_v3_device.rs index 1ea973e..8476778 100644 --- a/lab05/src/bin/ex4_v3_device.rs +++ b/lab05/src/bin/ex4_v3_device.rs @@ -32,7 +32,7 @@ async fn main(_spawner: Spawner) { // - MOSI - D11 (PA7) // - CLK - D13 (PA5) // - // We use the asynchronous API an we need two free + // We use the asynchronous API and we need two free // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 let spi = Spi::new( peripherals.SPI1, @@ -47,7 +47,7 @@ async fn main(_spawner: Spawner) { // We use the D7 (PA8) pin as CS let mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); - // Create a Mutex that so that we can safely share the SPI bus between devices. + // Create a Mutex so that we can safely share the SPI bus between devices. // // Due to the Mutex, only one device will have access to the bus at a time. let spi_mutex = Mutex::::new(spi); diff --git a/lab05/src/bin/ex5.rs b/lab05/src/bin/ex5.rs index 09e26ec..d2e3b4d 100644 --- a/lab05/src/bin/ex5.rs +++ b/lab05/src/bin/ex5.rs @@ -68,7 +68,7 @@ async fn main(_spawner: Spawner) { // - MOSI - D11 (PA7) // - CLK - D13 (PA5) // - // We need a blocking SPI as the `mididsi` display drivers require a blocking SPI device. + // We need a blocking SPI as the `mipidsi` display drivers require a blocking SPI device. let spi = Spi::new_blocking( peripherals.SPI1, peripherals.PA5, @@ -77,7 +77,7 @@ async fn main(_spawner: Spawner) { spi::Config::default(), ); - // Create a Mutex that so that we can safely share the SPI bus between devices. + // Create a Mutex so that we can safely share the SPI bus between devices. // // Due to the Mutex, only one device will have access to the bus at a time. let spi_bus_mutex: Mutex = Mutex::new(RefCell::new(spi)); @@ -128,9 +128,9 @@ async fn main(_spawner: Spawner) { screen.clear(Rgb565::BLACK).unwrap(); let mut style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); - // We set the background color to print the characters with a background, so that we can - // fully overwrite the previous text. Unless we use a background color, the text will - // superpose the letters and digits on the old text. + // We set the background color to print characters with a background, so that we can + // fully overwrite the previous text. Otherwise, the new text will just be drawn + // on top of the old text, making it unreadable. style.set_background_color(Some(Rgb565::BLACK)); if mpu6500.is_connected() { diff --git a/lab05/src/mpu6500/bus.rs b/lab05/src/mpu6500/bus.rs index f7d2ce2..01b1dc2 100644 --- a/lab05/src/mpu6500/bus.rs +++ b/lab05/src/mpu6500/bus.rs @@ -2,7 +2,7 @@ //! //! This driver requires the SPI bus, meaning that the //! SPI bus is not available for other drivers while this -//! driver is available and has not ben dropped. The SPI +//! driver is available and has not been dropped. The SPI //! bus is not available to other drivers even if this //! driver is not transferring anything. //! @@ -116,7 +116,7 @@ impl<'a, S: SpiBus> Mpu6500<'a, S> { Ok(()) => { // If the register's value is the one expected, // we confirm that the MPU 6500 is connected to - // the SPI by returning `true` other wise we + // the SPI by returning `true` otherwise we // return `false`. rx[1] == WHO_AM_I_VALUE } diff --git a/lab05/src/mpu6500/device.rs b/lab05/src/mpu6500/device.rs index cc8e974..7ebed18 100644 --- a/lab05/src/mpu6500/device.rs +++ b/lab05/src/mpu6500/device.rs @@ -108,7 +108,7 @@ impl<'a, S: SpiDevice> Mpu6500<'a, S> { Ok(()) => { // If the register's value is the one expected, // we confirm that the MPU 6500 is connected to - // the SPI by returning `true` other wise we + // the SPI by returning `true` otherwise we // return `false`. rx[1] == WHO_AM_I_VALUE } diff --git a/lab05/src/mpu6500/device_blocking.rs b/lab05/src/mpu6500/device_blocking.rs index 2aa6834..86118ec 100644 --- a/lab05/src/mpu6500/device_blocking.rs +++ b/lab05/src/mpu6500/device_blocking.rs @@ -108,7 +108,7 @@ impl<'a, S: SpiDevice> Mpu6500<'a, S> { Ok(()) => { // If the register's value is the one expected, // we confirm that the MPU 6500 is connected to - // the SPI by returning `true` other wise we + // the SPI by returning `true` otherwise we // return `false`. rx[1] == WHO_AM_I_VALUE } From e5bf0275ed29ecaaea4b70ea8c09ab5f0bae432e Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Thu, 20 Nov 2025 22:05:55 +0200 Subject: [PATCH 18/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/mpu6500/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/mpu6500/mod.rs b/lab05/src/mpu6500/mod.rs index 5ba1558..88cc787 100644 --- a/lab05/src/mpu6500/mod.rs +++ b/lab05/src/mpu6500/mod.rs @@ -25,7 +25,7 @@ const G: f32 = 9.80665; /// /// Instead of using numbers we defined this as an enum to /// make sure that users cannot use any other values -/// than the these. +/// than these. /// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword From c4ec4b36de2a02107c15e517eb6162e1587f682d Mon Sep 17 00:00:00 2001 From: Alexandru Radovici Date: Thu, 20 Nov 2025 22:18:01 +0200 Subject: [PATCH 19/19] typo Co-authored-by: Omer Genan <81963672+genan2003@users.noreply.github.com> --- lab05/src/mpu6500/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab05/src/mpu6500/mod.rs b/lab05/src/mpu6500/mod.rs index 88cc787..6e0ab0d 100644 --- a/lab05/src/mpu6500/mod.rs +++ b/lab05/src/mpu6500/mod.rs @@ -44,7 +44,7 @@ pub enum ConfigRegister { /// /// Instead of using numbers we defined this as an enum to /// make sure that users cannot use any other values -/// than the these. +/// than these. /// /// This is represented as a `u8` so that it can be cast /// to a `u8` using the `as` keyword