I'm developing an embedded project in Rust using the embassy framework on an STM32F103C8 microcontroller. My board includes an EEPROM module (24C16N), and I need to use it for reading and writing data.
I have attempted two implementations:
#![no_std]
#![no_main]
use cortex_m::delay::Delay;
use cortex_m_rt::entry;
use defmt::{error, info};
use defmt_rtt as _;
use panic_halt as _;
use stm32f1xx_hal::{
i2c::{BlockingI2c, Mode},
pac,
prelude::*,
};
#[entry]
fn main() -> ! {
let cp = cortex_m::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain();
let rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
let mut gpiob = dp.GPIOB.split();
let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh);
let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh);
let mut afio = dp.AFIO.constrain();
let mut i2c = BlockingI2c::i2c1(
dp.I2C1,
(scl, sda),
&mut afio.mapr,
Mode::Standard {
frequency: 100.kHz(),
},
clocks,
1000, // Timeout for operations
10, // Retry attempts
1000, // Timeout for start condition
1000, // Timeout for stop condition
);
let mut delay = Delay::new(cp.SYST, clocks.sysclk().to_Hz());
const EEPROM_ADDR: u8 = 0x50;
let mem_addr: u8 = 0x00;
let write_data = [mem_addr, 0x41];
match i2c.write(EEPROM_ADDR, &write_data) {
Ok(_) => info!("Byte written successfully"),
Err(_) => error!("Error writing byte"),
}
delay.delay_ms(15);
let mut buffer = [0u8; 1];
let read_addr = [mem_addr];
match i2c.write_read(EEPROM_ADDR, &read_addr, &mut buffer) {
Ok(_) => info!("Read byte: 0x{:x}", buffer[0]),
Err(_) => error!("Error reading byte"),
}
loop {
info!("System running...");
delay.delay_ms(10000);
}
}
#![no_std]
#![no_main]
use defmt::*;
use defmt_rtt as _;
use embassy_executor::Spawner;
use embassy_stm32::Config;
use embassy_time::Timer;
use panic_probe as _;
use embassy_stm32::i2c;
use embassy_stm32::time::Hertz;
use embassy_stm32::peripherals::I2C1;
use embassy_stm32::bind_interrupts;
bind_interrupts!(struct Irqs {
I2C1_EV => i2c::EventInterruptHandler<I2C1>;
I2C1_ER => i2c::ErrorInterruptHandler<I2C1>;
});
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let config = Config::default();
let p = embassy_stm32::init(config);
let config = i2c::Config::default();
let mut i2c = i2c::I2c::new(
p.I2C1,
p.PB8,
p.PB9,
Irqs,
p.DMA1_CH6,
p.DMA1_CH7,
Hertz(100_000),
config,
);
const EEPROM_ADDR: u8 = 0x50;
let mem_addr: u8 = 0x00;
let write_data = [mem_addr, 0x41];
match i2c.blocking_write(EEPROM_ADDR, &write_data) {
Ok(()) => info!("Byte written successfully"),
Err(e) => error!("Error writing byte: {:?}", e),
}
Timer::after_millis(15).await;
let read_addr = [mem_addr];
let mut read_buffer = [0u8; 1];
match i2c.blocking_write_read(EEPROM_ADDR, &read_addr, &mut read_buffer) {
Ok(()) => info!("Read byte: 0x{:x}", read_buffer[0]),
Err(e) => error!("Error reading byte: {:?}", e),
}
}
I've also tried using new_blocking, but the communication still fails: in async mode, the await never returns (it hangs), and in blocking mode, I get timeout errors during both writing and reading.
Additional details:
I'm 100% sure that the hardware (wiring, pull-up resistors, device address, etc.) is correct because the HAL version works without any issue.
My Cargo.toml includes the following dependencies:
[package]
name = "embassy_eeprom_orm"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "embassy_eeprom_orm"
path = "src/main.rs"
test = false
doctest = false
bench = false
[dependencies]
# For embassy code:
embassy-stm32 = { version = "0.2.0", path = "./embassy/embassy-stm32", features = ["defmt", "stm32f103c8", "unstable-pac", "memory-x", "time-driver-any"] }
embassy-executor = { version = "0.7.0", path = "./embassy/embassy-executor", features = ["arch-cortex-m", "executor-thread", "defmt"] }
embassy-time = { version = "0.4.0", path = "./embassy/embassy-time", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
embassy-sync = { version = "0.6.2", path = "./embassy/embassy-sync", features = ["defmt"] }
embassy-futures = { version = "0.1.0", path = "./embassy/embassy-futures" }
# For non-embassy code (used in my HAL version):
stm32f1xx-hal = { version = "0.10.0", features = ["rt", "stm32f103", "medium"] }
panic-halt = "1.0.0"
defmt = "0.3"
defmt-rtt = "0.4"
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
cortex-m-rt = "0.7.0"
embedded-hal = "1.0.0"
panic-probe = { version = "0.3", features = ["print-defmt"] }
heapless = { version = "0.8", default-features = false }
nb = "1.1.0"
static_cell = "2.0.0"
I've spent nearly two weeks trying to get the embassy version to work without success. The question is: Why does the embassy-based I2C communication (both async and blocking versions) fail to establish communication with the EEPROM (24C16N), while the HAL version works perfectly?
Any help or pointers to what might be causing this issue would be greatly appreciated!
The actual solution (from dirbaio in Matrix Embassy chat):
try this before creating the i2c:
embassy_stm32::pac::AFIO.mapr().modify(|w| w.set_i2c1_remap(true));
(gpio on F1 is weird, it has this remap thing that the embassy-stm32 hal doesn't do automatically for you yet)