add stm32 i2c slave example

This commit is contained in:
jrmoulton 2024-08-13 12:53:58 -06:00
parent bfc162d437
commit fc342915e6
No known key found for this signature in database
3 changed files with 167 additions and 18 deletions

View File

@ -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,
}

View File

@ -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<Command, Error> {
pub async fn listen(&mut self) -> Result<SlaveCommand, Error> {
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<usize, Error> {
pub async fn respond_to_write(&mut self, buffer: &mut [u8]) -> Result<usize, Error> {
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<SendStatus, Error> {
/// Respond to a read request from an I2C master.
pub async fn respond_to_read(&mut self, write: &[u8]) -> Result<SendStatus, Error> {
let timeout = self.timeout();
timeout.with(self.write_dma_internal_slave(write, timeout)).await
}

View File

@ -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<peripherals::I2C1>;
I2C1_EV => i2c::EventInterruptHandler<peripherals::I2C1>;
I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
});
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)));
}