From fc342915e6155dec7bafa3e135da7f37a9a07f5c Mon Sep 17 00:00:00 2001 From: jrmoulton Date: Tue, 13 Aug 2024 12:53:58 -0600 Subject: [PATCH] add stm32 i2c slave example --- embassy-stm32/src/i2c/mod.rs | 10 +- embassy-stm32/src/i2c/v2.rs | 26 ++--- examples/stm32g4/src/bin/i2c_slave.rs | 149 ++++++++++++++++++++++++++ 3 files changed, 167 insertions(+), 18 deletions(-) create mode 100644 examples/stm32g4/src/bin/i2c_slave.rs diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs index 94507eb34..2ff21702b 100644 --- a/embassy-stm32/src/i2c/mod.rs +++ b/embassy-stm32/src/i2c/mod.rs @@ -70,19 +70,19 @@ pub mod mode { #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// The command kind to the slave from the master -pub enum CommandKind { +pub enum SlaveCommandKind { /// Write to the slave - SlaveReceive, + Write, /// Read from the slave - SlaveSend, + Read, } #[derive(Debug, Clone, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// The command kind to the slave from the master and the address that the slave matched -pub struct Command { +pub struct SlaveCommand { /// The kind of command - pub kind: CommandKind, + pub kind: SlaveCommandKind, /// The address that the slave matched pub address: Address, } diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs index 1cc41fb5f..64ccd24c7 100644 --- a/embassy-stm32/src/i2c/v2.rs +++ b/embassy-stm32/src/i2c/v2.rs @@ -887,7 +887,7 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { /// Listen for incoming I2C messages. /// /// The listen method is an asynchronous method but it does not require DMA to be asynchronous. - pub async fn listen(&mut self) -> Result { + pub async fn listen(&mut self) -> Result { let state = self.state; self.info.regs.cr1().modify(|reg| { reg.set_addrie(true); @@ -902,12 +902,12 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { // we do not clear the address flag here as it will be cleared by the dma read/write // if we clear it here the clock stretching will stop and the master will read in data before the slave is ready to send it match isr.dir() { - i2c::vals::Dir::WRITE => Poll::Ready(Ok(Command { - kind: CommandKind::SlaveReceive, + i2c::vals::Dir::WRITE => Poll::Ready(Ok(SlaveCommand { + kind: SlaveCommandKind::Write, address: self.determine_matched_address()?, })), - i2c::vals::Dir::READ => Poll::Ready(Ok(Command { - kind: CommandKind::SlaveSend, + i2c::vals::Dir::READ => Poll::Ready(Ok(SlaveCommand { + kind: SlaveCommandKind::Read, address: self.determine_matched_address()?, })), } @@ -916,30 +916,30 @@ impl<'d, M: Mode> I2c<'d, M, MultiMaster> { .await } - /// Respond to a receive command. - pub fn blocking_respond_to_receive(&self, read: &mut [u8]) -> Result<(), Error> { + /// Respond to a write command. + pub fn blocking_respond_to_write(&self, read: &mut [u8]) -> Result<(), Error> { let timeout = self.timeout(); self.slave_read_internal(read, timeout) } - /// Respond to a send command. - pub fn blocking_respond_to_send(&mut self, write: &[u8]) -> Result<(), Error> { + /// Respond to a read command. + pub fn blocking_respond_to_read(&mut self, write: &[u8]) -> Result<(), Error> { let timeout = self.timeout(); self.slave_write_internal(write, timeout) } } impl<'d> I2c<'d, Async, MultiMaster> { - /// Respond to a receive command. + /// Respond to a write command. /// /// Returns the total number of bytes received. - pub async fn respond_to_receive(&mut self, buffer: &mut [u8]) -> Result { + pub async fn respond_to_write(&mut self, buffer: &mut [u8]) -> Result { let timeout = self.timeout(); timeout.with(self.read_dma_internal_slave(buffer, timeout)).await } - /// Respond to a send request from an I2C master. - pub async fn respond_to_send(&mut self, write: &[u8]) -> Result { + /// Respond to a read request from an I2C master. + pub async fn respond_to_read(&mut self, write: &[u8]) -> Result { let timeout = self.timeout(); timeout.with(self.write_dma_internal_slave(write, timeout)).await } diff --git a/examples/stm32g4/src/bin/i2c_slave.rs b/examples/stm32g4/src/bin/i2c_slave.rs new file mode 100644 index 000000000..a723a0e18 --- /dev/null +++ b/examples/stm32g4/src/bin/i2c_slave.rs @@ -0,0 +1,149 @@ +//! This example shows how to use an stm32 as both a master and a slave. +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::i2c::{Address, OwnAddresses, SlaveCommandKind}; +use embassy_stm32::mode::Async; +use embassy_stm32::time::Hertz; +use embassy_stm32::{bind_interrupts, i2c, peripherals}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs { + I2C1_ER => i2c::ErrorInterruptHandler; + I2C1_EV => i2c::EventInterruptHandler; + I2C2_ER => i2c::ErrorInterruptHandler; + I2C2_EV => i2c::EventInterruptHandler; +}); + +const DEV_ADDR: u8 = 0x42; + +#[embassy_executor::task] +async fn device_task(mut dev: i2c::I2c<'static, Async, i2c::MultiMaster>) -> ! { + info!("Device start"); + + let mut state = 0; + + loop { + let mut buf = [0u8; 128]; + match dev.listen().await { + Ok(i2c::SlaveCommand { + kind: SlaveCommandKind::Read, + address: Address::SevenBit(DEV_ADDR), + }) => match dev.respond_to_read(&[state]).await { + Ok(i2c::SendStatus::LeftoverBytes(x)) => info!("tried to write {} extra bytes", x), + Ok(i2c::SendStatus::Done) => {} + Err(e) => error!("error while responding {}", e), + }, + Ok(i2c::SlaveCommand { + kind: SlaveCommandKind::Write, + address: Address::SevenBit(DEV_ADDR), + }) => match dev.respond_to_write(&mut buf).await { + Ok(len) => { + info!("Device received write: {}", buf[..len]); + + if match buf[0] { + // Set the state + 0xC2 => { + state = buf[1]; + true + } + // Reset State + 0xC8 => { + state = 0; + true + } + x => { + error!("Invalid Write Read {:x}", x); + false + } + } { + match dev.respond_to_read(&[state]).await { + Ok(read_status) => info!( + "This read is part of a write/read transaction. The response read status {}", + read_status + ), + Err(i2c::Error::Timeout) => { + info!("The device only performed a write and it not also do a read") + } + Err(e) => error!("error while responding {}", e), + } + } + } + Err(e) => error!("error while receiving {}", e), + }, + Ok(i2c::SlaveCommand { address, .. }) => { + defmt::unreachable!( + "The slave matched address: {}, which it was not configured for", + address + ); + } + Err(e) => error!("{}", e), + } + } +} + +#[embassy_executor::task] +async fn controller_task(mut con: i2c::I2c<'static, Async, i2c::Master>) { + info!("Controller start"); + + loop { + let mut resp_buff = [0u8; 1]; + for i in 0..10 { + match con.write_read(DEV_ADDR, &[0xC2, i], &mut resp_buff).await { + Ok(_) => { + info!("write_read response: {}", resp_buff); + defmt::assert_eq!(resp_buff[0], i); + } + Err(e) => error!("Error writing {}", e), + } + + Timer::after_millis(100).await; + } + match con.read(DEV_ADDR, &mut resp_buff).await { + Ok(_) => { + info!("read response: {}", resp_buff); + // assert that the state is the last index that was written + defmt::assert_eq!(resp_buff[0], 9); + } + Err(e) => error!("Error writing {}", e), + } + match con.write_read(DEV_ADDR, &[0xC8], &mut resp_buff).await { + Ok(_) => { + info!("write_read response: {}", resp_buff); + // assert that the state has been reset + defmt::assert_eq!(resp_buff[0], 0); + } + Err(e) => error!("Error writing {}", e), + } + Timer::after_millis(100).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + info!("Hello World!"); + + let speed = Hertz::khz(400); + let config = i2c::Config::default(); + + let d_addr_config = i2c::SlaveAddrConfig { + addr: OwnAddresses::OA1(Address::SevenBit(DEV_ADDR)), + general_call: false, + }; + let d_sda = p.PA8; + let d_scl = p.PA9; + let device = i2c::I2c::new(p.I2C2, d_scl, d_sda, Irqs, p.DMA1_CH1, p.DMA1_CH2, speed, config) + .into_slave_multimaster(d_addr_config); + + unwrap!(spawner.spawn(device_task(device))); + + let c_sda = p.PB8; + let c_scl = p.PB7; + let controller = i2c::I2c::new(p.I2C1, c_sda, c_scl, Irqs, p.DMA1_CH3, p.DMA1_CH4, speed, config); + + unwrap!(spawner.spawn(controller_task(controller))); +}