1 Commits

Author SHA1 Message Date
3f98fe7d93 Updates and fixes
Some checks failed
Rust/va416xx-rs/pipeline/pr-main There was a failure building this commit
- Improve and fix SPI HAL and example
- Fix RTIC example
2024-09-20 10:51:08 +02:00
30 changed files with 679 additions and 1254 deletions

View File

@ -11,11 +11,9 @@ panic-rtt-target = { version = "0.1.3" }
panic-halt = { version = "0.2" } panic-halt = { version = "0.2" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
crc = "3" crc = "3"
static_assertions = "1"
[dependencies.va416xx-hal] [dependencies.va416xx-hal]
path = "../va416xx-hal" path = "../va416xx-hal"
features = ["va41630"]
[features] [features]
default = [] default = []

View File

@ -11,12 +11,12 @@ The bootloader uses the following memory map:
| ------ | ---- | ---- | | ------ | ---- | ---- |
| 0x0 | Bootloader start | code up to 0x3FFC bytes | | 0x0 | Bootloader start | code up to 0x3FFC bytes |
| 0x3FFC | Bootloader CRC | word | | 0x3FFC | Bootloader CRC | word |
| 0x4000 | App image A start | code up to 0x1DFF8 (~120K) bytes | | 0x4000 | App image A start | code up to 0x1DFFC (~120K) bytes |
| 0x21FF8 | App image A CRC check length | word | | 0x21FFC | App image A CRC check length | word |
| 0x21FFC | App image A CRC check value | word | | 0x21FFE | App image A CRC check value | word |
| 0x22000 | App image B start | code up to 0x1DFF8 (~120K) bytes | | 0x22000 | App image B start | code up to 0x1DFFC (~120K) bytes |
| 0x3FFF8 | App image B CRC check length | word | | 0x3FFFC | App image B CRC check length | word |
| 0x3FFFC | App image B CRC check value | word | | 0x3FFFE | App image B CRC check value | word |
| 0x40000 | End of NVM | end | | 0x40000 | End of NVM | end |
## Additional Information ## Additional Information

View File

@ -39,36 +39,20 @@ const RTT_PRINTOUT: bool = true;
// Important bootloader addresses and offsets, vector table information. // Important bootloader addresses and offsets, vector table information.
const NVM_SIZE: u32 = 0x40000;
const BOOTLOADER_START_ADDR: u32 = 0x0; const BOOTLOADER_START_ADDR: u32 = 0x0;
const BOOTLOADER_CRC_ADDR: u32 = BOOTLOADER_END_ADDR - 4;
const BOOTLOADER_END_ADDR: u32 = 0x4000; const BOOTLOADER_END_ADDR: u32 = 0x4000;
const BOOTLOADER_CRC_ADDR: u32 = 0x3FFC;
// 0x4000 const APP_A_START_ADDR: u32 = 0x4000;
const APP_A_START_ADDR: u32 = BOOTLOADER_END_ADDR; pub const APP_A_END_ADDR: u32 = 0x22000;
// The actual size of the image which is relevant for CRC calculation will be store at this // The actual size of the image which is relevant for CRC calculation.
// address. const APP_A_SIZE_ADDR: u32 = 0x21FF8;
// 0x21FF8 const APP_A_CRC_ADDR: u32 = 0x21FFC;
const APP_A_SIZE_ADDR: u32 = APP_B_END_ADDR - 8; const APP_B_START_ADDR: u32 = 0x22000;
// 0x21FFC pub const APP_B_END_ADDR: u32 = 0x40000;
const APP_A_CRC_ADDR: u32 = APP_B_END_ADDR - 4; // The actual size of the image which is relevant for CRC calculation.
pub const APP_A_END_ADDR: u32 = BOOTLOADER_END_ADDR + APP_IMG_SZ; const APP_B_SIZE_ADDR: u32 = 0x3FFF8;
const APP_B_CRC_ADDR: u32 = 0x3FFFC;
// 0x22000 pub const APP_IMG_SZ: u32 = 0x1E000;
const APP_B_START_ADDR: u32 = APP_A_END_ADDR;
// The actual size of the image which is relevant for CRC calculation will be stored at this
// address.
// 0x3FFF8
const APP_B_SIZE_ADDR: u32 = APP_B_END_ADDR - 8;
// 0x3FFFC
const APP_B_CRC_ADDR: u32 = APP_B_END_ADDR - 4;
// 0x40000
pub const APP_B_END_ADDR: u32 = NVM_SIZE;
pub const APP_IMG_SZ: u32 = APP_B_END_ADDR - APP_A_START_ADDR / 2;
static_assertions::const_assert!((APP_B_END_ADDR - BOOTLOADER_END_ADDR) % 2 == 0);
pub const VECTOR_TABLE_OFFSET: u32 = 0x0; pub const VECTOR_TABLE_OFFSET: u32 = 0x0;
pub const VECTOR_TABLE_LEN: u32 = 0x350; pub const VECTOR_TABLE_LEN: u32 = 0x350;

View File

@ -7,7 +7,6 @@ edition = "2021"
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
embedded-io = "0.6"
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
panic-rtt-target = { version = "0.1" } panic-rtt-target = { version = "0.1" }
@ -17,10 +16,6 @@ embassy-sync = { version = "0.6.0" }
embassy-time = { version = "0.3.2" } embassy-time = { version = "0.3.2" }
embassy-time-driver = { version = "0.1" } embassy-time-driver = { version = "0.1" }
[dependencies.ringbuf]
version = "0.4"
default-features = false
[dependencies.once_cell] [dependencies.once_cell]
version = "1" version = "1"
default-features = false default-features = false

View File

@ -1,161 +0,0 @@
//! This is an example of using the UART HAL abstraction with the IRQ support and embassy.
//!
//! It uses the UART0 for communication with another MCU or a host computer (recommended).
//! You can connect a USB-to-Serial converter to the UART0 pins and then use a serial terminal
//! application like picocom to send data to the microcontroller, which should be echoed
//! back to the sender.
//!
//! This application uses the interrupt support of the VA416xx to read the data arriving
//! on the UART without requiring polling.
#![no_std]
#![no_main]
use core::cell::RefCell;
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner;
use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::{Duration, Ticker};
use embedded_hal::digital::StatefulOutputPin;
use embedded_io::Write;
use panic_rtt_target as _;
use ringbuf::{
traits::{Consumer, Observer, Producer},
StaticRb,
};
use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{
gpio::{OutputReadablePushPull, Pin, PinsG, PG5},
pac::{self, interrupt},
prelude::*,
time::Hertz,
uart,
};
pub type SharedUart = Mutex<CriticalSectionRawMutex, RefCell<Option<uart::RxWithIrq<pac::Uart0>>>>;
static RX: SharedUart = Mutex::new(RefCell::new(None));
const BAUDRATE: u32 = 115200;
// Ring buffer size.
const RING_BUF_SIZE: usize = 2048;
pub type SharedRingBuf =
Mutex<CriticalSectionRawMutex, RefCell<Option<StaticRb<u8, RING_BUF_SIZE>>>>;
// Ring buffers to handling variable sized telemetry
static RINGBUF: SharedRingBuf = Mutex::new(RefCell::new(None));
// See https://embassy.dev/book/#_sharing_using_a_mutex for background information about sharing
// a peripheral with embassy.
#[embassy_executor::main]
async fn main(spawner: Spawner) {
rtt_init_print!();
rprintln!("VA416xx UART-Embassy Example");
let mut dp = pac::Peripherals::take().unwrap();
// Initialize the systick interrupt & obtain the token to prove that we did
// Use the external clock connected to XTAL_N.
let clocks = dp
.clkgen
.constrain()
.xtal_n_clk_with_src_freq(Hertz::from_raw(EXTCLK_FREQ))
.freeze(&mut dp.sysconfig)
.unwrap();
// Safety: Only called once here.
unsafe {
embassy_example::init(
&mut dp.sysconfig,
&dp.irq_router,
dp.tim15,
dp.tim14,
&clocks,
)
};
let portg = PinsG::new(&mut dp.sysconfig, dp.portg);
let tx = portg.pg0.into_funsel_1();
let rx = portg.pg1.into_funsel_1();
let uart0 = uart::Uart::new(
dp.uart0,
(tx, rx),
Hertz::from_raw(BAUDRATE),
&mut dp.sysconfig,
&clocks,
);
let (mut tx, rx) = uart0.split();
let mut rx = rx.into_rx_with_irq();
rx.start();
RX.lock(|static_rx| {
static_rx.borrow_mut().replace(rx);
});
RINGBUF.lock(|static_rb| {
static_rb.borrow_mut().replace(StaticRb::default());
});
let led = portg.pg5.into_readable_push_pull_output();
let mut ticker = Ticker::every(Duration::from_millis(50));
let mut processing_buf: [u8; RING_BUF_SIZE] = [0; RING_BUF_SIZE];
let mut read_bytes = 0;
spawner.spawn(blinky(led)).expect("failed to spawn blinky");
loop {
RINGBUF.lock(|static_rb| {
let mut rb_borrow = static_rb.borrow_mut();
let rb_mut = rb_borrow.as_mut().unwrap();
read_bytes = rb_mut.occupied_len();
rb_mut.pop_slice(&mut processing_buf[0..read_bytes]);
});
// Simply send back all received data.
tx.write_all(&processing_buf[0..read_bytes])
.expect("sending back read data failed");
ticker.next().await;
}
}
#[embassy_executor::task]
async fn blinky(mut led: Pin<PG5, OutputReadablePushPull>) {
let mut ticker = Ticker::every(Duration::from_millis(500));
loop {
led.toggle().ok();
ticker.next().await;
}
}
#[interrupt]
#[allow(non_snake_case)]
fn UART0_RX() {
let mut buf: [u8; 16] = [0; 16];
let mut read_len: usize = 0;
let mut errors = None;
RX.lock(|static_rx| {
let mut rx_borrow = static_rx.borrow_mut();
let rx_mut_ref = rx_borrow.as_mut().unwrap();
let result = rx_mut_ref.irq_handler(&mut buf);
read_len = result.bytes_read;
if result.errors.is_some() {
errors = result.errors;
}
});
let mut ringbuf_full = false;
if read_len > 0 {
// Send the received buffer to the main thread for processing via a ring buffer.
RINGBUF.lock(|static_rb| {
let mut rb_borrow = static_rb.borrow_mut();
let rb_mut_ref = rb_borrow.as_mut().unwrap();
if rb_mut_ref.vacant_len() < read_len {
ringbuf_full = true;
for _ in rb_mut_ref.pop_iter() {}
}
rb_mut_ref.push_slice(&buf[0..read_len]);
});
}
if errors.is_some() {
rprintln!("UART error: {:?}", errors);
}
if ringbuf_full {
rprintln!("ringbuffer is full, deleted oldest data");
}
}

View File

@ -1,6 +1,4 @@
#![no_std] #![no_std]
pub mod time_driver; pub mod time_driver;
pub const EXTCLK_FREQ: u32 = 40_000_000;
pub use time_driver::init; pub use time_driver::init;

View File

@ -1,6 +1,5 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
use embassy_example::EXTCLK_FREQ;
use embassy_executor::Spawner; use embassy_executor::Spawner;
use embassy_time::{Duration, Instant, Ticker}; use embassy_time::{Duration, Instant, Ticker};
use embedded_hal::digital::StatefulOutputPin; use embedded_hal::digital::StatefulOutputPin;
@ -8,6 +7,8 @@ use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz}; use va416xx_hal::{gpio::PinsG, pac, prelude::*, time::Hertz};
const EXTCLK_FREQ: u32 = 40_000_000;
// main is itself an async function. // main is itself an async function.
#[embassy_executor::main] #[embassy_executor::main]
async fn main(_spawner: Spawner) { async fn main(_spawner: Spawner) {

View File

@ -17,7 +17,7 @@ use va416xx_hal::{
enable_interrupt, enable_interrupt,
irq_router::enable_and_init_irq_router, irq_router::enable_and_init_irq_router,
pac::{self, interrupt}, pac::{self, interrupt},
timer::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim}, pwm::{assert_tim_reset_for_two_cycles, enable_tim_clk, ValidTim},
}; };
pub type TimekeeperClk = pac::Tim15; pub type TimekeeperClk = pac::Tim15;

