libs/kaptan-runtime: add single-owner IMU service
This commit is contained in:
1 parent
998cf5ecb7
commit
5e6cf7a99e
1 file changed
+137
@@ -0,0 +1,137 @@
|
|||||||
|
//! Single-owner MPU acquisition service.
|
||||||
|
|
||||||
|
use kaptan_drivers::mpu6000::{Error as Mpu6000Error, Mpu6000, Mpu6000Event};
|
||||||
|
use kaptan_sensor::ImuSnapshot;
|
||||||
|
use kaptan_spi::{SpiCompletion, SpiDevice};
|
||||||
|
use kaptan_time::MonotonicClock;
|
||||||
|
|
||||||
|
/// One observable result from the IMU worker.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ImuEvent<BusError> {
|
||||||
|
/// No accepted sample or terminal fault was produced.
|
||||||
|
Idle,
|
||||||
|
/// MPU configuration completed and the DRDY route may be enabled.
|
||||||
|
Initialized,
|
||||||
|
/// A coherent IMU observation is ready for telemetry or control.
|
||||||
|
Sample(ImuSnapshot),
|
||||||
|
/// The selected sensor path entered a terminal fault state.
|
||||||
|
Fault(Mpu6000Error<BusError>),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Owns MPU state, its clock, and sample acceptance.
|
||||||
|
///
|
||||||
|
/// Exactly one worker invokes this service. Hardware ISRs only report deferred
|
||||||
|
/// DRDY, timer, and SPI-DMA terminal events to that worker.
|
||||||
|
pub struct ImuService<Clock, Bus> {
|
||||||
|
clock: Clock,
|
||||||
|
imu: Mpu6000<Bus>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Clock, Bus> ImuService<Clock, Bus> {
|
||||||
|
/// Creates an inactive acquisition service.
|
||||||
|
pub const fn new(clock: Clock, imu: Mpu6000<Bus>) -> Self {
|
||||||
|
Self { clock, imu }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Clock, Bus> ImuService<Clock, Bus>
|
||||||
|
where
|
||||||
|
Clock: MonotonicClock,
|
||||||
|
Bus: SpiDevice,
|
||||||
|
{
|
||||||
|
/// Submits the asynchronous sensor reset transaction.
|
||||||
|
pub fn begin(&mut self) -> Result<(), Mpu6000Error<Bus::StartError>> {
|
||||||
|
self.imu.begin_initialization()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advances the reset-settling state on a board periodic event.
|
||||||
|
pub fn on_periodic_tick(&mut self) -> ImuEvent<Bus::StartError> {
|
||||||
|
let _ = self.clock.now();
|
||||||
|
match self.imu.advance_initialization() {
|
||||||
|
Ok(()) => ImuEvent::Idle,
|
||||||
|
Err(error) => ImuEvent::Fault(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles one coalesced data-ready event.
|
||||||
|
pub fn on_data_ready(&mut self) -> ImuEvent<Bus::StartError> {
|
||||||
|
if !self.imu.is_ready() {
|
||||||
|
return ImuEvent::Idle;
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.imu.request_sample(self.clock.now()) {
|
||||||
|
Ok(()) | Err(Mpu6000Error::NotReady) => ImuEvent::Idle,
|
||||||
|
Err(error) => ImuEvent::Fault(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Advances the MPU transaction state from a DMA terminal result.
|
||||||
|
pub fn on_transfer_complete(&mut self, completion: SpiCompletion) -> ImuEvent<Bus::StartError> {
|
||||||
|
match self.imu.on_transfer_complete(completion) {
|
||||||
|
Mpu6000Event::NotPending | Mpu6000Event::Initializing => ImuEvent::Idle,
|
||||||
|
Mpu6000Event::Initialized => ImuEvent::Initialized,
|
||||||
|
Mpu6000Event::Sample(sample) => ImuEvent::Sample(sample),
|
||||||
|
Mpu6000Event::Fault(error) => ImuEvent::Fault(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{ImuEvent, ImuService};
|
||||||
|
use kaptan_drivers::mpu6000::{Mpu6000, Mpu6000Config};
|
||||||
|
use kaptan_spi::{SpiCompletion, SpiDevice};
|
||||||
|
use kaptan_time::{MonotonicClock, Timestamp};
|
||||||
|
|
||||||
|
struct Clock;
|
||||||
|
|
||||||
|
impl MonotonicClock for Clock {
|
||||||
|
fn now(&mut self) -> Timestamp {
|
||||||
|
Timestamp::from_microseconds(42)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Bus {
|
||||||
|
response: [u8; 15],
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SpiDevice for Bus {
|
||||||
|
type StartError = ();
|
||||||
|
|
||||||
|
fn start_transfer(&mut self, _: &[u8], receive: &mut [u8]) -> Result<(), Self::StartError> {
|
||||||
|
receive.copy_from_slice(&self.response[..receive.len()]);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete_transfer(&mut self, _: SpiCompletion) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accepts_a_sample_only_after_initialization() {
|
||||||
|
let mut response = [0; 15];
|
||||||
|
response[1] = 0x68;
|
||||||
|
let mut service = ImuService::new(
|
||||||
|
Clock,
|
||||||
|
Mpu6000::new(Bus { response }, Mpu6000Config::default()),
|
||||||
|
);
|
||||||
|
|
||||||
|
service.begin().unwrap();
|
||||||
|
assert!(matches!(service.on_data_ready(), ImuEvent::Idle));
|
||||||
|
assert!(matches!(
|
||||||
|
service.on_transfer_complete(SpiCompletion::Complete),
|
||||||
|
ImuEvent::Idle
|
||||||
|
));
|
||||||
|
assert!(matches!(service.on_periodic_tick(), ImuEvent::Idle));
|
||||||
|
for _ in 0..9 {
|
||||||
|
assert!(matches!(
|
||||||
|
service.on_transfer_complete(SpiCompletion::Complete),
|
||||||
|
ImuEvent::Idle
|
||||||
|
));
|
||||||
|
}
|
||||||
|
assert!(matches!(
|
||||||
|
service.on_transfer_complete(SpiCompletion::Complete),
|
||||||
|
ImuEvent::Initialized
|
||||||
|
));
|
||||||
|
assert!(matches!(service.on_data_ready(), ImuEvent::Idle));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user