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

move code for UART and I2C scan into dedicated source files

parent 3d146e57
No related branches found
No related tags found
No related merge requests found
......@@ -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)));
......
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
}
}
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 { }
*/
#![no_std]
pub mod rp;
pub mod dont_abort;
pub mod i2c_scan;
pub mod rs485;
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));
},
_ => {
},
}
}
}
}
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