View File

@ -12,7 +12,7 @@ use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock}; use va416xx_hal::dma::{Dma, DmaCfg, DmaChannel, DmaCtrlBlock};
use va416xx_hal::irq_router::enable_and_init_irq_router; use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::timer::CountdownTimer; use va416xx_hal::pwm::CountdownTimer;
use va416xx_hal::{ use va416xx_hal::{
pac::{self, interrupt}, pac::{self, interrupt},
prelude::*, prelude::*,

View File

@ -11,8 +11,7 @@ use va416xx_hal::{
gpio::PinsA, gpio::PinsA,
pac, pac,
prelude::*, prelude::*,
pwm::{self, get_duty_from_percent, PwmA, PwmB, ReducedPwmPin}, pwm::{self, get_duty_from_percent, CountdownTimer, PwmA, PwmB, ReducedPwmPin},
timer::CountdownTimer,
}; };
#[entry] #[entry]

View File

@ -8,7 +8,7 @@ use embedded_hal::spi::{Mode, SpiBus, MODE_0};
use panic_rtt_target as _; use panic_rtt_target as _;
use rtt_target::{rprintln, rtt_init_print}; use rtt_target::{rprintln, rtt_init_print};
use simple_examples::peb1; use simple_examples::peb1;
use va416xx_hal::spi::{Spi, SpiClkConfig}; use va416xx_hal::spi::{Spi, SpiClkConfig, TransferConfigWithHwcs};
use va416xx_hal::{ use va416xx_hal::{
gpio::{PinsB, PinsC}, gpio::{PinsB, PinsC},
pac, pac,
@ -55,16 +55,14 @@ fn main() -> ! {
pins_c.pc1.into_funsel_1(), pins_c.pc1.into_funsel_1(),
); );
let mut spi_cfg = SpiConfig::default() let mut spi_cfg = SpiConfig::default().clk_cfg(
.clk_cfg( SpiClkConfig::from_clk(Hertz::from_raw(SPI_SPEED_KHZ), &clocks)
SpiClkConfig::from_clk(Hertz::from_raw(SPI_SPEED_KHZ), &clocks) .expect("invalid target clock"),
.expect("invalid target clock"), );
)
.mode(SPI_MODE)
.blockmode(BLOCKMODE);
if EXAMPLE_SEL == ExampleSelect::Loopback { if EXAMPLE_SEL == ExampleSelect::Loopback {
spi_cfg = spi_cfg.loopback(true) spi_cfg = spi_cfg.loopback(true)
} }
let transfer_cfg = TransferConfigWithHwcs::new_no_hw_cs(None, Some(SPI_MODE), BLOCKMODE, false);
// Create SPI peripheral. // Create SPI peripheral.
let mut spi0 = Spi::new( let mut spi0 = Spi::new(
&mut dp.sysconfig, &mut dp.sysconfig,
@ -72,7 +70,9 @@ fn main() -> ! {
dp.spi0, dp.spi0,
(sck, miso, mosi), (sck, miso, mosi),
spi_cfg, spi_cfg,
); Some(&transfer_cfg.downgrade()),
)
.expect("creating SPI peripheral failed");
spi0.set_fill_word(FILL_WORD); spi0.set_fill_word(FILL_WORD);
loop { loop {
let tx_buf: [u8; 4] = [1, 2, 3, 0]; let tx_buf: [u8; 4] = [1, 2, 3, 0];

View File

@ -6,18 +6,12 @@ a simple PUS (CCSDS) interface to update the software. It also provides a Python
called the `image-loader.py` which can be used to upload compiled images to the flashloader called the `image-loader.py` which can be used to upload compiled images to the flashloader
application to write them to the NVM. application to write them to the NVM.
Please note that the both the application and the image loader are tailored towards usage
with the [bootloader provided by this repository](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/bootloader).
The software can quickly be adapted to interface with a real primary on-board software instead of The software can quickly be adapted to interface with a real primary on-board software instead of
the Python script provided here to upload images because it uses a low-level CCSDS based packet the Python script provided here to upload images because it uses a low-level CCSDS based packet
interface. interface.
## Using the Python image loader ## Using the Python image loader
The Python image loader communicates with the Rust flashload application using a dedicated serial
port with a baudrate of 115200.
It is recommended to run the script in a dedicated virtual environment. For example, on UNIX It is recommended to run the script in a dedicated virtual environment. For example, on UNIX
systems you can use `python3 -m venv venv` and then `source venv/bin/activate` to create systems you can use `python3 -m venv venv` and then `source venv/bin/activate` to create
and activate a virtual environment. and activate a virtual environment.

View File

@ -1,8 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from typing import List, Tuple
from spacepackets.ecss.defs import PusService from spacepackets.ecss.defs import PusService
from spacepackets.ecss.tm import PusTm from spacepackets.ecss.tm import PusTm
from tmtccmd.com import ComInterface
import toml import toml
import struct import struct
import logging import logging
@ -23,27 +21,20 @@ from elftools.elf.elffile import ELFFile
BAUD_RATE = 115200 BAUD_RATE = 115200
BOOTLOADER_START_ADDR = 0x0 BOOTLOADER_START_ADDR = 0x0
BOOTLOADER_END_ADDR = 0x4000 BOOTLOADER_END_ADDR = 0x4000
BOOTLOADER_CRC_ADDR = BOOTLOADER_END_ADDR - 4 BOOTLOADER_CRC_ADDR = 0x3FFC
BOOTLOADER_MAX_SIZE = BOOTLOADER_END_ADDR - BOOTLOADER_START_ADDR - 4
APP_A_START_ADDR = 0x4000 APP_A_START_ADDR = 0x4000
APP_A_END_ADDR = 0x22000 APP_A_END_ADDR = 0x22000
# The actual size of the image which is relevant for CRC calculation. # The actual size of the image which is relevant for CRC calculation.
APP_A_SIZE_ADDR = APP_A_END_ADDR - 8 APP_A_SIZE_ADDR = 0x21FF8
APP_A_CRC_ADDR = APP_A_END_ADDR - 4 APP_A_CRC_ADDR = 0x21FFC
APP_A_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8
APP_B_START_ADDR = 0x22000 APP_B_START_ADDR = 0x22000
APP_B_END_ADDR = 0x40000 APP_B_END_ADDR = 0x40000
# The actual size of the image which is relevant for CRC calculation. # The actual size of the image which is relevant for CRC calculation.
APP_B_SIZE_ADDR = APP_B_END_ADDR - 8 APP_B_SIZE_ADDR = 0x3FFF8
APP_B_CRC_ADDR = APP_B_END_ADDR - 4 APP_B_CRC_ADDR = 0x3FFFC
APP_B_MAX_SIZE = APP_A_END_ADDR - APP_A_START_ADDR - 8 APP_IMG_SZ = 0x1E000
APP_IMG_SZ = (APP_B_END_ADDR - APP_A_START_ADDR) // 2
CHUNK_SIZE = 896 CHUNK_SIZE = 896
@ -61,7 +52,6 @@ class ActionId(enum.IntEnum):
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SEQ_PROVIDER = SeqCountProvider(bit_width=14)
@dataclasses.dataclass @dataclasses.dataclass
@ -72,174 +62,7 @@ class LoadableSegment:
data: bytes data: bytes
class Target(enum.Enum): SEQ_PROVIDER = SeqCountProvider(bit_width=14)
BOOTLOADER = 0
APP_A = 1
APP_B = 2
class ImageLoader:
def __init__(self, com_if: ComInterface, verificator: PusVerificator) -> None:
self.com_if = com_if
self.verificator = verificator
def handle_ping_cmd(self):
_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),
)
self.verificator.add_tc(ping_tc)
self.com_if.send(bytes(ping_tc.pack()))
data_available = self.com_if.data_available(0.4)
if not data_available:
_LOGGER.warning("no ping reply received")
for reply in self.com_if.receive():
result = self.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")
def handle_corruption_cmd(self, target: Target):
if target == Target.BOOTLOADER:
_LOGGER.error("can not corrupt bootloader")
if target == Target.APP_A:
self.send_tc(
PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_A,
),
)
if target == Target.APP_B:
self.send_tc(
PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_B,
),
)
def handle_flash_cmd(self, target: Target, file_path: Path) -> int:
loadable_segments = []
_LOGGER.info("Parsing ELF file for loadable sections")
total_size = 0
loadable_segments, total_size = create_loadable_segments(target, file_path)
segments_info_str(target, loadable_segments, total_size, file_path)
result = self._perform_flashing_algorithm(loadable_segments)
if result != 0:
return result
self._crc_and_app_size_postprocessing(target, total_size, loadable_segments)
return 0
def _perform_flashing_algorithm(
self,
loadable_segments: List[LoadableSegment],
) -> int:
# Perform the flashing algorithm.
for segment in loadable_segments:
segment_end = segment.offset + segment.size
current_addr = segment.offset
pos_in_segment = 0
while pos_in_segment < segment.size:
next_chunk_size = min(segment_end - current_addr, CHUNK_SIZE)
data = segment.data[pos_in_segment : pos_in_segment + next_chunk_size]
next_packet = pack_memory_write_command(current_addr, data)
_LOGGER.info(
f"Sending memory write command for address {current_addr:#08x} and data with "
f"length {len(data)}"
)
self.verificator.add_tc(next_packet)
self.com_if.send(bytes(next_packet.pack()))
current_addr += next_chunk_size
pos_in_segment += next_chunk_size
start_time = time.time()
while True:
if time.time() - start_time > 1.0:
_LOGGER.error("Timeout while waiting for reply")
return -1
data_available = self.com_if.data_available(0.1)
done = False
if not data_available:
continue
replies = self.com_if.receive()
for reply in replies:
tm = PusTm.unpack(reply, 0)
if tm.service != 1:
continue
service_1_tm = Service1Tm.from_tm(tm, UnpackParams(0))
check_result = self.verificator.add_tm(service_1_tm)
# We could send after we have received the step reply, but that can
# somehow lead to overrun errors. I think it's okay to do it like
# this as long as the flash loader only uses polling..
if (
check_result is not None
and check_result.status.completed == StatusField.SUCCESS
):
done = True
# This is an optimized variant, but I think the small delay is not an issue.
"""
if (
check_result is not None
and check_result.status.step == StatusField.SUCCESS
and len(check_result.status.step_list) == 1
):
done = True
"""
self.verificator.remove_completed_entries()
if done:
break
return 0
def _crc_and_app_size_postprocessing(
self,
target: Target,
total_size: int,
loadable_segments: List[LoadableSegment],
):
if target == Target.BOOTLOADER:
_LOGGER.info("Blanking the bootloader checksum")
# Blank the checksum. For the bootloader, the bootloader will calculate the
# checksum itself on the initial run.
checksum_write_packet = pack_memory_write_command(
BOOTLOADER_CRC_ADDR, bytes([0x00, 0x00, 0x00, 0x00])
)
self.send_tc(checksum_write_packet)
else:
crc_addr = None
size_addr = None
if target == Target.APP_A:
crc_addr = APP_A_CRC_ADDR
size_addr = APP_A_SIZE_ADDR
elif target == Target.APP_B:
crc_addr = APP_B_CRC_ADDR
size_addr = APP_B_SIZE_ADDR
assert crc_addr is not None
assert size_addr is not None
_LOGGER.info(f"Writing app size {total_size} at address {size_addr:#08x}")
size_write_packet = pack_memory_write_command(
size_addr, struct.pack("!I", total_size)
)
self.com_if.send(bytes(size_write_packet.pack()))
time.sleep(0.2)
crc_calc = PredefinedCrc("crc-32")
for segment in loadable_segments:
crc_calc.update(segment.data)
checksum = crc_calc.digest()
_LOGGER.info(
f"Writing checksum 0x[{checksum.hex(sep=',')}] at address {crc_addr:#08x}"
)
self.send_tc(pack_memory_write_command(crc_addr, checksum))
def send_tc(self, tc: PusTc):
self.com_if.send(bytes(tc.pack()))
def main() -> int: def main() -> int:
@ -279,134 +102,213 @@ def main() -> int:
verificator = PusVerificator() verificator = PusVerificator()
com_if = SerialCobsComIF(serial_cfg) com_if = SerialCobsComIF(serial_cfg)
com_if.open() com_if.open()
target = None
if args.target == "bl":
target = Target.BOOTLOADER
elif args.target == "a":
target = Target.APP_A
elif args.target == "b":
target = Target.APP_B
image_loader = ImageLoader(com_if, verificator)
file_path = None file_path = None
result = -1
if args.ping: if args.ping:
image_loader.handle_ping_cmd() _LOGGER.info("Sending ping command")
com_if.close() ping_tc = PusTc(
return 0 apid=0x00,
if target: 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 not args.corrupt: if not args.corrupt:
if not args.path: if not args.path:
_LOGGER.error("App Path needs to be specified for the flash process") _LOGGER.error("App Path needs to be specified for the flash process")
return -1
file_path = Path(args.path) file_path = Path(args.path)
if not file_path.exists(): if not file_path.exists():
_LOGGER.error("File does not exist") _LOGGER.error("File does not exist")
return -1
if args.corrupt: if args.corrupt:
if not target: if not args.target:
_LOGGER.error("target for corruption command required") _LOGGER.error("target for corruption command required")
com_if.close()
return -1 return -1
image_loader.handle_corruption_cmd(target) if args.target == "bl":
_LOGGER.error("can not corrupt bootloader")
if args.target == "a":
packet = PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_A,
)
com_if.send(packet.pack())
if args.target == "b":
packet = PusTc(
apid=0,
service=ACTION_SERVICE,
subservice=ActionId.CORRUPT_APP_B,
)
com_if.send(packet.pack())
else: else:
assert file_path is not None assert file_path is not None
assert target is not None loadable_segments = []
result = image_loader.handle_flash_cmd(target, file_path) _LOGGER.info("Parsing ELF file for loadable sections")
total_size = 0
with open(file_path, "rb") as app_file:
elf_file = ELFFile(app_file)
com_if.close() for (idx, segment) in enumerate(elf_file.iter_segments("PT_LOAD")):
return result if segment.header.p_filesz == 0:
continue
# Basic validity checks of the base addresses.
def create_loadable_segments( if idx == 0:
target: Target, file_path: Path if (
) -> Tuple[List[LoadableSegment], int]: args.target == "bl"
loadable_segments = [] and segment.header.p_paddr != BOOTLOADER_START_ADDR
total_size = 0 ):
with open(file_path, "rb") as app_file: raise ValueError(
elf_file = ELFFile(app_file) f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"bootloader, expected {BOOTLOADER_START_ADDR}"
for idx, segment in enumerate(elf_file.iter_segments("PT_LOAD")): )
if segment.header.p_filesz == 0: if (
continue args.target == "a"
# Basic validity checks of the base addresses. and segment.header.p_paddr != APP_A_START_ADDR
if idx == 0: ):
if ( raise ValueError(
target == Target.BOOTLOADER f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
and segment.header.p_paddr != BOOTLOADER_START_ADDR f"App A, expected {APP_A_START_ADDR}"
): )
raise ValueError( if (
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for " args.target == "b"
f"bootloader, expected {BOOTLOADER_START_ADDR}" and segment.header.p_paddr != APP_B_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"App B, expected {APP_B_START_ADDR}"
)
name = None
for section in elf_file.iter_sections():
if (
section.header.sh_offset == segment.header.p_offset
and section.header.sh_size > 0
):
name = section.name
if name is None:
_LOGGER.warning("no fitting section found for segment")
continue
# print(f"Segment Addr: {segment.header.p_paddr}")
# print(f"Segment Offset: {segment.header.p_offset}")
# print(f"Segment Filesize: {segment.header.p_filesz}")
loadable_segments.append(
LoadableSegment(
name=name,
offset=segment.header.p_paddr,
size=segment.header.p_filesz,
data=segment.data(),
) )
if (
target == Target.APP_A
and segment.header.p_paddr != APP_A_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"App A, expected {APP_A_START_ADDR}"
)
if (
target == Target.APP_B
and segment.header.p_paddr != APP_B_START_ADDR
):
raise ValueError(
f"detected possibly invalid start address {segment.header.p_paddr:#08x} for "
f"App B, expected {APP_B_START_ADDR}"
)
name = None
for section in elf_file.iter_sections():
if (
section.header.sh_offset == segment.header.p_offset
and section.header.sh_size > 0
):
name = section.name
if name is None:
_LOGGER.warning("no fitting section found for segment")
continue
# print(f"Segment Addr: {segment.header.p_paddr}")
# print(f"Segment Offset: {segment.header.p_offset}")
# print(f"Segment Filesize: {segment.header.p_filesz}")
loadable_segments.append(
LoadableSegment(
name=name,
offset=segment.header.p_paddr,
size=segment.header.p_filesz,
data=segment.data(),
) )
total_size += segment.header.p_filesz
context_str = None
if args.target == "bl":
context_str = "Bootloader"
elif args.target == "a":
context_str = "App Slot A"
elif args.target == "b":
context_str = "App Slot B"
_LOGGER.info(
f"Flashing {context_str} with image {file_path} (size {total_size})"
) )
total_size += segment.header.p_filesz for idx, segment in enumerate(loadable_segments):
return loadable_segments, total_size _LOGGER.info(
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and size {segment.size}"
)
def segments_info_str( for segment in loadable_segments:
target: Target, segment_end = segment.offset + segment.size
loadable_segments: List[LoadableSegment], current_addr = segment.offset
total_size: int, pos_in_segment = 0
file_path: Path, while pos_in_segment < segment.size:
): next_chunk_size = min(segment_end - current_addr, CHUNK_SIZE)
# Set context string and perform basic sanity checks. data = segment.data[
if target == Target.BOOTLOADER: pos_in_segment : pos_in_segment + next_chunk_size
if total_size > BOOTLOADER_MAX_SIZE: ]
_LOGGER.error( next_packet = pack_memory_write_command(current_addr, data)
f"provided bootloader app larger than allowed {total_size} bytes" _LOGGER.info(
) f"Sending memory write command for address {current_addr:#08x} and data with "
return -1 f"length {len(data)}"
context_str = "Bootloader" )
elif target == Target.APP_A: verificator.add_tc(next_packet)
if total_size > APP_A_MAX_SIZE: com_if.send(next_packet.pack())
_LOGGER.error(f"provided App A larger than allowed {total_size} bytes") current_addr += next_chunk_size
return -1 pos_in_segment += next_chunk_size
context_str = "App Slot A" while True:
elif target == Target.APP_B: data_available = com_if.data_available(0.1)
if total_size > APP_B_MAX_SIZE: done = False
_LOGGER.error(f"provided App B larger than allowed {total_size} bytes") if not data_available:
return -1 continue
context_str = "App Slot B" replies = com_if.receive()
_LOGGER.info(f"Flashing {context_str} with image {file_path} (size {total_size})") for reply in replies:
for idx, segment in enumerate(loadable_segments): tm = PusTm.unpack(reply, 0)
_LOGGER.info( if tm.service != 1:
f"Loadable section {idx} {segment.name} with offset {segment.offset:#08x} and " continue
f"size {segment.size}" service_1_tm = Service1Tm.from_tm(tm, UnpackParams(0))
) check_result = verificator.add_tm(service_1_tm)
# We could send after we have received the step reply, but that can
# somehow lead to overrun errors. I think it's okay to do it like
# this as long as the flash loader only uses polling..
if (
check_result is not None
and check_result.status.completed == StatusField.SUCCESS
):
done = True
# Still keep a small delay
# time.sleep(0.05)
verificator.remove_completed_entries()
if done:
break
if args.target == "bl":
_LOGGER.info("Blanking the bootloader checksum")
# Blank the checksum. For the bootloader, the bootloader will calculate the
# checksum itself on the initial run.
checksum_write_packet = pack_memory_write_command(
BOOTLOADER_CRC_ADDR, bytes([0x00, 0x00, 0x00, 0x00])
)
com_if.send(checksum_write_packet.pack())
else:
crc_addr = None
size_addr = None
if args.target == "a":
crc_addr = APP_A_CRC_ADDR
size_addr = APP_A_SIZE_ADDR
elif args.target == "b":
crc_addr = APP_B_CRC_ADDR
size_addr = APP_B_SIZE_ADDR
assert crc_addr is not None
assert size_addr is not None
_LOGGER.info(
f"Writing app size {total_size} at address {size_addr:#08x}"
)
size_write_packet = pack_memory_write_command(
size_addr, struct.pack("!I", total_size)
)
com_if.send(size_write_packet.pack())
time.sleep(0.2)
crc_calc = PredefinedCrc("crc-32")
for segment in loadable_segments:
crc_calc.update(segment.data)
checksum = crc_calc.digest()
_LOGGER.info(
f"Writing checksum 0x[{checksum.hex(sep=',')}] at address {crc_addr:#08x}"
)
checksum_write_packet = pack_memory_write_command(crc_addr, checksum)
com_if.send(checksum_write_packet.pack())
com_if.close()
return 0
def pack_memory_write_command(addr: int, data: bytes) -> PusTc: def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
@ -422,7 +324,7 @@ def pack_memory_write_command(addr: int, data: bytes) -> PusTc:
service=MEMORY_SERVICE, service=MEMORY_SERVICE,
subservice=RAW_MEMORY_WRITE_SUBSERVICE, subservice=RAW_MEMORY_WRITE_SUBSERVICE,
seq_count=SEQ_PROVIDER.get_and_increment(), seq_count=SEQ_PROVIDER.get_and_increment(),
app_data=bytes(app_data), app_data=app_data,
) )

