Skip to content

Commit 34b208d

Browse files
authored
Merge pull request embassy-rs#5333 from HybridChild/fix/stm32-i2c-v2-slave
stm32/i2c v2: Fix async slave implementation and return actual bytes transferred
2 parents ea1d944 + 8899dd0 commit 34b208d

8 files changed

Lines changed: 1002 additions & 50 deletions

File tree

embassy-stm32/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
<!-- next-header -->
99
## Unreleased - ReleaseDate
1010

11+
- fix: stm32/i2c v2: Fix async slave by using DMA completion instead of TC flag for buffer-full detection
12+
- change: stm32/i2c v2: slave `respond_to_write` and `respond_to_read` now return actual bytes transferred instead of buffer size (breaking change, matching v1 behavior)
1113
- fix: stm32/i2c v1: `write_read` was losing last write byte before RESTART due to not waiting for BTF
1214
- fix: stm32/i2c v1: slave: async `respond_to_write` and `respond_to_read` now return actual bytes transferred instead of buffer size
1315
- fix: don't put USB pins into alternate mode on chips where USB is an additional function

embassy-stm32/src/i2c/v2.rs

Lines changed: 247 additions & 50 deletions
Large diffs are not rendered by default.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//! I2C Async Slave Example
2+
//!
3+
//! Demonstrates the async I2C slave (multimaster) implementation using DMA.
4+
//! The device listens for I2C transactions from an external master.
5+
//!
6+
//! # Hardware Setup
7+
//!
8+
//! - PB8 (SCL) and PB9 (SDA) with 4.7k pull-up resistors to 3.3V
9+
//! - Connect an I2C master device with clock stretching enabled
10+
//! - Default slave address: 0x42 (7-bit)
11+
//!
12+
//! # Behavior
13+
//!
14+
//! - Master WRITE: Receives up to BUFFER_SIZE bytes via DMA, excess bytes are drained
15+
//! - Master READ: Sends an 8-byte response pattern (0xA0-0xA7) via DMA
16+
17+
#![no_std]
18+
#![no_main]
19+
20+
use defmt::*;
21+
use embassy_executor::Spawner;
22+
use embassy_stm32::i2c::{self, I2c, SendStatus, SlaveAddrConfig, SlaveCommandKind};
23+
use embassy_stm32::{bind_interrupts, dma, peripherals};
24+
use embassy_time::Duration;
25+
use {defmt_rtt as _, panic_probe as _};
26+
27+
const I2C_ADDR: u8 = 0x42;
28+
const BUFFER_SIZE: usize = 32;
29+
30+
bind_interrupts!(struct Irqs {
31+
I2C1 => i2c::EventInterruptHandler<peripherals::I2C1>, i2c::ErrorInterruptHandler<peripherals::I2C1>;
32+
DMA1_CHANNEL2_3 => dma::InterruptHandler<peripherals::DMA1_CH2>, dma::InterruptHandler<peripherals::DMA1_CH3>;
33+
});
34+
35+
#[embassy_executor::main]
36+
async fn main(_spawner: Spawner) {
37+
let p = embassy_stm32::init(Default::default());
38+
39+
info!("I2C Async Slave Example");
40+
info!("Address: 0x{:02X}, Buffer: {} bytes", I2C_ADDR, BUFFER_SIZE);
41+
42+
let mut config = i2c::Config::default();
43+
config.timeout = Duration::from_secs(30);
44+
45+
let mut i2c = I2c::new(p.I2C1, p.PB8, p.PB9, p.DMA1_CH2, p.DMA1_CH3, Irqs, config)
46+
.into_slave_multimaster(SlaveAddrConfig::basic(I2C_ADDR));
47+
48+
info!("Slave ready, listening...");
49+
50+
let mut count: u32 = 0;
51+
loop {
52+
match i2c.listen().await {
53+
Ok(cmd) => {
54+
count += 1;
55+
match cmd.kind {
56+
SlaveCommandKind::Write => {
57+
let mut buffer = [0u8; BUFFER_SIZE];
58+
match i2c.respond_to_write(&mut buffer).await {
59+
Ok(len) => info!("[{}] Write: {} bytes: {:02X}", count, len, &buffer[..len]),
60+
Err(e) => error!("[{}] Write error: {:?}", count, e),
61+
}
62+
}
63+
SlaveCommandKind::Read => {
64+
let response: [u8; 8] = [0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7];
65+
match i2c.respond_to_read(&response).await {
66+
Ok(SendStatus::Done) => info!("[{}] Read: {} bytes", count, response.len()),
67+
Ok(SendStatus::LeftoverBytes(n)) => {
68+
info!("[{}] Read: {} of {} bytes", count, response.len() - n, response.len())
69+
}
70+
Err(e) => error!("[{}] Read error: {:?}", count, e),
71+
}
72+
}
73+
}
74+
}
75+
Err(e) => error!("Listen error: {:?}", e),
76+
}
77+
}
78+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
//! I2C Blocking Slave Example
2+
//!
3+
//! Demonstrates the blocking I2C slave (multimaster) implementation.
4+
//! The device listens for I2C transactions from an external master.
5+
//!
6+
//! # Hardware Setup
7+
//!
8+
//! - PB8 (SCL) and PB9 (SDA) with 4.7k pull-up resistors to 3.3V
9+
//! - Connect an I2C master device with clock stretching enabled
10+
//! - Default slave address: 0x42 (7-bit)
11+
//!
12+
//! # Behavior
13+
//!
14+
//! - Master WRITE: Receives up to BUFFER_SIZE bytes, excess bytes are drained
15+
//! - Master READ: Sends an 8-byte response pattern (0xA0-0xA7)
16+
17+
#![no_std]
18+
#![no_main]
19+
20+
use defmt::*;
21+
use embassy_executor::Spawner;
22+
use embassy_stm32::i2c::{self, I2c, SendStatus, SlaveAddrConfig, SlaveCommandKind};
23+
use embassy_stm32::{bind_interrupts, peripherals};
24+
use embassy_time::Duration;
25+
use {defmt_rtt as _, panic_probe as _};
26+
27+
const I2C_ADDR: u8 = 0x42;
28+
const BUFFER_SIZE: usize = 32;
29+
30+
bind_interrupts!(struct Irqs {
31+
I2C1 => i2c::EventInterruptHandler<peripherals::I2C1>, i2c::ErrorInterruptHandler<peripherals::I2C1>;
32+
});
33+
34+
#[embassy_executor::main]
35+
async fn main(_spawner: Spawner) {
36+
let p = embassy_stm32::init(Default::default());
37+
38+
info!("I2C Blocking Slave Example");
39+
info!("Address: 0x{:02X}, Buffer: {} bytes", I2C_ADDR, BUFFER_SIZE);
40+
41+
let mut config = i2c::Config::default();
42+
config.timeout = Duration::from_secs(30);
43+
44+
let mut i2c =
45+
I2c::new_blocking(p.I2C1, p.PB8, p.PB9, config).into_slave_multimaster(SlaveAddrConfig::basic(I2C_ADDR));
46+
47+
info!("Slave ready, listening...");
48+
49+
let mut count: u32 = 0;
50+
loop {
51+
match i2c.blocking_listen() {
52+
Ok(cmd) => {
53+
count += 1;
54+
match cmd.kind {
55+
SlaveCommandKind::Write => {
56+
let mut buffer = [0u8; BUFFER_SIZE];
57+
match i2c.blocking_respond_to_write(&mut buffer) {
58+
Ok(len) => info!("[{}] Write: {} bytes: {:02X}", count, len, &buffer[..len]),
59+
Err(e) => error!("[{}] Write error: {:?}", count, e),
60+
}
61+
}
62+
SlaveCommandKind::Read => {
63+
let response: [u8; 8] = [0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7];
64+
match i2c.blocking_respond_to_read(&response) {
65+
Ok(SendStatus::Done) => info!("[{}] Read: {} bytes", count, response.len()),
66+
Ok(SendStatus::LeftoverBytes(n)) => {
67+
info!("[{}] Read: {} of {} bytes", count, response.len() - n, response.len())
68+
}
69+
Err(e) => error!("[{}] Read error: {:?}", count, e),
70+
}
71+
}
72+
}
73+
}
74+
Err(e) => error!("Listen error: {:?}", e),
75+
}
76+
}
77+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[target.thumbv6m-none-eabi]
2+
runner = 'probe-rs run --chip STM32F072RB'
3+
4+
[build]
5+
target = "thumbv6m-none-eabi"
6+
7+
[env]
8+
DEFMT_LOG = "trace"

examples/stm32f072/Cargo.toml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[package]
2+
name = "embassy-stm32f0-examples"
3+
version = "0.1.0"
4+
edition = "2024"
5+
license = "MIT OR Apache-2.0"
6+
publish = false
7+
8+
[dependencies]
9+
# Change stm32f091rc to your chip name, if necessary.
10+
embassy-stm32 = { version = "0.5.0", path = "../../embassy-stm32", features = [ "defmt", "memory-x", "stm32f072rb", "time-driver-tim2", "exti", "unstable-pac"] }
11+
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
12+
cortex-m-rt = "0.7.0"
13+
defmt = "1.0.1"
14+
defmt-rtt = "1.0.0"
15+
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
16+
embassy-sync = { version = "0.7.2", path = "../../embassy-sync", features = ["defmt"] }
17+
embassy-executor = { version = "0.9.0", path = "../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "defmt"] }
18+
embassy-time = { version = "0.5.0", path = "../../embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
19+
embassy-futures = { version = "0.1.0", path = "../../embassy-futures" }
20+
embedded-hal-1 = { package = "embedded-hal", version = "1.0" }
21+
static_cell = "2"
22+
portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] }
23+
24+
[profile.release]
25+
debug = 2
26+
27+
[package.metadata.embassy]
28+
build = [
29+
{ target = "thumbv6m-none-eabi", artifact-dir = "out/examples/stm32f0" }
30+
]

examples/stm32f072/build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fn main() {
2+
println!("cargo:rustc-link-arg-bins=--nmagic");
3+
println!("cargo:rustc-link-arg-bins=-Tlink.x");
4+
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
5+
}

0 commit comments

Comments
 (0)