Make flashload COM more reliable
Some checks are pending
Rust/va416xx-rs/pipeline/pr-main Build queued...

Fixes for UART RX with IRQ implementation
This commit is contained in:
Robin Müller 2024-09-13 18:09:29 +02:00
parent cad968342a
commit 78dd7ee5c3
5 changed files with 425 additions and 491 deletions

View File

@ -1,12 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from spacepackets.ecss import RequestId
from spacepackets.ecss.defs import PusService from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm from spacepackets.ecss.tm import PusTm
import toml import toml
import struct import struct
import logging import logging
import argparse import argparse
import threading
import time import time
import enum import enum
from tmtccmd.com.serial_base import SerialCfg from tmtccmd.com.serial_base import SerialCfg
@ -45,6 +43,7 @@ ACTION_SERVICE = 8
RAW_MEMORY_WRITE_SUBSERVICE = 2 RAW_MEMORY_WRITE_SUBSERVICE = 2
BOOT_NVM_MEMORY_ID = 1 BOOT_NVM_MEMORY_ID = 1
PING_PAYLOAD_SIZE = 0
class ActionId(enum.IntEnum): class ActionId(enum.IntEnum):
@ -104,6 +103,29 @@ def main() -> int:
com_if = SerialCobsComIF(serial_cfg) com_if = SerialCobsComIF(serial_cfg)
com_if.open() com_if.open()
file_path = None file_path = None
if args.ping:
_LOGGER.info("Sending ping command")
ping_tc = PusTc(
apid=0x00,
service=PusService.S17_TEST,
subservice=1,
seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=bytes(PING_PAYLOAD_SIZE),
)
verificator.add_tc(ping_tc)
com_if.send(ping_tc.pack())
data_available = com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no ping reply received")
for reply in com_if.receive():
result = verificator.add_tm(
Service1Tm.from_tm(PusTm.unpack(reply, 0), UnpackParams(0))
)
if result is not None and result.completed:
_LOGGER.info("received ping completion reply")
if not args.target:
return 0
if args.target: if args.target:
if not args.corrupt: if not args.corrupt:
if not args.path: if not args.path:
@ -113,15 +135,6 @@ def main() -> int:
if not file_path.exists(): if not file_path.exists():
_LOGGER.error("File does not exist") _LOGGER.error("File does not exist")
return -1 return -1
if args.ping:
_LOGGER.info("Sending ping command")
ping_tc = PusTc(
apid=0x00,
service=PusService.S17_TEST,
subservice=1,
seq_count=SEQ_PROVIDER.get_and_increment(),
)
com_if.send(ping_tc.pack())
if args.corrupt: if args.corrupt:
if not args.target: if not args.target:
_LOGGER.error("target for corruption command required") _LOGGER.error("target for corruption command required")
@ -254,7 +267,7 @@ def main() -> int:
): ):
done = True done = True
# Still keep a small delay # Still keep a small delay
time.sleep(0.01) # time.sleep(0.05)
verificator.remove_completed_entries() verificator.remove_completed_entries()
if done: if done:
break break

View File