View File

@ -7,11 +7,11 @@ edition = "2021"
[dependencies] [dependencies]
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
va416xx-hal = { path = "../../va416xx-hal" }
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
embedded-hal = "1" embedded-hal = "1"
va416xx-hal = { path = "../../va416xx-hal", features = ["va41630"] }
[profile.dev] [profile.dev]
codegen-units = 1 codegen-units = 1

View File

@ -7,11 +7,11 @@ edition = "2021"
[dependencies] [dependencies]
cortex-m-rt = "0.7" cortex-m-rt = "0.7"
va416xx-hal = { path = "../../va416xx-hal" }
panic-rtt-target = { version = "0.1.3" } panic-rtt-target = { version = "0.1.3" }
rtt-target = { version = "0.5" } rtt-target = { version = "0.5" }
cortex-m = { version = "0.7", features = ["critical-section-single-core"] } cortex-m = { version = "0.7", features = ["critical-section-single-core"] }
embedded-hal = "1" embedded-hal = "1"
va416xx-hal = { path = "../../va416xx-hal", features = ["va41630"] }
[profile.dev] [profile.dev]
codegen-units = 1 codegen-units = 1

View File

@ -109,7 +109,6 @@ mod app {
tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket, tc::PusTcReader, tm::PusTmCreator, EcssEnumU8, PusPacket, WritablePusPacket,
}; };
use va416xx_hal::irq_router::enable_and_init_irq_router; use va416xx_hal::irq_router::enable_and_init_irq_router;
use va416xx_hal::uart::IrqContextTimeoutOrMaxSize;
use va416xx_hal::{ use va416xx_hal::{
clock::ClkgenExt, clock::ClkgenExt,
edac, edac,
@ -133,7 +132,6 @@ mod app {
struct Local { struct Local {
uart_rx: uart::RxWithIrq<pac::Uart0>, uart_rx: uart::RxWithIrq<pac::Uart0>,
uart_tx: uart::Tx<pac::Uart0>, uart_tx: uart::Tx<pac::Uart0>,
rx_context: IrqContextTimeoutOrMaxSize,
rom_spi: Option<pac::Spi3>, rom_spi: Option<pac::Spi3>,
// We handle all TM in one task. // We handle all TM in one task.
tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>, tm_cons: DataConsumer<BUF_RB_SIZE_TM, SIZES_RB_SIZE_TM>,
@ -169,9 +167,9 @@ mod app {
enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router); enable_and_init_irq_router(&mut cx.device.sysconfig, &cx.device.irq_router);
setup_edac(&mut cx.device.sysconfig); setup_edac(&mut cx.device.sysconfig);
let gpiog = PinsG::new(&mut cx.device.sysconfig, cx.device.portg); let gpiob = PinsG::new(&mut cx.device.sysconfig, cx.device.portg);
let tx = gpiog.pg0.into_funsel_1(); let tx = gpiob.pg0.into_funsel_1();
let rx = gpiog.pg1.into_funsel_1(); let rx = gpiob.pg1.into_funsel_1();
let uart0 = Uart::new( let uart0 = Uart::new(
cx.device.uart0, cx.device.uart0,
@ -180,7 +178,7 @@ 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 verif_reporter = VerificationReportCreator::new(0).unwrap(); let verif_reporter = VerificationReportCreator::new(0).unwrap();
@ -193,9 +191,7 @@ mod app {
Mono::start(cx.core.SYST, clocks.sysclk().raw()); Mono::start(cx.core.SYST, clocks.sysclk().raw());
CLOCKS.set(clocks).unwrap(); CLOCKS.set(clocks).unwrap();
let mut rx = rx.into_rx_with_irq(); rx.read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
let mut rx_context = IrqContextTimeoutOrMaxSize::new(MAX_TC_FRAME_SIZE);
rx.read_fixed_len_or_timeout_based_using_irq(&mut rx_context)
.expect("initiating UART RX failed"); .expect("initiating UART RX failed");
pus_tc_handler::spawn().unwrap(); pus_tc_handler::spawn().unwrap();
pus_tm_tx_handler::spawn().unwrap(); pus_tm_tx_handler::spawn().unwrap();
@ -209,7 +205,6 @@ mod app {
Local { Local {
uart_rx: rx, uart_rx: rx,
uart_tx: tx, uart_tx: tx,
rx_context,
rom_spi: Some(cx.device.spi3), rom_spi: Some(cx.device.spi3),
tm_cons: DataConsumer { tm_cons: DataConsumer {
buf_cons: buf_cons_tm, buf_cons: buf_cons_tm,
@ -236,26 +231,20 @@ mod app {
} }
} }
// This is the interrupt handler to read all bytes received on the UART0.
#[task( #[task(
binds = UART0_RX, binds = UART0_RX,
local = [ local = [
cnt: u32 = 0, cnt: u32 = 0,
rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE], rx_buf: [u8; MAX_TC_FRAME_SIZE] = [0; MAX_TC_FRAME_SIZE],
rx_context,
uart_rx, uart_rx,
tc_prod tc_prod
], ],
)] )]
fn uart_rx_irq(cx: uart_rx_irq::Context) { fn uart_rx_irq(cx: uart_rx_irq::Context) {
match cx match cx.local.uart_rx.irq_handler(cx.local.rx_buf) {
.local
.uart_rx
.irq_handler_max_size_or_timeout_based(cx.local.rx_context, cx.local.rx_buf)
{
Ok(result) => { Ok(result) => {
if RX_DEBUGGING { if RX_DEBUGGING {
log::debug!("RX Info: {:?}", cx.local.rx_context); log::debug!("RX Info: {:?}", cx.local.uart_rx.irq_info());
log::debug!("RX Result: {:?}", result); log::debug!("RX Result: {:?}", result);
} }
if result.complete() { if result.complete() {
@ -290,11 +279,11 @@ mod app {
// Initiate next transfer. // Initiate next transfer.
cx.local cx.local
.uart_rx .uart_rx
.read_fixed_len_or_timeout_based_using_irq(cx.local.rx_context) .read_fixed_len_using_irq(MAX_TC_FRAME_SIZE, true)
.expect("read operation failed"); .expect("read operation failed");
} }
if result.has_errors() { if result.error() {
log::warn!("UART error: {:?}", result.errors.unwrap()); log::warn!("UART error: {:?}", result.error());
} }
} }
Err(e) => { Err(e) => {
@ -449,12 +438,7 @@ mod app {
return; return;
} }
let data = &app_data[10..10 + data_len as usize]; let data = &app_data[10..10 + data_len as usize];
log::info!( log::info!("writing {} bytes at offset {} to NVM", data_len, offset);
target: "TC Handler",
"writing {} bytes at offset {} to NVM",
data_len,
offset
);
// Safety: We only use this for NVM handling and we only do NVM // Safety: We only use this for NVM handling and we only do NVM
// handling here. // handling here.
let mut sys_cfg = unsafe { pac::Sysconfig::steal() }; let mut sys_cfg = unsafe { pac::Sysconfig::steal() };
@ -471,9 +455,7 @@ mod app {
.completion_success(cx.local.src_data_buf, started_token, 0, 0, &[]) .completion_success(cx.local.src_data_buf, started_token, 0, 0, &[])
.expect("completion success failed"); .expect("completion success failed");
write_and_send(&tm); write_and_send(&tm);
log::info!( log::info!("NVM operation done");
target: "TC Handler",
"NVM operation done");
} }
} }
} }

View File

@ -1,7 +1,7 @@
/* Special linker script for application slot A with an offset at address 0x4000 */ /* Special linker script for application slot A with an offset at address 0x4000 */
MEMORY MEMORY
{ {
FLASH : ORIGIN = 0x00004000, LENGTH = 0x1DFF8 FLASH : ORIGIN = 0x00004000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */ /* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K

View File

@ -1,7 +1,7 @@
/* Special linker script for application slot B with an offset at address 0x22000 */ /* Special linker script for application slot B with an offset at address 0x22000 */
MEMORY MEMORY
{ {
FLASH : ORIGIN = 0x00022000, LENGTH = 0x1DFF8 FLASH : ORIGIN = 0x00022000, LENGTH = 256K
/* RAM is a mandatory region. This RAM refers to the SRAM_0 */ /* RAM is a mandatory region. This RAM refers to the SRAM_0 */
RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K RAM : ORIGIN = 0x1FFF8000, LENGTH = 32K
SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K SRAM_1 : ORIGIN = 0x20000000, LENGTH = 32K

View File

@ -8,18 +8,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased] # [unreleased]
# [v0.3.0] 2024-30-09
## Changed ## Changed
- Improve and fix SPI abstractions. Add new low level interface. The primary SPI constructor now - Improve and fix SPI abstractions. Add new low level interface. The primary SPI constructor now
only expects a configuration structure and the transfer configuration needs to be applied in a only expects a configuration structure and the transfer configuration needs to be applied in a
separate step. separate step.
- Added an additional way to read the UART RX with IRQs. The module documentation provides
more information.
- Made the UART with IRQ API more flexible for future additions.
- Improved UART API result and error handling, added low level API to read from and write
to the FIFO directly
## Fixed ## Fixed

View File

@ -1,6 +1,6 @@
[package] [package]
name = "va416xx-hal" name = "va416xx-hal"
version = "0.3.0" version = "0.2.0"
authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"] authors = ["Robin Mueller <muellerr@irs.uni-stuttgart.de>"]
edition = "2021" edition = "2021"
description = "HAL for the Vorago VA416xx family of MCUs" description = "HAL for the Vorago VA416xx family of MCUs"

View File

@ -113,6 +113,14 @@ pub(super) unsafe trait RegisterInterface {
/// this type. /// this type.
fn id(&self) -> DynPinId; fn id(&self) -> DynPinId;
const PORTA: *const PortRegisterBlock = Porta::ptr();
const PORTB: *const PortRegisterBlock = Portb::ptr();
const PORTC: *const PortRegisterBlock = Portc::ptr();
const PORTD: *const PortRegisterBlock = Portd::ptr();
const PORTE: *const PortRegisterBlock = Porte::ptr();
const PORTF: *const PortRegisterBlock = Portf::ptr();
const PORTG: *const PortRegisterBlock = Portg::ptr();
/// Change the pin mode /// Change the pin mode
#[inline] #[inline]
fn change_mode(&mut self, mode: DynPinMode) { fn change_mode(&mut self, mode: DynPinMode) {
@ -147,13 +155,13 @@ pub(super) unsafe trait RegisterInterface {
#[inline] #[inline]
fn port_reg(&self) -> &PortRegisterBlock { fn port_reg(&self) -> &PortRegisterBlock {
match self.id().group { match self.id().group {
DynGroup::A => unsafe { &(*Porta::ptr()) }, DynGroup::A => unsafe { &(*Self::PORTA) },
DynGroup::B => unsafe { &(*Portb::ptr()) }, DynGroup::B => unsafe { &(*Self::PORTB) },
DynGroup::C => unsafe { &(*Portc::ptr()) }, DynGroup::C => unsafe { &(*Self::PORTC) },
DynGroup::D => unsafe { &(*Portd::ptr()) }, DynGroup::D => unsafe { &(*Self::PORTD) },
DynGroup::E => unsafe { &(*Porte::ptr()) }, DynGroup::E => unsafe { &(*Self::PORTE) },
DynGroup::F => unsafe { &(*Portf::ptr()) }, DynGroup::F => unsafe { &(*Self::PORTF) },
DynGroup::G => unsafe { &(*Portg::ptr()) }, DynGroup::G => unsafe { &(*Self::PORTG) },
} }
} }

View File

@ -9,10 +9,8 @@ use core::convert::Infallible;
use core::marker::PhantomData; use core::marker::PhantomData;
use crate::pac; use crate::pac;
use crate::time::Hertz;
pub use crate::timer::ValidTim;
use crate::timer::{TimAndPinRegister, TimDynRegister, TimPin, TimRegInterface, ValidTimAndPin};
use crate::{clock::Clocks, gpio::DynPinId}; use crate::{clock::Clocks, gpio::DynPinId};
pub use crate::{gpio::PinId, time::Hertz, timer::*};
const DUTY_MAX: u16 = u16::MAX; const DUTY_MAX: u16 = u16::MAX;

View File

@ -255,9 +255,6 @@ pub struct TransferConfig {
/// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the /// the BMSTOP bit is set on a dataword. A frame is defined as CSn being active for the
/// duration of multiple data words /// duration of multiple data words
pub blockmode: bool, pub blockmode: bool,
/// Only used when blockmode is used. The SCK will be stalled until an explicit stop bit
/// is set on a written word.
pub bmstall: bool,
pub hw_cs: HwChipSelectId, pub hw_cs: HwChipSelectId,
} }
@ -266,7 +263,6 @@ impl TransferConfigWithHwcs<NoneT> {
clk_cfg: Option<SpiClkConfig>, clk_cfg: Option<SpiClkConfig>,
mode: Option<Mode>, mode: Option<Mode>,
blockmode: bool, blockmode: bool,
bmstall: bool,
sod: bool, sod: bool,
) -> Self { ) -> Self {
TransferConfigWithHwcs { TransferConfigWithHwcs {
@ -276,7 +272,6 @@ impl TransferConfigWithHwcs<NoneT> {
mode, mode,
sod, sod,
blockmode, blockmode,
bmstall,
hw_cs: HwChipSelectId::Invalid, hw_cs: HwChipSelectId::Invalid,
}, },
} }
@ -289,7 +284,6 @@ impl<HwCs: HwCsProvider> TransferConfigWithHwcs<HwCs> {
mode: Option<Mode>, mode: Option<Mode>,
hw_cs: Option<HwCs>, hw_cs: Option<HwCs>,
blockmode: bool, blockmode: bool,
bmstall: bool,
sod: bool, sod: bool,
) -> Self { ) -> Self {
TransferConfigWithHwcs { TransferConfigWithHwcs {
@ -299,7 +293,6 @@ impl<HwCs: HwCsProvider> TransferConfigWithHwcs<HwCs> {
mode, mode,
sod, sod,
blockmode, blockmode,
bmstall,
hw_cs: HwCs::CS_ID, hw_cs: HwCs::CS_ID,
}, },
} }
@ -377,21 +370,6 @@ impl SpiConfig {
self self
} }
pub fn blockmode(mut self, enable: bool) -> Self {
self.blockmode = enable;
self
}
pub fn bmstall(mut self, enable: bool) -> Self {
self.bmstall = enable;
self
}
pub fn mode(mut self, mode: Mode) -> Self {
self.init_mode = mode;
self
}
pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self { pub fn clk_cfg(mut self, clk_cfg: SpiClkConfig) -> Self {
self.clk = clk_cfg; self.clk = clk_cfg;
self self
@ -741,8 +719,12 @@ where
} else { } else {
w.sod().clear_bit(); w.sod().clear_bit();
} }
w.blockmode().bit(transfer_cfg.cfg.blockmode); if transfer_cfg.cfg.blockmode {
w.bmstall().bit(transfer_cfg.cfg.bmstall) w.blockmode().set_bit();
} else {
w.blockmode().clear_bit();
}
w
}); });
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +0,0 @@
Change Log
=======
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
# [unreleased]
# [v0.1.0] 2024-10-01
- Initial release

View File

@ -16,10 +16,14 @@ cortex-m-rt = "0.7"
embedded-hal = "1" embedded-hal = "1"
[dependencies.va416xx-hal] [dependencies.va416xx-hal]
path = "../va416xx-hal"
features = ["va41630"] features = ["va41630"]
version = ">=0.3, <0.4" version = "0.2.0"
[dependencies.lis2dh12] [dependencies.lis2dh12]
git = "https://github.com/us-irs/lis2dh12.git"
# path = "../../lis2dh12"
branch = "all-features"
version = "0.7" version = "0.7"
features = ["out_f32"] features = ["out_f32"]

View File

@ -1,12 +1,15 @@
[![Crates.io](https://img.shields.io/crates/v/vorago-peb1)](https://crates.io/crates/vorago-peb1)
[![docs.rs](https://img.shields.io/docsrs/vorago-peb1)](https://docs.rs/vorago-peb1)
# Rust BSP for the Vorago PEB1 development board # Rust BSP for the Vorago PEB1 development board
This is the Rust **B**oard **S**upport **P**ackage crate for the Vorago PEB1 development board. ## Using the `.cargo/config.toml` file
Its aim is to provide drivers for the board features of the PEB1 board.
The BSP builds on top of the [HAL crate for VA416xx devices](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/va416xx-hal). Use the following command to have a starting `config.toml` file
```sh
cp .cargo/def-config.toml .cargo/config.toml
```
You then can adapt the `config.toml` to your needs. For example, you can configure runners
to conveniently flash with `cargo run`.
## Notes on board revisions ## Notes on board revisions

View File

@ -5,10 +5,6 @@
pub use lis2dh12; pub use lis2dh12;
/// Support for the LIS2DH12 accelerometer on the GPIO board. /// Support for the LIS2DH12 accelerometer on the GPIO board.
///
/// # Example
///
/// - [PEB1 Accelerometer](https://egit.irs.uni-stuttgart.de/rust/va416xx-rs/src/branch/main/examples/simple/examples/peb1-accelerometer.rs)
pub mod accelerometer { pub mod accelerometer {
use lis2dh12::{self, detect_i2c_addr, AddrDetectionError, Lis2dh12}; use lis2dh12::{self, detect_i2c_addr, AddrDetectionError, Lis2dh12};

View File

@ -350,36 +350,6 @@
] ]
} }
}, },
{
"type": "cortex-debug",
"request": "launch",
"name": "UART Echo with IRQ",
"servertype": "jlink",
"jlinkscript": "${workspaceFolder}/jlink/JLinkSettings.JLinkScript",
"cwd": "${workspaceRoot}",
"device": "Cortex-M4",
"svdFile": "${workspaceFolder}/va416xx/svd/va416xx.svd.patched",
"preLaunchTask": "uart-echo-with-irq",
"overrideLaunchCommands": [
"monitor halt",
"monitor reset",
"load",
],
"executable": "${workspaceFolder}/target/thumbv7em-none-eabihf/debug/uart-echo-with-irq",
"interface": "swd",
"runToEntryPoint": "main",
"rttConfig": {
"enabled": true,
"address": "auto",
"decoders": [
{
"port": 0,
"timestamp": true,
"type": "console"
}
]
}
},
{ {
"type": "cortex-debug", "type": "cortex-debug",
"request": "launch", "request": "launch",

View File

@ -95,19 +95,6 @@
"kind": "build", "kind": "build",
} }
}, },
{
"label": "uart-echo-with-irq",
"type": "shell",
"command": "~/.cargo/bin/cargo", // note: full path to the cargo
"args": [
"build",
"--bin",
"uart-echo-with-irq"
],
"group": {
"kind": "build",
}
},
{ {
"label": "pwm-example", "label": "pwm-example",
"type": "shell", "type": "shell",