Skip to content
Snippets Groups Projects
Commit 410262bb authored by Samuel Tardieu's avatar Samuel Tardieu
Browse files

Start bootloader as a I²C repeater

Implement I²C slave as a library
parent 42d95216
No related branches found
No related tags found
No related merge requests found
...@@ -524,6 +524,30 @@ dependencies = [ ...@@ -524,6 +524,30 @@ dependencies = [
"stable_deref_trait", "stable_deref_trait",
] ]
[[package]]
name = "i2c-bootloader"
version = "0.1.0"
dependencies = [
"cortex-m",
"cortex-m-rt",
"defmt",
"defmt-rtt",
"embassy-executor",
"embassy-stm32",
"embassy-time",
"i2c2-slave",
"panic-probe",
]
[[package]]
name = "i2c2-slave"
version = "0.1.0"
dependencies = [
"defmt",
"embassy-stm32",
"embassy-sync",
]
[[package]] [[package]]
name = "ident_case" name = "ident_case"
version = "1.0.1" version = "1.0.1"
......
[workspace] [workspace]
members = ["controller"] members = ["controller", "i2c-bootloader", "i2c2-slave"]
resolver = "2" resolver = "2"
[profile.release] [profile.release]
......
[package]
name = "i2c-bootloader"
version = "0.1.0"
authors = ["Samuel Tardieu <sam@rfc1149.net>"]
edition = "2021"
[dependencies]
cortex-m = { version = "0.7.7", features = ["critical-section-single-core", "inline-asm"] }
cortex-m-rt = "0.7.3"
defmt = "0.3.5"
defmt-rtt = "0.4.0"
embassy-executor = { git = "https://github.com/embassy-rs/embassy", features = ["arch-cortex-m", "executor-thread", "defmt", "integrated-timers"] }
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "stm32f103c8", "unstable-pac", "time-driver-tim4", "memory-x"] }
embassy-time = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
i2c2-slave = { path = "../i2c2-slave" }
panic-probe = { version = "0.3.1", features = ["print-defmt"] }
#![no_std]
#![no_main]
use defmt_rtt as _;
use embassy_executor::Spawner;
use embassy_stm32::{
gpio::{Level, Output, Speed},
interrupt::Priority,
peripherals::PB15,
time::mhz,
Config,
};
use embassy_time::Timer;
use panic_probe as _;
#[embassy_executor::main]
async fn main(spawner: Spawner) {
let mut config = Config::default();
config.rcc.hse = Some(mhz(8));
config.rcc.sys_ck = Some(mhz(72));
config.rcc.pclk1 = Some(mhz(36));
let p = embassy_stm32::init(config);
defmt::info!("I²C bootloader starting");
spawner.spawn(blink(p.PB15)).unwrap();
let mut i2c2 = i2c2_slave::I2C2::new(p.I2C2, p.PB10, p.PB11, 0x57, Priority::P3);
loop {
let mut buffer = [0; 8];
match i2c2.receive(&mut buffer).await {
Ok(n) => {
defmt::info!("Received command {:#x}, reversing", &buffer[..n]);
buffer[..n].reverse();
i2c2.send(&buffer[..n]).unwrap();
}
Err(i2c2_slave::Error::ExtraBytes(n)) => {
defmt::info!(
"Received {} extra bytes after command {:#x}, sending 0xDE 0xAD",
n,
buffer
);
i2c2.send(&[0xDE, 0xAD]).unwrap();
}
Err(e) => {
defmt::info!("Received another error: {:?}", defmt::Debug2Format(&e));
}
}
}
}
#[embassy_executor::task]
async fn blink(pin: PB15) {
let mut led = Output::new(pin, Level::High, Speed::Low);
loop {
for _ in 0..4 {
led.toggle();
Timer::after_millis(100).await;
}
Timer::after_secs(1).await;
}
}
[package]
name = "i2c2-slave"
version = "0.1.0"
authors = ["Samuel Tardieu <sam@rfc1149.net>"]
edition = "2021"
[dependencies]
defmt = "0.3.5"
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", features = ["defmt", "stm32f103c8", "unstable-pac", "time-driver-tim4"] }
embassy-sync = { git = "https://github.com/embassy-rs/embassy", features = ["defmt"] }
#![no_std]
use embassy_stm32::{
gpio::low_level::{AFType, Pin},
i2c::{self, SclPin},
interrupt,
interrupt::{InterruptExt, Priority},
pac,
peripherals::{self, PB10, PB11},
};
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, channel::Channel};
enum I2cRxEvent {
Byte(u8),
Stop,
}
static RX_QUEUE: Channel<CriticalSectionRawMutex, I2cRxEvent, 9> = Channel::new();
static TX_QUEUE: Channel<CriticalSectionRawMutex, u8, 8> = Channel::new();
static mut STATE: State = State::Idle { after_write: false };
#[derive(Debug)]
pub enum Error {
ExtraBytes(usize),
FifoFull,
}
pub type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(Clone, Copy, Debug)]
enum State {
Idle { after_write: bool },
ReadInProgress { waiting_for_data: bool },
WriteInProgress,
}
impl State {
fn get_state() -> Self {
unsafe { STATE }
}
fn set_state(state: Self) {
defmt::trace!("Setting new state to {:?}", defmt::Debug2Format(&state));
unsafe { STATE = state }
}
fn is_write(&self) -> bool {
matches!(
self,
Self::Idle { after_write: true } | Self::WriteInProgress
)
}
}
pub struct I2C2 {
_pb10: PB10,
_pb11: PB11,
_i2c2: peripherals::I2C2,
}
impl I2C2 {
#[must_use]
pub fn new(i2c2: peripherals::I2C2, pb10: PB10, pb11: PB11, addr: u16, prio: Priority) -> Self {
// Set pins in AF mode
pb10.set_as_af(SclPin::af_num(&pb10), AFType::OutputOpenDrain);
pb11.set_as_af(i2c::SdaPin::af_num(&pb11), AFType::OutputOpenDrain);
// Turn I2C2 clock on
pac::RCC.apb1enr().modify(|w| w.set_i2c2en(true));
// Reset I2C2 peripheral
pac::RCC.apb1rstr().modify(|w| w.set_i2c2rst(true));
pac::RCC.apb1rstr().modify(|w| w.set_i2c2rst(false));
pac::I2C2.cr1().write(|_| ());
// Configure address
pac::I2C2.oar1().write(|w| w.set_add(addr << 1));
// Enable event interrupt for I2C2 and 2MHz frequency
pac::I2C2.cr2().write(|w| {
w.set_itevten(true);
w.set_iterren(true);
w.set_freq(36);
});
// Enable I2C2
pac::I2C2.cr1().write(|w| {
w.set_pe(true);
});
// Set ack generation
pac::I2C2.cr1().modify(|w| {
w.set_ack(true);
});
interrupt::I2C2_EV.set_priority(prio);
interrupt::I2C2_ER.set_priority(prio);
unsafe {
interrupt::I2C2_EV.enable();
interrupt::I2C2_ER.enable();
}
Self {
_pb10: pb10,
_pb11: pb11,
_i2c2: i2c2,
}
}
pub async fn receive(&mut self, buffer: &mut [u8]) -> Result<usize> {
for (i, b) in buffer.iter_mut().enumerate() {
match RX_QUEUE.receive().await {
I2cRxEvent::Byte(v) => *b = v,
I2cRxEvent::Stop => return Ok(i),
}
}
for extra in 0..usize::MAX {
if matches!(RX_QUEUE.receive().await, I2cRxEvent::Stop) {
return Err(Error::ExtraBytes(extra));
}
}
unreachable!();
}
pub fn send(&mut self, data: &[u8]) -> Result<()> {
defmt::trace!("Enqueuing {:?}", data);
// Enqueue data to send next
for &b in data {
if TX_QUEUE.try_send(b).is_err() {
return Err(Error::FifoFull);
}
}
// If the controller is already waiting for data (and
// stretching the clock down), send it right away by
// trigerring an interrupt.
if matches!(
State::get_state(),
State::ReadInProgress {
waiting_for_data: true
}
) {
pac::I2C2.cr2().modify(|w| {
w.set_itbufen(true);
});
}
Ok(())
}
fn empty_tx_queue() {
while TX_QUEUE.try_receive().is_ok() {}
}
}
#[interrupt]
unsafe fn I2C2_EV() {
let sr1 = pac::I2C2.sr1().read();
defmt::info!(
"I2C2_EV, sr1 = {:#06x}, cr1 = {:#06x}, cr2 = {:#06x}",
sr1.0,
pac::I2C2.cr1().read().0,
pac::I2C2.cr2().read().0
);
if sr1.addr() {
defmt::trace!("I2C2_EV addr");
// Addr event is cleared by reading sr2
let sr2 = pac::I2C2.sr2().read();
// If a write operation was in progress, send a simulated Stop to
// the RX queue.
let previous_state = State::get_state();
if matches!(previous_state, State::WriteInProgress)
&& RX_QUEUE.try_send(I2cRxEvent::Stop).is_err()
{
defmt::error!("Cannot send STOP event due to repeated start to the receive queue");
}
let new_state = if sr2.tra() {
let outgoing = if !previous_state.is_write() {
// Another read transaction happened just before this one,
// send 0 to avoid locking the bus.
defmt::trace!("Read transaction without write, sending 0 to prevent bus locking");
Some(0)
} else if let Ok(b) = TX_QUEUE.try_receive() {
// If bytes are ready to send already, send the first
// one and enable the buffer events to receive the
// next TXE.
defmt::trace!("Sending already ready byte {:#04x}", b);
Some(b)
} else {
// Otherwise, indicate that data is waiting.
defmt::trace!("No data ready, stretching clock");
None
};
outgoing
.clone()
.map(|b| pac::I2C2.dr().write(|w| w.set_dr(b)));
State::ReadInProgress {
waiting_for_data: outgoing.is_none(),
}
} else {
// In case of a write request, fill the transmit buffer
// and enable the buffer events to receive only the RXNE
// events, and not the TXE ones. Also, empty the receive
// buffer in case it already contains something. The
// outgoing buffer will later be cleared when the stop
// event is received.
pac::I2C2.dr().modify(|_| ());
pac::I2C2.cr2().modify(|w| {
w.set_itbufen(true);
});
defmt::trace!("Current state of CR1: {:#06x}", pac::I2C2.cr1().read().0);
defmt::trace!("Current state of CR2: {:#06x}", pac::I2C2.cr2().read().0);
// Empty a previously filled TX queue in case a read request
// arrives right after.
I2C2::empty_tx_queue();
defmt::trace!("Waiting for RXNE or STOPF event");
State::WriteInProgress
};
State::set_state(new_state);
pac::I2C2.cr2().modify(|w| {
w.set_itbufen(!matches!(
new_state,
State::ReadInProgress {
waiting_for_data: true
}
))
});
} else if sr1.rxne() {
defmt::trace!("I2C2_EV RXNE");
let b = pac::I2C2.dr().read().0 as u8;
defmt::trace!("Received byte {:#04x}", &b);
if RX_QUEUE.try_send(I2cRxEvent::Byte(b)).is_err() {
defmt::error!("I2C byte has overfilled the buffer");
}
} else if sr1.txe() {
defmt::trace!("I2C2_EV TXE");
if let Ok(b) = TX_QUEUE.try_receive() {
defmt::trace!("Sending byte {:#04x}", b);
pac::I2C2.dr().write(|w| w.set_dr(b));
} else {
defmt::trace!("Nothing to send, storing 0 in DR to bus locking");
pac::I2C2.dr().write(|w| w.set_dr(0));
}
} else if sr1.stopf() {
defmt::trace!(
"I2C2_EV STOPF while state is {:?}",
defmt::Debug2Format(&State::get_state())
);
// Clear stopf by writing to cr1
pac::I2C2.cr1().modify(|_w| ());
// Opt out of the TXE/RXNE interrupt
pac::I2C2.cr2().modify(|w| {
w.set_itbufen(false);
});
// Do a clearing sequence as recommended in the reference manual
// by reading and writing dr
pac::I2C2.dr().modify(|_w| ());
// If receiving, indicate the end of data
let previous_state = State::get_state();
if matches!(previous_state, State::WriteInProgress) {
if RX_QUEUE.try_send(I2cRxEvent::Stop).is_err() {
defmt::error!("Unable to send stop event to the buffer");
}
State::set_state(State::Idle { after_write: true });
} else {
State::set_state(State::Idle { after_write: false });
}
} else {
defmt::error!("I2C2_EV interrupt without cause");
}
}
#[interrupt]
unsafe fn I2C2_ER() {
let sr1 = pac::I2C2.sr1().read();
if sr1.af() {
defmt::trace!("I2C2_ER NACKF");
pac::I2C2.sr1().modify(|w| w.set_af(false));
// Nack are only used when transmitting, opt out of the RXNE interrupt
pac::I2C2.cr2().modify(|w| {
w.set_itbufen(false);
});
// Flush unsend bytes
while let Ok(b) = TX_QUEUE.try_receive() {
defmt::trace!("Discarding {:#04x}", b);
}
State::set_state(State::Idle { after_write: false });
} else {
defmt::error!(
"I2C2_ER sr1={:#06x} sr2={:#06x}",
sr1.0,
pac::I2C2.sr2().read().0
);
panic!();
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment