From 6522374ce0bd8fb300db3b21195c4306c4d264a4 Mon Sep 17 00:00:00 2001 From: Benjamin Koch <bbbsnowball@gmail.com> Date: Sat, 13 May 2023 06:36:15 +0200 Subject: [PATCH] give up: do I2C scan with bitbanging - like everyone else seems to do --- firmware/rust1/src/bin/heizung.rs | 119 +++++++++++++++++++++++++++--- 1 file changed, 107 insertions(+), 12 deletions(-) diff --git a/firmware/rust1/src/bin/heizung.rs b/firmware/rust1/src/bin/heizung.rs index 42947b7..0203324 100644 --- a/firmware/rust1/src/bin/heizung.rs +++ b/firmware/rust1/src/bin/heizung.rs @@ -4,11 +4,12 @@ use defmt::*; use embassy_executor::Spawner; +use embassy_futures::select::select; +use embassy_futures::select::Either::*; use embassy_rp::gpio; use embassy_time::{Duration, Timer}; -use embassy_rp::gpio::{Input, Level, Output, Pull}; -//use embassy_rp::i2c; -use heizung::rp::i2c; +use embassy_rp::gpio::{Input, Level, Output, Pull, Flex}; +use embassy_rp::i2c; use embassy_rp::interrupt; use embedded_hal_async::i2c::I2c; use embassy_rp::peripherals::{UART0, I2C0}; @@ -71,21 +72,116 @@ fn format_scan_result<const N : usize>(msg : &mut String<N>, scan_result : [Scan Ok(()) } +async fn _clockStretch<'d, SCL: gpio::Pin>(scl: &mut Flex<'d, SCL>) -> Result<(), i2c::Error> { + use i2c::Error::*; + use i2c::AbortReason::*; + + if scl.is_high() { + return Ok(()) + } + + let is_high = scl.wait_for_high(); + let timeout = Timer::after(Duration::from_micros(100)); + + return match select(is_high, timeout).await { + First(_) => Ok(()), + Second(_) => Err(Abort(ArbitrationLoss)), + } +} + +// adapted from https://github.com/earlephilhower/arduino-pico/blob/6e52b72523b11470b2ad2b0578ca1500be238108/libraries/Wire/src/Wire.cpp#L257 +async fn i2c_write_zero_bytes_middle_part<'d, SCL: gpio::Pin, SDA: gpio::Pin, const FREQ : u64>( + mut addr: u16, delay: Duration, scl: &mut Flex<'d, SCL>, sda: &mut Flex<'d, SDA>) + -> Result<(), i2c::Error> { + sda.set_as_input(); + Timer::after(delay).await; + scl.set_as_input(); + _clockStretch(scl).await?; + sda.set_as_output(); + Timer::after(delay).await; + scl.set_as_output(); + Timer::after(delay).await; + for i in 0..8 { + addr <<= 1; + sda.set_level(if (addr & (1 << 7)) != 0 { gpio::Level::High } else { gpio::Level::Low }); + Timer::after(delay).await; + scl.set_as_input(); + Timer::after(delay).await; + _clockStretch(scl).await?; + scl.set_as_output(); + Timer::after(Duration::from_micros(5)).await; // Ensure we don't change too close to clock edge + } + + sda.set_as_input(); + Timer::after(delay).await; + scl.set_as_input(); + _clockStretch(scl).await?; + + let result = if sda.is_low() { + Ok(()) + } else { + use i2c::Error::*; + use i2c::AbortReason::*; + Err(Abort(NoAcknowledge)) + }; + Timer::after(delay).await; + scl.set_as_output(); + + result +} +async fn i2c_write_zero_bytes<SCL: gpio::Pin, SDA: gpio::Pin, const FREQ : u64>(mut addr: u16, scl: &mut SCL, sda: &mut SDA) + -> Result<(), i2c::Error> { + use i2c::Error::*; + use i2c::AbortReason::*; + + let delay = Duration::from_micros((1000000 / FREQ) / 2); + let ack = false; + + let mut sda = Flex::new(sda); + let mut scl = Flex::new(scl); + sda.set_as_input(); + sda.set_pull(Pull::Up); + scl.set_as_input(); + scl.set_pull(Pull::Up); + // We only need low or pullup so we can set the value to low and only toggle direction. + // This is how OutputOpenDrain does it (and the only reason we use Flex is because we want the pullup). + sda.set_low(); + scl.set_low(); + + Timer::after(delay).await; + if sda.is_low() || scl.is_low() { + return Err(Abort(ArbitrationLoss)) + } + + let result = i2c_write_zero_bytes_middle_part::<_, _, FREQ>(addr, delay, &mut scl, &mut sda).await; + + Timer::after(delay).await; + sda.set_as_output(); + Timer::after(delay).await; + scl.set_as_input(); + Timer::after(delay).await; + sda.set_as_input(); + Timer::after(delay).await; + + return result; +} + #[embassy_executor::task] -async fn i2c_task(mut i2c: i2c::I2c<'static, I2C0, i2c::Async>) { +async fn i2c_task(mut i2c_peri: embassy_rp::peripherals::I2C0, + mut scl: embassy_rp::peripherals::PIN_13, mut sda: embassy_rp::peripherals::PIN_12, + mut i2c_irq: embassy_rp::interrupt::I2C0_IRQ, i2c_config: i2c::Config) { use i2c::Error::*; use i2c::AbortReason::*; loop { + if true { + let mut i2c = i2c::I2c::new_async(&mut i2c_peri, &mut scl, &mut sda, &mut i2c_irq, i2c_config); + } + const MAX_ADDRESS : u8 = 128; let mut scan_result = [ScanResult::Error; MAX_ADDRESS as usize]; for i in 0..MAX_ADDRESS { - //NOTE It looks like the blocking implementation doesn't accept an empty buffer - // but the async one does. Lucky for us because we need that here! - // -> Actually, it doesn't. It just hangs. - info!("I2C testing: {}", i); - //FIXME Don't test with buffer size 1 because that will actually write, which can cause all kinds of issues! - match i2c.write_async(i.into(), [0; 0]).await { + match i2c_write_zero_bytes::<_, _, 400_000>(i as u16, &mut scl, &mut sda).await { Result::Ok(()) => { //info!("I2C ok: {}", i); scan_result[i as usize] = ScanResult::Ack @@ -155,8 +251,7 @@ async fn main(spawner: Spawner) { let i2c_irq = interrupt::take!(I2C0_IRQ); let mut i2c_config = i2c::Config::default(); i2c_config.frequency = 400_000; - let i2c = i2c::I2c::new_async(p.I2C0, scl, sda, i2c_irq, i2c_config); - unwrap!(spawner.spawn(i2c_task(i2c))); + unwrap!(spawner.spawn(i2c_task(p.I2C0, scl, sda, i2c_irq, i2c_config))); // use 19200 baud in 8E1 mode - not great but Modbus default let mut uart_config = uart::Config::default(); -- GitLab