From 2de020f31d4a0b6e291e7be1cd3b74ddeb0db84a Mon Sep 17 00:00:00 2001 From: Eva Cosma Date: Wed, 8 Apr 2026 18:17:01 +0300 Subject: [PATCH 1/2] feat: lab06 Signed-off-by: Eva Cosma --- Cargo.lock | 22 +++++++ Cargo.toml | 2 +- lab06/.cargo/config.toml | 8 +++ lab06/Cargo.toml | 32 ++++++++++ lab06/build.rs | 46 ++++++++++++++ lab06/src/bin/ex1.rs | 53 ++++++++++++++++ lab06/src/bin/ex2.rs | 96 +++++++++++++++++++++++++++++ lab06/src/bin/ex3.rs | 93 ++++++++++++++++++++++++++++ lab06/src/bin/ex4.rs | 103 +++++++++++++++++++++++++++++++ lab06/src/bin/ex5.rs | 130 +++++++++++++++++++++++++++++++++++++++ lab06/src/lib.rs | 1 + 11 files changed, 585 insertions(+), 1 deletion(-) create mode 100644 lab06/.cargo/config.toml create mode 100644 lab06/Cargo.toml create mode 100644 lab06/build.rs create mode 100644 lab06/src/bin/ex1.rs create mode 100644 lab06/src/bin/ex2.rs create mode 100644 lab06/src/bin/ex3.rs create mode 100644 lab06/src/bin/ex4.rs create mode 100644 lab06/src/bin/ex5.rs create mode 100644 lab06/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f6e3047..0f21e71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -708,6 +708,28 @@ dependencies = [ "panic-probe", ] +[[package]] +name = "lab06" +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", +] + [[package]] name = "litrs" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 92958b6..e014a32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "3" -members = ["lab01", "lab02", "lab03", "lab04", "lab05"] +members = ["lab01", "lab02", "lab03", "lab04", "lab05", "lab06"] [workspace.dependencies] # Low level access to Cortex-M processors diff --git a/lab06/.cargo/config.toml b/lab06/.cargo/config.toml new file mode 100644 index 0000000..2399423 --- /dev/null +++ b/lab06/.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/lab06/Cargo.toml b/lab06/Cargo.toml new file mode 100644 index 0000000..f7c49a7 --- /dev/null +++ b/lab06/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "lab06" +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/lab06/build.rs b/lab06/build.rs new file mode 100644 index 0000000..f5ee10f --- /dev/null +++ b/lab06/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/lab06/src/bin/ex1.rs b/lab06/src/bin/ex1.rs new file mode 100644 index 0000000..dc42817 --- /dev/null +++ b/lab06/src/bin/ex1.rs @@ -0,0 +1,53 @@ +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + bind_interrupts, + i2c::{self, I2c}, + peripherals, +}; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +const LOWEST_I2C_ADDR: u8 = 0x08; +const HIGHEST_I2C_ADDR: u8 = 0x77; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + // I2C pins + let sda = peripherals.PB7; + let scl = peripherals.PB6; + + // I2C definition + let mut i2c = I2c::new( + peripherals.I2C1, + scl, + sda, + Irqs, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + Default::default(), + ); + + for addr in LOWEST_I2C_ADDR..=HIGHEST_I2C_ADDR { + let mut rx_buf = [0x00u8; 1]; + let res = i2c.write_read(addr, &[0x00], &mut rx_buf).await; + info!( + "Result for address 0x{:02x}: {:?} | Data: {:?}", + addr, res, rx_buf + ); + } + + #[allow(clippy::empty_loop)] + loop {} +} diff --git a/lab06/src/bin/ex2.rs b/lab06/src/bin/ex2.rs new file mode 100644 index 0000000..c4b130e --- /dev/null +++ b/lab06/src/bin/ex2.rs @@ -0,0 +1,96 @@ +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + bind_interrupts, + i2c::{self, I2c}, + peripherals, +}; +use embassy_time::Timer; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +// Note: A0 must be hooked to low, otherwise the address is 0x77 +const BMP390_ADDR: u8 = 0x76; + +const REGISTER_PWR_CTRL: u8 = 0x1B; +const PWR_MODE_ON: u8 = 0b0011_0000; +const PWR_TEMP_EN: u8 = 0b0000_0010; +const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; + +const REGISTER_OSR: u8 = 0x1C; +const OSR_TEMP_X2: u8 = 0b0000_1000; +const OSR_VAL: u8 = OSR_TEMP_X2; + +const REGISTER_TEMP_XLSB: u8 = 0x07; +const REGISTER_TEMP_LSB: u8 = 0x08; +const REGISTER_TEMP_MSB: u8 = 0x09; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + // I2C pins + let sda = peripherals.PB7; + let scl = peripherals.PB6; + + // I2C definition + let mut i2c = I2c::new( + peripherals.I2C1, + scl, + sda, + Irqs, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + Default::default(), + ); + + i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) + .await + .unwrap(); + + i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) + .await + .unwrap(); + + loop { + let mut raw_temp_data = [0u8; 3]; + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) + .await + .unwrap(); + + let mut raw_xlsb = [0u8; 1]; + let mut raw_lsb = [0u8; 1]; + let mut raw_msb = [0u8; 1]; + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_xlsb) + .await + .unwrap(); + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_LSB], &mut raw_lsb) + .await + .unwrap(); + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_MSB], &mut raw_msb) + .await + .unwrap(); + + info!("Raw data: {:?}", raw_temp_data); + info!( + "Raw byte by byte: {:?}", + [raw_xlsb[0], raw_lsb[0], raw_msb[0]] + ); + + let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) + | ((raw_temp_data[1] as i32) << 8) + | (raw_temp_data[0] as i32); + info!("Raw temperature value: {}", raw_temp); + + Timer::after_millis(400).await; + } +} diff --git a/lab06/src/bin/ex3.rs b/lab06/src/bin/ex3.rs new file mode 100644 index 0000000..30ec350 --- /dev/null +++ b/lab06/src/bin/ex3.rs @@ -0,0 +1,93 @@ +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + bind_interrupts, + i2c::{self, I2c}, + peripherals, +}; +use embassy_time::Timer; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +// Note: A0 must be hooked to low, otherwise the address is 0x77 +const BMP390_ADDR: u8 = 0x76; + +const REGISTER_PWR_CTRL: u8 = 0x1B; +const PWR_MODE_ON: u8 = 0b0011_0000; +const PWR_TEMP_EN: u8 = 0b0000_0010; +const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; + +const REGISTER_OSR: u8 = 0x1C; +const OSR_TEMP_X2: u8 = 0b0000_1000; +const OSR_VAL: u8 = OSR_TEMP_X2; + +const REGISTER_TEMP_XLSB: u8 = 0x07; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + // I2C pins + let sda = peripherals.PB7; + let scl = peripherals.PB6; + + // I2C definition + let mut i2c = I2c::new( + peripherals.I2C1, + scl, + sda, + Irqs, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + Default::default(), + ); + + i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) + .await + .unwrap(); + + i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) + .await + .unwrap(); + + // Mock calibration parameters + let nvm_par_t1: u16 = 27504; + let nvm_par_t2: u16 = 26435; + let nvm_par_t3: i8 = -50; + + let par_t1 = (nvm_par_t1 as f32) / 0.00390625; // 2^-8 + let par_t2 = (nvm_par_t2 as f32) / 1073741824.0; // 2^30 + let par_t3 = (nvm_par_t3 as f32) / 281474976710656.0; // 2^48 + + loop { + let mut raw_temp_data = [0u8; 3]; + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) + .await + .unwrap(); + + let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) + | ((raw_temp_data[1] as i32) << 8) + | (raw_temp_data[0] as i32); + info!("Raw temperature value: {}", raw_temp); + + // Based on Appendix 8.5: Temperature compensation + // `raw_temp` is the u32 value read from registers 0x07..0x09 + let partial_data1 = (raw_temp as f32) - par_t1; + let partial_data2 = partial_data1 * par_t2; + + // t_lin is the compensated temperature in degrees Celsius + let t_lin = partial_data2 + (partial_data1 * partial_data1) * par_t3; + + info!("Compensated temperature value: {} °C", t_lin); + Timer::after_millis(400).await; + } +} diff --git a/lab06/src/bin/ex4.rs b/lab06/src/bin/ex4.rs new file mode 100644 index 0000000..eb7c27d --- /dev/null +++ b/lab06/src/bin/ex4.rs @@ -0,0 +1,103 @@ +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + bind_interrupts, + i2c::{self, I2c}, + peripherals, +}; +use embassy_time::Timer; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +// Note: A0 must be hooked to low, otherwise the address is 0x77 +const BMP390_ADDR: u8 = 0x76; + +const REGISTER_PWR_CTRL: u8 = 0x1B; +const PWR_MODE_ON: u8 = 0b0011_0000; +const PWR_TEMP_EN: u8 = 0b0000_0010; +const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; + +const REGISTER_OSR: u8 = 0x1C; +const OSR_TEMP_X2: u8 = 0b0000_1000; +const OSR_VAL: u8 = OSR_TEMP_X2; + +const REGISTER_TEMP_XLSB: u8 = 0x07; + +const REGISTER_NVM_PAR_T1: u8 = 0x31; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + // I2C pins + let sda = peripherals.PB7; + let scl = peripherals.PB6; + + // I2C definition + let mut i2c = I2c::new( + peripherals.I2C1, + scl, + sda, + Irqs, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + Default::default(), + ); + + i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) + .await + .unwrap(); + + i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) + .await + .unwrap(); + + // Read NVM calibration parameters (5 bytes in total) + let mut nvm_data = [0u8; 5]; + i2c.write_read(BMP390_ADDR, &[REGISTER_NVM_PAR_T1], &mut nvm_data) + .await + .unwrap(); + + // 0x31 (LSB) & 0x32 (MSB) -> u16 + let nvm_par_t1: u16 = ((nvm_data[1] as u16) << 8) | (nvm_data[0] as u16); + // 0x33 (LSB) & 0x34 (MSB) -> u16 + let nvm_par_t2: u16 = ((nvm_data[3] as u16) << 8) | (nvm_data[2] as u16); + // 0x35 -> i8 (Note: This is an 8-bit signed value!) + let nvm_par_t3: i8 = nvm_data[4] as i8; + + let par_t1 = (nvm_par_t1 as f32) / 0.00390625; // 2^-8 + let par_t2 = (nvm_par_t2 as f32) / 1073741824.0; // 2^30 + let par_t3 = (nvm_par_t3 as f32) / 281474976710656.0; // 2^48 + + loop { + let mut raw_temp_data = [0u8; 3]; + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) + .await + .unwrap(); + + let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) + | ((raw_temp_data[1] as i32) << 8) + | (raw_temp_data[0] as i32); + info!("Raw temperature value: {}", raw_temp); + + // Based on Appendix 8.5: Temperature compensation + // `raw_temp` is the u32 value read from registers 0x07..0x09 + let partial_data1 = (raw_temp as f32) - par_t1; + let partial_data2 = partial_data1 * par_t2; + + // t_lin is the compensated temperature in degrees Celsius + let t_lin = partial_data2 + (partial_data1 * partial_data1) * par_t3; + + info!("Compensated temperature value: {} °C", t_lin); + Timer::after_millis(400).await; + } +} diff --git a/lab06/src/bin/ex5.rs b/lab06/src/bin/ex5.rs new file mode 100644 index 0000000..361e8a1 --- /dev/null +++ b/lab06/src/bin/ex5.rs @@ -0,0 +1,130 @@ +#![no_std] +#![no_main] + +use defmt::info; +use defmt_rtt as _; +use embassy_executor::Spawner; +use embassy_stm32::{ + bind_interrupts, + i2c::{self, I2c}, + peripherals, +}; +use embassy_time::Timer; +use panic_probe as _; + +bind_interrupts!(struct Irqs { + I2C1_EV => i2c::EventInterruptHandler; + I2C1_ER => i2c::ErrorInterruptHandler; +}); + +// Note: A0 must be hooked to low, otherwise the address is 0x77 +const BMP390_ADDR: u8 = 0x76; +const AT24C256_ADDR: u8 = 0x50; // Note: A0, A1, A2 are all hooked to GND + +const REGISTER_PWR_CTRL: u8 = 0x1B; +const PWR_MODE_ON: u8 = 0b0011_0000; +const PWR_TEMP_EN: u8 = 0b0000_0010; +const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; + +const REGISTER_OSR: u8 = 0x1C; +const OSR_TEMP_X2: u8 = 0b0000_1000; +const OSR_VAL: u8 = OSR_TEMP_X2; + +const REGISTER_TEMP_XLSB: u8 = 0x07; + +const REGISTER_NVM_PAR_T1: u8 = 0x31; + +const TEMP_SAVE_ADDRESS: u16 = 0xACDC; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let peripherals = embassy_stm32::init(Default::default()); + info!("Device started"); + + // I2C pins + let sda = peripherals.PB7; + let scl = peripherals.PB6; + + // I2C definition + let mut i2c = I2c::new( + peripherals.I2C1, + scl, + sda, + Irqs, + peripherals.GPDMA1_CH0, + peripherals.GPDMA1_CH1, + Default::default(), + ); + + i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) + .await + .unwrap(); + + i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) + .await + .unwrap(); + + // Read NVM calibration parameters (5 bytes in total) + let mut nvm_data = [0u8; 5]; + i2c.write_read(BMP390_ADDR, &[REGISTER_NVM_PAR_T1], &mut nvm_data) + .await + .unwrap(); + + // 0x31 (LSB) & 0x32 (MSB) -> u16 + let nvm_par_t1: u16 = ((nvm_data[1] as u16) << 8) | (nvm_data[0] as u16); + // 0x33 (LSB) & 0x34 (MSB) -> u16 + let nvm_par_t2: u16 = ((nvm_data[3] as u16) << 8) | (nvm_data[2] as u16); + // 0x35 -> i8 (Note: This is an 8-bit signed value!) + let nvm_par_t3: i8 = nvm_data[4] as i8; + + let par_t1 = (nvm_par_t1 as f32) / 0.00390625; // 2^-8 + let par_t2 = (nvm_par_t2 as f32) / 1073741824.0; // 2^30 + let par_t3 = (nvm_par_t3 as f32) / 281474976710656.0; // 2^48 + + // Read previous temperature value from EEPROM + let mut prev_temp_data = [0u8; 4]; + i2c.write_read( + AT24C256_ADDR, + &TEMP_SAVE_ADDRESS.to_be_bytes(), + &mut prev_temp_data, + ) + .await + .unwrap(); + let prev_temp_hundredths = i32::from_be_bytes(prev_temp_data); + let prev_temp = (prev_temp_hundredths as f32) / 100.0; + info!( + "Previous temperature value read from EEPROM: {} °C", + prev_temp + ); + + loop { + let mut raw_temp_data = [0u8; 3]; + i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) + .await + .unwrap(); + + let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) + | ((raw_temp_data[1] as i32) << 8) + | (raw_temp_data[0] as i32); + info!("Raw temperature value: {}", raw_temp); + + // Based on Appendix 8.5: Temperature compensation + // `raw_temp` is the u32 value read from registers 0x07..0x09 + let partial_data1 = (raw_temp as f32) - par_t1; + let partial_data2 = partial_data1 * par_t2; + + // t_lin is the compensated temperature in degrees Celsius + let t_lin = partial_data2 + (partial_data1 * partial_data1) * par_t3; + + info!("Compensated temperature value: {} °C", t_lin); + + let hundredths = (t_lin * 100.0) as i32; + let mut tx_buf = [0u8; 2 + 4]; + tx_buf[..2].copy_from_slice(&TEMP_SAVE_ADDRESS.to_be_bytes()); + tx_buf[2..].copy_from_slice(&hundredths.to_be_bytes()); + + i2c.write(AT24C256_ADDR, &tx_buf).await.unwrap(); + + Timer::after_millis(400).await; + } +} diff --git a/lab06/src/lib.rs b/lab06/src/lib.rs new file mode 100644 index 0000000..0c9ac1a --- /dev/null +++ b/lab06/src/lib.rs @@ -0,0 +1 @@ +#![no_std] From 85e346323da4eeadde24051bd0311d41c208d087 Mon Sep 17 00:00:00 2001 From: Eva Cosma Date: Wed, 8 Apr 2026 23:42:53 +0300 Subject: [PATCH 2/2] feat: lab06 clarifying comments Signed-off-by: Eva Cosma --- lab06/src/bin/ex1.rs | 36 ++++++++++++--- lab06/src/bin/ex2.rs | 71 +++++++++++++++++++++++++++-- lab06/src/bin/ex3.rs | 67 +++++++++++++++++++++++++--- lab06/src/bin/ex4.rs | 71 ++++++++++++++++++++++++++--- lab06/src/bin/ex5.rs | 104 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 320 insertions(+), 29 deletions(-) diff --git a/lab06/src/bin/ex1.rs b/lab06/src/bin/ex1.rs index dc42817..cc363d3 100644 --- a/lab06/src/bin/ex1.rs +++ b/lab06/src/bin/ex1.rs @@ -9,24 +9,32 @@ use embassy_stm32::{ i2c::{self, I2c}, peripherals, }; +use embassy_time::Timer; use panic_probe as _; +// For I2C to work, we need to bind the interrupts to the correct handlers. bind_interrupts!(struct Irqs { I2C1_EV => i2c::EventInterruptHandler; I2C1_ER => i2c::ErrorInterruptHandler; }); +/// The lowest I2C address a device can have. Addresses below this are reserved +/// for special purposes. const LOWEST_I2C_ADDR: u8 = 0x08; +/// The highest I2C address a device can have. Addresses above this are reserved +/// for special purposes. const HIGHEST_I2C_ADDR: u8 = 0x77; #[embassy_executor::main] async fn main(_spawner: Spawner) { + // Initialize the device peripherals let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); - // I2C pins - let sda = peripherals.PB7; + // Define the I2C pins. In this lab we will use the I2C1 peripheral, which + // is connected to PB6 (SCL) and PB7 (SDA). let scl = peripherals.PB6; + let sda = peripherals.PB7; // I2C definition let mut i2c = I2c::new( @@ -39,8 +47,25 @@ async fn main(_spawner: Spawner) { Default::default(), ); + // Scan the I2C bus for devices. We do this by trying to read from each + // possible address. + for addr in LOWEST_I2C_ADDR..=HIGHEST_I2C_ADDR { - let mut rx_buf = [0x00u8; 1]; + // To execute a "scan", we can either try to execute a read directly, or + // we can write a dummy register (0x00) and then wait for a response + // + // The first approach (read-only) may work with some devices, but it may + // not work devices which decide to ignore read requests without a + // preceding write. It will also take longer to get a timeout, which + // will make the scan take longer to complete. + // + // The second approach (write-read) is more likely to work with a wider + // range of devices, but it is not guaranteed that all devices will have + // a `0x00` register. + // + // For this lab, the BMP390 has its chip ID register at `0x00`, and the + // AT24C256 has its first memory address at `0x00`. + let mut rx_buf = [0u8; 1]; let res = i2c.write_read(addr, &[0x00], &mut rx_buf).await; info!( "Result for address 0x{:02x}: {:?} | Data: {:?}", @@ -48,6 +73,7 @@ async fn main(_spawner: Spawner) { ); } - #[allow(clippy::empty_loop)] - loop {} + loop { + Timer::after_secs(1).await; + } } diff --git a/lab06/src/bin/ex2.rs b/lab06/src/bin/ex2.rs index c4b130e..f0a38e7 100644 --- a/lab06/src/bin/ex2.rs +++ b/lab06/src/bin/ex2.rs @@ -12,35 +12,84 @@ use embassy_stm32::{ use embassy_time::Timer; use panic_probe as _; +// For I2C to work, we need to bind the interrupts to the correct handlers. bind_interrupts!(struct Irqs { I2C1_EV => i2c::EventInterruptHandler; I2C1_ER => i2c::ErrorInterruptHandler; }); -// Note: A0 must be hooked to low, otherwise the address is 0x77 +/// I2C device address for the BMP390. This is the default address when A0 is +/// connected to low. If A0 is connected to high, the address will be 0x77. +/// +/// This will likely work even if A0 is not connected at all, in theory this +/// should be a "floating" pin, but in practice for our PM Lab board it will +/// function as if it were connected to low. const BMP390_ADDR: u8 = 0x76; +/// Register addresses and values for the `PWR_CTRL` register. (Turns on and off +/// measurements and sets the power mode.) +/// +/// | Bits | Description | +/// |------|-------------------------------------------------| +/// | 0 | Pressure measurement on/off. 1 = on, 0 = off | +/// | 1 | Temperature measurement on/off. 1 = on, 0 = off | +/// | 2-3 | Reserved, must be set to 0 | +/// | 4-5 | Power mode. 00 = sleep, 01/10 = forced (one +/// measurement), 11 = normal | +/// | 6-7 | Reserved, must be set to 0 | const REGISTER_PWR_CTRL: u8 = 0x1B; +/// Bits to set in the `PWR_CTRL` register to set normal power mode. const PWR_MODE_ON: u8 = 0b0011_0000; +/// Bits to set in the `PWR_CTRL` register to enable temperature measurement. const PWR_TEMP_EN: u8 = 0b0000_0010; +/// Value to write to the `PWR_CTRL` register to enable temperature measurement +/// and set normal power mode. const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; +/// Register addresses and values for the `OSR` register. (Controls how many +/// samples are taken and averaged for each measurement) +/// +/// | Bits | Description | +/// |------|-------------------------------------------------| +/// | 0-2 | Pressure oversampling. +/// 000 = no oversampling (1 sample), +/// 001 = x2 (2 samples), +/// 010 = x4 (4 samples), +/// 011 = x8 (8 samples), +/// 100 = x16 (16 samples), +/// 101 = x32 (32 samples), | +/// | 3-5 | Temperature oversampling. +/// 000 = no oversampling (1 sample), +/// 001 = x2 (2 samples), +/// 010 = x4 (4 samples), +/// 011 = x8 (8 samples), +/// 100 = x16 (16 samples), +/// 101 = x32 (32 samples), | +/// | 6-7 | Reserved, must be set to 0 | const REGISTER_OSR: u8 = 0x1C; +/// Bits to set in the `OSR` register to set temperature oversampling to x2. const OSR_TEMP_X2: u8 = 0b0000_1000; +/// Value to write to the `OSR` register to set temperature oversampling to x2 +/// and pressure oversampling to none. const OSR_VAL: u8 = OSR_TEMP_X2; +/// Register addresses for the raw temperature data (Least significant bits). const REGISTER_TEMP_XLSB: u8 = 0x07; +/// Register addresses for the raw temperature data (Middle significant bits). const REGISTER_TEMP_LSB: u8 = 0x08; +/// Register addresses for the raw temperature data (Most significant bits). const REGISTER_TEMP_MSB: u8 = 0x09; #[embassy_executor::main] async fn main(_spawner: Spawner) { + // Initialize the device peripherals let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); - // I2C pins - let sda = peripherals.PB7; + // Define the I2C pins. In this lab we will use the I2C1 peripheral, which + // is connected to PB6 (SCL) and PB7 (SDA). let scl = peripherals.PB6; + let sda = peripherals.PB7; // I2C definition let mut i2c = I2c::new( @@ -53,20 +102,35 @@ async fn main(_spawner: Spawner) { Default::default(), ); + // Before we can read any data from the sensor, we need to configure the power. i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) .await .unwrap(); + // And we also need to configure the oversampling settings. i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) .await .unwrap(); + // Read the raw temperature data. There are 2 ways we can do this. loop { + // The first way is to read all 3 bytes in one go. Since the registers + // are consecutige, the BMP automatically increments the register + // address after each byte, so we can just read from the first register, + // and so long as we have more bytes to read, the main (us, the MCU) + // will send ACK to continue reading. After we read the last byte (as + // determined by the size of the array), we will send a NACK indicating + // to the BMP that we are done reading. This will ensure that the + // temperature data is consistent. let mut raw_temp_data = [0u8; 3]; i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) .await .unwrap(); + // Alternatively, we can read each byte one by one. This is more likely + // to cause inconsistent data, since the BMP may update the temperature + // data in between our reads, but it is still possible to get the + // correct data if we are lucky. let mut raw_xlsb = [0u8; 1]; let mut raw_lsb = [0u8; 1]; let mut raw_msb = [0u8; 1]; @@ -86,6 +150,7 @@ async fn main(_spawner: Spawner) { [raw_xlsb[0], raw_lsb[0], raw_msb[0]] ); + // The raw temperature data is a 24-bit signed integer let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) | ((raw_temp_data[1] as i32) << 8) | (raw_temp_data[0] as i32); diff --git a/lab06/src/bin/ex3.rs b/lab06/src/bin/ex3.rs index 30ec350..659fa4a 100644 --- a/lab06/src/bin/ex3.rs +++ b/lab06/src/bin/ex3.rs @@ -12,33 +12,80 @@ use embassy_stm32::{ use embassy_time::Timer; use panic_probe as _; +// For I2C to work, we need to bind the interrupts to the correct handlers. bind_interrupts!(struct Irqs { I2C1_EV => i2c::EventInterruptHandler; I2C1_ER => i2c::ErrorInterruptHandler; }); -// Note: A0 must be hooked to low, otherwise the address is 0x77 +/// I2C device address for the BMP390. This is the default address when A0 is +/// connected to low. If A0 is connected to high, the address will be 0x77. +/// +/// This will likely work even if A0 is not connected at all, in theory this +/// should be a "floating" pin, but in practice for our PM Lab board it will +/// function as if it were connected to low. const BMP390_ADDR: u8 = 0x76; +/// Register addresses and values for the `PWR_CTRL` register. (Turns on and off +/// measurements and sets the power mode.) +/// +/// | Bits | Description | +/// |------|-------------------------------------------------| +/// | 0 | Pressure measurement on/off. 1 = on, 0 = off | +/// | 1 | Temperature measurement on/off. 1 = on, 0 = off | +/// | 2-3 | Reserved, must be set to 0 | +/// | 4-5 | Power mode. 00 = sleep, 01/10 = forced (one +/// measurement), 11 = normal | +/// | 6-7 | Reserved, must be set to 0 | const REGISTER_PWR_CTRL: u8 = 0x1B; +/// Bits to set in the `PWR_CTRL` register to set normal power mode. const PWR_MODE_ON: u8 = 0b0011_0000; +/// Bits to set in the `PWR_CTRL` register to enable temperature measurement. const PWR_TEMP_EN: u8 = 0b0000_0010; +/// Value to write to the `PWR_CTRL` register to enable temperature measurement +/// and set normal power mode. const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; +/// Register addresses and values for the `OSR` register. (Controls how many +/// samples are taken and averaged for each measurement) +/// +/// | Bits | Description | +/// |------|-------------------------------------------------| +/// | 0-2 | Pressure oversampling. +/// 000 = no oversampling (1 sample), +/// 001 = x2 (2 samples), +/// 010 = x4 (4 samples), +/// 011 = x8 (8 samples), +/// 100 = x16 (16 samples), +/// 101 = x32 (32 samples), | +/// | 3-5 | Temperature oversampling. +/// 000 = no oversampling (1 sample), +/// 001 = x2 (2 samples), +/// 010 = x4 (4 samples), +/// 011 = x8 (8 samples), +/// 100 = x16 (16 samples), +/// 101 = x32 (32 samples), | +/// | 6-7 | Reserved, must be set to 0 | const REGISTER_OSR: u8 = 0x1C; +/// Bits to set in the `OSR` register to set temperature oversampling to x2. const OSR_TEMP_X2: u8 = 0b0000_1000; +/// Value to write to the `OSR` register to set temperature oversampling to x2 +/// and pressure oversampling to none. const OSR_VAL: u8 = OSR_TEMP_X2; +/// Register addresses for the raw temperature data (Least significant bits). const REGISTER_TEMP_XLSB: u8 = 0x07; #[embassy_executor::main] async fn main(_spawner: Spawner) { + // Initialize the device peripherals let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); - // I2C pins - let sda = peripherals.PB7; + // Define the I2C pins. In this lab we will use the I2C1 peripheral, which + // is connected to PB6 (SCL) and PB7 (SDA). let scl = peripherals.PB6; + let sda = peripherals.PB7; // I2C definition let mut i2c = I2c::new( @@ -51,36 +98,44 @@ async fn main(_spawner: Spawner) { Default::default(), ); + // Before we can read any data from the sensor, we need to configure the power. i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) .await .unwrap(); + // And we also need to configure the oversampling settings. i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) .await .unwrap(); - // Mock calibration parameters + // We will be using the mock calibration parameters from the lab + // instructions: let nvm_par_t1: u16 = 27504; let nvm_par_t2: u16 = 26435; let nvm_par_t3: i8 = -50; + // And calculate the parameters we need for the compensation formula based + // on the raw values. Formula can be found in datasheet. let par_t1 = (nvm_par_t1 as f32) / 0.00390625; // 2^-8 let par_t2 = (nvm_par_t2 as f32) / 1073741824.0; // 2^30 let par_t3 = (nvm_par_t3 as f32) / 281474976710656.0; // 2^48 loop { + // Read the raw temperature data in one go. (See ex2 for further + // explanation) let mut raw_temp_data = [0u8; 3]; i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) .await .unwrap(); + // Based on Appendix 8.5: Temperature compensation `raw_temp` is the u32 + // value read from registers 0x07..0x09 let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) | ((raw_temp_data[1] as i32) << 8) | (raw_temp_data[0] as i32); info!("Raw temperature value: {}", raw_temp); - // Based on Appendix 8.5: Temperature compensation - // `raw_temp` is the u32 value read from registers 0x07..0x09 + // Formulas taken from datasheet let partial_data1 = (raw_temp as f32) - par_t1; let partial_data2 = partial_data1 * par_t2; diff --git a/lab06/src/bin/ex4.rs b/lab06/src/bin/ex4.rs index eb7c27d..d703595 100644 --- a/lab06/src/bin/ex4.rs +++ b/lab06/src/bin/ex4.rs @@ -12,35 +12,84 @@ use embassy_stm32::{ use embassy_time::Timer; use panic_probe as _; +// For I2C to work, we need to bind the interrupts to the correct handlers. bind_interrupts!(struct Irqs { I2C1_EV => i2c::EventInterruptHandler; I2C1_ER => i2c::ErrorInterruptHandler; }); -// Note: A0 must be hooked to low, otherwise the address is 0x77 +/// I2C device address for the BMP390. This is the default address when A0 is +/// connected to low. If A0 is connected to high, the address will be 0x77. +/// +/// This will likely work even if A0 is not connected at all, in theory this +/// should be a "floating" pin, but in practice for our PM Lab board it will +/// function as if it were connected to low. const BMP390_ADDR: u8 = 0x76; +/// Register addresses and values for the `PWR_CTRL` register. (Turns on and off +/// measurements and sets the power mode.) +/// +/// | Bits | Description | +/// |------|-------------------------------------------------| +/// | 0 | Pressure measurement on/off. 1 = on, 0 = off | +/// | 1 | Temperature measurement on/off. 1 = on, 0 = off | +/// | 2-3 | Reserved, must be set to 0 | +/// | 4-5 | Power mode. 00 = sleep, 01/10 = forced (one +/// measurement), 11 = normal | +/// | 6-7 | Reserved, must be set to 0 | const REGISTER_PWR_CTRL: u8 = 0x1B; +/// Bits to set in the `PWR_CTRL` register to set normal power mode. const PWR_MODE_ON: u8 = 0b0011_0000; +/// Bits to set in the `PWR_CTRL` register to enable temperature measurement. const PWR_TEMP_EN: u8 = 0b0000_0010; +/// Value to write to the `PWR_CTRL` register to enable temperature measurement +/// and set normal power mode. const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; +/// Register addresses and values for the `OSR` register. (Controls how many +/// samples are taken and averaged for each measurement) +/// +/// | Bits | Description | +/// |------|-------------------------------------------------| +/// | 0-2 | Pressure oversampling. +/// 000 = no oversampling (1 sample), +/// 001 = x2 (2 samples), +/// 010 = x4 (4 samples), +/// 011 = x8 (8 samples), +/// 100 = x16 (16 samples), +/// 101 = x32 (32 samples), | +/// | 3-5 | Temperature oversampling. +/// 000 = no oversampling (1 sample), +/// 001 = x2 (2 samples), +/// 010 = x4 (4 samples), +/// 011 = x8 (8 samples), +/// 100 = x16 (16 samples), +/// 101 = x32 (32 samples), | +/// | 6-7 | Reserved, must be set to 0 | const REGISTER_OSR: u8 = 0x1C; +/// Bits to set in the `OSR` register to set temperature oversampling to x2. const OSR_TEMP_X2: u8 = 0b0000_1000; +/// Value to write to the `OSR` register to set temperature oversampling to x2 +/// and pressure oversampling to none. const OSR_VAL: u8 = OSR_TEMP_X2; +/// Register addresses for the raw temperature data (Least significant bits). const REGISTER_TEMP_XLSB: u8 = 0x07; - +/// Register addresses for the calibration data. The calibration data is 5 +/// registers in order. We can read them in one go by starting a sequential read +/// from the first register. const REGISTER_NVM_PAR_T1: u8 = 0x31; #[embassy_executor::main] async fn main(_spawner: Spawner) { + // Initialize the device peripherals let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); - // I2C pins - let sda = peripherals.PB7; + // Define the I2C pins. In this lab we will use the I2C1 peripheral, which + // is connected to PB6 (SCL) and PB7 (SDA). let scl = peripherals.PB6; + let sda = peripherals.PB7; // I2C definition let mut i2c = I2c::new( @@ -53,20 +102,25 @@ async fn main(_spawner: Spawner) { Default::default(), ); + // Before we can read any data from the sensor, we need to configure the power. i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) .await .unwrap(); + // And we also need to configure the oversampling settings. i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) .await .unwrap(); - // Read NVM calibration parameters (5 bytes in total) + // Read NVM calibration parameters (5 bytes in total). See ex2 for more + // details on the sequential read. let mut nvm_data = [0u8; 5]; i2c.write_read(BMP390_ADDR, &[REGISTER_NVM_PAR_T1], &mut nvm_data) .await .unwrap(); + // Formulas for the partial calibration parameters taken from the datasheet. + // 0x31 (LSB) & 0x32 (MSB) -> u16 let nvm_par_t1: u16 = ((nvm_data[1] as u16) << 8) | (nvm_data[0] as u16); // 0x33 (LSB) & 0x34 (MSB) -> u16 @@ -79,18 +133,21 @@ async fn main(_spawner: Spawner) { let par_t3 = (nvm_par_t3 as f32) / 281474976710656.0; // 2^48 loop { + // Read the raw temperature data in one go. (See ex2 for further + // explanation) let mut raw_temp_data = [0u8; 3]; i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) .await .unwrap(); + // Based on Appendix 8.5: Temperature compensation `raw_temp` is the u32 + // value read from registers 0x07..0x09 let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) | ((raw_temp_data[1] as i32) << 8) | (raw_temp_data[0] as i32); info!("Raw temperature value: {}", raw_temp); - // Based on Appendix 8.5: Temperature compensation - // `raw_temp` is the u32 value read from registers 0x07..0x09 + // Formulas taken from datasheet let partial_data1 = (raw_temp as f32) - par_t1; let partial_data2 = partial_data1 * par_t2; diff --git a/lab06/src/bin/ex5.rs b/lab06/src/bin/ex5.rs index 361e8a1..fd27d06 100644 --- a/lab06/src/bin/ex5.rs +++ b/lab06/src/bin/ex5.rs @@ -12,38 +12,91 @@ use embassy_stm32::{ use embassy_time::Timer; use panic_probe as _; +// For I2C to work, we need to bind the interrupts to the correct handlers. bind_interrupts!(struct Irqs { I2C1_EV => i2c::EventInterruptHandler; I2C1_ER => i2c::ErrorInterruptHandler; }); -// Note: A0 must be hooked to low, otherwise the address is 0x77 +/// I2C device address for the BMP390. This is the default address when A0 is +/// connected to low. If A0 is connected to high, the address will be 0x77. +/// +/// This will likely work even if A0 is not connected at all, in theory this +/// should be a "floating" pin, but in practice for our PM Lab board it will +/// function as if it were connected to low. const BMP390_ADDR: u8 = 0x76; -const AT24C256_ADDR: u8 = 0x50; // Note: A0, A1, A2 are all hooked to GND +/// I2C device address for the AT24C256 EEPROM. A0-A2 are all connected to low +/// on the PM Lab board, so the address is 0x50. +const AT24C256_ADDR: u8 = 0x50; + +/// Register addresses and values for the `PWR_CTRL` register. (Turns on and off +/// measurements and sets the power mode.) +/// +/// | Bits | Description | +/// |------|-------------------------------------------------| +/// | 0 | Pressure measurement on/off. 1 = on, 0 = off | +/// | 1 | Temperature measurement on/off. 1 = on, 0 = off | +/// | 2-3 | Reserved, must be set to 0 | +/// | 4-5 | Power mode. 00 = sleep, 01/10 = forced (one +/// measurement), 11 = normal | +/// | 6-7 | Reserved, must be set to 0 | const REGISTER_PWR_CTRL: u8 = 0x1B; +/// Bits to set in the `PWR_CTRL` register to set normal power mode. const PWR_MODE_ON: u8 = 0b0011_0000; +/// Bits to set in the `PWR_CTRL` register to enable temperature measurement. const PWR_TEMP_EN: u8 = 0b0000_0010; +/// Value to write to the `PWR_CTRL` register to enable temperature measurement +/// and set normal power mode. const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN; +/// Register addresses and values for the `OSR` register. (Controls how many +/// samples are taken and averaged for each measurement) +/// +/// | Bits | Description | +/// |------|-------------------------------------------------| +/// | 0-2 | Pressure oversampling. +/// 000 = no oversampling (1 sample), +/// 001 = x2 (2 samples), +/// 010 = x4 (4 samples), +/// 011 = x8 (8 samples), +/// 100 = x16 (16 samples), +/// 101 = x32 (32 samples), | +/// | 3-5 | Temperature oversampling. +/// 000 = no oversampling (1 sample), +/// 001 = x2 (2 samples), +/// 010 = x4 (4 samples), +/// 011 = x8 (8 samples), +/// 100 = x16 (16 samples), +/// 101 = x32 (32 samples), | +/// | 6-7 | Reserved, must be set to 0 | const REGISTER_OSR: u8 = 0x1C; +/// Bits to set in the `OSR` register to set temperature oversampling to x2. const OSR_TEMP_X2: u8 = 0b0000_1000; +/// Value to write to the `OSR` register to set temperature oversampling to x2 +/// and pressure oversampling to none. const OSR_VAL: u8 = OSR_TEMP_X2; +/// Register addresses for the raw temperature data (Least significant bits). const REGISTER_TEMP_XLSB: u8 = 0x07; - +/// Register addresses for the calibration data. The calibration data is 5 +/// registers in order. We can read them in one go by starting a sequential read +/// from the first register. const REGISTER_NVM_PAR_T1: u8 = 0x31; +/// EEPROM address to save the temperature value. const TEMP_SAVE_ADDRESS: u16 = 0xACDC; #[embassy_executor::main] async fn main(_spawner: Spawner) { + // Initialize the device peripherals let peripherals = embassy_stm32::init(Default::default()); info!("Device started"); - // I2C pins - let sda = peripherals.PB7; + // Define the I2C pins. In this lab we will use the I2C1 peripheral, which + // is connected to PB6 (SCL) and PB7 (SDA). let scl = peripherals.PB6; + let sda = peripherals.PB7; // I2C definition let mut i2c = I2c::new( @@ -56,20 +109,25 @@ async fn main(_spawner: Spawner) { Default::default(), ); + // Before we can read any data from the sensor, we need to configure the power. i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL]) .await .unwrap(); + // And we also need to configure the oversampling settings. i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL]) .await .unwrap(); - // Read NVM calibration parameters (5 bytes in total) + // Read NVM calibration parameters (5 bytes in total). See ex2 for more + // details on the sequential read. let mut nvm_data = [0u8; 5]; i2c.write_read(BMP390_ADDR, &[REGISTER_NVM_PAR_T1], &mut nvm_data) .await .unwrap(); + // Formulas for the partial calibration parameters taken from the datasheet. + // 0x31 (LSB) & 0x32 (MSB) -> u16 let nvm_par_t1: u16 = ((nvm_data[1] as u16) << 8) | (nvm_data[0] as u16); // 0x33 (LSB) & 0x34 (MSB) -> u16 @@ -82,6 +140,15 @@ async fn main(_spawner: Spawner) { let par_t3 = (nvm_par_t3 as f32) / 281474976710656.0; // 2^48 // Read previous temperature value from EEPROM + + // The length of the data to read is 4 bytes, since we will save the + // temperature value as a 32-bit integer (i32) representing the temperature + // in hundredths of degrees Celsius. For example, a temperature of 25.34 °C + // would be saved as 2534 (0x000009E6 in hexadecimal). + + // The "register" from which to read is just the EEPROM address to read + // from, which we need to send as the first two bytes in BIG ENDIAN format + // (most significant byte first). let mut prev_temp_data = [0u8; 4]; i2c.write_read( AT24C256_ADDR, @@ -90,7 +157,12 @@ async fn main(_spawner: Spawner) { ) .await .unwrap(); + + // Convert the read bytes back into an i32 value. Since we saved the value + // in big endian format, we need to convert it back using from_be_bytes. let prev_temp_hundredths = i32::from_be_bytes(prev_temp_data); + + // Convert the value from hundredths of degrees Celsius to degrees Celsius. let prev_temp = (prev_temp_hundredths as f32) / 100.0; info!( "Previous temperature value read from EEPROM: {} °C", @@ -98,18 +170,21 @@ async fn main(_spawner: Spawner) { ); loop { + // Read the raw temperature data in one go. (See ex2 for further + // explanation) let mut raw_temp_data = [0u8; 3]; i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data) .await .unwrap(); + // Based on Appendix 8.5: Temperature compensation `raw_temp` is the u32 + // value read from registers 0x07..0x09 let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16) | ((raw_temp_data[1] as i32) << 8) | (raw_temp_data[0] as i32); info!("Raw temperature value: {}", raw_temp); - // Based on Appendix 8.5: Temperature compensation - // `raw_temp` is the u32 value read from registers 0x07..0x09 + // Formulas taken from datasheet let partial_data1 = (raw_temp as f32) - par_t1; let partial_data2 = partial_data1 * par_t2; @@ -118,7 +193,20 @@ async fn main(_spawner: Spawner) { info!("Compensated temperature value: {} °C", t_lin); + // Save the temperature value to EEPROM. + + // First, get the hundredths of degrees Celsius as an i32 value. let hundredths = (t_lin * 100.0) as i32; + + // To write to the EEPROM, we need to send the address to write to as + // the first two bytes in big endian format, followed by the data to + // write (the hundredths value as 4 bytes in big endian format). + // + // While the format of the EEPROM address must be in big endian, the + // format of the temperature value is actually not specified by the + // EEPROM, since it just sees it as 4 bytes of data. However, using big + // endian for the temperature value is more consistent with how we read + // it back. let mut tx_buf = [0u8; 2 + 4]; tx_buf[..2].copy_from_slice(&TEMP_SAVE_ADDRESS.to_be_bytes()); tx_buf[2..].copy_from_slice(&hundredths.to_be_bytes());