use std::num::{NonZeroU32, NonZeroU64};
use crate::chip::fram::Fm25v02;
use crate::chip::icm20608::Icm20608;
use crate::chip::lis3mdl::Lis3mdl;
use crate::chip::memory_card::MemoryCard;
use crate::chip::mpu9250::Mpu9250;
use crate::chip::ms5611::Ms5611;
use crate::chip::nmea::{self, Position};
use crate::chip::{Placed, uart};
use crate::device::Level;
use crate::edge::{Condition, Coupled, Edge, Field, Pulse, Sensed, flag};
use crate::emulator::InterruptLine;
use crate::machine::armv7m;
use crate::machine::{Board, Crystal, HostWire, Supply, Target};
use crate::snapshot::store;
use crate::soc::Error;
use crate::soc::stm32f427::Stm32f427;
use crate::soc::stm32f427::peripherals::adc::Channel as AnalogChannel;
use crate::soc::stm32f427::peripherals::gpio::Pin;
use crate::soc::stm32f427::peripherals::spi::Select;
use crate::soc::stm32f427::peripherals::tim::Channel;
use crate::time::VirtualNanos;
pub const NAME: &str = "pixracer";
pub const MCU: &str = "stm32f427";
const CLOCK_HZ: NonZeroU64 = NonZeroU64::new(168_000_000).expect("a running clock");
const REFCLK_DIVIDER: NonZeroU32 = NonZeroU32::new(8).expect("a divider divides");
const PRIORITY_BITS: u8 = 4;
const VARIANT: armv7m::Variant = armv7m::Variant::M4;
const FPU: armv7m::Fpu = armv7m::Fpu::FpV4Sp;
const MPU_REGIONS: armv7m::MpuRegions = armv7m::MpuRegions::Eight;
const BOOT: &str = "flash";
const INSTRUCTIONS_PER_SECOND: NonZeroU64 = NonZeroU64::new(250_000_000).expect("a running core");
const FRAM_ON: usize = 1;
const FRAM_CS: Select = low("GPIOD", 10);
const BARO_CS: Select = low("GPIOD", 7);
const IMU_ON: usize = 0;
const IMU_CS: Select = low("GPIOC", 2);
const SECOND_IMU_CS: Select = low("GPIOC", 15);
const MAG_CS: Select = low("GPIOE", 15);
const fn low(port: &'static str, pin: u8) -> Select {
Select {
port,
pin: Pin::new(pin).expect("a port has sixteen pins"),
active: Level::Low,
}
}
const CARD_BLOCKS: usize = 8 * 1024 * 1024 * 1024 / crate::chip::sd::BLOCK;
const TELEMETRY: usize = 1;
const GPS: usize = 6;
const TELEMETRY_WIRE: HostWire = HostWire {
name: "serial1",
port: 5760,
};
const ICM20608_RATE_NOISE: [&str; 3] = [
"icm20608.rate.noise.x",
"icm20608.rate.noise.y",
"icm20608.rate.noise.z",
];
const ICM20608_ACCELERATION_NOISE: [&str; 3] = [
"icm20608.acceleration.noise.x",
"icm20608.acceleration.noise.y",
"icm20608.acceleration.noise.z",
];
const MPU9250_RATE_NOISE: [&str; 3] = [
"mpu9250.rate.noise.x",
"mpu9250.rate.noise.y",
"mpu9250.rate.noise.z",
];
const MPU9250_ACCELERATION_NOISE: [&str; 3] = [
"mpu9250.acceleration.noise.x",
"mpu9250.acceleration.noise.y",
"mpu9250.acceleration.noise.z",
];
const MPU9250_FIELD_NOISE: [&str; 3] = [
"mpu9250.field.noise.x",
"mpu9250.field.noise.y",
"mpu9250.field.noise.z",
];
const LIS3MDL_FIELD_NOISE: [&str; 3] = [
"lis3mdl.field.noise.x",
"lis3mdl.field.noise.y",
"lis3mdl.field.noise.z",
];
const MS5611_PRESSURE_NOISE: [&str; 1] = ["ms5611.pressure.noise"];
const GPS_NOISE_ERRORS: &str = "gps.noise_errors";
const GPS_FRAMING_ERRORS: &str = "gps.framing_errors";
const FM25V02_PEER: usize = 0;
const MPU9250_PEER: usize = 0;
const ICM20608_PEER: usize = 1;
const LIS3MDL_PEER: usize = 2;
const MS5611_PEER: usize = 1;
const MAIN_TIMER: usize = 0;
const MAIN_OUTPUTS: [(&str, Channel); 4] = [
("PWM1", channel(4)),
("PWM2", channel(3)),
("PWM3", channel(2)),
("PWM4", channel(1)),
];
const AUXILIARY_TIMER: usize = 2;
const AUXILIARY_OUTPUTS: [(&str, Channel); 2] = [("PWM5", channel(2)), ("PWM6", channel(3))];
const SPEED_CONTROLLER: Curve = Curve {
zero: VirtualNanos(1_000_000),
full: VirtualNanos(2_000_000),
};
const BATTERY_CONVERTER: usize = 0;
const BATTERY_VOLTAGE: AnalogChannel = analog(2);
const BATTERY_CURRENT: AnalogChannel = analog(3);
const PACK_PER_SENSED: u32 = 101;
const SENSED_PER_PACK: u32 = 10;
const MILLIAMPS_PER_MILLIVOLT: u32 = 17;
const FIVE_VOLT_SENSE: AnalogChannel = analog(4);
const FIVE_VOLT_RAIL: Supply = Supply(5000);
const fn channel(number: u8) -> Channel {
match Channel::new(number) {
Some(channel) => channel,
None => panic!("a timer has four channels"),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Curve {
pub zero: VirtualNanos,
pub full: VirtualNanos,
}
pub const FULL: u32 = 1000;
impl Curve {
#[must_use]
pub fn command(self, pulse: Pulse) -> u32 {
let span = self.full.0.saturating_sub(self.zero.0).max(1);
let above = pulse.width.0.saturating_sub(self.zero.0).min(span);
u32::try_from(above * u64::from(FULL) / span).expect("at most the whole")
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Actuator {
pub name: &'static str,
pub pulse: Option<Pulse>,
pub command: Option<u32>,
}
impl Actuator {
#[must_use]
pub fn on(name: &'static str, pulse: Option<Pulse>, curve: Curve) -> Self {
Self {
name,
pulse,
command: pulse.map(|pulse| curve.command(pulse)),
}
}
const fn idle(name: &'static str) -> Self {
Self {
name,
pulse: None,
command: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Outputs(pub [Actuator; 6]);
const IDLE: Outputs = Outputs([
Actuator::idle(MAIN_OUTPUTS[0].0),
Actuator::idle(MAIN_OUTPUTS[1].0),
Actuator::idle(MAIN_OUTPUTS[2].0),
Actuator::idle(MAIN_OUTPUTS[3].0),
Actuator::idle(AUXILIARY_OUTPUTS[0].0),
Actuator::idle(AUXILIARY_OUTPUTS[1].0),
]);
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct Signals {
pub angular_rate: [i32; 3],
pub specific_force: [i32; 3],
pub magnetic_field: [i32; 3],
pub pressure: u32,
pub temperature: i32,
pub battery_voltage: u32,
pub battery_current: i32,
pub position: Position,
pub velocity: [i32; 3],
}
pub const AT_REST: Signals = Signals {
angular_rate: [0; 3],
specific_force: [0, 0, -1_000_000],
magnetic_field: [22_000, 0, 42_000],
pressure: 101_325,
temperature: 2_000,
battery_voltage: 16_800,
battery_current: 0,
position: Position {
latitude: 398_283_000,
longitude: -985_795_000,
altitude: 500_000,
},
velocity: [0; 3],
};
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize,
)]
#[expect(
clippy::struct_excessive_bools,
reason = "each is a different chip's silence, one byte on the wire, and no two make a state"
)]
pub struct Conditions {
pub icm20608_silent: bool,
pub icm20608_rate: Condition<3>,
pub icm20608_acceleration: Condition<3>,
pub mpu9250_silent: bool,
pub mpu9250_rate: Condition<3>,
pub mpu9250_acceleration: Condition<3>,
pub mpu9250_field: Condition<3>,
pub lis3mdl_silent: bool,
pub lis3mdl_field: Condition<3>,
pub ms5611_silent: bool,
pub ms5611_pressure: Condition<1>,
pub fm25v02_silent: bool,
pub gps_silent: bool,
pub gps_noise_errors: u32,
pub gps_framing_errors: u32,
pub card_silent: bool,
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
struct Handed {
icm20608_rate: Sensed<i32, 3>,
icm20608_acceleration: Sensed<i32, 3>,
mpu9250_rate: Sensed<i32, 3>,
mpu9250_acceleration: Sensed<i32, 3>,
mpu9250_field: Sensed<i32, 3>,
lis3mdl_field: Sensed<i32, 3>,
ms5611_pressure: Sensed<u32, 1>,
}
const fn analog(number: u8) -> AnalogChannel {
match AnalogChannel::new(number) {
Some(channel) => channel,
None => panic!("a converter has sixteen pin channels"),
}
}
#[derive(Debug)]
pub struct Pixracer {
pub mcu: Stm32f427,
pub hse: Crystal,
pub lse: Crystal,
pub rail: Supply,
conditions: Conditions,
handed: Handed,
sensed: Signals,
}
impl Pixracer {
pub fn new() -> Result<Self, Error> {
let mut board = Self {
mcu: Stm32f427::new()?,
hse: Crystal(24_000_000),
lse: Crystal(32_768),
rail: Supply(3300),
conditions: Conditions::default(),
handed: Handed::default(),
sensed: AT_REST,
};
board.fit();
board.sense(&AT_REST);
Ok(board)
}
fn fit(&mut self) {
self.mcu.rcc.attach_crystals(self.hse.0, self.lse.0);
let rail = self.rail.0;
self.mcu.set_supply(rail);
for converter in &mut self.mcu.adc {
converter.set_reference(rail);
}
self.mcu.adc[BATTERY_CONVERTER].set_input(FIVE_VOLT_SENSE, FIVE_VOLT_RAIL.0 / 2);
self.mcu.usart[GPS].attach(nmea::Receiver::new());
self.mcu.spi[FRAM_ON].attach_selected(Fm25v02::new(), FRAM_CS);
self.mcu.spi[FRAM_ON].attach_selected(Ms5611::new(), BARO_CS);
self.mcu.spi[IMU_ON].attach_selected(Mpu9250::new(), IMU_CS);
self.mcu.spi[IMU_ON].attach_selected(Icm20608::new(), SECOND_IMU_CS);
self.mcu.spi[IMU_ON].attach_selected(Lis3mdl::new(), MAG_CS);
self.mcu.sdio.attach(MemoryCard::formatted(CARD_BLOCKS));
}
}
store!(Pixracer {
values: [conditions, handed, sensed],
parts: [mcu],
built: [hse, lse, rail],
});
impl Board for Pixracer {
fn name(&self) -> &'static str {
NAME
}
fn target_names(&self) -> Vec<&'static str> {
vec![MCU]
}
fn mcu(&mut self, target: usize) -> Option<&mut dyn crate::mcu::Mcu> {
(target == 0).then_some(&mut self.mcu)
}
fn targets(&mut self) -> Vec<Target<'_>> {
let core = armv7m::Core {
variant: VARIANT,
clock_hz: CLOCK_HZ,
interrupt_lines: NonZeroU32::new(u32::from(
crate::mcu::Mcu::cpu(&self.mcu).interrupt_lines,
))
.expect("an MCU with interrupts"),
priority_bits: PRIORITY_BITS,
refclk_divider: REFCLK_DIVIDER,
instructions_per_second: INSTRUCTIONS_PER_SECOND,
boot: BOOT,
}
.spec_with_parts(
&crate::cpu::armv7m::windows(crate::mcu::Mcu::bus(&self.mcu)),
FPU,
MPU_REGIONS,
);
vec![Target {
name: MCU,
mcu: &mut self.mcu,
core,
interrupt_line: InterruptLine::new,
}]
}
fn edge(&mut self) -> Option<&mut dyn Coupled> {
Some(self)
}
fn hosts(&self) -> Vec<HostWire> {
vec![TELEMETRY_WIRE]
}
fn attach_host(&mut self, name: &str, peer: Box<dyn uart::Peer>) -> bool {
if name != TELEMETRY_WIRE.name {
return false;
}
self.mcu.usart[TELEMETRY].attach_boxed(peer);
true
}
fn host_mut(&mut self, name: &str) -> Option<&mut dyn uart::Peer> {
if name == TELEMETRY_WIRE.name {
self.mcu.usart[TELEMETRY].peer_mut()
} else {
None
}
}
}
impl Edge for Pixracer {
type Signals = Signals;
type Outputs = Outputs;
type Conditions = Conditions;
const OUTPUT_FIELDS: &'static [Field] = &[
Field::new("PWM1.width", "ns", 8, false),
Field::new("PWM1.period", "ns", 8, false),
Field::new("PWM1.command", "1e-3", 4, false),
Field::new("PWM2.width", "ns", 8, false),
Field::new("PWM2.period", "ns", 8, false),
Field::new("PWM2.command", "1e-3", 4, false),
Field::new("PWM3.width", "ns", 8, false),
Field::new("PWM3.period", "ns", 8, false),
Field::new("PWM3.command", "1e-3", 4, false),
Field::new("PWM4.width", "ns", 8, false),
Field::new("PWM4.period", "ns", 8, false),
Field::new("PWM4.command", "1e-3", 4, false),
Field::new("PWM5.width", "ns", 8, false),
Field::new("PWM5.period", "ns", 8, false),
Field::new("PWM5.command", "1e-3", 4, false),
Field::new("PWM6.width", "ns", 8, false),
Field::new("PWM6.period", "ns", 8, false),
Field::new("PWM6.command", "1e-3", 4, false),
];
const FIELDS: &'static [Field] = &[
Field::new("angular_rate.x", "mdeg/s", 4, true),
Field::new("angular_rate.y", "mdeg/s", 4, true),
Field::new("angular_rate.z", "mdeg/s", 4, true),
Field::new("specific_force.x", "1e-6 g", 4, true),
Field::new("specific_force.y", "1e-6 g", 4, true),
Field::new("specific_force.z", "1e-6 g", 4, true),
Field::new("magnetic_field.x", "nT", 4, true),
Field::new("magnetic_field.y", "nT", 4, true),
Field::new("magnetic_field.z", "nT", 4, true),
Field::new("pressure", "Pa", 4, false),
Field::new("temperature", "0.01 degC", 4, true),
Field::new("battery_voltage", "mV", 4, false),
Field::new("battery_current", "mA", 4, true),
Field::new("position.latitude", "1e-7 deg", 4, true),
Field::new("position.longitude", "1e-7 deg", 4, true),
Field::new("position.altitude", "mm", 4, true),
Field::new("velocity.north", "mm/s", 4, true),
Field::new("velocity.east", "mm/s", 4, true),
Field::new("velocity.down", "mm/s", 4, true),
];
const CONDITIONS: &'static [Field] = &[
Field::new("icm20608.silent", "flag", 1, false),
Field::new("icm20608.rate.stuck", "flag", 1, false),
Field::new("icm20608.rate.bias.x", "mdeg/s", 4, true),
Field::new("icm20608.rate.bias.y", "mdeg/s", 4, true),
Field::new("icm20608.rate.bias.z", "mdeg/s", 4, true),
Field::new(ICM20608_RATE_NOISE[0], "mdeg/s", 4, false),
Field::new(ICM20608_RATE_NOISE[1], "mdeg/s", 4, false),
Field::new(ICM20608_RATE_NOISE[2], "mdeg/s", 4, false),
Field::new("icm20608.acceleration.stuck", "flag", 1, false),
Field::new("icm20608.acceleration.bias.x", "1e-6 g", 4, true),
Field::new("icm20608.acceleration.bias.y", "1e-6 g", 4, true),
Field::new("icm20608.acceleration.bias.z", "1e-6 g", 4, true),
Field::new(ICM20608_ACCELERATION_NOISE[0], "1e-6 g", 4, false),
Field::new(ICM20608_ACCELERATION_NOISE[1], "1e-6 g", 4, false),
Field::new(ICM20608_ACCELERATION_NOISE[2], "1e-6 g", 4, false),
Field::new("mpu9250.silent", "flag", 1, false),
Field::new("mpu9250.rate.stuck", "flag", 1, false),
Field::new("mpu9250.rate.bias.x", "mdeg/s", 4, true),
Field::new("mpu9250.rate.bias.y", "mdeg/s", 4, true),
Field::new("mpu9250.rate.bias.z", "mdeg/s", 4, true),
Field::new(MPU9250_RATE_NOISE[0], "mdeg/s", 4, false),
Field::new(MPU9250_RATE_NOISE[1], "mdeg/s", 4, false),
Field::new(MPU9250_RATE_NOISE[2], "mdeg/s", 4, false),
Field::new("mpu9250.acceleration.stuck", "flag", 1, false),
Field::new("mpu9250.acceleration.bias.x", "1e-6 g", 4, true),
Field::new("mpu9250.acceleration.bias.y", "1e-6 g", 4, true),
Field::new("mpu9250.acceleration.bias.z", "1e-6 g", 4, true),
Field::new(MPU9250_ACCELERATION_NOISE[0], "1e-6 g", 4, false),
Field::new(MPU9250_ACCELERATION_NOISE[1], "1e-6 g", 4, false),
Field::new(MPU9250_ACCELERATION_NOISE[2], "1e-6 g", 4, false),
Field::new("mpu9250.field.stuck", "flag", 1, false),
Field::new("mpu9250.field.bias.x", "nT", 4, true),
Field::new("mpu9250.field.bias.y", "nT", 4, true),
Field::new("mpu9250.field.bias.z", "nT", 4, true),
Field::new(MPU9250_FIELD_NOISE[0], "nT", 4, false),
Field::new(MPU9250_FIELD_NOISE[1], "nT", 4, false),
Field::new(MPU9250_FIELD_NOISE[2], "nT", 4, false),
Field::new("lis3mdl.silent", "flag", 1, false),
Field::new("lis3mdl.field.stuck", "flag", 1, false),
Field::new("lis3mdl.field.bias.x", "nT", 4, true),
Field::new("lis3mdl.field.bias.y", "nT", 4, true),
Field::new("lis3mdl.field.bias.z", "nT", 4, true),
Field::new(LIS3MDL_FIELD_NOISE[0], "nT", 4, false),
Field::new(LIS3MDL_FIELD_NOISE[1], "nT", 4, false),
Field::new(LIS3MDL_FIELD_NOISE[2], "nT", 4, false),
Field::new("ms5611.silent", "flag", 1, false),
Field::new("ms5611.pressure.stuck", "flag", 1, false),
Field::new("ms5611.pressure.bias", "Pa", 4, true),
Field::new(MS5611_PRESSURE_NOISE[0], "Pa", 4, false),
Field::new("fm25v02.silent", "flag", 1, false),
Field::new("gps.silent", "flag", 1, false),
Field::new(GPS_NOISE_ERRORS, "1e-6", 4, false),
Field::new(GPS_FRAMING_ERRORS, "1e-6", 4, false),
Field::new("card.silent", "flag", 1, false),
];
const REST: Signals = AT_REST;
const CHIPS: &'static [&'static str] = &[
"icm20608", "mpu9250", "lis3mdl", "ms5611", "fm25v02", "gps", "card",
];
fn place(&mut self, chip: usize) -> Option<&mut dyn Placed> {
match *Self::CHIPS.get(chip)? {
"icm20608" => self.mcu.spi[IMU_ON].place_mut(ICM20608_PEER),
"mpu9250" => self.mcu.spi[IMU_ON].place_mut(MPU9250_PEER),
"lis3mdl" => self.mcu.spi[IMU_ON].place_mut(LIS3MDL_PEER),
"ms5611" => self.mcu.spi[FRAM_ON].place_mut(MS5611_PEER),
"fm25v02" => self.mcu.spi[FRAM_ON].place_mut(FM25V02_PEER),
"gps" => self.mcu.usart[GPS].place_mut(),
"card" => self.mcu.sdio.place_mut(),
_ => None,
}
}
fn sense(&mut self, signals: &Signals) {
self.sensed = *signals;
let (conditions, handed) = (&self.conditions, &mut self.handed);
let imus = &mut self.mcu.spi[IMU_ON];
let unit: &mut Mpu9250 = imus.chip_mut(MPU9250_PEER).expect("fit hangs it");
let [x, y, z] = handed
.mpu9250_rate
.hand(signals.angular_rate, &conditions.mpu9250_rate);
unit.set_rate(x, y, z);
let [x, y, z] = handed
.mpu9250_acceleration
.hand(signals.specific_force, &conditions.mpu9250_acceleration);
unit.set_acceleration(x, y, z);
unit.set_temperature(signals.temperature);
let [x, y, z] = handed
.mpu9250_field
.hand(signals.magnetic_field, &conditions.mpu9250_field);
unit.set_field(x, y, z);
let unit: &mut Icm20608 = imus.chip_mut(ICM20608_PEER).expect("fit hangs it");
let [x, y, z] = handed
.icm20608_rate
.hand(signals.angular_rate, &conditions.icm20608_rate);
unit.set_rate(x, y, z);
let [x, y, z] = handed
.icm20608_acceleration
.hand(signals.specific_force, &conditions.icm20608_acceleration);
unit.set_acceleration(x, y, z);
unit.set_temperature(signals.temperature);
let compass: &mut Lis3mdl = imus.chip_mut(LIS3MDL_PEER).expect("fit hangs it");
let [x, y, z] = handed
.lis3mdl_field
.hand(signals.magnetic_field, &conditions.lis3mdl_field);
compass.set_field(x, y, z);
compass.set_temperature(signals.temperature);
let barometer: &mut Ms5611 = self.mcu.spi[FRAM_ON]
.chip_mut(MS5611_PEER)
.expect("fit hangs it");
let [pressure] = handed
.ms5611_pressure
.hand([signals.pressure], &conditions.ms5611_pressure);
barometer.set_pressure(pressure);
barometer.set_temperature(signals.temperature);
let receiver: &mut nmea::Receiver = self.mcu.usart[GPS].chip_mut().expect("fit hangs it");
receiver.set_position(signals.position);
receiver.set_velocity(signals.velocity);
let converter = &mut self.mcu.adc[BATTERY_CONVERTER];
converter.set_input(
BATTERY_VOLTAGE,
signals.battery_voltage * SENSED_PER_PACK / PACK_PER_SENSED,
);
converter.set_input(
BATTERY_CURRENT,
u32::try_from(signals.battery_current).unwrap_or(0) / MILLIAMPS_PER_MILLIVOLT,
);
}
fn condition(&mut self, conditions: &Conditions) {
self.conditions = *conditions;
let imus = &mut self.mcu.spi[IMU_ON];
let mut fitted = imus.silence(ICM20608_PEER, conditions.icm20608_silent)
& imus.silence(MPU9250_PEER, conditions.mpu9250_silent)
& imus.silence(LIS3MDL_PEER, conditions.lis3mdl_silent);
let bus = &mut self.mcu.spi[FRAM_ON];
fitted &= bus.silence(FM25V02_PEER, conditions.fm25v02_silent)
& bus.silence(MS5611_PEER, conditions.ms5611_silent);
fitted &= self.mcu.usart[GPS].silence(conditions.gps_silent)
& self.mcu.sdio.silence(conditions.card_silent);
assert!(fitted, "fit hangs every chip a condition names");
let errors = self.mcu.usart[GPS].errors_mut();
errors.noise = uart::Rate {
name: GPS_NOISE_ERRORS,
per_million: conditions.gps_noise_errors,
};
errors.framing = uart::Rate {
name: GPS_FRAMING_ERRORS,
per_million: conditions.gps_framing_errors,
};
let sensed = self.sensed;
self.sense(&sensed);
}
fn draw(&mut self, seed: u64, index: u64) {
self.mcu.usart[GPS].errors_mut().seed = seed;
let (conditions, handed) = (&self.conditions, &mut self.handed);
let moved = [
handed
.icm20608_rate
.draw(&conditions.icm20608_rate, &ICM20608_RATE_NOISE, seed, index),
handed.icm20608_acceleration.draw(
&conditions.icm20608_acceleration,
&ICM20608_ACCELERATION_NOISE,
seed,
index,
),
handed
.mpu9250_rate
.draw(&conditions.mpu9250_rate, &MPU9250_RATE_NOISE, seed, index),
handed.mpu9250_acceleration.draw(
&conditions.mpu9250_acceleration,
&MPU9250_ACCELERATION_NOISE,
seed,
index,
),
handed
.mpu9250_field
.draw(&conditions.mpu9250_field, &MPU9250_FIELD_NOISE, seed, index),
handed
.lis3mdl_field
.draw(&conditions.lis3mdl_field, &LIS3MDL_FIELD_NOISE, seed, index),
handed.ms5611_pressure.draw(
&conditions.ms5611_pressure,
&MS5611_PRESSURE_NOISE,
seed,
index,
),
];
if moved.contains(&true) {
let sensed = self.sensed;
self.sense(&sensed);
}
}
fn drive(&mut self) -> Outputs {
let (main, registers) = self.mcu.advanced_timer(MAIN_TIMER);
let [one, two, three, four] = MAIN_OUTPUTS.map(|(name, channel)| {
Actuator::on(name, main.pulse(channel, registers), SPEED_CONTROLLER)
});
let (auxiliary, registers) = self.mcu.timer(AUXILIARY_TIMER);
let [five, six] = AUXILIARY_OUTPUTS.map(|(name, channel)| {
Actuator::on(name, auxiliary.pulse(channel, registers), SPEED_CONTROLLER)
});
Outputs([one, two, three, four, five, six])
}
}
pub fn boxed() -> Result<Box<dyn Board>, Error> {
Ok(Box::new(Pixracer::new()?))
}
impl crate::link::Wire for Outputs {
fn encode(&self, out: &mut Vec<u8>) {
for actuator in &self.0 {
let (width, period) = actuator
.pulse
.map_or((0, 0), |pulse| (pulse.width.0, pulse.period.0));
width.encode(out);
period.encode(out);
actuator.command.unwrap_or(0).encode(out);
}
}
fn decode(bytes: &mut &[u8]) -> Result<Self, crate::link::WireError> {
let mut outputs = IDLE;
for actuator in &mut outputs.0 {
let width: VirtualNanos = crate::link::Wire::decode(bytes)?;
let period: VirtualNanos = crate::link::Wire::decode(bytes)?;
let command: u32 = crate::link::Wire::decode(bytes)?;
actuator.pulse = (period.0 != 0).then_some(Pulse { width, period });
actuator.command = actuator.pulse.map(|_| command);
}
Ok(outputs)
}
}
impl crate::link::Wire for Conditions {
fn encode(&self, out: &mut Vec<u8>) {
self.icm20608_silent.encode(out);
self.icm20608_rate.encode(out);
self.icm20608_acceleration.encode(out);
self.mpu9250_silent.encode(out);
self.mpu9250_rate.encode(out);
self.mpu9250_acceleration.encode(out);
self.mpu9250_field.encode(out);
self.lis3mdl_silent.encode(out);
self.lis3mdl_field.encode(out);
self.ms5611_silent.encode(out);
self.ms5611_pressure.encode(out);
self.fm25v02_silent.encode(out);
self.gps_silent.encode(out);
self.gps_noise_errors.encode(out);
self.gps_framing_errors.encode(out);
self.card_silent.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, crate::link::WireError> {
Ok(Self {
icm20608_silent: flag(bytes)?,
icm20608_rate: crate::link::Wire::decode(bytes)?,
icm20608_acceleration: crate::link::Wire::decode(bytes)?,
mpu9250_silent: flag(bytes)?,
mpu9250_rate: crate::link::Wire::decode(bytes)?,
mpu9250_acceleration: crate::link::Wire::decode(bytes)?,
mpu9250_field: crate::link::Wire::decode(bytes)?,
lis3mdl_silent: flag(bytes)?,
lis3mdl_field: crate::link::Wire::decode(bytes)?,
ms5611_silent: flag(bytes)?,
ms5611_pressure: crate::link::Wire::decode(bytes)?,
fm25v02_silent: flag(bytes)?,
gps_silent: flag(bytes)?,
gps_noise_errors: crate::link::Wire::decode(bytes)?,
gps_framing_errors: crate::link::Wire::decode(bytes)?,
card_silent: flag(bytes)?,
})
}
}
impl crate::link::Wire for Signals {
fn encode(&self, out: &mut Vec<u8>) {
self.angular_rate.encode(out);
self.specific_force.encode(out);
self.magnetic_field.encode(out);
self.pressure.encode(out);
self.temperature.encode(out);
self.battery_voltage.encode(out);
self.battery_current.encode(out);
self.position.encode(out);
self.velocity.encode(out);
}
fn decode(bytes: &mut &[u8]) -> Result<Self, crate::link::WireError> {
Ok(Self {
angular_rate: crate::link::Wire::decode(bytes)?,
specific_force: crate::link::Wire::decode(bytes)?,
magnetic_field: crate::link::Wire::decode(bytes)?,
pressure: crate::link::Wire::decode(bytes)?,
temperature: crate::link::Wire::decode(bytes)?,
battery_voltage: crate::link::Wire::decode(bytes)?,
battery_current: crate::link::Wire::decode(bytes)?,
position: crate::link::Wire::decode(bytes)?,
velocity: crate::link::Wire::decode(bytes)?,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chip::ak8963::{ADDRESS, Ak8963};
use crate::chip::i2c;
use crate::chip::spi::Peer;
use crate::device::DeviceContext;
use crate::edge::normal;
use crate::link::{
Advance, Held, LinkError, Set, Wire, attach_engine_ports, deliver, sent, values_of,
};
use crate::mcu::Mcu;
use crate::soc::stm32f427::generated::rcc::{Register as RccRegister, apb2enr};
use crate::soc::stm32f427::generated::tim1::{Register as Tim1Register, cr1, egr};
use crate::soc::stm32f427::generated::{RCC, TIM1};
use crate::soc::stm32f427::peripherals::rcc::Apb;
use crate::soc::{Layout, Width};
const MICROSECOND: u64 = 1_000;
const MILLISECOND: u64 = 1_000_000;
const READ: u8 = 0x80;
const PWR_MGMT_1: u8 = 0x6B;
const CLOCK_ON: u8 = 0x01;
const ACCEL_XOUT_H: u8 = 0x3B;
const COUNTS_PER_GRAVITY: i16 = 16_384;
const BURST: u8 = 0xC0;
const CTRL_REG1: u8 = 0x20;
const CTRL_REG3: u8 = 0x22;
const CONTINUOUS: u8 = 0x00;
const ODR_80_ULTRAHIGH: u8 = 0x7C;
const OUT_X_L: u8 = 0x28;
const COUNTS_PER_GAUSS: i16 = 6842;
const TEMP_EN: u8 = 0x80;
const GAUSS_NANOTESLA: i32 = 100_000;
const ONE_GRAVITY: i32 = 1_000_000;
const CNTL1: u8 = 0x0A;
const CONTINUOUS_2_SIXTEEN_BITS: u8 = 0x16;
const HXL: u8 = 0x03;
const RESET: u8 = 0x1E;
const CONVERT_D1: u8 = 0x48;
const CONVERT_D2: u8 = 0x58;
const ADC_READ: u8 = 0x00;
fn read(board: &mut Pixracer, at: u64, base: u64, offset: u32) -> u64 {
board
.mcu
.read(VirtualNanos(at), base + u64::from(offset), Width::W32)
.unwrap()
}
fn write(board: &mut Pixracer, at: u64, base: u64, offset: u32, value: u64) {
board
.mcu
.write(
VirtualNanos(at),
base + u64::from(offset),
Width::W32,
value,
)
.unwrap();
}
fn cycle(board: &mut Pixracer, at: u64, interface: usize, nth: usize, bytes: &[u8]) -> Vec<u8> {
board
.mcu
.drive_serial(VirtualNanos(at), interface, |spi, _, context| {
let peer = spi.peer_mut(nth).expect("fitted");
peer.select(context);
let answers = bytes
.iter()
.map(|&byte| u8::try_from(peer.transfer(u16::from(byte), context)).unwrap())
.collect();
peer.deselect(context);
answers
})
}
fn alike(
board: &mut Pixracer,
at: u64,
(interface, nth): (usize, usize),
reference: &mut dyn Peer,
bytes: &[u8],
) -> (Vec<u8>, Vec<u8>) {
board
.mcu
.drive_serial(VirtualNanos(at), interface, |spi, _, context| {
let answers = |peer: &mut dyn Peer| -> Vec<u8> {
peer.select(context);
let answers = bytes
.iter()
.map(|&byte| u8::try_from(peer.transfer(u16::from(byte), context)).unwrap())
.collect();
peer.deselect(context);
answers
};
(
answers(spi.peer_mut(nth).expect("fitted")),
answers(reference),
)
})
}
fn die_write(die: &mut dyn i2c::Peer, register: u8, byte: u8, context: &DeviceContext<'_>) {
die.start(ADDRESS, false, context);
die.write(register, context);
die.write(byte, context);
die.stop(context);
}
fn die_read(
die: &mut dyn i2c::Peer,
register: u8,
count: usize,
context: &DeviceContext<'_>,
) -> Vec<u8> {
die.start(ADDRESS, false, context);
die.write(register, context);
die.start(ADDRESS, true, context);
let bytes = (0..count)
.map(|index| die.read(index + 1 == count, context))
.collect();
die.stop(context);
bytes
}
fn compass_die_fields(board: &mut Pixracer, reference: &mut Mpu9250) -> (Vec<u8>, Vec<u8>) {
board
.mcu
.drive_serial(VirtualNanos(10 * MILLISECOND), IMU_ON, |spi, _, context| {
for die in [
spi.chip_mut::<Mpu9250>(MPU9250_PEER)
.expect("fit hangs it")
.magnetometer(),
reference.magnetometer(),
] {
die_write(die, CNTL1, CONTINUOUS_2_SIXTEEN_BITS, context);
}
});
board
.mcu
.drive_serial(VirtualNanos(30 * MILLISECOND), IMU_ON, |spi, _, context| {
let on_board: &mut Ak8963 = spi
.chip_mut::<Mpu9250>(MPU9250_PEER)
.expect("fit hangs it")
.magnetometer();
(
die_read(on_board, HXL, 6, context),
die_read(reference.magnetometer(), HXL, 6, context),
)
})
}
fn words(bytes: &[u8], big_endian: bool) -> [i16; 3] {
let word = |pair: &[u8]| {
if big_endian {
i16::from_be_bytes([pair[0], pair[1]])
} else {
i16::from_le_bytes([pair[0], pair[1]])
}
};
[word(&bytes[0..2]), word(&bytes[2..4]), word(&bytes[4..6])]
}
#[test]
fn the_declared_fields_walk_the_encoded_signals_one_by_one() {
let signals = Signals {
angular_rate: [-1, -2, -3],
specific_force: [-4, -5, -6],
magnetic_field: [-7, -8, -9],
pressure: 3_000_000_000,
temperature: -11,
battery_voltage: 3_000_000_001,
battery_current: -13,
position: Position {
latitude: -14,
longitude: -15,
altitude: -16,
},
velocity: [-17, -18, -19],
};
let mut bytes = Vec::new();
signals.encode(&mut bytes);
assert_eq!(
values_of(<Pixracer as Edge>::FIELDS, &bytes).unwrap(),
[
-1,
-2,
-3,
-4,
-5,
-6,
-7,
-8,
-9,
3_000_000_000,
-11,
3_000_000_001,
-13,
-14,
-15,
-16,
-17,
-18,
-19
],
"every signed field negative and every unsigned one past an i32, so a wrong sign shows"
);
}
#[test]
fn the_declared_conditions_walk_the_encoded_conditions_one_by_one_and_rest_at_zero() {
let mut bytes = Vec::new();
Conditions::default().encode(&mut bytes);
assert!(
values_of(<Pixracer as Edge>::CONDITIONS, &bytes)
.unwrap()
.iter()
.all(|&value| value == 0),
"the board as built crosses as zeros"
);
let wrong = |stuck, bias: i32| Condition {
stuck,
bias: [bias, bias - 1, bias - 2],
noise: [1, 2, 3].map(|axis| bias.unsigned_abs() * 1_000 + axis),
};
let conditions = Conditions {
icm20608_silent: true,
icm20608_rate: wrong(true, -1),
icm20608_acceleration: wrong(false, -4),
mpu9250_silent: true,
mpu9250_rate: wrong(true, -7),
mpu9250_acceleration: wrong(false, -10),
mpu9250_field: wrong(true, -13),
lis3mdl_silent: false,
lis3mdl_field: wrong(true, -16),
ms5611_silent: true,
ms5611_pressure: Condition {
stuck: false,
bias: [-19],
noise: [19_001],
},
fm25v02_silent: true,
gps_silent: false,
gps_noise_errors: 3_000_000_000,
gps_framing_errors: 7,
card_silent: true,
};
let mut bytes = Vec::new();
conditions.encode(&mut bytes);
assert_eq!(
values_of(<Pixracer as Edge>::CONDITIONS, &bytes).unwrap(),
[
1,
1,
-1,
-2,
-3,
1_001,
1_002,
1_003,
0,
-4,
-5,
-6,
4_001,
4_002,
4_003,
1,
1,
-7,
-8,
-9,
7_001,
7_002,
7_003,
0,
-10,
-11,
-12,
10_001,
10_002,
10_003,
1,
-13,
-14,
-15,
13_001,
13_002,
13_003,
0,
1,
-16,
-17,
-18,
16_001,
16_002,
16_003,
1,
0,
-19,
19_001,
1,
0,
3_000_000_000,
7,
1
],
"every flag, bias, noise and rate where its field says, each bias negative and one rate \
past an i32 so a wrong sign shows"
);
}
#[test]
fn each_chip_senses_through_its_own_conditions() {
let biased = |bias: i32| Condition {
bias: [bias, 2 * bias, 3 * bias],
..Condition::default()
};
let plus = |[x, y, z]: [i32; 3], bias: i32| [x + bias, y + 2 * bias, z + 3 * bias];
let mut board = Pixracer::new().unwrap();
let signals = Signals {
angular_rate: [100_000, 0, -100_000],
specific_force: [0, 0, -2 * ONE_GRAVITY],
magnetic_field: [GAUSS_NANOTESLA, 0, GAUSS_NANOTESLA],
pressure: 95_000,
..AT_REST
};
board.sense(&signals);
board.condition(&Conditions {
icm20608_rate: biased(1_000),
icm20608_acceleration: biased(10_000),
mpu9250_rate: biased(2_000),
mpu9250_acceleration: Condition {
stuck: true,
..biased(20_000)
},
mpu9250_field: biased(3_000),
lis3mdl_field: biased(4_000),
ms5611_pressure: Condition {
bias: [-500],
..Condition::default()
},
..Conditions::default()
});
let mut mpu9250 = Mpu9250::new();
let [x, y, z] = plus(signals.angular_rate, 2_000);
mpu9250.set_rate(x, y, z);
let [x, y, z] = signals.specific_force;
mpu9250.set_acceleration(x, y, z);
let [x, y, z] = plus(signals.magnetic_field, 3_000);
mpu9250.set_field(x, y, z);
mpu9250.set_temperature(signals.temperature);
let mut icm20608 = Icm20608::new();
let [x, y, z] = plus(signals.angular_rate, 1_000);
icm20608.set_rate(x, y, z);
let [x, y, z] = plus(signals.specific_force, 10_000);
icm20608.set_acceleration(x, y, z);
icm20608.set_temperature(signals.temperature);
let mut lis3mdl = Lis3mdl::new();
let [x, y, z] = plus(signals.magnetic_field, 4_000);
lis3mdl.set_field(x, y, z);
lis3mdl.set_temperature(signals.temperature);
let mut ms5611 = Ms5611::new();
ms5611.set_pressure(94_500);
ms5611.set_temperature(signals.temperature);
answers_as(&mut board, mpu9250, icm20608, lis3mdl, ms5611);
}
#[test]
fn each_chips_noise_is_its_own_and_moves_with_each_advance() {
const SEED: u64 = 7;
let noisy = [
(MPU9250_RATE_NOISE[0], 2_000),
(ICM20608_ACCELERATION_NOISE[2], 30_000),
(LIS3MDL_FIELD_NOISE[1], 5_000),
(MS5611_PRESSURE_NOISE[0], 50),
];
for advances in 1..=2_u64 {
let mut board = Pixracer::new().unwrap();
let mut held = Held::at_rest(&board, SEED).unwrap();
for advance in 0..advances {
let conditions = if advance == 0 {
noisy
.iter()
.map(|&(name, deviation)| Set {
index: u16::try_from(
<Pixracer as Edge>::CONDITIONS
.iter()
.position(|field| field.name == name)
.unwrap(),
)
.unwrap(),
value: deviation,
})
.collect()
} else {
Vec::new()
};
let quiet = Advance {
until: VirtualNanos(0),
inputs: Vec::new(),
ports: Vec::new(),
conditions,
armed: Vec::new(),
};
deliver(&mut board, &mut held, &[], &quiet, VirtualNanos(0)).unwrap();
}
let drawn = |(name, deviation): (&str, i64)| {
let deviation = u32::try_from(deviation).unwrap();
i32::try_from(normal(SEED, name, advances - 1, deviation)).unwrap()
};
let mut mpu9250 = Mpu9250::new();
let [x, y, z] = AT_REST.angular_rate;
mpu9250.set_rate(x + drawn(noisy[0]), y, z);
let [x, y, z] = AT_REST.specific_force;
mpu9250.set_acceleration(x, y, z);
let [x, y, z] = AT_REST.magnetic_field;
mpu9250.set_field(x, y, z);
mpu9250.set_temperature(AT_REST.temperature);
let mut icm20608 = Icm20608::new();
let [x, y, z] = AT_REST.angular_rate;
icm20608.set_rate(x, y, z);
let [x, y, z] = AT_REST.specific_force;
icm20608.set_acceleration(x, y, z + drawn(noisy[1]));
icm20608.set_temperature(AT_REST.temperature);
let mut lis3mdl = Lis3mdl::new();
let [x, y, z] = AT_REST.magnetic_field;
lis3mdl.set_field(x, y + drawn(noisy[2]), z);
lis3mdl.set_temperature(AT_REST.temperature);
let mut ms5611 = Ms5611::new();
ms5611.set_pressure(AT_REST.pressure.saturating_add_signed(drawn(noisy[3])));
ms5611.set_temperature(AT_REST.temperature);
assert_ne!(drawn(noisy[3]), 0, "the pressure's noise is there to see");
answers_as(&mut board, mpu9250, icm20608, lis3mdl, ms5611);
}
}
#[test]
fn a_garbled_gps_port_frames_its_words_wrong_by_the_seed_and_a_part_reset_keeps_it() {
use crate::device::ResetCause;
use crate::soc::stm32f427::generated::UART4;
use crate::soc::stm32f427::generated::rcc::apb1enr;
use crate::soc::stm32f427::generated::uart4::{
Register as UartRegister, cr1 as uart_cr1, sr as uart_sr,
};
let framed_wrong = |per_million: u32, seed: u64, reset: bool| {
let mut board = Pixracer::new().unwrap();
board.condition(&Conditions {
gps_framing_errors: per_million,
..Conditions::default()
});
Edge::draw(&mut board, seed, 0);
if reset {
board.mcu.reset(ResetCause::Core);
}
let rcc = |register: RccRegister| register.spec().offset;
write(
&mut board,
0,
RCC.base,
rcc(RccRegister::Apb1enr),
apb1enr::UART4EN.mask(),
);
let uart = |register: UartRegister| register.spec().offset;
write(&mut board, 0, UART4.base, uart(UartRegister::Brr), 0x10);
write(
&mut board,
0,
UART4.base,
uart(UartRegister::Cr1),
uart_cr1::UE.mask() | uart_cr1::RE.mask(),
);
let mut framed = Vec::new();
for millisecond in 1..=1_000 {
let now = millisecond * MILLISECOND;
board.mcu.poll(VirtualNanos(now));
board.mcu.advance_to(VirtualNanos(now));
let status = read(&mut board, now, UART4.base, uart(UartRegister::Sr));
if uart_sr::RXNE.is_set(status) {
read(&mut board, now, UART4.base, uart(UartRegister::Dr));
framed.push(uart_sr::FE.is_set(status));
}
}
assert!(!framed.is_empty(), "the receiver reports where it is");
framed
};
for reset in [false, true] {
let framed = framed_wrong(uart::Rate::EVERY_WORD, 3, reset);
assert!(
framed.iter().all(|&wrong| wrong),
"after a part reset: {reset}"
);
}
let half = uart::Rate::EVERY_WORD / 2;
let three = framed_wrong(half, 3, false);
assert!(three.contains(&true) && three.contains(&false));
assert_eq!(three, framed_wrong(half, 3, false));
assert_ne!(
three,
framed_wrong(half, 4, false),
"another seed, another pattern"
);
}
const BYTE: u64 = 2 * MICROSECOND;
type Wiring = (usize, Select);
fn base(interface: usize) -> u64 {
use crate::soc::stm32f427::generated::PERIPHERALS;
use crate::soc::stm32f427::peripherals::SERIAL_INTERFACES;
PERIPHERALS
.iter()
.find(|peripheral| peripheral.name == SERIAL_INTERFACES[interface])
.expect("the part has every serial interface")
.base
}
fn through(
board: &mut Pixracer,
at: &mut u64,
(interface, select): Wiring,
bytes: &[u8],
) -> Vec<u8> {
use crate::device::{Device, RequestChange};
use crate::soc::stm32f427::generated::spi1::Register as SpiRegister;
let data = SpiRegister::Dr.spec().offset;
let drive = |board: &mut Pixracer, at: u64, level| {
board
.mcu
.drive_serial(VirtualNanos(at), interface, |spi, registers, context| {
let change = RequestChange {
instance: select.port,
request: select.pin.index(),
level,
};
spi.on_request(change, registers, context);
});
};
drive(board, *at, select.active);
let answers = bytes
.iter()
.map(|&byte| {
write(board, *at, base(interface), data, u64::from(byte));
*at += BYTE;
board.mcu.advance_to(VirtualNanos(*at));
u8::try_from(read(board, *at, base(interface), data) & 0xFF).unwrap()
})
.collect();
drive(board, *at, !select.active);
answers
}
fn with_its_buses_up() -> (Pixracer, u64) {
use crate::soc::stm32f427::generated::rcc::apb1enr;
use crate::soc::stm32f427::generated::spi1::{Register as SpiRegister, cr1 as spi_cr1};
let mut board = Pixracer::new().unwrap();
let rcc = |register: RccRegister| register.spec().offset;
write(
&mut board,
0,
RCC.base,
rcc(RccRegister::Apb2enr),
apb2enr::SPI1EN.mask(),
);
write(
&mut board,
0,
RCC.base,
rcc(RccRegister::Apb1enr),
apb1enr::SPI2EN.mask(),
);
let master =
spi_cr1::MSTR.mask() | spi_cr1::SSM.mask() | spi_cr1::SSI.mask() | spi_cr1::SPE.mask();
for interface in [IMU_ON, FRAM_ON] {
write(
&mut board,
0,
base(interface),
SpiRegister::Cr1.spec().offset,
master,
);
}
let mut at = MICROSECOND;
through(&mut board, &mut at, (FRAM_ON, BARO_CS), &[RESET]);
(board, at + 3 * MILLISECOND)
}
const WHO_AM_I: u8 = 0x75 | READ;
const LIS3MDL_WHO_AM_I: u8 = 0x0F | READ;
const FRAM_RDID: u8 = 0x9F;
const PROM_C1: u8 = 0xA2;
const SELECTED: [(&str, Wiring, &[u8]); 5] = [
("icm20608", (IMU_ON, SECOND_IMU_CS), &[WHO_AM_I, 0]),
("mpu9250", (IMU_ON, IMU_CS), &[WHO_AM_I, 0]),
("lis3mdl", (IMU_ON, MAG_CS), &[LIS3MDL_WHO_AM_I, 0]),
("fm25v02", (FRAM_ON, FRAM_CS), &[FRAM_RDID, 0]),
("ms5611", (FRAM_ON, BARO_CS), &[PROM_C1, 0, 0]),
];
#[test]
fn each_chip_listed_has_a_silence_and_a_place() {
let mut board = Pixracer::new().unwrap();
for (index, chip) in <Pixracer as Edge>::CHIPS.iter().enumerate() {
let silence = format!("{chip}.silent");
assert!(
<Pixracer as Edge>::CONDITIONS
.iter()
.any(|condition| condition.name == silence),
"{chip}"
);
assert!(Edge::place(&mut board, index).is_some(), "{chip}");
}
assert!(Edge::place(&mut board, <Pixracer as Edge>::CHIPS.len()).is_none());
}
#[test]
fn each_chips_place_counts_its_own_cycles_and_takes_its_own_faults() {
let index = |name: &str| {
<Pixracer as Edge>::CHIPS
.iter()
.position(|chip| *chip == name)
.unwrap()
};
let (mut board, mut at) = with_its_buses_up();
let clean: Vec<Vec<u8>> = SELECTED
.iter()
.map(|&(_, wiring, identity)| through(&mut board, &mut at, wiring, identity))
.collect();
for (slot, &(faulted, wiring, identity)) in SELECTED.iter().enumerate() {
let (mut board, mut at) = with_its_buses_up();
let before: Vec<u64> = SELECTED
.iter()
.map(|&(name, _, _)| Edge::place(&mut board, index(name)).unwrap().ended())
.collect();
Edge::place(&mut board, index(faulted)).unwrap().drop_next();
for (&(name, wiring, identity), clean) in SELECTED.iter().zip(&clean) {
let answers = through(&mut board, &mut at, wiring, identity);
if name == faulted {
assert!(answers.iter().all(|&byte| byte == 0), "{name}: {answers:?}");
} else {
assert_eq!(&answers, clean, "{name} beside {faulted}");
}
}
for (&(name, _, _), before) in SELECTED.iter().zip(before) {
let ended = Edge::place(&mut board, index(name)).unwrap().ended();
assert_eq!(ended, before + 1, "{name}, with {faulted} dropped");
}
Edge::place(&mut board, index(faulted)).unwrap().flip(0x81);
let answers = through(&mut board, &mut at, wiring, identity);
let clean = &clean[slot];
assert_eq!(answers[0], clean[0] ^ 0x81, "{faulted}");
assert_eq!(answers[1..], clean[1..], "{faulted}");
}
}
#[test]
fn each_silence_reaches_its_own_chip() {
type Silence = fn(&mut Conditions);
let chips: [(&str, Silence, Wiring, &[u8]); 5] = [
(
"the ICM-20608",
|conditions| conditions.icm20608_silent = true,
(IMU_ON, SECOND_IMU_CS),
&[WHO_AM_I, 0],
),
(
"the MPU-9250",
|conditions| conditions.mpu9250_silent = true,
(IMU_ON, IMU_CS),
&[WHO_AM_I, 0],
),
(
"the LIS3MDL",
|conditions| conditions.lis3mdl_silent = true,
(IMU_ON, MAG_CS),
&[LIS3MDL_WHO_AM_I, 0],
),
(
"the FM25V02",
|conditions| conditions.fm25v02_silent = true,
(FRAM_ON, FRAM_CS),
&[FRAM_RDID, 0],
),
(
"the MS5611",
|conditions| conditions.ms5611_silent = true,
(FRAM_ON, BARO_CS),
&[PROM_C1, 0, 0],
),
];
for (silenced, silence, _, _) in chips {
let (mut board, mut at) = with_its_buses_up();
let mut conditions = Conditions::default();
silence(&mut conditions);
board.condition(&conditions);
for (name, _, chip, identity) in chips {
let answers = through(&mut board, &mut at, chip, identity);
if name == silenced {
assert!(
answers.iter().all(|&byte| byte == 0),
"{name} is silent: {answers:?}"
);
} else {
assert!(
answers[1..].iter().any(|&byte| byte != 0),
"{name} answers beside {silenced}: {answers:?}"
);
}
}
}
}
#[test]
fn a_silent_receiver_sends_nothing_and_a_silent_card_answers_nothing() {
use crate::device::ResetCause;
use crate::soc::stm32f427::generated::rcc::apb1enr;
use crate::soc::stm32f427::generated::sdio::{Register as SdioRegister, cmd, power, sta};
use crate::soc::stm32f427::generated::uart4::{
Register as UartRegister, cr1 as uart_cr1, sr as uart_sr,
};
use crate::soc::stm32f427::generated::{SDIO, UART4};
const APP_CMD: u64 = 55;
let heard = |silent: bool, reset: bool| {
let mut board = Pixracer::new().unwrap();
board.condition(&Conditions {
gps_silent: silent,
card_silent: silent,
..Conditions::default()
});
if reset {
board.mcu.reset(ResetCause::Core);
}
let rcc = |register: RccRegister| register.spec().offset;
write(
&mut board,
0,
RCC.base,
rcc(RccRegister::Apb1enr),
apb1enr::UART4EN.mask(),
);
write(
&mut board,
0,
RCC.base,
rcc(RccRegister::Apb2enr),
apb2enr::SDIOEN.mask(),
);
let uart = |register: UartRegister| register.spec().offset;
write(&mut board, 0, UART4.base, uart(UartRegister::Brr), 0x10);
write(
&mut board,
0,
UART4.base,
uart(UartRegister::Cr1),
uart_cr1::UE.mask() | uart_cr1::RE.mask(),
);
let mut bytes = 0;
for millisecond in 1..=1_000 {
let now = millisecond * MILLISECOND;
board.mcu.poll(VirtualNanos(now));
board.mcu.advance_to(VirtualNanos(now));
if uart_sr::RXNE.is_set(read(&mut board, now, UART4.base, uart(UartRegister::Sr))) {
read(&mut board, now, UART4.base, uart(UartRegister::Dr));
bytes += 1;
}
}
let sdio = |register: SdioRegister| register.spec().offset;
let now = 1_001 * MILLISECOND;
write(
&mut board,
now,
SDIO.base,
sdio(SdioRegister::Power),
power::PWRCTRL.set(0, 0b11),
);
write(
&mut board,
now,
SDIO.base,
sdio(SdioRegister::Cmd),
cmd::CPSMEN.mask() | cmd::WAITRESP.set(0, 0b01) | cmd::CMDINDEX.set(0, APP_CMD),
);
let status = read(&mut board, now, SDIO.base, sdio(SdioRegister::Sta));
(
bytes,
sta::CMDREND.is_set(status),
sta::CTIMEOUT.is_set(status),
)
};
let (bytes, answered, timed_out) = heard(false, false);
assert!(bytes > 0, "the receiver reports where it is");
assert!(answered && !timed_out, "the card answers");
assert_eq!(
heard(true, false),
(0, false, true),
"and silent, neither does"
);
assert_eq!(
heard(true, true),
(0, false, true),
"nor after a part reset, since a condition is the board's"
);
}
#[test]
fn an_advance_carrying_only_a_condition_reaches_the_board() {
const PROM_C1: u8 = 0xA2;
let (mut board, mut at) = with_its_buses_up();
let mut held = Held::at_rest(&board, 0).unwrap();
let silent = <Pixracer as Edge>::CONDITIONS
.iter()
.position(|field| field.name == "ms5611.silent")
.unwrap();
deliver(
&mut board,
&mut held,
&[],
&Advance {
until: VirtualNanos(at),
inputs: Vec::new(),
ports: Vec::new(),
conditions: vec![Set {
index: u16::try_from(silent).unwrap(),
value: 1,
}],
armed: Vec::new(),
},
VirtualNanos(at),
)
.unwrap();
let answers = through(&mut board, &mut at, (FRAM_ON, BARO_CS), &[PROM_C1, 0, 0]);
assert_eq!(answers, [0; 3], "an empty place");
}
#[test]
fn an_advance_sets_an_input_the_board_senses_whole_and_queues_a_ports_bytes() {
let mut board = Pixracer::new().unwrap();
assert_eq!(
attach_engine_ports(&mut board).unwrap(),
[TELEMETRY_WIRE.name]
);
let mut held = Held::at_rest(&board, 0).unwrap();
let pressure = <Pixracer as Edge>::FIELDS
.iter()
.position(|field| field.name == "pressure")
.unwrap();
deliver(
&mut board,
&mut held,
&[TELEMETRY_WIRE.name],
&Advance {
until: VirtualNanos(0),
inputs: vec![Set {
index: u16::try_from(pressure).unwrap(),
value: 90_000,
}],
ports: vec![b"x".to_vec()],
conditions: Vec::new(),
armed: Vec::new(),
},
VirtualNanos(0),
)
.unwrap();
let mut ms5611 = Ms5611::new();
ms5611.set_pressure(90_000);
ms5611.set_temperature(AT_REST.temperature);
let mut at = 10 * MILLISECOND;
let mut barometer = |board: &mut Pixracer, bytes: &[u8]| {
at += 10 * MILLISECOND;
alike(board, at, (FRAM_ON, MS5611_PEER), &mut ms5611, bytes)
};
barometer(&mut board, &[RESET]);
barometer(&mut board, &[CONVERT_D1]);
let (on_board, expected) = barometer(&mut board, &[ADC_READ, 0, 0, 0]);
assert_eq!(
on_board, expected,
"the barometer converts the pressure set"
);
let port = board.host_mut(TELEMETRY_WIRE.name).unwrap();
assert_eq!(port.transmit(VirtualNanos(0)), Some(u16::from(b'x')));
}
#[test]
fn an_advance_for_another_boards_ports_or_inputs_is_refused_by_name() {
let mut board = Pixracer::new().unwrap();
attach_engine_ports(&mut board).unwrap();
let mut held = Held::at_rest(&board, 0).unwrap();
let too_many_ports = Advance {
until: VirtualNanos(0),
inputs: Vec::new(),
ports: vec![Vec::new(), Vec::new()],
conditions: Vec::new(),
armed: Vec::new(),
};
assert!(matches!(
deliver(
&mut board,
&mut held,
&[TELEMETRY_WIRE.name],
&too_many_ports,
VirtualNanos(0),
),
Err(LinkError::Misfit(_))
));
let no_such_input = Advance {
until: VirtualNanos(0),
inputs: vec![Set {
index: 99,
value: 0,
}],
ports: vec![Vec::new()],
conditions: Vec::new(),
armed: Vec::new(),
};
assert!(matches!(
deliver(
&mut board,
&mut held,
&[TELEMETRY_WIRE.name],
&no_such_input,
VirtualNanos(0),
),
Err(LinkError::Misfit(_))
));
}
#[test]
fn the_engine_s_port_on_serial1_carries_a_byte_each_way() {
use crate::soc::stm32f427::generated::USART2;
use crate::soc::stm32f427::generated::rcc::apb1enr;
use crate::soc::stm32f427::generated::usart6::{
Register as UsartRegister, cr1 as usart_cr1, sr as usart_sr,
};
let mut board = Pixracer::new().unwrap();
let ports = attach_engine_ports(&mut board).unwrap();
let usart = |register: UsartRegister| register.spec().offset;
write(
&mut board,
0,
RCC.base,
RccRegister::Apb1enr.spec().offset,
apb1enr::USART2EN.mask(),
);
write(&mut board, 0, USART2.base, usart(UsartRegister::Brr), 0x10);
write(
&mut board,
0,
USART2.base,
usart(UsartRegister::Cr1),
usart_cr1::UE.mask() | usart_cr1::TE.mask() | usart_cr1::RE.mask(),
);
let mut held = Held::at_rest(&board, 0).unwrap();
let advance = Advance {
until: VirtualNanos(MILLISECOND),
inputs: Vec::new(),
ports: vec![b"k".to_vec()],
conditions: Vec::new(),
armed: Vec::new(),
};
deliver(&mut board, &mut held, &ports, &advance, VirtualNanos(0)).unwrap();
board.mcu.poll(VirtualNanos(0));
let frame = 10 * MICROSECOND;
board.mcu.advance_to(VirtualNanos(frame));
let status = read(&mut board, frame, USART2.base, usart(UsartRegister::Sr));
assert!(usart_sr::RXNE.is_set(status), "the engine's byte arrived");
assert_eq!(
read(&mut board, frame, USART2.base, usart(UsartRegister::Dr)),
u64::from(b'k')
);
write(
&mut board,
frame,
USART2.base,
usart(UsartRegister::Dr),
u64::from(b'q'),
);
board.mcu.advance_to(VirtualNanos(3 * frame));
assert_eq!(sent(&mut board, &ports).unwrap(), [b"q".to_vec()]);
assert_eq!(
sent(&mut board, &ports).unwrap(),
[Vec::<u8>::new()],
"each byte is told once"
);
}
#[test]
fn the_outputs_are_the_description_s_six_pwm_pins_read_off_their_timers() {
let mut board = Pixracer::new().unwrap();
assert_eq!(
board.drive(),
IDLE,
"nothing drives a pin before the firmware"
);
let named: Vec<String> = IDLE
.0
.iter()
.flat_map(|actuator| {
["width", "period", "command"].map(|part| format!("{}.{part}", actuator.name))
})
.collect();
let fields: Vec<&str> = <Pixracer as Edge>::OUTPUT_FIELDS
.iter()
.map(|field| field.name.as_ref())
.collect();
assert_eq!(
fields, named,
"the fields name the outputs in the order they cross"
);
let timer_hz = {
let (clocks, registers) = board.mcu.clocks();
clocks.timer_clock(registers, Apb::Two)
};
let tim1 = |register: Tim1Register| register.spec().offset;
write(
&mut board,
0,
RCC.base,
RccRegister::Apb2enr.spec().offset,
apb2enr::TIM1EN.mask(),
);
write(
&mut board,
0,
TIM1.base,
tim1(Tim1Register::Psc),
timer_hz / 1_000_000 - 1,
);
write(&mut board, 0, TIM1.base, tim1(Tim1Register::Arr), 19_999);
write(&mut board, 0, TIM1.base, tim1(Tim1Register::Ccr4), 1_500);
write(
&mut board,
0,
TIM1.base,
tim1(Tim1Register::Ccmr2Output),
0x6800,
);
write(&mut board, 0, TIM1.base, tim1(Tim1Register::Ccer), 0x1000);
write(&mut board, 0, TIM1.base, tim1(Tim1Register::Bdtr), 0x8000);
write(
&mut board,
0,
TIM1.base,
tim1(Tim1Register::Egr),
egr::UG.mask(),
);
write(
&mut board,
0,
TIM1.base,
tim1(Tim1Register::Cr1),
cr1::CEN.mask(),
);
let outputs = board.drive();
assert_eq!(
outputs.0[0],
Actuator {
name: "PWM1",
pulse: Some(Pulse {
width: VirtualNanos(1_500 * MICROSECOND),
period: VirtualNanos(20 * MILLISECOND),
}),
command: Some(500),
}
);
assert!(
outputs.0[1..]
.iter()
.all(|actuator| actuator.pulse.is_none())
);
let mut bytes = Vec::new();
outputs.encode(&mut bytes);
let values = values_of(<Pixracer as Edge>::OUTPUT_FIELDS, &bytes).unwrap();
assert_eq!(values[..6], [1_500_000, 20_000_000, 500, 0, 0, 0]);
assert_eq!(
Outputs::decode(&mut bytes.as_slice()).unwrap(),
outputs,
"an output with no period comes back with no pulse and no command"
);
}
const SERVO: Curve = Curve {
zero: VirtualNanos(1_000_000),
full: VirtualNanos(2_000_000),
};
fn width(nanos: u64) -> Pulse {
Pulse {
width: VirtualNanos(nanos),
period: VirtualNanos(20_000_000),
}
}
#[test]
fn a_command_is_where_the_width_falls_between_the_ends_and_never_past_them() {
assert_eq!(SERVO.command(width(1_000_000)), 0);
assert_eq!(SERVO.command(width(1_250_000)), 250);
assert_eq!(SERVO.command(width(2_000_000)), FULL);
assert_eq!(SERVO.command(width(0)), 0, "narrower than nothing");
assert_eq!(
SERVO.command(width(3_000_000)),
FULL,
"wider than everything"
);
}
#[test]
fn an_output_without_a_pulse_commands_nothing() {
let off = Actuator::on("OUT", None, SERVO);
assert_eq!(off.command, None);
let on = Actuator::on("OUT", Some(width(1_500_000)), SERVO);
assert_eq!(on.command, Some(500));
}
#[test]
fn the_telemetry_wire_is_the_second_serial_port() {
let mut board = Pixracer::new().unwrap();
assert_eq!(board.hosts(), [TELEMETRY_WIRE]);
assert!(!board.attach_host(
"serial9",
Box::new(crate::soc::stm32f427::peripherals::usart::Pattern::default())
));
assert!(!board.mcu.usart[TELEMETRY].attached());
assert!(board.attach_host(
"serial1",
Box::new(crate::soc::stm32f427::peripherals::usart::Pattern::default())
));
assert!(board.mcu.usart[TELEMETRY].attached());
assert_eq!(
crate::soc::stm32f427::peripherals::SERIAL_PORTS[TELEMETRY].instance,
"USART2"
);
}
#[test]
fn the_receiver_on_the_gps_port_reports_where_the_board_is() {
let mut board = Pixracer::new().unwrap();
assert_eq!(
crate::soc::stm32f427::peripherals::SERIAL_PORTS[GPS].instance,
"UART4"
);
board.sense(&Signals {
position: Position {
latitude: 515_074_000,
longitude: -1_278_000,
altitude: 11_000,
},
velocity: [10_000, 0, 0],
..AT_REST
});
let receiver = board.mcu.usart[GPS].peer_mut().expect("fit hangs it");
let epoch: Vec<u8> = std::iter::from_fn(|| receiver.transmit(VirtualNanos(0)))
.map(|word| u8::try_from(word).expect("a byte"))
.collect();
let epoch = String::from_utf8(epoch).unwrap();
assert!(
epoch.starts_with("$GPGGA,000000.00,5130.44400,N,00007.66800,W,1,10,0.90,11.000,M,"),
"{epoch}"
);
assert!(
epoch.contains("$GPRMC,000000.00,A,5130.44400,N,00007.66800,W,19.43,0.00,"),
"ten metres a second north is 19.43 knots on course zero: {epoch}"
);
}
#[test]
fn the_battery_reaches_the_first_converter_through_the_descriptions_dividers() {
use crate::soc::stm32f427::generated::ADC1;
use crate::soc::stm32f427::generated::adc1::{
Register as AdcRegister, cr1 as adc_cr1, cr2, sqr1, sqr3, sr,
};
let mut board = Pixracer::new().unwrap();
board.sense(&Signals {
battery_voltage: 16_800,
battery_current: 17_000,
..AT_REST
});
let adc = |register: AdcRegister| register.spec().offset;
write(
&mut board,
0,
RCC.base,
RccRegister::Apb2enr.spec().offset,
apb2enr::ADC1EN.mask(),
);
write(
&mut board,
0,
ADC1.base,
adc(AdcRegister::Sqr3),
sqr3::SQ3.set(sqr3::SQ2.set(sqr3::SQ1.set(0, 2), 3), 4),
);
write(
&mut board,
0,
ADC1.base,
adc(AdcRegister::Sqr1),
sqr1::L.set(0, 2),
);
write(
&mut board,
0,
ADC1.base,
adc(AdcRegister::Cr1),
adc_cr1::SCAN.mask(),
);
let running = cr2::ADON.mask() | cr2::EOCS.mask();
write(&mut board, 0, ADC1.base, adc(AdcRegister::Cr2), running);
write(
&mut board,
0,
ADC1.base,
adc(AdcRegister::Cr2),
running | cr2::SWSTART.mask(),
);
let words: Vec<u64> = (1..=3)
.map(|nth| {
let now = nth * 1_875;
board.mcu.advance_to(VirtualNanos(now));
assert!(sr::EOC.is_set(read(&mut board, now, ADC1.base, adc(AdcRegister::Sr))));
read(&mut board, now, ADC1.base, adc(AdcRegister::Dr))
})
.collect();
assert_eq!(words, [2063, 1240, 3102]);
}
#[test]
fn the_board_is_built_at_rest() {
let mut board = Pixracer::new().unwrap();
for unit in [MPU9250_PEER, ICM20608_PEER] {
cycle(
&mut board,
MILLISECOND,
IMU_ON,
unit,
&[PWR_MGMT_1, CLOCK_ON],
);
let mut frame = vec![ACCEL_XOUT_H | READ];
frame.extend([0; 6]);
let bytes = cycle(&mut board, 5 * MILLISECOND, IMU_ON, unit, &frame);
assert_eq!(
words(&bytes[1..], true),
[0, 0, -COUNTS_PER_GRAVITY],
"unit {unit}"
);
}
cycle(
&mut board,
MILLISECOND,
IMU_ON,
LIS3MDL_PEER,
&[CTRL_REG1, ODR_80_ULTRAHIGH],
);
cycle(
&mut board,
MILLISECOND,
IMU_ON,
LIS3MDL_PEER,
&[CTRL_REG3, CONTINUOUS],
);
let mut frame = vec![OUT_X_L | BURST];
frame.extend([0; 6]);
let bytes = cycle(&mut board, 30 * MILLISECOND, IMU_ON, LIS3MDL_PEER, &frame);
let [north, east, down] = words(&bytes[1..], false);
assert!(
north > 0 && east == 0 && down > 0,
"north and down: {north} {east} {down}"
);
}
struct Answered {
units: [Vec<u8>; 2],
die: Vec<u8>,
magnetometer: Vec<u8>,
pressure: Vec<u8>,
temperature: Vec<u8>,
}
fn answers_as(
board: &mut Pixracer,
mut mpu9250: Mpu9250,
mut icm20608: Icm20608,
mut lis3mdl: Lis3mdl,
mut ms5611: Ms5611,
) -> Answered {
let mut sample = vec![ACCEL_XOUT_H | READ];
sample.extend([0; 14]);
let units: [(usize, &mut dyn Peer); 2] =
[(MPU9250_PEER, &mut mpu9250), (ICM20608_PEER, &mut icm20608)];
let units = units.map(|(nth, unit)| {
alike(
board,
MILLISECOND,
(IMU_ON, nth),
unit,
&[PWR_MGMT_1, CLOCK_ON],
);
let (on_board, expected) = alike(board, 5 * MILLISECOND, (IMU_ON, nth), unit, &sample);
assert_eq!(on_board, expected, "unit {nth}");
expected
});
let (on_board, die) = compass_die_fields(board, &mut mpu9250);
assert_eq!(on_board, die, "the compass die inside the first unit");
alike(
board,
MILLISECOND,
(IMU_ON, LIS3MDL_PEER),
&mut lis3mdl,
&[CTRL_REG1, TEMP_EN | ODR_80_ULTRAHIGH],
);
alike(
board,
MILLISECOND,
(IMU_ON, LIS3MDL_PEER),
&mut lis3mdl,
&[CTRL_REG3, CONTINUOUS],
);
let mut frame = vec![OUT_X_L | BURST];
frame.extend([0; 8]);
let (on_board, magnetometer) = alike(
board,
30 * MILLISECOND,
(IMU_ON, LIS3MDL_PEER),
&mut lis3mdl,
&frame,
);
assert_eq!(on_board, magnetometer, "the magnetometer");
let mut at = 40 * MILLISECOND;
let mut barometer = |board: &mut Pixracer, bytes: &[u8]| {
at += 10 * MILLISECOND;
alike(board, at, (FRAM_ON, MS5611_PEER), &mut ms5611, bytes)
};
barometer(board, &[RESET]);
barometer(board, &[CONVERT_D1]);
let (on_board, pressure) = barometer(board, &[ADC_READ, 0, 0, 0]);
assert_eq!(on_board, pressure, "the barometer's pressure conversion");
barometer(board, &[CONVERT_D2]);
let (on_board, temperature) = barometer(board, &[ADC_READ, 0, 0, 0]);
assert_eq!(
on_board, temperature,
"the barometer's temperature conversion"
);
Answered {
units,
die,
magnetometer,
pressure,
temperature,
}
}
#[test]
fn sensing_hands_each_chip_the_body_s_axes() {
const RATE: i32 = 100_000;
let mut board = Pixracer::new().unwrap();
board.sense(&Signals {
angular_rate: [RATE, 0, -RATE],
specific_force: [ONE_GRAVITY, 0, -ONE_GRAVITY],
magnetic_field: [GAUSS_NANOTESLA, 0, GAUSS_NANOTESLA],
pressure: 95_000,
temperature: 3_000,
..AT_REST
});
let mut mpu9250 = Mpu9250::new();
mpu9250.set_rate(RATE, 0, -RATE);
mpu9250.set_acceleration(ONE_GRAVITY, 0, -ONE_GRAVITY);
mpu9250.set_temperature(3_000);
mpu9250.set_field(GAUSS_NANOTESLA, 0, GAUSS_NANOTESLA);
let mut icm20608 = Icm20608::new();
icm20608.set_rate(RATE, 0, -RATE);
icm20608.set_acceleration(ONE_GRAVITY, 0, -ONE_GRAVITY);
icm20608.set_temperature(3_000);
let mut lis3mdl = Lis3mdl::new();
lis3mdl.set_field(GAUSS_NANOTESLA, 0, GAUSS_NANOTESLA);
lis3mdl.set_temperature(3_000);
let mut ms5611 = Ms5611::new();
ms5611.set_pressure(95_000);
ms5611.set_temperature(3_000);
let answered = answers_as(&mut board, mpu9250, icm20608, lis3mdl, ms5611);
for expected in &answered.units {
assert_eq!(
words(&expected[1..7], true),
[COUNTS_PER_GRAVITY, 0, -COUNTS_PER_GRAVITY]
);
assert_ne!(&expected[7..9], [0, 0], "a temperature");
assert_ne!(words(&expected[9..15], true), [0; 3], "a rate");
}
assert_ne!(answered.die, [0; 6]);
assert_eq!(
words(&answered.magnetometer[1..7], false),
[COUNTS_PER_GAUSS, 0, COUNTS_PER_GAUSS]
);
assert_ne!(
&answered.magnetometer[7..9],
[0, 0],
"the magnetometer's temperature"
);
assert_ne!(&answered.pressure[1..], [0, 0, 0]);
assert_ne!(&answered.temperature[1..], [0, 0, 0]);
}
#[test]
fn the_board_has_its_mcu_and_reaches_a_model_by_field() {
let mut board = Pixracer::new().unwrap();
assert_eq!(board.name(), NAME);
let (clocks, registers) = board.mcu.clocks();
assert_eq!(
clocks.sysclk(registers),
16_000_000,
"the part wakes on its own oscillator"
);
assert_eq!(board.hse, Crystal(24_000_000));
}
#[test]
fn a_board_can_hang_a_chip_on_one_of_its_interfaces() {
#[derive(Debug, Default)]
struct Counter(u16);
impl crate::snapshot::Store for Counter {}
impl Peer for Counter {
fn transfer(&mut self, mosi: u16, _context: &DeviceContext<'_>) -> u16 {
self.0 = mosi.wrapping_add(1);
self.0
}
}
let mut board = Pixracer::new().unwrap();
assert!(
!board.mcu.spi[3].attached(),
"a Pixracer fits nothing on its fourth interface"
);
board.mcu.spi[3].attach(Counter::default());
assert!(board.mcu.spi[3].attached());
}
#[test]
fn the_machine_routes_every_cpu_line_and_no_more() {
let mut board = Pixracer::new().unwrap();
let lines = board.mcu.cpu().interrupt_lines;
let targets = board.targets();
let [target] = &targets[..] else {
panic!("one MCU on the board");
};
assert!((target.interrupt_line)(lines - 1).is_some());
}
#[test]
fn the_machine_takes_its_interrupt_count_from_the_part() {
let mut board = Pixracer::new().unwrap();
let lines = board.mcu.cpu().interrupt_lines;
let targets = board.targets();
let declared = targets[0]
.core
.properties
.iter()
.find(|property| property.name == armv7m::INTERRUPT_LINES)
.expect("the machine is told its interrupt count");
assert_eq!(
declared.value,
lines.to_string(),
"the count the core is given is the count the MCU has"
);
}
}