Skip to content
Snippets Groups Projects
Commit 6522374c authored by Benjamin Koch's avatar Benjamin Koch
Browse files

give up: do I2C scan with bitbanging - like everyone else seems to do

parent 9e796b4f
No related branches found
No related tags found
No related merge requests found
......@@ -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();
......
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