Examples
Reception examples for both transports, and command transmission.
The platform_dependant_rs422_* / platform_dependant_canfd_* functions are placeholders for the target platform's HAL.
Minimal Example
Received bytes are passed to the parser, which yields one OscpFrame per valid frame.
use oscp_imu::{OscpFrame, OscpParser};
fn main() {
let mut parser = OscpParser::new();
let mut buf = [0u8; 256];
loop {
let n = uart_read(&mut buf);
if n == 0 {
break;
}
for frame in parser.feed_buf(&buf[..n]) {
if let OscpFrame::Euler(e) = frame {
println!("Roll {:.2} Pitch {:.2} Yaw {:.2}", e.roll, e.pitch, e.yaw);
}
}
}
}
On a bare-metal target, bytes can be passed one at a time, e.g. from a queue filled by the UART receive ISR:
while let Some(byte) = rx_queue.dequeue() {
if let Some(frame) = parser.feed(byte) {
handle_frame(frame);
}
}
Each received message is decoded with one call. data.len() may exceed
the frame length because of DLC padding.
use oscp_imu::{oscp_frame_decode, OscpFrame};
/* FDCAN RX callback */
fn on_can_message(id: u32, data: &[u8]) {
if id == 0x6FD {
return; /* command response, not an operating frame */
}
let Ok(frame) = oscp_frame_decode(data) else {
return; /* dropped */
};
if let OscpFrame::Raw(r) = frame {
ingest_imu(r.gyro_x, r.gyro_y, r.gyro_z, r.accel_x, r.accel_y, r.accel_z);
}
}
Handling every frame type
match on the returned frame:
use oscp_imu::{OscpFrame, OscpFrameCmd};
fn handle_frame(frame: OscpFrame) {
match frame {
OscpFrame::Raw(r) => {
let (gx, gy, gz) = (r.gyro_x, r.gyro_y, r.gyro_z);
/* ... */
}
OscpFrame::Quat(q) => {
let (w, x, y, z) = (q.w, q.x, q.y, q.z);
/* ... */
}
OscpFrame::Gnss(g) => {
let (lat, lon) = (g.latitude, g.longitude);
/* ... */
}
OscpFrame::Startup(s) => {
let mark = core::str::from_utf8(&s.mark_number)
.unwrap_or("<invalid>")
.trim_end_matches('\0'); /* mark_number is a byte array */
println!("Unit {} #{} fw {}.{}.{}", mark, s.unit_number,
s.sw_major_ver, s.sw_minor_ver, s.sw_patch_ver);
}
/* command responses (RS422): */
OscpFrame::CmdResponse(OscpFrameCmd::Success) => println!("CMD OK"),
OscpFrame::CmdResponse(OscpFrameCmd::Failed) => println!("CMD FAILED"),
OscpFrame::CmdResponse(OscpFrameCmd::Unknown) => println!("CMD UNKNOWN"),
OscpFrame::CmdResponse(OscpFrameCmd::NotImpl) => println!("CMD NOT IMPL"),
_ => {}
}
}
Sending commands
Commands are encoded into a caller-owned buffer. The caller transmits the bytes.
use oscp_imu::{oscp_cmd_config, OscpTransport};
let mut cmd = [0u8; 32];
/* RS422: COBS-encoded, 0x00-delimited */
if let Ok(len) = oscp_cmd_config(&mut cmd, OscpTransport::Rs422) {
/* Transmit as-is */
platform_dependant_rs422_write(&cmd[..len]);
/* Delay between consecutive commands */
platform_dependant_delay_ms(10);
}
use oscp_imu::{oscp_cmd_enable_oft, OscpFrameSel, OscpTransport};
let mut cmd = [0u8; 32];
/* CAN-FD: ASCII payload; caller sets the command identifier and DLC */
if let Ok(len) = oscp_cmd_enable_oft(&mut cmd, OscpFrameSel::Raw, OscpTransport::Canfd) {
/* CAN command ID 0x6FC; caller applies DLC padding */
platform_dependant_canfd_write(0x6FC, &cmd[..len]);
/* Delay between consecutive commands */
platform_dependant_delay_ms(10);
}
Configuration sequence (RS422)
use oscp_imu::{
oscp_cmd_config, oscp_cmd_dra, oscp_cmd_drg, oscp_cmd_enable_oft, oscp_cmd_exit, oscp_cmd_om,
OscpAccelDr, OscpErr, OscpFrameSel, OscpGyroDr, OscpOmSel, OscpTransport,
};
fn send_cmd(b: &[u8]) {
platform_dependant_rs422_write(b);
platform_dependant_delay_ms(10);
}
fn configure_unit() -> Result<(), OscpErr> {
let mut cmd = [0u8; 32];
let t = OscpTransport::Rs422;
/* 1. enter CONFIG from OPERATING */
let len = oscp_cmd_config(&mut cmd, t)?;
send_cmd(&cmd[..len]);
/* 2. apply settings */
let len = oscp_cmd_om(&mut cmd, OscpOmSel::Medium, t)?;
send_cmd(&cmd[..len]);
let len = oscp_cmd_drg(&mut cmd, OscpGyroDr::Dps125, t)?;
send_cmd(&cmd[..len]);
let len = oscp_cmd_dra(&mut cmd, OscpAccelDr::G2, t)?;
send_cmd(&cmd[..len]);
let len = oscp_cmd_enable_oft(&mut cmd, OscpFrameSel::Euler, t)?;
send_cmd(&cmd[..len]);
/* 3. return to OPERATING */
let len = oscp_cmd_exit(&mut cmd, t)?;
send_cmd(&cmd[..len]);
Ok(())
}
Application state (RS422)
The parser returns frames instead of calling a callback, so there is no
context pointer. Application state is updated in the receive loop. Separate
OscpParser instances are independent and can be used for multiple units.
use oscp_imu::{OscpFrame, OscpParser};
/* Application state updated in the receive loop */
#[derive(Default)]
struct ImuApp {
roll: f32,
pitch: f32,
yaw: f32, /* latest attitude */
euler_frames: u32, /* number of Euler frames received */
}
fn main() {
let mut app = ImuApp::default();
let mut parser = OscpParser::new();
let mut buf = [0u8; 256];
loop {
let n = uart_read(&mut buf);
if n == 0 {
break;
}
for frame in parser.feed_buf(&buf[..n]) {
if let OscpFrame::Euler(e) = frame {
app.roll = e.roll;
app.pitch = e.pitch;
app.yaw = e.yaw;
app.euler_frames += 1;
}
}
}
println!("Got {} Euler frames; last yaw = {:.2}", app.euler_frames, app.yaw);
}
Note
Frames are returned by value and implement Copy. They can be stored,
sent over a channel or moved to another thread without borrowing from the
parser.
Monitoring link health
The parser counts dropped frames by cause. Reading the counters periodically gives an indication of link quality:
let s = parser.stats;
println!("ok={} framing={} crc={} cobs={} overflow={}",
s.frames_ok, s.framing_errors, s.crc_errors,
s.cobs_errors, s.overflows);
parser.stats_reset(); /* zero the counters for the next window */
Note
parser.stats cannot be read while a feed_buf iterator exists, because
the iterator holds a mutable borrow of the parser. Read it after the loop
that consumes the iterator.
An increase in crc_errors or cobs_errors typically indicates line noise or
a baud rate mismatch. An increase in overflows indicates more than
OSCP_FRAME_MAX_LEN bytes received between delimiters, typically caused by a
lost delimiter byte.