Embedded (no_std)¶
The Rust library builds without the standard library. It needs an allocator and nothing else, so Pelorus runs on microcontrollers and in kernels as well as on a desktop. Only the library does: the command line tool, the ROS 2 node and the C and Python bindings use std.
Adding the crate¶
The crate is pelorus_sdk, imported as pelorus. Turn its default features off to drop std:
The target needs atomic compare-and-swap. Every Cortex-M3 or newer, every RISC-V core with the A extension such as riscv32imac, and every 32-bit or 64-bit application processor has it. Cortex-M0 and RISC-V cores without the A extension, such as riscv32i and riscv32imc, do not. The binary needs an #[global_allocator].
Running the odometry¶
Without std, Pelorus cannot read Ratslang configuration files. The Odometer takes a resolved LioConfig instead. LioConfig::defaults() is the configuration the embedded default.rl resolves to. Change its fields for your sensors before you create the odometer:
Create the odometer once and feed it every message the sensor driver receives:
use pelorus::{ImuMeasurement, LioConfig, Odometer};
/// One message from the sensor driver.
pub enum Input {
Imu(ImuMeasurement),
Points {
sec: i32,
nanosec: u32,
points: Vec<[f32; 5]>,
},
}
pub fn run(
mut receive: impl FnMut() -> Input,
mut publish: impl FnMut((i32, u32), [f64; 3], [f64; 4]),
) -> Result<(), pelorus::Error> {
let mut config = LioConfig::defaults();
config.map_resolution = 0.5;
let mut odometer = Odometer::from_config(config, true)?;
loop {
match receive() {
Input::Imu(imu) => odometer.add_imu(imu),
Input::Points { sec, nanosec, points } => {
// Skip a scan while an older one still waits, so the queue cannot grow.
if odometer.pending_scans() < 2 {
odometer.add_points_xyzit(sec, nanosec, &points);
}
}
}
// An IMU message can complete a waiting scan, so drain after every input.
while let Some(scan) = odometer.next_scan() {
publish(scan.timestamp(), scan.position(), scan.orientation());
}
}
}
Everything runs on the calling thread:
add_imuandadd_points_xyzitonly queue their input. A cloud is filtered and downsampled as it is added.next_scantakes the oldest scan whose IMU interval is complete, deskews it and registers it against the map. It returns once that is done.- A scan is complete once IMU messages past its end have arrived, so it usually comes out a few IMU messages after its cloud went in.
- At the start, clouds that arrive during the IMU initialization are dropped and the first scans only build the map.
next_scanreturns nothing for about the first second. - While
next_scanworks, the loop receives nothing. Buffer the network packets in the driver, for example in DMA rings, so none are lost during a registration.
Bridging a configuration to the device¶
A configuration written in Ratslang can still reach the device. Resolve it on a host with std, where pelorus::load_config reads the file, and serialize the LioConfig with serde, for example with postcard: