Skip to content

Commit ca08e5f

Browse files
authored
feat: lab06 (#14)
* feat: lab06 Signed-off-by: Eva Cosma <eva.cosma.mail@gmail.com> * feat: lab06 clarifying comments Signed-off-by: Eva Cosma <eva.cosma.mail@gmail.com> --------- Signed-off-by: Eva Cosma <eva.cosma.mail@gmail.com>
1 parent 62b25cb commit ca08e5f

11 files changed

Lines changed: 876 additions & 1 deletion

File tree

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[workspace]
22
resolver = "3"
3-
members = ["lab01", "lab02", "lab03", "lab04", "lab05"]
3+
members = ["lab01", "lab02", "lab03", "lab04", "lab05", "lab06"]
44

55
[workspace.dependencies]
66
# Low level access to Cortex-M processors

lab06/.cargo/config.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[build]
2+
target = "thumbv8m.main-none-eabihf"
3+
4+
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
5+
runner = "probe-rs run --chip STM32U545RETxQ"
6+
7+
[env]
8+
DEFMT_LOG = "debug"

lab06/Cargo.toml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "lab06"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
7+
# Low level access to Cortex-M processors
8+
cortex-m.workspace = true
9+
# Boostrap crate for Cortex-M Processors
10+
cortex-m-rt.workspace = true
11+
# Defmt support
12+
defmt.workspace = true
13+
defmt-rtt.workspace = true
14+
# Embedded HAL utilities
15+
embassy-embedded-hal.workspace = true
16+
# Async/await executor
17+
embassy-executor.workspace = true
18+
# Utilities for working with futures, compatible with no_std and not using alloc
19+
embassy-futures.workspace = true
20+
# STM32 HAL Implementation
21+
embassy-stm32.workspace = true
22+
# Synchronization primitives and data structures with async support
23+
embassy-sync.workspace = true
24+
# Timekeeping, delays and timeouts
25+
embassy-time.workspace = true
26+
embedded-graphics = "0.8.1"
27+
embedded-hal = "1.0.0"
28+
embedded-hal-async = "1.0.0"
29+
heapless = "0.9.2"
30+
mipidsi = "0.9.0"
31+
# Panic handler that exits `probe-run` with an error code
32+
panic-probe.workspace = true

lab06/build.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//! This build script copies the `memory.x` file from the crate root into
2+
//! a directory where the linker can always find it at build time.
3+
//! For many projects this is optional, as the linker always searches the
4+
//! project root directory -- wherever `Cargo.toml` is. However, if you
5+
//! are using a workspace or have a more complicated build setup, this
6+
//! build script becomes required. Additionally, by requesting that
7+
//! Cargo re-run the build script whenever `memory.x` is changed,
8+
//! updating `memory.x` ensures a rebuild of the application with the
9+
//! new memory settings.
10+
11+
// use std::env;
12+
// use std::fs::File;
13+
// use std::io::Write;
14+
// use std::path::PathBuf;
15+
16+
fn main() {
17+
// This section is not needed as the `embassy-stm32` crate
18+
// provides `memory.x` and adds it to the linker's path.
19+
//
20+
// If using a crate that does not provide it, please
21+
// uncomment the this section and the related imported
22+
// items.
23+
24+
// Put `memory.x` in our output directory and ensure it's
25+
// on the linker search path.
26+
// let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
27+
// File::create(out.join("memory.x"))
28+
// .unwrap()
29+
// .write_all(include_bytes!("memory.x"))
30+
// .unwrap();
31+
// println!("cargo:rustc-link-search={}", out.display());
32+
33+
// By default, Cargo will re-run a build script whenever
34+
// any file in the project changes. By specifying `memory.x`
35+
// here, we ensure the build script is only re-run when
36+
// `memory.x` is changed.
37+
// println!("cargo:rerun-if-changed=memory.x");
38+
39+
// Prevent the linker from page aligning segments
40+
println!("cargo:rustc-link-arg-bins=--nmagic");
41+
42+
// Required for `cortex-m`
43+
println!("cargo:rustc-link-arg-bins=-Tlink.x");
44+
// Required for `defmt`
45+
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
46+
}

lab06/src/bin/ex1.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
use defmt::info;
5+
use defmt_rtt as _;
6+
use embassy_executor::Spawner;
7+
use embassy_stm32::{
8+
bind_interrupts,
9+
i2c::{self, I2c},
10+
peripherals,
11+
};
12+
use embassy_time::Timer;
13+
use panic_probe as _;
14+
15+
// For I2C to work, we need to bind the interrupts to the correct handlers.
16+
bind_interrupts!(struct Irqs {
17+
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
18+
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
19+
});
20+
21+
/// The lowest I2C address a device can have. Addresses below this are reserved
22+
/// for special purposes.
23+
const LOWEST_I2C_ADDR: u8 = 0x08;
24+
/// The highest I2C address a device can have. Addresses above this are reserved
25+
/// for special purposes.
26+
const HIGHEST_I2C_ADDR: u8 = 0x77;
27+
28+
#[embassy_executor::main]
29+
async fn main(_spawner: Spawner) {
30+
// Initialize the device peripherals
31+
let peripherals = embassy_stm32::init(Default::default());
32+
info!("Device started");
33+
34+
// Define the I2C pins. In this lab we will use the I2C1 peripheral, which
35+
// is connected to PB6 (SCL) and PB7 (SDA).
36+
let scl = peripherals.PB6;
37+
let sda = peripherals.PB7;
38+
39+
// I2C definition
40+
let mut i2c = I2c::new(
41+
peripherals.I2C1,
42+
scl,
43+
sda,
44+
Irqs,
45+
peripherals.GPDMA1_CH0,
46+
peripherals.GPDMA1_CH1,
47+
Default::default(),
48+
);
49+
50+
// Scan the I2C bus for devices. We do this by trying to read from each
51+
// possible address.
52+
53+
for addr in LOWEST_I2C_ADDR..=HIGHEST_I2C_ADDR {
54+
// To execute a "scan", we can either try to execute a read directly, or
55+
// we can write a dummy register (0x00) and then wait for a response
56+
//
57+
// The first approach (read-only) may work with some devices, but it may
58+
// not work devices which decide to ignore read requests without a
59+
// preceding write. It will also take longer to get a timeout, which
60+
// will make the scan take longer to complete.
61+
//
62+
// The second approach (write-read) is more likely to work with a wider
63+
// range of devices, but it is not guaranteed that all devices will have
64+
// a `0x00` register.
65+
//
66+
// For this lab, the BMP390 has its chip ID register at `0x00`, and the
67+
// AT24C256 has its first memory address at `0x00`.
68+
let mut rx_buf = [0u8; 1];
69+
let res = i2c.write_read(addr, &[0x00], &mut rx_buf).await;
70+
info!(
71+
"Result for address 0x{:02x}: {:?} | Data: {:?}",
72+
addr, res, rx_buf
73+
);
74+
}
75+
76+
loop {
77+
Timer::after_secs(1).await;
78+
}
79+
}

lab06/src/bin/ex2.rs

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
#![no_std]
2+
#![no_main]
3+
4+
use defmt::info;
5+
use defmt_rtt as _;
6+
use embassy_executor::Spawner;
7+
use embassy_stm32::{
8+
bind_interrupts,
9+
i2c::{self, I2c},
10+
peripherals,
11+
};
12+
use embassy_time::Timer;
13+
use panic_probe as _;
14+
15+
// For I2C to work, we need to bind the interrupts to the correct handlers.
16+
bind_interrupts!(struct Irqs {
17+
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
18+
I2C1_ER => i2c::ErrorInterruptHandler<peripherals::I2C1>;
19+
});
20+
21+
/// I2C device address for the BMP390. This is the default address when A0 is
22+
/// connected to low. If A0 is connected to high, the address will be 0x77.
23+
///
24+
/// This will likely work even if A0 is not connected at all, in theory this
25+
/// should be a "floating" pin, but in practice for our PM Lab board it will
26+
/// function as if it were connected to low.
27+
const BMP390_ADDR: u8 = 0x76;
28+
29+
/// Register addresses and values for the `PWR_CTRL` register. (Turns on and off
30+
/// measurements and sets the power mode.)
31+
///
32+
/// | Bits | Description |
33+
/// |------|-------------------------------------------------|
34+
/// | 0 | Pressure measurement on/off. 1 = on, 0 = off |
35+
/// | 1 | Temperature measurement on/off. 1 = on, 0 = off |
36+
/// | 2-3 | Reserved, must be set to 0 |
37+
/// | 4-5 | Power mode. 00 = sleep, 01/10 = forced (one
38+
/// measurement), 11 = normal |
39+
/// | 6-7 | Reserved, must be set to 0 |
40+
const REGISTER_PWR_CTRL: u8 = 0x1B;
41+
/// Bits to set in the `PWR_CTRL` register to set normal power mode.
42+
const PWR_MODE_ON: u8 = 0b0011_0000;
43+
/// Bits to set in the `PWR_CTRL` register to enable temperature measurement.
44+
const PWR_TEMP_EN: u8 = 0b0000_0010;
45+
/// Value to write to the `PWR_CTRL` register to enable temperature measurement
46+
/// and set normal power mode.
47+
const PWR_VAL: u8 = PWR_MODE_ON | PWR_TEMP_EN;
48+
49+
/// Register addresses and values for the `OSR` register. (Controls how many
50+
/// samples are taken and averaged for each measurement)
51+
///
52+
/// | Bits | Description |
53+
/// |------|-------------------------------------------------|
54+
/// | 0-2 | Pressure oversampling.
55+
/// 000 = no oversampling (1 sample),
56+
/// 001 = x2 (2 samples),
57+
/// 010 = x4 (4 samples),
58+
/// 011 = x8 (8 samples),
59+
/// 100 = x16 (16 samples),
60+
/// 101 = x32 (32 samples), |
61+
/// | 3-5 | Temperature oversampling.
62+
/// 000 = no oversampling (1 sample),
63+
/// 001 = x2 (2 samples),
64+
/// 010 = x4 (4 samples),
65+
/// 011 = x8 (8 samples),
66+
/// 100 = x16 (16 samples),
67+
/// 101 = x32 (32 samples), |
68+
/// | 6-7 | Reserved, must be set to 0 |
69+
const REGISTER_OSR: u8 = 0x1C;
70+
/// Bits to set in the `OSR` register to set temperature oversampling to x2.
71+
const OSR_TEMP_X2: u8 = 0b0000_1000;
72+
/// Value to write to the `OSR` register to set temperature oversampling to x2
73+
/// and pressure oversampling to none.
74+
const OSR_VAL: u8 = OSR_TEMP_X2;
75+
76+
/// Register addresses for the raw temperature data (Least significant bits).
77+
const REGISTER_TEMP_XLSB: u8 = 0x07;
78+
/// Register addresses for the raw temperature data (Middle significant bits).
79+
const REGISTER_TEMP_LSB: u8 = 0x08;
80+
/// Register addresses for the raw temperature data (Most significant bits).
81+
const REGISTER_TEMP_MSB: u8 = 0x09;
82+
83+
#[embassy_executor::main]
84+
async fn main(_spawner: Spawner) {
85+
// Initialize the device peripherals
86+
let peripherals = embassy_stm32::init(Default::default());
87+
info!("Device started");
88+
89+
// Define the I2C pins. In this lab we will use the I2C1 peripheral, which
90+
// is connected to PB6 (SCL) and PB7 (SDA).
91+
let scl = peripherals.PB6;
92+
let sda = peripherals.PB7;
93+
94+
// I2C definition
95+
let mut i2c = I2c::new(
96+
peripherals.I2C1,
97+
scl,
98+
sda,
99+
Irqs,
100+
peripherals.GPDMA1_CH0,
101+
peripherals.GPDMA1_CH1,
102+
Default::default(),
103+
);
104+
105+
// Before we can read any data from the sensor, we need to configure the power.
106+
i2c.write(BMP390_ADDR, &[REGISTER_PWR_CTRL, PWR_VAL])
107+
.await
108+
.unwrap();
109+
110+
// And we also need to configure the oversampling settings.
111+
i2c.write(BMP390_ADDR, &[REGISTER_OSR, OSR_VAL])
112+
.await
113+
.unwrap();
114+
115+
// Read the raw temperature data. There are 2 ways we can do this.
116+
loop {
117+
// The first way is to read all 3 bytes in one go. Since the registers
118+
// are consecutige, the BMP automatically increments the register
119+
// address after each byte, so we can just read from the first register,
120+
// and so long as we have more bytes to read, the main (us, the MCU)
121+
// will send ACK to continue reading. After we read the last byte (as
122+
// determined by the size of the array), we will send a NACK indicating
123+
// to the BMP that we are done reading. This will ensure that the
124+
// temperature data is consistent.
125+
let mut raw_temp_data = [0u8; 3];
126+
i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_temp_data)
127+
.await
128+
.unwrap();
129+
130+
// Alternatively, we can read each byte one by one. This is more likely
131+
// to cause inconsistent data, since the BMP may update the temperature
132+
// data in between our reads, but it is still possible to get the
133+
// correct data if we are lucky.
134+
let mut raw_xlsb = [0u8; 1];
135+
let mut raw_lsb = [0u8; 1];
136+
let mut raw_msb = [0u8; 1];
137+
i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_XLSB], &mut raw_xlsb)
138+
.await
139+
.unwrap();
140+
i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_LSB], &mut raw_lsb)
141+
.await
142+
.unwrap();
143+
i2c.write_read(BMP390_ADDR, &[REGISTER_TEMP_MSB], &mut raw_msb)
144+
.await
145+
.unwrap();
146+
147+
info!("Raw data: {:?}", raw_temp_data);
148+
info!(
149+
"Raw byte by byte: {:?}",
150+
[raw_xlsb[0], raw_lsb[0], raw_msb[0]]
151+
);
152+
153+
// The raw temperature data is a 24-bit signed integer
154+
let raw_temp: i32 = ((raw_temp_data[2] as i32) << 16)
155+
| ((raw_temp_data[1] as i32) << 8)
156+
| (raw_temp_data[0] as i32);
157+
info!("Raw temperature value: {}", raw_temp);
158+
159+
Timer::after_millis(400).await;
160+
}
161+
}

0 commit comments

Comments
 (0)