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/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..4c7604d 100644 --- a/codebook.toml +++ b/codebook.toml @@ -1,16 +1,23 @@ words = [ "boostrap", "chrono", + "clk", "debouncer", "defmt", + "dma", "dyn", "eabihf", "exti", + "gpdma", "ish", "linker's", "mcu", + "microcontroller", "mille", + "mipidsi", "moder", + "mosi", + "mpu", "nmagic", "pwm", "pwm's", @@ -21,4 +28,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..371c6a9 --- /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 and 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 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. + 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..04609ed --- /dev/null +++ b/lab05/src/bin/ex3.rs @@ -0,0 +1,322 @@ +#![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 gyro 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 and 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 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 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. + 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 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(); + + // 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 { + // 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: + // - 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 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(); + 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..b41a64a --- /dev/null +++ b/lab05/src/bin/ex4_v1.rs @@ -0,0 +1,358 @@ +#![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 lab05::mpu6500::{AccelScale, GyroScale}; +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; + +/// 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()); + 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 and 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 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 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. + 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 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(); + + // 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 { + // 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 + // + // We know that we have set the acceleration scale to 2G + info!( + "Acceleration: X {}, Y {}, Z {}", + 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 + + // 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 + // + // We know that we have set the gyro scale to 1000 deg/s + info!( + "Gyro: X {}, Y {}, Z {}", + 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 new file mode 100644 index 0000000..939c588 --- /dev/null +++ b/lab05/src/bin/ex4_v2.rs @@ -0,0 +1,443 @@ +#![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 _; + +// 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; + +/// 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: +/// - a reference to the SPI bus +/// - a reference to the CS pin +/// - the gyro scale value +async fn set_gyro_scale( + spi: &mut Spi<'_, Async>, + cs: &mut Output<'_>, + scale: GyroScale, +) -> Result<(), Error> { + // 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 +} + +/// 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<'_>, + scale: AccelScale, +) -> Result<(), 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 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. + // + // 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 = [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 to 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 { + // 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 + // + // 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: 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), + } +} + +/// Read the gyro +/// +/// 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: + // | 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 + // + // We have to convert the `i16` value to deg/s. We know that we have set the + // gyro scale to GyroScale::Gs1000. + match res { + // The transmission was successful, extract and return the gyro + Ok(()) => Ok(Gyro { + 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), + } +} + +#[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 and 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 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 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. + 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(); + 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 = 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..a2ed298 --- /dev/null +++ b/lab05/src/bin/ex4_v3_bus.rs @@ -0,0 +1,92 @@ +#![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 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 and 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 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 { + 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..8476778 --- /dev/null +++ b/lab05/src/bin/ex4_v3_device.rs @@ -0,0 +1,102 @@ +#![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 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 and we need two free + // DMA channels. We use GPDMA1_CH0 and GPDMA1_CH1 + let 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 mpu6500_cs_pin = Output::new(peripherals.PA8, Level::High, Speed::Low); + + // 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); + + // 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 { + 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..d2e3b4d --- /dev/null +++ b/lab05/src/bin/ex5.rs @@ -0,0 +1,187 @@ +#![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::{ + Config, + gpio::{Level, Output, Speed}, + rcc::{Pll, PllDiv, PllMul, PllPreDiv, PllSource, Sysclk, VoltageScale, mux}, + 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, renderer::CharacterStyle}, +}; +use mipidsi::{ + interface::SpiInterface, + models::ST7735s, + options::{Orientation, Rotation}, +}; +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 { + 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"); + + // 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); + + // 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 `mipidsi` display drivers require a blocking SPI device. + let spi = Spi::new_blocking( + peripherals.SPI1, + peripherals.PA5, + peripherals.PA7, + peripherals.PA6, + spi::Config::default(), + ); + + // 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)); + + // 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); + + // 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 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); + + // 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(); + let mut style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); + + // 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() { + 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 gyro = mpu6500.read_gyro().unwrap(); + + let mut acceleration_buf = heapless::String::<100>::new(); + core::write!( + &mut acceleration_buf, + "Acceleration:\n X {} \n Y {} \n Z {} ", + acceleration.x, + acceleration.y, + acceleration.z + ) + .unwrap(); + + Text::new(&acceleration_buf, Point::new(0, 20), style) + .draw(&mut screen) + .unwrap(); + + debug!( + "Acceleration: X {}, Y {}, Z {}", + acceleration.x, acceleration.y, acceleration.z + ); + + 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(&gyro_buf, Point::new(0, 120), 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..01b1dc2 --- /dev/null +++ b/lab05/src/mpu6500/bus.rs @@ -0,0 +1,325 @@ +//! MPU 6500 SPI async driver that uses the SPI Bus. +//! +//! 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 been 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. +//! +//! ## 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; + +// 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::{ + 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 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: 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, + // 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 { + // 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` otherwise 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 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> { + 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: 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: 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: SpiBus> 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]; + + // 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 new file mode 100644 index 0000000..7ebed18 --- /dev/null +++ b/lab05/src/mpu6500/device.rs @@ -0,0 +1,285 @@ +//! 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. +//! +//! ## 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 +// 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::{ + 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, + // 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 { + // 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` otherwise 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.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 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: 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: 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 new file mode 100644 index 0000000..86118ec --- /dev/null +++ b/lab05/src/mpu6500/device_blocking.rs @@ -0,0 +1,278 @@ +//! 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. +//! +//! ## 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 +// 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::{ + 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, + // 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 { + // 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` otherwise 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> { + 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 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: 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: 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 new file mode 100644 index 0000000..6e0ab0d --- /dev/null +++ b/lab05/src/mpu6500/mod.rs @@ -0,0 +1,141 @@ +//! 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; + +/// 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 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 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 ValueRegister { + AccelXOutH = 0x3b, + GyroXOutH = 0x43, +} + +/// 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 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 GyroScale { + Gs250 = 0b00, + Gs500 = 0b01, + Gs1000 = 0b10, + 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. +/// +/// Instead of using numbers we defined this as an enum to +/// make sure that users cannot use any other values +/// than 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 AccelScale { + G2 = 0b00, + G4 = 0b01, + G8 = 0b10, + 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: f32, + pub y: f32, + pub z: f32, +} + +/// Stores the gyro values on all the three axes. +pub struct Gyro { + pub x: f32, + pub y: f32, + pub z: f32, +}