diff --git a/firmware/rust1/src/bin/heizung.rs b/firmware/rust1/src/bin/heizung.rs index e0d30a4065dd908c4994cd9098541831cf8a854d..17156398ca1a8525485408f9cf953d04d7b41fa1 100644 --- a/firmware/rust1/src/bin/heizung.rs +++ b/firmware/rust1/src/bin/heizung.rs @@ -13,215 +13,30 @@ use embassy_rp::i2c; use embassy_rp::interrupt; //use embedded_hal_async::i2c::I2c; use embassy_embedded_hal::SetConfig; -use embassy_rp::peripherals::{UART0}; -use embassy_rp::uart::{self, UartRx, Parity}; +use embassy_rp::uart::{self, Parity}; use embassy_rp::pwm::{self, Pwm}; -use embassy_rp::pio::{Config, Pio, ShiftConfig, ShiftDirection, FifoJoin}; -use embassy_rp::relocate::RelocatedProgram; -use embassy_rp::Peripheral; use {defmt_rtt as _, panic_probe as _}; use heapless::String; -use fixed::traits::ToFixed; -use fixed_macro::types::U56F8; -fn append_hex1<const N : usize>(s : &mut String<N>, num : u8) -> Result<(), ()> { - let ch = if num < 10 { - ('0' as u8 + num) as char - } else { - ('a' as u8 + num - 10) as char - }; - s.push(ch) -} - -fn append_hex2<const N : usize>(s : &mut String<N>, num : u8) -> Result<(), ()> { - append_hex1(s, num >> 4)?; - append_hex1(s, num & 0xf) -} - -#[derive(PartialEq, Eq, Copy, Clone, Debug)] -#[cfg_attr(feature = "defmt", derive(defmt::Format))] -enum ScanResult { - NAck, - Ack, - Reserved, - Error -} - -fn format_scan_result<const N : usize>(msg : &mut String<N>, scan_result : [ScanResult; 128]) -> Result<(), ()> { - const MAX_ADDRESS : u8 = 128; - for i in 0..MAX_ADDRESS/16 { - let base = i*16; - append_hex2(msg, base)?; - msg.push_str(":")?; - for j in 0..16 { - msg.push_str(" ")?; - match scan_result[(base+j) as usize] { - ScanResult::Ack => append_hex2(msg, base+j)?, - ScanResult::NAck => msg.push_str("--")?, - ScanResult::Reserved => msg.push_str("rr")?, - ScanResult::Error => msg.push_str("!!")?, - } - } - msg.push_str("\n")?; - } - Ok(()) -} - - -// adapted from arduino-pico: -// https://github.com/earlephilhower/arduino-pico/blob/6e52b72523b11470b2ad2b0578ca1500be238108/libraries/Wire/src/Wire.cpp#L257 -// This seems to be the only way to do zero-length writes: -// see https://github.com/raspberrypi/pico-sdk/issues/238 -async fn handle_clock_stretching<'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 { - Either::First(_) => Ok(()), - Either::Second(_) => Err(Abort(ArbitrationLoss)), - } -} -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(); - handle_clock_stretching(scl).await?; - sda.set_as_output(); - Timer::after(delay).await; - scl.set_as_output(); - Timer::after(delay).await; - for _ 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; - handle_clock_stretching(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(); - handle_clock_stretching(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>(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 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; - - // abort if SCL is stuck low - handle_clock_stretching(&mut scl).await?; - - // handle SDA being stuck low - // (This can happen for very simple I2C devices that only use SCL and don't have any other clocks that - // they can use to handle timeouts, e.g. simple port expanders.) - if sda.is_low() { - // I think I have read that DW_apb_i2c does nine clocks. This seems resonable so let's do the same. - for _ in 0..9 { - scl.set_as_output(); - Timer::after(delay).await; - scl.set_as_input(); - Timer::after(delay).await; - - if sda.is_high() { - break; - } - } - - if sda.is_low() || scl.is_low() { - // There is isn't much that we can do here. A start condition would reset the state but we cannot - // generate it while one of the lines is stuck low. - return Err(Abort(ArbitrationLoss)) - } - - // SDA is high, again. Good. We will generate a start condition soon. This should abort any - // partial transfers that some devices might think are ongoing. We have already waited for half - // a clock so we are good to go. - } - - 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; -} +use heizung::i2c_scan::{self, ScanResultForBus}; +use heizung::rs485::RS485; #[embassy_executor::task] 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::*; - - const MAX_ADDRESS : u8 = 128; - let mut prev_scan_result = [ScanResult::Reserved; MAX_ADDRESS as usize]; + let mut prev_scan_result = ScanResultForBus::new(); loop { if true { let _i2c = i2c::I2c::new_async(&mut i2c_peri, &mut scl, &mut sda, &mut i2c_irq, i2c_config); } - let mut scan_result = [ScanResult::Error; MAX_ADDRESS as usize]; - for i in 0..MAX_ADDRESS { - scan_result[i as usize] = match i2c_write_zero_bytes::<_, _, 400_000>(i as u16, &mut scl, &mut sda).await { - Result::Ok(()) => ScanResult::Ack, - Result::Err(Abort(NoAcknowledge)) => ScanResult::NAck, - Result::Err(AddressReserved(_)) => ScanResult::Reserved, - Result::Err(e) => { - warn!("I2C problem: {:02x}: {:?}", i, e); - ScanResult::Error - } - } - } - - if prev_scan_result.iter().zip(scan_result.iter()).any(|(x,y)| x != y) { + let scan_result = ScanResultForBus::scan(&mut scl, &mut sda).await; + if prev_scan_result != scan_result { prev_scan_result = scan_result; - const MSG_LEN : usize = (MAX_ADDRESS as usize)*3 + (MAX_ADDRESS as usize)/16*6; + const MSG_LEN : usize = i2c_scan::SCAN_RESULT_STRING_LENGTH; let mut msg = String::<MSG_LEN>::new(); - match format_scan_result(&mut msg, scan_result) { + match scan_result.format(&mut msg) { Ok(()) => info!("I2C scan result:\n{}", msg), Err(()) => info!("I2C scan result: too long for our buffer!"), } @@ -255,318 +70,10 @@ async fn beeper_task(pwm_channel: embassy_rp::peripherals::PWM_CH3, beeper_pin: } } - -fn pin_io<P: embassy_rp::gpio::Pin>(pin: &P) -> embassy_rp::pac::io::Gpio { - use embassy_rp::gpio::Bank; - use embassy_rp::pac; - - let block = match pin.bank() { - Bank::Bank0 => pac::IO_BANK0, - Bank::Qspi => pac::IO_QSPI, - }; - block.gpio(pin.pin() as _) -} - -async fn debug_print_pio_addr(sm: embassy_rp::pac::pio::StateMachine) { - let mut prev = 42u8; - loop { - let addr = unsafe { sm.addr().read().addr() }; - if prev != addr { - prev = addr; - info!("SM addr: {}", addr); - } - Timer::after(Duration::from_millis(200)).await; - } -} - -mod dont_abort { - use core::future::*; - use core::pin::Pin; - use core::task::*; - - pub struct DontAbort<A> { - a: A, - done: bool - } - - pub struct DontAbortFuture<'a, A> { - inner: &'a mut DontAbort<A> - } - - impl<A> DontAbort<A> { - pub fn new(a: A) -> DontAbort<A> { - DontAbort { a, done: false } - } - - #[allow(dead_code)] - pub fn done(self: &Self) -> bool { - return self.done; - } - - pub fn continue_wait<'a>(self: &'a mut Self) -> DontAbortFuture<'a, A> { - DontAbortFuture { inner: self } - } - } - - impl<A: Unpin> Unpin for DontAbort<A> {} - impl<'a, A: Unpin> Unpin for DontAbortFuture<'a, A> {} - - impl<'a, A> Future for DontAbortFuture<'a, A> - where - A: Future, - { - type Output = A::Output; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { - let this = unsafe { self.get_unchecked_mut() }; - if this.inner.done { - panic!("Ready future is called again!"); - } - let a = unsafe { Pin::new_unchecked(&mut this.inner.a) }; - if let Poll::Ready(x) = a.poll(cx) { - this.inner.done = true; - return Poll::Ready(x); - } - Poll::Pending - } - } -} - #[embassy_executor::task] -async fn uart_task(pio0: embassy_rp::peripherals::PIO0, rx_pin: embassy_rp::peripherals::PIN_17, - dma_channel: embassy_rp::peripherals::DMA_CH0, - mut rx: UartRx<'static, UART0, uart::Async>) { - let Pio { - mut common, - sm0: mut sm, - mut irq0, - .. - } = Pio::new(pio0); - - sm.set_enable(false); - - let prg_set_osr = pio_proc::pio_asm!( - ".origin 0", - "pull", - "hang:", - "jmp hang", - ); - - let prg = pio_proc::pio_asm!( - ".origin 0", - - // Send zero to RX FIFO to signal start of next measurement. - ".wrap_target", - "timeout:", - "in null, 32", - - // Wait for pin to be high for a longer time (UART is idle), copy waiting time to X - // and decrement until done, start again when pin changes. - "wait_idle:", - "wait 1 pin 0", - "mov x, osr", - "continue_wait_idle:", - "jmp pin still_one", - "jmp wait_idle", // we got a zero, start again - "still_one:", - "jmp x--, continue_wait_idle", - - // We have a long 1, i.e. UART is idle. Wait for start bit. - "have_long_high:", - "wait 0 pin 0", - "nop [4]", - "jmp pin wait_idle", // abort if zero was just a glitch - - // We want to measure 9 bits (start bit plus eight (or nine) data bits). However, we need - // different code for high and low so it's easier to measure 4x2 bits. Weuse Y as the loop counter. - "set y, 3", - - // Start counting. We count down from the value in OSR (which is our timeout). - "measure_next:", - "mov x, osr", - "measure_low:", - "jmp pin changed_to_one", - //"nop", - "jmp x-- measure_low", - "jmp timeout", - "changed_to_one:", - "in x, 32" - - // Start counting, again, but for a high pulse. - "mov x, osr", - "measure_high:", - "jmp pin still_high2", - "jmp changed_to_low", - "still_high2:", - "jmp x-- measure_high", - "jmp timeout", - "changed_to_low:", - "in x, 32", - "jmp y-- measure_next", - - // We are done. - "irq set 0", - ".wrap", - ); - - let relocated = RelocatedProgram::new(&prg_set_osr.program); - let mut cfg = Config::default(); - let loaded_program = common.load_program(&relocated); - cfg.use_program(&loaded_program, &[]); - const SM_FREQ: u32 = 125_000_000; - cfg.clock_divider = (U56F8!(125_000_000) / U56F8!(125_000_000 /* SM_FREQ */)).to_fixed(); - cfg.shift_in = ShiftConfig { - auto_fill: true, - threshold: 32, - direction: ShiftDirection::Left, - }; - cfg.shift_out = ShiftConfig { - auto_fill: false, - threshold: 32, - direction: ShiftDirection::Right, - }; - let rx_pin_pio = common.make_pio_pin(unsafe { rx_pin.clone_unchecked() }); - cfg.set_in_pins(&[&rx_pin_pio]); - cfg.set_jmp_pin(&rx_pin_pio); - - sm.set_config(&cfg); - sm.set_enable(true); - - // Timeout: Modbus wants 1.5 ms between frames so we make this a bit smaller. SM runs at 25 MHz - // but we need two clocks per loop. - let timeout_start_value = (SM_FREQ as f32 * 1.2e-3 / 2.) as u32; - sm.tx().push(timeout_start_value); - info!("timeout_start_value: {} = 0x{:08x}", timeout_start_value, timeout_start_value); - - // switch to the real program and join FIFOs - unsafe { common.free_instr(loaded_program.used_memory); }; - sm.set_enable(false); - let relocated = RelocatedProgram::new(&prg.program); - let loaded_program = common.load_program(&relocated); - cfg.use_program(&loaded_program, &[]); - cfg.fifo_join = FifoJoin::RxOnly; - sm.set_config(&cfg); - sm.set_enable(true); - - // set rx pin function back to UART - // (PIO can always read and we don't want to write but embassy_rp cannot know that so it "helpfully" configured the pin for PIO function.) - unsafe { - pin_io(&rx_pin).ctrl().write(|w| w.set_funcsel(embassy_rp::pac::io::vals::Gpio17ctrlFuncsel::UART0_RX.0)); - } - - info!("Program size for auto-baud: {}", prg.program.code.len()); - - let mut dma_in_ref = dma_channel.into_ref(); - let mut din = [42u32; 9]; - let mut bit_index = 0; - let mut rx_buf = [0; 1]; - let mut rx_future = dont_abort::DontAbort::new(rx.read(&mut rx_buf)); - loop { - let x = select4( - irq0.wait(), - sm.rx().dma_pull(dma_in_ref.reborrow(), &mut din), - debug_print_pio_addr(embassy_rp::pac::PIO0.sm(0)), - rx_future.continue_wait(), - ).await; - match x { - Either4::First(_) => { - let mut sum = 0; - let mut first = 0; - let mut ok = false; - let mut got_to_eight = false; - for i in 0..din.len() { - if din[i] == 0 { - bit_index = 0; - info!("SM in {}: {:08x} -> start", i, din[i]); - } else { - let mut delay = timeout_start_value - din[i]; - if bit_index == 0 { - delay += 7; - } else if bit_index%2 == 1 { - //delay += 4; - delay += 3; - } else { - delay += 3; - } - let millis = (delay) as f32 / SM_FREQ as f32 * 1000. * 2.; - let baud = 1000. / millis; - info!("SM in {} ({}): {:08x} -> {} -> {} ms -> {}", i, bit_index, din[i], delay, millis, baud); - if bit_index == 0 { - sum = 0; - first = delay; - ok = true; - } else if delay < first-first/4 || delay > first+first/4 { - ok = false; - } - sum += delay; - bit_index += 1; - got_to_eight = bit_index == 8; - } - } - - for _ in bit_index..8 { - if let Some(x) = sm.rx().try_pull() { - let mut delay = timeout_start_value - x; - if bit_index == 0 { - delay += 7; - } else if bit_index%2 == 1 { - //delay += 4; - delay += 3; - } else { - delay += 3; - } - let millis = (delay) as f32 / SM_FREQ as f32 * 1000. * 2.; - let baud = 1000. / millis; - info!("SM in ({}): {:08x} -> {} -> {} ms -> {}", bit_index, x, delay, millis, baud); - if bit_index == 0 { - sum = 0; - first = delay; - ok = true; - } else if delay < first-first/4 || delay > first+first/4 { - ok = false; - } - sum += delay; - bit_index += 1; - got_to_eight = bit_index == 8; - } else { - break - } - } - - if got_to_eight && ok { - let millis = sum as f32 / SM_FREQ as f32 * 1000. * 2. / 8.; - let baud = 1000. / millis; - info!("Guessed baud rate: {}", baud); - } - }, - Either4::Second(_) => { - if false { - for i in 0..din.len() { - if din[i] == 0 { - bit_index = 0 - } else { - bit_index += 1 - } - info!("SM in {}: {:08x}", i, din[i]); - } - } - }, - Either4::Fourth(x) => { - drop(rx_future); - match x { - Result::Ok(()) => { - info!("RX {:?}", rx_buf); - }, - Result::Err(e) => { - info!("RX error {:?}", e); - }, - } - rx_future = dont_abort::DontAbort::new(rx.read(&mut rx_buf)); - }, - _ => { - }, - } - } +async fn uart_task(this: RS485) { + info!("abc"); + this.run_task().await; } #[embassy_executor::main] @@ -595,7 +102,6 @@ async fn main(spawner: Spawner) { let _tx_en = p.PIN_15; let _tx = p.PIN_16; let rx = p.PIN_17; - let rx_for_autobaud = unsafe { rx.clone_unchecked() }; //FIXME move UART and auto-baud into same task let _matrix_in2 = Input::new(p.PIN_18, Pull::Up); let _reed3 = Input::new(p.PIN_19, Pull::Up); let _reed4 = Input::new(p.PIN_20, Pull::Up); @@ -618,14 +124,8 @@ async fn main(spawner: Spawner) { let mut uart_config = uart::Config::default(); uart_config.baudrate = 19200; uart_config.parity = Parity::ParityEven; - let uart_rx = UartRx::new( - p.UART0, - rx, - interrupt::take!(UART0_IRQ), - p.DMA_CH1, - uart_config, - ); - unwrap!(spawner.spawn(uart_task(p.PIO0, rx_for_autobaud, p.DMA_CH0, uart_rx))); + let rs485 = RS485::new(p.UART0, rx, interrupt::take!(UART0_IRQ), p.DMA_CH1, uart_config, p.PIO0, p.DMA_CH0); + unwrap!(spawner.spawn(uart_task(rs485))); if false { unwrap!(spawner.spawn(beeper_task(p.PWM_CH3, led_r))); diff --git a/firmware/rust1/src/dont_abort.rs b/firmware/rust1/src/dont_abort.rs new file mode 100644 index 0000000000000000000000000000000000000000..29544a119a37cc0d7a4b51f80031335ceb554166 --- /dev/null +++ b/firmware/rust1/src/dont_abort.rs @@ -0,0 +1,50 @@ +use core::future::*; +use core::pin::Pin; +use core::task::*; + +pub struct DontAbort<A> { + a: A, + done: bool +} + +pub struct DontAbortFuture<'a, A> { + inner: &'a mut DontAbort<A> +} + +impl<A> DontAbort<A> { + pub fn new(a: A) -> DontAbort<A> { + DontAbort { a, done: false } + } + + #[allow(dead_code)] + pub fn done(self: &Self) -> bool { + return self.done; + } + + pub fn continue_wait<'a>(self: &'a mut Self) -> DontAbortFuture<'a, A> { + DontAbortFuture { inner: self } + } +} + +impl<A: Unpin> Unpin for DontAbort<A> {} +impl<'a, A: Unpin> Unpin for DontAbortFuture<'a, A> {} + +impl<'a, A> Future for DontAbortFuture<'a, A> +where + A: Future, +{ + type Output = A::Output; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { + let this = unsafe { self.get_unchecked_mut() }; + if this.inner.done { + panic!("Ready future is called again!"); + } + let a = unsafe { Pin::new_unchecked(&mut this.inner.a) }; + if let Poll::Ready(x) = a.poll(cx) { + this.inner.done = true; + return Poll::Ready(x); + } + Poll::Pending + } +} diff --git a/firmware/rust1/src/i2c_scan.rs b/firmware/rust1/src/i2c_scan.rs new file mode 100644 index 0000000000000000000000000000000000000000..78bb44d445438730683326cb58130d7a396cc43c --- /dev/null +++ b/firmware/rust1/src/i2c_scan.rs @@ -0,0 +1,217 @@ +use defmt::*; +use {defmt_rtt as _, panic_probe as _}; +use heapless::String; + +use embassy_futures::select::*; +use embassy_time::{Duration, Timer}; +use embassy_rp::gpio::{self, Pull, Flex}; +use embassy_rp::i2c; + +fn append_hex1<const N : usize>(s : &mut String<N>, num : u8) -> Result<(), ()> { + let ch = if num < 10 { + ('0' as u8 + num) as char + } else { + ('a' as u8 + num - 10) as char + }; + s.push(ch) +} + +fn append_hex2<const N : usize>(s : &mut String<N>, num : u8) -> Result<(), ()> { + append_hex1(s, num >> 4)?; + append_hex1(s, num & 0xf) +} + +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum ScanResult { + NAck, + Ack, + Error +} + +// adapted from arduino-pico: +// https://github.com/earlephilhower/arduino-pico/blob/6e52b72523b11470b2ad2b0578ca1500be238108/libraries/Wire/src/Wire.cpp#L257 +// This seems to be the only way to do zero-length writes: +// see https://github.com/raspberrypi/pico-sdk/issues/238 +async fn handle_clock_stretching<'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 { + Either::First(_) => Ok(()), + Either::Second(_) => Err(Abort(ArbitrationLoss)), + } +} +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(); + handle_clock_stretching(scl).await?; + sda.set_as_output(); + Timer::after(delay).await; + scl.set_as_output(); + Timer::after(delay).await; + for _ 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; + handle_clock_stretching(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(); + handle_clock_stretching(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>(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 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; + + // abort if SCL is stuck low + handle_clock_stretching(&mut scl).await?; + + // handle SDA being stuck low + // (This can happen for very simple I2C devices that only use SCL and don't have any other clocks that + // they can use to handle timeouts, e.g. simple port expanders.) + if sda.is_low() { + // I think I have read that DW_apb_i2c does nine clocks. This seems resonable so let's do the same. + for _ in 0..9 { + scl.set_as_output(); + Timer::after(delay).await; + scl.set_as_input(); + Timer::after(delay).await; + + if sda.is_high() { + break; + } + } + + if sda.is_low() || scl.is_low() { + // There is isn't much that we can do here. A start condition would reset the state but we cannot + // generate it while one of the lines is stuck low. + return Err(Abort(ArbitrationLoss)) + } + + // SDA is high, again. Good. We will generate a start condition soon. This should abort any + // partial transfers that some devices might think are ongoing. We have already waited for half + // a clock so we are good to go. + } + + 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; +} + +#[derive(PartialEq, Eq, Copy, Clone, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct ScanResultForBus([ScanResult; 128]); + +pub const MAX_ADDRESS : u8 = 128; +pub const SCAN_RESULT_STRING_LENGTH : usize = (MAX_ADDRESS as usize)*3 + (MAX_ADDRESS as usize)/16*6; + +impl ScanResultForBus { + pub fn new() -> ScanResultForBus { + ScanResultForBus([ScanResult::Error; MAX_ADDRESS as usize]) + } + + pub async fn scan<SCL: gpio::Pin, SDA: gpio::Pin>(scl: &mut SCL, sda: &mut SDA) -> ScanResultForBus { + let mut results = Self::new(); + results.scan_i2c(scl, sda).await; + return results; + } + + pub async fn scan_i2c<SCL: gpio::Pin, SDA: gpio::Pin>(self: &mut Self, scl: &mut SCL, sda: &mut SDA) { + use i2c::Error::*; + use i2c::AbortReason::*; + + for i in 0..MAX_ADDRESS { + self.0[i as usize] = match i2c_write_zero_bytes::<_, _, 400_000>(i as u16, scl, sda).await { + Result::Ok(()) => ScanResult::Ack, + Result::Err(Abort(NoAcknowledge)) => ScanResult::NAck, + Result::Err(e) => { + warn!("I2C problem: {:02x}: {:?}", i, e); + ScanResult::Error + } + } + } + } + + pub fn format<const N : usize>(self: &Self, msg : &mut String<N>) -> Result<(), ()> { + self::assert!(N >= SCAN_RESULT_STRING_LENGTH); + + for i in 0..MAX_ADDRESS/16 { + let base = i*16; + append_hex2(msg, base)?; + msg.push_str(":")?; + for j in 0..16 { + msg.push_str(" ")?; + match self.0[(base+j) as usize] { + ScanResult::Ack => append_hex2(msg, base+j)?, + ScanResult::NAck => msg.push_str("--")?, + ScanResult::Error => msg.push_str("!!")?, + } + } + msg.push_str("\n")?; + } + Ok(()) + } +} + +/* +impl PartialEq for ScanResultForBus { + fn eq(self: &Self, other: &Self) -> bool { + self.0.iter().zip(other.0.iter()).all(|(x,y)| x == y) + } +} + +impl Eq for ScanResultForBus { } + */ diff --git a/firmware/rust1/src/lib.rs b/firmware/rust1/src/lib.rs index 26e5f5c2c222b0247e1981eb41c010e1c068ce8a..c969570ef887d1bea8b906fa4264985119554e79 100644 --- a/firmware/rust1/src/lib.rs +++ b/firmware/rust1/src/lib.rs @@ -1,2 +1,5 @@ #![no_std] pub mod rp; +pub mod dont_abort; +pub mod i2c_scan; +pub mod rs485; diff --git a/firmware/rust1/src/rs485.rs b/firmware/rust1/src/rs485.rs new file mode 100644 index 0000000000000000000000000000000000000000..e8b9d8a9dd4980129e486a75821b28437c0b2fea --- /dev/null +++ b/firmware/rust1/src/rs485.rs @@ -0,0 +1,299 @@ +use defmt::*; +use embassy_futures::select::*; +use embassy_rp::gpio; +use embassy_rp::pac; +use embassy_rp::interrupt::UART0_IRQ; +use embassy_time::{Duration, Timer}; +use embassy_embedded_hal::SetConfig; +use embassy_rp::peripherals::{self, UART0}; +use embassy_rp::uart::{self, UartRx}; +use embassy_rp::pio::{Config, Pio, ShiftConfig, ShiftDirection, FifoJoin}; +use embassy_rp::relocate::RelocatedProgram; +use embassy_rp::Peripheral; +use {defmt_rtt as _, panic_probe as _}; +use fixed::traits::ToFixed; +use fixed_macro::types::U56F8; + +use crate::dont_abort::DontAbort; + +pub struct RS485 { + pio: peripherals::PIO0, + rx_pin: peripherals::PIN_17, + dma_channel: peripherals::DMA_CH0, + rx: UartRx<'static, UART0, uart::Async> +} + +fn pin_io<P: gpio::Pin>(pin: &P) -> pac::io::Gpio { + use gpio::Bank; + + let block = match pin.bank() { + Bank::Bank0 => pac::IO_BANK0, + Bank::Qspi => pac::IO_QSPI, + }; + block.gpio(pin.pin() as _) +} + +async fn debug_print_pio_addr(sm: pac::pio::StateMachine) { + let mut prev = 42u8; + loop { + let addr = unsafe { sm.addr().read().addr() }; + if prev != addr { + prev = addr; + info!("SM addr: {}", addr); + } + Timer::after(Duration::from_millis(200)).await; + } +} + +impl RS485 { + pub fn new( + uart: UART0, rx: peripherals::PIN_17, rx_irq: UART0_IRQ, rx_dma_channel: peripherals::DMA_CH1, uart_config: uart::Config, + pio: peripherals::PIO0, dma_channel: peripherals::DMA_CH0 + ) -> RS485 { + // SAFETY: The auto-baud will only read from this pin and we will set it back to UART mode after initialising the PIO. + let rx_pin_for_autobaud = unsafe { rx.clone_unchecked() }; + + let uart_rx = UartRx::new( + uart, + rx, + rx_irq, + rx_dma_channel, + uart_config, + ); + RS485 { pio, rx_pin: rx_pin_for_autobaud, dma_channel, rx: uart_rx } + } + + pub async fn run_task(mut self: Self) { + let Pio { + mut common, + sm0: mut sm, + mut irq0, + .. + } = Pio::new(self.pio); + + sm.set_enable(false); + + let prg_set_osr = pio_proc::pio_asm!( + ".origin 0", + "pull", + "hang:", + "jmp hang", + ); + + let prg = pio_proc::pio_asm!( + ".origin 0", + + // Send zero to RX FIFO to signal start of next measurement. + ".wrap_target", + "timeout:", + "in null, 32", + + // Wait for pin to be high for a longer time (UART is idle), copy waiting time to X + // and decrement until done, start again when pin changes. + "wait_idle:", + "wait 1 pin 0", + "mov x, osr", + "continue_wait_idle:", + "jmp pin still_one", + "jmp wait_idle", // we got a zero, start again + "still_one:", + "jmp x--, continue_wait_idle", + + // We have a long 1, i.e. UART is idle. Wait for start bit. + "have_long_high:", + "wait 0 pin 0", + "nop [4]", + "jmp pin wait_idle", // abort if zero was just a glitch + + // We want to measure 9 bits (start bit plus eight (or nine) data bits). However, we need + // different code for high and low so it's easier to measure 4x2 bits. Weuse Y as the loop counter. + "set y, 3", + + // Start counting. We count down from the value in OSR (which is our timeout). + "measure_next:", + "mov x, osr", + "measure_low:", + "jmp pin changed_to_one", + //"nop", + "jmp x-- measure_low", + "jmp timeout", + "changed_to_one:", + "in x, 32" + + // Start counting, again, but for a high pulse. + "mov x, osr", + "measure_high:", + "jmp pin still_high2", + "jmp changed_to_low", + "still_high2:", + "jmp x-- measure_high", + "jmp timeout", + "changed_to_low:", + "in x, 32", + "jmp y-- measure_next", + + // We are done. + "irq set 0", + ".wrap", + ); + + let relocated = RelocatedProgram::new(&prg_set_osr.program); + let mut cfg = Config::default(); + let loaded_program = common.load_program(&relocated); + cfg.use_program(&loaded_program, &[]); + const SM_FREQ: u32 = 125_000_000; + cfg.clock_divider = (U56F8!(125_000_000) / U56F8!(125_000_000 /* SM_FREQ */)).to_fixed(); + cfg.shift_in = ShiftConfig { + auto_fill: true, + threshold: 32, + direction: ShiftDirection::Left, + }; + cfg.shift_out = ShiftConfig { + auto_fill: false, + threshold: 32, + direction: ShiftDirection::Right, + }; + let rx_pin_pio = common.make_pio_pin(unsafe { self.rx_pin.clone_unchecked() }); + cfg.set_in_pins(&[&rx_pin_pio]); + cfg.set_jmp_pin(&rx_pin_pio); + + sm.set_config(&cfg); + sm.set_enable(true); + + // Timeout: Modbus wants 1.5 ms between frames so we make this a bit smaller. SM runs at 25 MHz + // but we need two clocks per loop. + let timeout_start_value = (SM_FREQ as f32 * 1.2e-3 / 2.) as u32; + sm.tx().push(timeout_start_value); + info!("timeout_start_value: {} = 0x{:08x}", timeout_start_value, timeout_start_value); + + // switch to the real program and join FIFOs + unsafe { common.free_instr(loaded_program.used_memory); }; + sm.set_enable(false); + let relocated = RelocatedProgram::new(&prg.program); + let loaded_program = common.load_program(&relocated); + cfg.use_program(&loaded_program, &[]); + cfg.fifo_join = FifoJoin::RxOnly; + sm.set_config(&cfg); + sm.set_enable(true); + + // set rx pin function back to UART + // (PIO can always read and we don't want to write but embassy_rp cannot know that so it "helpfully" configured the pin for PIO function.) + unsafe { + pin_io(&self.rx_pin).ctrl().write(|w| w.set_funcsel(embassy_rp::pac::io::vals::Gpio17ctrlFuncsel::UART0_RX.0)); + } + + info!("Program size for auto-baud: {}", prg.program.code.len()); + + let mut dma_in_ref = self.dma_channel.into_ref(); + let mut din = [42u32; 9]; + let mut bit_index = 0; + let mut rx_buf = [0; 1]; + let mut rx_future = DontAbort::new(self.rx.read(&mut rx_buf)); + loop { + let x = select4( + irq0.wait(), + sm.rx().dma_pull(dma_in_ref.reborrow(), &mut din), + debug_print_pio_addr(embassy_rp::pac::PIO0.sm(0)), + rx_future.continue_wait(), + ).await; + match x { + Either4::First(_) => { + let mut sum = 0; + let mut first = 0; + let mut ok = false; + let mut got_to_eight = false; + for i in 0..din.len() { + if din[i] == 0 { + bit_index = 0; + info!("SM in {}: {:08x} -> start", i, din[i]); + } else { + let mut delay = timeout_start_value - din[i]; + if bit_index == 0 { + delay += 7; + } else if bit_index%2 == 1 { + //delay += 4; + delay += 3; + } else { + delay += 3; + } + let millis = (delay) as f32 / SM_FREQ as f32 * 1000. * 2.; + let baud = 1000. / millis; + info!("SM in {} ({}): {:08x} -> {} -> {} ms -> {}", i, bit_index, din[i], delay, millis, baud); + if bit_index == 0 { + sum = 0; + first = delay; + ok = true; + } else if delay < first-first/4 || delay > first+first/4 { + ok = false; + } + sum += delay; + bit_index += 1; + got_to_eight = bit_index == 8; + } + } + + for _ in bit_index..8 { + if let Some(x) = sm.rx().try_pull() { + let mut delay = timeout_start_value - x; + if bit_index == 0 { + delay += 7; + } else if bit_index%2 == 1 { + //delay += 4; + delay += 3; + } else { + delay += 3; + } + let millis = (delay) as f32 / SM_FREQ as f32 * 1000. * 2.; + let baud = 1000. / millis; + info!("SM in ({}): {:08x} -> {} -> {} ms -> {}", bit_index, x, delay, millis, baud); + if bit_index == 0 { + sum = 0; + first = delay; + ok = true; + } else if delay < first-first/4 || delay > first+first/4 { + ok = false; + } + sum += delay; + bit_index += 1; + got_to_eight = bit_index == 8; + } else { + break + } + } + + if got_to_eight && ok { + let millis = sum as f32 / SM_FREQ as f32 * 1000. * 2. / 8.; + let baud = 1000. / millis; + info!("Guessed baud rate: {}", baud); + } + }, + Either4::Second(_) => { + if false { + for i in 0..din.len() { + if din[i] == 0 { + bit_index = 0 + } else { + bit_index += 1 + } + info!("SM in {}: {:08x}", i, din[i]); + } + } + }, + Either4::Fourth(x) => { + drop(rx_future); + match x { + Result::Ok(()) => { + info!("RX {:?}", rx_buf); + }, + Result::Err(e) => { + info!("RX error {:?}", e); + }, + } + rx_future = DontAbort::new(self.rx.read(&mut rx_buf)); + }, + _ => { + }, + } + } + } +}