@ -18,13 +18,11 @@
#![no_main] #![no_main]
#![no_std] #![no_std]
use embedded_hal_nb::serial::Read;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use panic_rtt_target as _; use panic_rtt_target as _;
use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt}; use va416xx_hal::{clock::Clocks, edac, pac, time::Hertz, wdt::Wdt};
const EXTCLK_FREQ: u32 = 40_000_000; const EXTCLK_FREQ: u32 = 40_000_000;
const COBS_FRAME_SEPARATOR: u8 = 0x0;
const MAX_TC_SIZE: usize = 1024; const MAX_TC_SIZE: usize = 1024;
const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE); const MAX_TC_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TC_SIZE);
@ -33,10 +31,8 @@ const MAX_TM_SIZE: usize = 128;
const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE); const MAX_TM_FRAME_SIZE: usize = cobs::max_encoding_length(MAX_TM_SIZE);
const UART_BAUDRATE: u32 = 115200; const UART_BAUDRATE: u32 = 115200;
const SERIAL_RX_WIRETAPPING: bool = false;
const COBS_RX_DEBUGGING: bool = false;
const BOOT_NVM_MEMORY_ID: u8 = 1; const BOOT_NVM_MEMORY_ID: u8 = 1;
const RX_DEBUGGING: bool = false;
pub enum ActionId { pub enum ActionId {
CorruptImageA = 128, CorruptImageA = 128,
@ -62,13 +58,24 @@ use ringbuf::{
CachingCons, StaticProd, StaticRb, CachingCons, StaticProd, StaticRb,
}; };
const BUF_RB_SIZE_TX: usize = 1024; // Larger buffer for TC to be able to hold the possibly large memory write packets.
const SIZES_RB_SIZE_TX: usize = 16; const BUF_RB_SIZE_TC: usize = 2048;
const SIZES_RB_SIZE_TC: usize = 16;
static mut BUF_RB_TX: Lazy<StaticRb<u8, BUF_RB_SIZE_TX>> = const BUF_RB_SIZE_TM: usize = 512;
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TX>::default); const SIZES_RB_SIZE_TM: usize = 16;
static mut SIZES_RB_TX: Lazy<StaticRb<usize, SIZES_RB_SIZE_TX>> =
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TX>::default); // Ring buffers to handling variable sized telemetry
static mut BUF_RB_TM: Lazy<StaticRb<u8, BUF_RB_SIZE_TM>> =
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TM>::default);
static mut SIZES_RB_TM: Lazy<StaticRb<usize, SIZES_RB_SIZE_TM>> =
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TM>::default);
// Ring buffers to handling variable sized telecommands
static mut BUF_RB_TC: Lazy<StaticRb<u8, BUF_RB_SIZE_TC>> =
Lazy::new(StaticRb::<u8, BUF_RB_SIZE_TC>::default);
static mut SIZES_RB_TC: Lazy<StaticRb<usize, SIZES_RB_SIZE_TC>> =
Lazy::new(StaticRb::<usize, SIZES_RB_SIZE_TC>::default);
pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> { pub struct DataProducer<const BUF_SIZE: usize, const SIZES_LEN: usize> {
pub buf_prod: StaticProd<'static, u8, BUF_SIZE>, pub buf_prod: StaticProd<'static, u8, BUF_SIZE>,
@ -91,15 +98,10 @@ pub const APP_B_END_ADDR: u32 = 0x40000;
mod app { mod app {
use super::*; use super::*;
use cortex_m::asm; use cortex_m::asm;
use embedded_hal_nb::nb;
use embedded_io::Write; use embedded_io::Write;
use panic_rtt_target as _; use panic_rtt_target as _;
use rtic::Mutex; use rtic::Mutex;
use rtic_monotonics::systick::prelude::*; use rtic_monotonics::systick::prelude::*;
use rtic_sync::{
channel::{Receiver, Sender},
make_channel,
};
use rtt_target::rprintln; use rtt_target::rprintln;
use satrs::pus::verification::VerificationReportCreator; use satrs::pus::verification::VerificationReportCreator;
use spacepackets::ecss::PusServiceId; use spacepackets::ecss::PusServiceId;
@ -127,26 +129,24 @@ mod app {
#[local] #[local]
struct Local { struct Local {
uart_rx: uart::Rx<pac::Uart0>, uart_rx: uart::RxWithIrq<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>, uart_tx: uart::Tx<pac::Uart0>,
cobs_reader_state: CobsReaderStates,
tc_tx: TcTx,
tc_rx: TcRx,
rom_spi: Option<pac::Spi3>, rom_spi: Option<pac::Spi3>,
tx_cons: DataConsumer<BUF_RB_SIZE_TX, SIZES_RB_SIZE_TX>, // We handle all TM in one task.
tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
// We consume all TC in one task.
tc_cons: DataConsumer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
// We produce all TC in one task.
tc_prod: DataProducer<BUF_RB_SIZE_TC, SIZES_RB_SIZE_TC>,
verif_reporter: VerificationReportCreator, verif_reporter: VerificationReportCreator,
} }
#[shared] #[shared]
struct Shared { struct Shared {
decode_buffer_busy: bool, // Having this shared allows multiple tasks to generate telemetry.
decode_buf: [u8; MAX_TC_SIZE], tm_prod: DataProducer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
tx_prod: DataProducer<BUF_RB_SIZE_TX, SIZES_RB_SIZE_TX>,
} }
pub type TcTx = Sender<'static, usize, 2>;
pub type TcRx = Receiver<'static, usize, 2>;
rtic_monotonics::systick_monotonic!(Mono, 10_000); rtic_monotonics::systick_monotonic!(Mono, 10_000);
#[init] #[init]
@ -176,38 +176,45 @@ mod app {
&mut cx.device.sysconfig, &mut cx.device.sysconfig,
&clocks, &clocks,
); );
let (tx, rx) = uart0.split(); let (tx, mut rx, _) = uart0.split_with_irq();
let (tc_tx, tc_rx) = make_channel!(usize, 2);
let verif_reporter = VerificationReportCreator::new(0).unwrap(); let verif_reporter = VerificationReportCreator::new(0).unwrap();
let (buf_prod, buf_cons) = unsafe { BUF_RB_TX.split_ref() }; let (buf_prod_tm, buf_cons_tm) = unsafe { BUF_RB_TM.split_ref() };
let (sizes_prod, sizes_cons) = unsafe { SIZES_RB_TX.split_ref() }; let (sizes_prod_tm, sizes_cons_tm) = unsafe { SIZES_RB_TM.split_ref() };
let (buf_prod_tc, buf_cons_tc) = unsafe { BUF_RB_TC.split_ref() };
let (sizes_prod_tc, sizes_cons_tc) = unsafe { SIZES_RB_TC.split_ref() };
Mono::start(cx.core.SYST, clocks.sysclk().raw()); Mono::start(cx.core.SYST, clocks.sysclk().raw());
CLOCKS.set(clocks).unwrap(); CLOCKS.set(clocks).unwrap();
rx.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
.expect("initiating UART RX failed");
pus_tc_handler::spawn().unwrap(); pus_tc_handler::spawn().unwrap();
uart_reader_task::spawn().unwrap();
pus_tm_tx_handler::spawn().unwrap(); pus_tm_tx_handler::spawn().unwrap();
( (
Shared { Shared {
decode_buffer_busy: false, tm_prod: DataProducer {
decode_buf: [0; MAX_TC_SIZE], buf_prod: buf_prod_tm,
tx_prod: DataProducer { sizes_prod: sizes_prod_tm,
buf_prod,
sizes_prod,
}, },
}, },
Local { Local {
uart_rx: rx, uart_rx: rx,
uart_tx: tx, uart_tx: tx,
cobs_reader_state: CobsReaderStates::default(),
tc_tx,
tc_rx,
rom_spi: Some(cx.device.spi3), rom_spi: Some(cx.device.spi3),
tx_cons: DataConsumer { tm_cons: DataConsumer {
buf_cons, buf_cons: buf_cons_tm,
sizes_cons, sizes_cons: sizes_cons_tm,
},
tc_cons: DataConsumer {
buf_cons: buf_cons_tc,
sizes_cons: sizes_cons_tc,
},
tc_prod: DataProducer {
buf_prod: buf_prod_tc,
sizes_prod: sizes_prod_tc,
}, },
verif_reporter, verif_reporter,
}, },
@ -223,120 +230,62 @@ mod app {
} }
#[task( #[task(
priority = 4, binds = UART0_RX,
local=[ local = [
read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE], cnt: u32 = 0,
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
uart_rx, uart_rx,
cobs_reader_state, tc_prod
tc_tx
], ],
shared=[decode_buffer_busy, decode_buf]
)] )]
async fn uart_reader_task(mut cx: uart_reader_task::Context) { fn uart_rx_irq(cx: uart_rx_irq::Context) {
let mut current_idx = 0; match cx.local.uart_rx.irq_handler(cx.local.rx_buf) {
loop { Ok(result) => {
match cx.local.uart_rx.read() { if RX_DEBUGGING {
Ok(byte) => { log::debug!("RX Info: {:?}", cx.local.uart_rx.irq_info());
if SERIAL_RX_WIRETAPPING { log::debug!("RX Result: {:?}", result);
log::debug!("RX Byte: 0x{:x?}", byte);
}
handle_single_rx_byte(&mut cx, byte, &mut current_idx)
} }
Err(e) => { if result.complete() {
match e { // Check frame validity (must have COBS format) and decode the frame.
nb::Error::Other(e) => { // Currently, we expect a full frame or a frame received through a timeout
log::warn!("UART error: {:?}", e); // to be one COBS frame. We could parse for multiple COBS packets in one
match e { // frame, but the additional complexity is not necessary here..
uart::Error::Overrun => { if cx.local.rx_buf[0] == 0 && cx.local.rx_buf[result.bytes_read - 1] == 0 {
cx.local.uart_rx.clear_fifo(); let decoded_size =
} cobs::decode_in_place(&mut cx.local.rx_buf[1..result.bytes_read]);
uart::Error::FramingError => (), if decoded_size.is_err() {
uart::Error::ParityError => (), log::warn!("COBS decoding failed");
uart::Error::BreakCondition => (), } else {
uart::Error::TransferPending => (), let decoded_size = decoded_size.unwrap();
uart::Error::BufferTooShort => (), if cx.local.tc_prod.sizes_prod.vacant_len() >= 1
&& cx.local.tc_prod.buf_prod.vacant_len() >= decoded_size
{
// Should never fail, we checked there is enough space.
cx.local.tc_prod.sizes_prod.try_push(decoded_size).unwrap();
cx.local
.tc_prod
.buf_prod
.push_slice(&cx.local.rx_buf[1..1 + decoded_size]);
} else {
log::warn!("COBS TC queue full");
} }
} }
nb::Error::WouldBlock => { } else {
// Delay for a short period before polling again. log::warn!("COBS frame with invalid format, start and end bytes are not 0");
Mono::delay(400.micros()).await;
}
} }
}
}
}
}
fn handle_single_rx_byte( // Initiate next transfer.
cx: &mut uart_reader_task::Context, cx.local
byte: u8, .uart_rx
current_idx: &mut usize, .read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
) { .expect("read operation failed");
match cx.local.cobs_reader_state { }
CobsReaderStates::WaitingForStart => { if result.error() {
if byte == COBS_FRAME_SEPARATOR { log::warn!("UART error: {:?}", result.error());
if COBS_RX_DEBUGGING {
log::debug!("COBS start marker detected");
}
*cx.local.cobs_reader_state = CobsReaderStates::WatingForEnd;
*current_idx = 0;
} }
} }
CobsReaderStates::WatingForEnd => { Err(e) => {
if byte == COBS_FRAME_SEPARATOR { log::warn!("UART error: {:?}", e);
if COBS_RX_DEBUGGING {
log::debug!("COBS end marker detected");
}
let mut sending_failed = false;
let mut decoding_error = false;
let mut decode_buffer_busy = false;
cx.shared.decode_buffer_busy.lock(|busy| {
if *busy {
decode_buffer_busy = true;
} else {
cx.shared.decode_buf.lock(|buf| {
match cobs::decode(&cx.local.read_buf[..*current_idx], buf) {
Ok(packet_len) => {
if COBS_RX_DEBUGGING {
log::debug!(
"COBS decoded packet with length {}",
packet_len
);
}
if cx.local.tc_tx.try_send(packet_len).is_err() {
sending_failed = true;
}
*busy = true;
}
Err(_) => {
decoding_error = true;
}
}
});
}
});
if sending_failed {
log::warn!("sending TC packet failed, queue full");
}
if decoding_error {
log::warn!("decoding error");
}
if decode_buffer_busy {
log::warn!("decode buffer busy. data arriving too fast");
}
*cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart;
} else if *current_idx >= cx.local.read_buf.len() {
*cx.local.cobs_reader_state = CobsReaderStates::FrameOverflow;
} else {
cx.local.read_buf[*current_idx] = byte;
*current_idx += 1;
}
}
CobsReaderStates::FrameOverflow => {
if byte == COBS_FRAME_SEPARATOR {
*cx.local.cobs_reader_state = CobsReaderStates::WaitingForStart;
*current_idx = 0;
}
} }
} }
} }
@ -344,149 +293,167 @@ mod app {
#[task( #[task(
priority = 2, priority = 2,
local=[ local=[
read_buf: [u8;MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE], tc_buf: [u8; MAX_TC_SIZE] = [0; MAX_TC_SIZE],
src_data_buf: [u8; 16] = [0; 16], src_data_buf: [u8; 16] = [0; 16],
verif_buf: [u8; 32] = [0; 32], verif_buf: [u8; 32] = [0; 32],
tc_rx, tc_cons,
rom_spi, rom_spi,
verif_reporter verif_reporter
], ],
shared=[decode_buffer_busy, decode_buf, tx_prod] shared=[tm_prod]
)] )]
async fn pus_tc_handler(mut cx: pus_tc_handler::Context) { async fn pus_tc_handler(mut cx: pus_tc_handler::Context) {
loop { loop {
let packet_len = cx.local.tc_rx.recv().await.expect("all senders down"); // Try to read a TC from the ring buffer.
let packet_len = cx.local.tc_cons.sizes_cons.try_pop();
if packet_len.is_none() {
// Small delay, TCs might arrive very quickly.
Mono::delay(20.millis()).await;
continue;
}
let packet_len = packet_len.unwrap();
log::info!(target: "TC Handler", "received packet with length {}", packet_len); log::info!(target: "TC Handler", "received packet with length {}", packet_len);
// We still copy the data to a local buffer, so the exchange buffer can already be used assert_eq!(
// for the next packet / decode process. cx.local
cx.shared .tc_cons
.decode_buf .buf_cons
.lock(|buf| cx.local.read_buf[0..buf.len()].copy_from_slice(buf)); .pop_slice(&mut cx.local.tc_buf[0..packet_len]),
cx.shared.decode_buffer_busy.lock(|busy| *busy = false); packet_len
match PusTcReader::new(cx.local.read_buf) { );
Ok((pus_tc, _)) => { // Read a telecommand, now handle it.
let mut write_and_send = |tm: &PusTmCreator| { handle_valid_pus_tc(&mut cx);
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap(); }
cx.shared.tx_prod.lock(|prod| { }
prod.sizes_prod.try_push(tm.len_written()).unwrap();
prod.buf_prod
.push_slice(&cx.local.verif_buf[0..written_size]);
});
};
let token = cx.local.verif_reporter.add_tc(&pus_tc);
let (tm, accepted_token) = cx
.local
.verif_reporter
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
let (tm, started_token) = cx fn handle_valid_pus_tc(cx: &mut pus_tc_handler::Context) {
.local let pus_tc = PusTcReader::new(cx.local.tc_buf);
.verif_reporter if pus_tc.is_err() {
.start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[]) log::warn!("PUS TC error: {}", pus_tc.unwrap_err());
.expect("acceptance success failed"); return;
write_and_send(&tm); }
let (pus_tc, _) = pus_tc.unwrap();
let mut write_and_send = |tm: &PusTmCreator| {
let written_size = tm.write_to_bytes(cx.local.verif_buf).unwrap();
cx.shared.tm_prod.lock(|prod| {
prod.sizes_prod.try_push(tm.len_written()).unwrap();
prod.buf_prod
.push_slice(&cx.local.verif_buf[0..written_size]);
});
};
let token = cx.local.verif_reporter.add_tc(&pus_tc);
let (tm, accepted_token) = cx
.local
.verif_reporter
.acceptance_success(cx.local.src_data_buf, token, 0, 0, &[])
.expect("acceptance success failed");
write_and_send(&tm);
if pus_tc.service() == PusServiceId::Action as u8 { let (tm, started_token) = cx
let mut corrupt_image = |base_addr: u32| { .local
// Safety: We only use this for NVM handling and we only do NVM .verif_reporter
// handling here. .start_success(cx.local.src_data_buf, accepted_token, 0, 0, &[])
let mut sys_cfg = unsafe { pac::Sysconfig::steal() }; .expect("acceptance success failed");
let nvm = Nvm::new( write_and_send(&tm);
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(), if pus_tc.service() == PusServiceId::Action as u8 {
CLOCKS.get().as_ref().unwrap(), let mut corrupt_image = |base_addr: u32| {
); // Safety: We only use this for NVM handling and we only do NVM
let mut buf = [0u8; 4]; // handling here.
nvm.read_data(base_addr + 32, &mut buf); let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
buf[0] += 1; let nvm = Nvm::new(
nvm.write_data(base_addr + 32, &buf); &mut sys_cfg,
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg)); cx.local.rom_spi.take().unwrap(),
let tm = cx CLOCKS.get().as_ref().unwrap(),
.local );
.verif_reporter let mut buf = [0u8; 4];
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) nvm.read_data(base_addr + 32, &mut buf);
.expect("completion success failed"); buf[0] += 1;
write_and_send(&tm); nvm.write_data(base_addr + 32, &buf);
}; *cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
if pus_tc.subservice() == ActionId::CorruptImageA as u8 { let tm = cx
rprintln!("corrupting App Image A"); .local
corrupt_image(APP_A_START_ADDR); .verif_reporter
} .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
if pus_tc.subservice() == ActionId::CorruptImageB as u8 { .expect("completion success failed");
rprintln!("corrupting App Image B"); write_and_send(&tm);
corrupt_image(APP_B_START_ADDR); };
} if pus_tc.subservice() == ActionId::CorruptImageA as u8 {
} rprintln!("corrupting App Image A");
if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 { corrupt_image(APP_A_START_ADDR);
log::info!(target: "TC Handler", "received ping TC"); }
} else if pus_tc.service() == PusServiceId::MemoryManagement as u8 { if pus_tc.subservice() == ActionId::CorruptImageB as u8 {
let tm = cx rprintln!("corrupting App Image B");
.local corrupt_image(APP_B_START_ADDR);
.verif_reporter }
.step_success( }
cx.local.src_data_buf, if pus_tc.service() == PusServiceId::Test as u8 && pus_tc.subservice() == 1 {
&started_token, log::info!(target: "TC Handler", "received ping TC");
0, let tm = cx
0, .local
&[], .verif_reporter
EcssEnumU8::new(0), .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
) .expect("completion success failed");
.expect("step success failed"); write_and_send(&tm);
write_and_send(&tm); } else if pus_tc.service() == PusServiceId::MemoryManagement as u8 {
// Raw memory write TC let tm = cx
if pus_tc.subservice() == 2 { .local
let app_data = pus_tc.app_data(); .verif_reporter
if app_data.len() < 10 { .step_success(
log::warn!( cx.local.src_data_buf,
target: "TC Handler", &started_token,
"app data for raw memory write is too short: {}", 0,
app_data.len() 0,
); &[],
} EcssEnumU8::new(0),
let memory_id = app_data[0]; )
if memory_id != BOOT_NVM_MEMORY_ID { .expect("step success failed");
log::warn!(target: "TC Handler", "memory ID {} not supported", memory_id); write_and_send(&tm);
// TODO: Error reporting // Raw memory write TC
return; if pus_tc.subservice() == 2 {
} let app_data = pus_tc.app_data();
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap()); if app_data.len() < 10 {
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap()); log::warn!(
if 10 + data_len as usize > app_data.len() { target: "TC Handler",
log::warn!( "app data for raw memory write is too short: {}",
target: "TC Handler", app_data.len()
"invalid data length {} for raw mem write detected", );
data_len
);
// TODO: Error reporting
return;
}
let data = &app_data[10..10 + data_len as usize];
log::info!("writing {} bytes at offset {} to NVM", data_len, offset);
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
nvm.write_data(offset, data);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
log::info!("NVM operation done");
}
}
} }
Err(e) => { let memory_id = app_data[0];
log::warn!("PUS TC error: {}", e); if memory_id != BOOT_NVM_MEMORY_ID {
log::warn!(target: "TC Handler", "memory ID {} not supported", memory_id);
// TODO: Error reporting
return;
} }
let offset = u32::from_be_bytes(app_data[2..6].try_into().unwrap());
let data_len = u32::from_be_bytes(app_data[6..10].try_into().unwrap());
if 10 + data_len as usize > app_data.len() {
log::warn!(
target: "TC Handler",
"invalid data length {} for raw mem write detected",
data_len
);
// TODO: Error reporting
return;
}
let data = &app_data[10..10 + data_len as usize];
log::info!("writing {} bytes at offset {} to NVM", data_len, offset);
// Safety: We only use this for NVM handling and we only do NVM
// handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
let nvm = Nvm::new(
&mut sys_cfg,
cx.local.rom_spi.take().unwrap(),
CLOCKS.get().as_ref().unwrap(),
);
nvm.write_data(offset, data);
*cx.local.rom_spi = Some(nvm.release(&mut sys_cfg));
let tm = cx
.local
.verif_reporter
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed");
write_and_send(&tm);
log::info!("NVM operation done");
} }
} }
} }
@ -497,16 +464,16 @@ mod app {
read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE], read_buf: [u8;MAX_TM_SIZE] = [0; MAX_TM_SIZE],
encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE], encoded_buf: [u8;MAX_TM_FRAME_SIZE] = [0; MAX_TM_FRAME_SIZE],
uart_tx, uart_tx,
tx_cons, tm_cons
], ],
shared=[] shared=[]
)] )]
async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) { async fn pus_tm_tx_handler(cx: pus_tm_tx_handler::Context) {
loop { loop {
while cx.local.tx_cons.sizes_cons.occupied_len() > 0 { while cx.local.tm_cons.sizes_cons.occupied_len() > 0 {
let next_size = cx.local.tx_cons.sizes_cons.try_pop().unwrap(); let next_size = cx.local.tm_cons.sizes_cons.try_pop().unwrap();
cx.local cx.local
.tx_cons .tm_cons
.buf_cons .buf_cons
.pop_slice(&mut cx.local.read_buf[0..next_size]); .pop_slice(&mut cx.local.read_buf[0..next_size]);
cx.local.encoded_buf[0] = 0; cx.local.encoded_buf[0] = 0;

View File

@ -17,12 +17,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
- Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be - Added `va41620`, `va41630`, `va41628` and `va41629` device features. A device now has to be
selected for HAL compilation to work properly selected for HAL compilation to work properly
- Adaptions for the UART IRQ feature which are now only implemented for the RX part of the UART.
## Fixed ## Fixed
- Small fixes and improvements for ADC drivers - Small fixes and improvements for ADC drivers
- Fixes for the SPI implementation where the clock divider values were not calculated - Fixes for the SPI implementation where the clock divider values were not calculated
correctly correctly
- Fixes for UART IRQ handler implementation
## Added ## Added

View File

@ -20,7 +20,7 @@ embedded-io = "0.6"
num_enum = { version = "0.7", default-features = false } num_enum = { version = "0.7", default-features = false }
typenum = "1" typenum = "1"
bitflags = "2" bitflags = "2"
bitfield = "0.15" bitfield = "0.17"
defmt = { version = "0.3", optional = true } defmt = { version = "0.3", optional = true }
fugit = "0.3" fugit = "0.3"
delegate = "0.12" delegate = "0.12"

View File

@ -197,66 +197,36 @@ impl From<Hertz> for Config {
// IRQ Definitions // IRQ Definitions
//================================================================================================== //==================================================================================================
struct IrqInfo { #[derive(Debug)]
pub struct IrqInfo {
rx_len: usize, rx_len: usize,
rx_idx: usize, rx_idx: usize,
mode: IrqReceptionMode, mode: IrqReceptionMode,
} }
pub enum IrqResultMask {
Complete = 0,
Overflow = 1,
FramingError = 2,
ParityError = 3,
Break = 4,
Timeout = 5,
Addr9 = 6,
/// Should not happen
Unknown = 7,
}
/// This struct is used to return the default IRQ handler result to the user /// This struct is used to return the default IRQ handler result to the user
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct IrqResult { pub struct IrqResult {
raw_res: u32, complete: bool,
timeout: bool,
pub errors: IrqUartError,
pub bytes_read: usize, pub bytes_read: usize,
} }
impl IrqResult { impl IrqResult {
pub const fn new() -> Self { pub fn new() -> Self {
IrqResult { IrqResult {
raw_res: 0, complete: false,
timeout: false,
errors: IrqUartError::default(),
bytes_read: 0, bytes_read: 0,
} }
} }
} }
impl IrqResult { impl IrqResult {
#[inline]
pub fn raw_result(&self) -> u32 {
self.raw_res
}
#[inline]
pub(crate) fn clear_result(&mut self) {
self.raw_res = 0;
}
#[inline]
pub(crate) fn set_result(&mut self, flag: IrqResultMask) {
self.raw_res |= 1 << flag as u32;
}
#[inline]
pub fn complete(&self) -> bool {
if ((self.raw_res >> IrqResultMask::Complete as u32) & 0x01) == 0x01 {
return true;
}
false
}
#[inline] #[inline]
pub fn error(&self) -> bool { pub fn error(&self) -> bool {
if self.overflow_error() || self.framing_error() || self.parity_error() { if self.errors.overflow || self.errors.parity || self.errors.framing {
return true; return true;
} }
false false
@ -264,34 +234,27 @@ impl IrqResult {
#[inline] #[inline]
pub fn overflow_error(&self) -> bool { pub fn overflow_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::Overflow as u32) & 0x01) == 0x01 { self.errors.overflow
return true;
}
false
} }
#[inline] #[inline]
pub fn framing_error(&self) -> bool { pub fn framing_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::FramingError as u32) & 0x01) == 0x01 { self.errors.framing
return true;
}
false
} }
#[inline] #[inline]
pub fn parity_error(&self) -> bool { pub fn parity_error(&self) -> bool {
if ((self.raw_res >> IrqResultMask::ParityError as u32) & 0x01) == 0x01 { self.errors.parity
return true;
}
false
} }
#[inline] #[inline]
pub fn timeout(&self) -> bool { pub fn timeout(&self) -> bool {
if ((self.raw_res >> IrqResultMask::Timeout as u32) & 0x01) == 0x01 { self.timeout
return true; }
}
false #[inline]
pub fn complete(&self) -> bool {
self.complete
} }
} }
@ -317,41 +280,27 @@ pub struct Uart<UartInstance, Pins> {
pins: Pins, pins: Pins,
} }
/// UART using the IRQ capabilities of the peripheral. Can be created with the /// Serial receiver
/// [`Uart::into_uart_with_irq`] function. Currently, only the RX side for IRQ based reception pub struct Rx<Uart>(Uart);
/// is implemented.
pub struct UartWithIrq<Uart, Pins> {
base: UartWithIrqBase<Uart>,
pins: Pins,
}
/// Type-erased UART using the IRQ capabilities of the peripheral. Can be created with the // Serial receiver, using interrupts to offload reading to the hardware.
/// [`UartWithIrq::downgrade`] function. Currently, only the RX side for IRQ based reception pub struct RxWithIrq<Uart> {
/// is implemented. inner: Rx<Uart>,
pub struct UartWithIrqBase<UART> {
pub inner: UartBase<UART>,
irq_info: IrqInfo, irq_info: IrqInfo,
} }
/// Serial receiver
pub struct Rx<Uart> {
uart: Uart,
}
/// Serial transmitter /// Serial transmitter
pub struct Tx<Uart> { pub struct Tx<Uart>(Uart);
uart: Uart,
}
impl<Uart: Instance> Rx<Uart> { impl<Uart: Instance> Rx<Uart> {
fn new(uart: Uart) -> Self { fn new(uart: Uart) -> Self {
Self { uart } Self(uart)
} }
} }
impl<Uart> Tx<Uart> { impl<Uart> Tx<Uart> {
fn new(uart: Uart) -> Self { fn new(uart: Uart) -> Self {
Self { uart } Self(uart)
} }
} }
@ -602,20 +551,30 @@ impl<TxPinInst: TxPin<UartInstance>, RxPinInst: RxPin<UartInstance>, UartInstanc
} }
/// If the IRQ capabilities of the peripheral are used, the UART needs to be converted /// If the IRQ capabilities of the peripheral are used, the UART needs to be converted
/// with this function /// with this function. Currently, IRQ abstractions are only implemented for the RX part
pub fn into_uart_with_irq(self) -> UartWithIrq<UartInstance, (TxPinInst, RxPinInst)> { /// of the UART, so this function will release a TX and RX handle as well as the pin
/// instances.
pub fn split_with_irq(
self,
) -> (
Tx<UartInstance>,
RxWithIrq<UartInstance>,
(TxPinInst, RxPinInst),
) {
let (inner, pins) = self.downgrade_internal(); let (inner, pins) = self.downgrade_internal();
UartWithIrq { let (tx, rx) = inner.split();
pins, (
base: UartWithIrqBase { tx,
inner, RxWithIrq {
inner: rx,
irq_info: IrqInfo { irq_info: IrqInfo {
rx_len: 0, rx_len: 0,
rx_idx: 0, rx_idx: 0,
mode: IrqReceptionMode::Idle, mode: IrqReceptionMode::Idle,
}, },
}, },
} pins,
)
} }
delegate::delegate! { delegate::delegate! {
@ -673,11 +632,26 @@ impl<Uart: Instance> Rx<Uart> {
/// ///
/// You must ensure that only registers related to the operation of the RX side are used. /// You must ensure that only registers related to the operation of the RX side are used.
pub unsafe fn uart(&self) -> &Uart { pub unsafe fn uart(&self) -> &Uart {
&self.uart &self.0
} }
#[inline]
pub fn clear_fifo(&self) { pub fn clear_fifo(&self) {
self.uart.fifo_clr().write(|w| w.rxfifo().set_bit()); self.0.fifo_clr().write(|w| w.rxfifo().set_bit());
}
#[inline]
pub fn enable(&mut self) {
self.0.enable().modify(|_, w| w.rxenable().set_bit());
}
#[inline]
pub fn disable(&mut self) {
self.0.enable().modify(|_, w| w.rxenable().clear_bit());
}
pub fn release(self) -> Uart {
self.0
} }
} }
@ -688,11 +662,22 @@ impl<Uart: Instance> Tx<Uart> {
/// ///
/// You must ensure that only registers related to the operation of the TX side are used. /// You must ensure that only registers related to the operation of the TX side are used.
pub unsafe fn uart(&self) -> &Uart { pub unsafe fn uart(&self) -> &Uart {
&self.uart &self.0
} }
#[inline]
pub fn clear_fifo(&self) { pub fn clear_fifo(&self) {
self.uart.fifo_clr().write(|w| w.txfifo().set_bit()); self.0.fifo_clr().write(|w| w.txfifo().set_bit());
}
#[inline]
pub fn enable(&mut self) {
self.0.enable().modify(|_, w| w.txenable().set_bit());
}
#[inline]
pub fn disable(&mut self) {
self.0.enable().modify(|_, w| w.txenable().clear_bit());
} }
} }
@ -701,6 +686,7 @@ pub struct IrqUartError {
overflow: bool, overflow: bool,
framing: bool, framing: bool,
parity: bool, parity: bool,
other: bool,
} }
impl IrqUartError { impl IrqUartError {
@ -715,7 +701,7 @@ pub enum IrqError {
Uart(IrqUartError), Uart(IrqUartError),
} }
impl<Uart: Instance> UartWithIrqBase<Uart> { impl<Uart: Instance> RxWithIrq<Uart> {
/// This initializes a non-blocking read transfer using the IRQ capabilities of the UART /// This initializes a non-blocking read transfer using the IRQ capabilities of the UART
/// peripheral. /// peripheral.
/// ///
@ -735,8 +721,7 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
self.irq_info.mode = IrqReceptionMode::Pending; self.irq_info.mode = IrqReceptionMode::Pending;
self.irq_info.rx_idx = 0; self.irq_info.rx_idx = 0;
self.irq_info.rx_len = max_len; self.irq_info.rx_len = max_len;
self.inner.enable_rx(); self.inner.enable();
self.inner.enable_tx();
self.enable_rx_irq_sources(enb_timeout_irq); self.enable_rx_irq_sources(enb_timeout_irq);
unsafe { enable_interrupt(Uart::IRQ_RX) }; unsafe { enable_interrupt(Uart::IRQ_RX) };
Ok(()) Ok(())
@ -744,7 +729,7 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
#[inline] #[inline]
fn enable_rx_irq_sources(&mut self, timeout: bool) { fn enable_rx_irq_sources(&mut self, timeout: bool) {
self.inner.uart.irq_enb().modify(|_, w| { self.inner.0.irq_enb().modify(|_, w| {
if timeout { if timeout {
w.irq_rx_to().set_bit(); w.irq_rx_to().set_bit();
} }
@ -755,30 +740,24 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
#[inline] #[inline]
fn disable_rx_irq_sources(&mut self) { fn disable_rx_irq_sources(&mut self) {
self.inner.uart.irq_enb().modify(|_, w| { self.inner.0.irq_enb().modify(|_, w| {
w.irq_rx_to().clear_bit(); w.irq_rx_to().clear_bit();
w.irq_rx_status().clear_bit(); w.irq_rx_status().clear_bit();
w.irq_rx().clear_bit() w.irq_rx().clear_bit()
}); });
} }
#[inline]
pub fn enable_tx(&mut self) {
self.inner.enable_tx()
}
#[inline]
pub fn disable_tx(&mut self) {
self.inner.disable_tx()
}
pub fn cancel_transfer(&mut self) { pub fn cancel_transfer(&mut self) {
self.disable_rx_irq_sources(); self.disable_rx_irq_sources();
self.inner.clear_tx_fifo(); self.inner.clear_fifo();
self.irq_info.rx_idx = 0; self.irq_info.rx_idx = 0;
self.irq_info.rx_len = 0; self.irq_info.rx_len = 0;
} }
pub fn uart(&self) -> &Uart {
&self.inner.0
}
/// Default IRQ handler which can be used to read the packets arriving on the UART peripheral. /// Default IRQ handler which can be used to read the packets arriving on the UART peripheral.
/// ///
/// If passed buffer is equal to or larger than the specified maximum length, an /// If passed buffer is equal to or larger than the specified maximum length, an
@ -791,104 +770,102 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
}); });
} }
let mut res = IrqResult::default(); let mut res = IrqResult::default();
let mut possible_error = IrqUartError::default();
let rx_status = self.inner.uart.rxstatus().read(); let irq_end = self.inner.0.irq_end().read();
res.raw_res = rx_status.bits(); let enb_status = self.inner.0.enable().read();
let irq_end = self.inner.uart.irq_end().read();
let enb_status = self.inner.uart.enable().read();
let rx_enabled = enb_status.rxenable().bit_is_set(); let rx_enabled = enb_status.rxenable().bit_is_set();
let _tx_enabled = enb_status.txenable().bit_is_set();
let read_handler = |res: &mut IrqResult, // Half-Full interrupt. We have a guaranteed amount of data we can read.
possible_error: &mut IrqUartError,
read_res: nb::Result<u8, Error>|
-> Option<u8> {
match read_res {
Ok(byte) => Some(byte),
Err(nb::Error::WouldBlock) => None,
Err(nb::Error::Other(e)) => {
match e {
Error::Overrun => {
possible_error.overflow = true;
}
Error::FramingError => {
possible_error.framing = true;
}
Error::ParityError => {
possible_error.parity = true;
}
_ => {
res.set_result(IrqResultMask::Unknown);
}
}
None
}
}
};
if irq_end.irq_rx().bit_is_set() { if irq_end.irq_rx().bit_is_set() {
// Determine the number of bytes to read, ensuring we leave 1 byte in the FIFO.
// We use this trick/hack because the timeout feature of the peripheral relies on data
// being in the RX FIFO. If data continues arriving, another half-full IRQ will fire.
// If not, the last byte(s) is/are emptied by the timeout interrupt.
let available_bytes = self.inner.0.rxfifoirqtrg().read().bits() as usize;
let bytes_to_read = core::cmp::min(
available_bytes.saturating_sub(1),
self.irq_info.rx_len - self.irq_info.rx_idx,
);
// If this interrupt bit is set, the trigger level is available at the very least. // If this interrupt bit is set, the trigger level is available at the very least.
// Read everything as fast as possible // Read everything as fast as possible
for _ in 0..core::cmp::min( for _ in 0..bytes_to_read {
self.inner.uart.rxfifoirqtrg().read().bits() as usize, buf[self.irq_info.rx_idx] = (self.inner.0.data().read().bits() & 0xff) as u8;
self.irq_info.rx_len,
) {
buf[self.irq_info.rx_idx] = (self.inner.uart.data().read().bits() & 0xff) as u8;
self.irq_info.rx_idx += 1; self.irq_info.rx_idx += 1;
} }
// On high-baudrates, data might be available immediately, and we possible have to
// read continuosly? Then again, the CPU should always be faster than that. I'd rather
// rely on the hardware firing another IRQ. I have not tried baudrates higher than
// 115200 so far.
}
let read_handler =
|possible_error: &mut IrqUartError, read_res: nb::Result<u8, Error>| -> Option<u8> {
match read_res {
Ok(byte) => Some(byte),
Err(nb::Error::WouldBlock) => None,
Err(nb::Error::Other(e)) => {
match e {
Error::Overrun => {
possible_error.overflow = true;
}
Error::FramingError => {
possible_error.framing = true;
}
Error::ParityError => {
possible_error.parity = true;
}
_ => {
possible_error.other = true;
}
}
None
}
}
};
// Timeout, empty the FIFO completely.
if irq_end.irq_rx_to().bit_is_set() {
// While there is data in the FIFO, write it into the reception buffer // While there is data in the FIFO, write it into the reception buffer
loop { loop {
if self.irq_info.rx_idx == self.irq_info.rx_len { if self.irq_info.rx_idx == self.irq_info.rx_len {
self.irq_completion_handler(&mut res); break;
return Ok(res);
} }
if let Some(byte) = read_handler(&mut res, &mut possible_error, self.inner.read()) { if let Some(byte) = read_handler(&mut res.errors, self.inner.read()) {
buf[self.irq_info.rx_idx] = byte; buf[self.irq_info.rx_idx] = byte;
self.irq_info.rx_idx += 1; self.irq_info.rx_idx += 1;
} else { } else {
break; break;
} }
} }
self.irq_completion_handler(&mut res);
return Ok(res);
} }
// RX transfer not complete, check for RX errors // RX transfer not complete, check for RX errors
if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled { if (self.irq_info.rx_idx < self.irq_info.rx_len) && rx_enabled {
// Read status register again, might have changed since reading received data // Read status register again, might have changed since reading received data
let rx_status = self.inner.uart.rxstatus().read(); let rx_status = self.inner.0.rxstatus().read();
res.raw_res = rx_status.bits();
if rx_status.rxovr().bit_is_set() { if rx_status.rxovr().bit_is_set() {
possible_error.overflow = true; res.errors.overflow = true;
} }
if rx_status.rxfrm().bit_is_set() { if rx_status.rxfrm().bit_is_set() {
possible_error.framing = true; res.errors.framing = true;
} }
if rx_status.rxpar().bit_is_set() { if rx_status.rxpar().bit_is_set() {
possible_error.parity = true; res.errors.parity = true;
}
if rx_status.rxto().bit_is_set() {
// A timeout has occured but there might be some leftover data in the FIFO,
// so read that data as well
while let Some(byte) =
read_handler(&mut res, &mut possible_error, self.inner.read())
{
buf[self.irq_info.rx_idx] = byte;
self.irq_info.rx_idx += 1;
}
self.irq_completion_handler(&mut res);
res.set_result(IrqResultMask::Timeout);
return Ok(res);
} }
// If it is not a timeout, it's an error // If it is not a timeout, it's an error
if possible_error.error() { if res.error() {
self.disable_rx_irq_sources(); self.disable_rx_irq_sources();
return Err(IrqError::Uart(possible_error)); return Err(IrqError::Uart(res.errors));
} }
} }
// Clear the interrupt status bits // Clear the interrupt status bits
self.inner self.inner
.uart .0
.irq_clr() .irq_clr()
.write(|w| unsafe { w.bits(irq_end.bits()) }); .write(|w| unsafe { w.bits(irq_end.bits()) });
Ok(res) Ok(res)
@ -896,48 +873,23 @@ impl<Uart: Instance> UartWithIrqBase<Uart> {
fn irq_completion_handler(&mut self, res: &mut IrqResult) { fn irq_completion_handler(&mut self, res: &mut IrqResult) {
self.disable_rx_irq_sources(); self.disable_rx_irq_sources();
self.inner.disable_rx(); self.inner.disable();
res.bytes_read = self.irq_info.rx_idx; res.bytes_read = self.irq_info.rx_idx;
res.clear_result(); res.complete = true;
res.set_result(IrqResultMask::Complete);
self.irq_info.mode = IrqReceptionMode::Idle; self.irq_info.mode = IrqReceptionMode::Idle;
self.irq_info.rx_idx = 0; self.irq_info.rx_idx = 0;
self.irq_info.rx_len = 0; self.irq_info.rx_len = 0;
} }
pub fn irq_info(&self) -> &IrqInfo {
&self.irq_info
}
pub fn release(self) -> Uart { pub fn release(self) -> Uart {
self.inner.release() self.inner.release()
} }
} }
impl<Uart: Instance, Pins> UartWithIrq<Uart, Pins> {
/// See [`UartWithIrqBase::read_fixed_len_using_irq`] doc
pub fn read_fixed_len_using_irq(
&mut self,
max_len: usize,
enb_timeout_irq: bool,
) -> Result<(), Error> {
self.base.read_fixed_len_using_irq(max_len, enb_timeout_irq)
}
pub fn cancel_transfer(&mut self) {
self.base.cancel_transfer()
}
/// See [`UartWithIrqBase::irq_handler`] doc
pub fn irq_handler(&mut self, buf: &mut [u8]) -> Result<IrqResult, IrqError> {
self.base.irq_handler(buf)
}
pub fn release(self) -> (Uart, Pins) {
(self.base.release(), self.pins)
}
pub fn downgrade(self) -> (UartWithIrqBase<Uart>, Pins) {
(self.base, self.pins)
}
}
impl embedded_io::Error for Error { impl embedded_io::Error for Error {
fn kind(&self) -> embedded_io::ErrorKind { fn kind(&self) -> embedded_io::ErrorKind {
embedded_io::ErrorKind::Other embedded_io::ErrorKind::Other