use std::num::{NonZeroU32, NonZeroU64};
use crate::chip::dps310::{Dps310, Sdo};
use crate::chip::icm42605::Icm42605;
use crate::chip::icm42688p::Icm42688p;
use crate::chip::qmc5883l::Qmc5883l;
use crate::chip::{Placed, nmea, uart};
use crate::cpu::armv7m::registers::{mask, set};
use crate::device::Level;
use crate::edge::{Condition, Coupled, Edge, Field, Pulse, Sensed, flag};
use crate::link::wire;
use crate::machine::armv7m;
use crate::machine::flight::{self, AT_REST, Actuator, Curve, SIGNAL_FIELDS, Signals};
use crate::machine::{Board, HostWire, Target};
use crate::snapshot::store;
use crate::soc::stm32h743::Stm32h743;
use crate::soc::stm32h743::peripherals::adc::Channel as AnalogChannel;
use crate::soc::stm32h743::peripherals::gpio::Pin;
use crate::soc::stm32h743::peripherals::spi::Select;
use crate::soc::stm32h743::peripherals::tim::Channel;
use crate::soc::{Access, Error, FieldSpec};
use crate::time::VirtualNanos;
pub const NAME: &str = "matekh743";
pub const MCU: &str = "stm32h743";
const CLOCK_HZ: NonZeroU64 = NonZeroU64::new(400_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::M7;
const FPU: armv7m::Fpu = armv7m::Fpu::FpV5Dp;
const MPU_REGIONS: armv7m::MpuRegions = armv7m::MpuRegions::Sixteen;
const BOOT: &str = "flash";
const HSE_HZ: u64 = 8_000_000;
const SUPPLY_MV: u32 = 3300;
const IMU1_ON: usize = 0;
const IMU1_CS: Select = Select {
port: "GPIOC",
pin: Pin::new(15).expect("a port has sixteen pins"),
active: Level::Low,
};
const IMU2_ON: usize = 3;
const IMU2_CS: Select = Select {
port: "GPIOC",
pin: Pin::new(13).expect("a port has sixteen pins"),
active: Level::Low,
};
const BARO_ON: usize = 1;
const COMPASS_ON: usize = 0;
const ICM42688P_PEER: usize = 0;
const ICM42605_PEER: usize = 0;
const DPS310_PEER: usize = 0;
const QMC5883L_PEER: usize = 0;
const GPS: usize = 1;
const BATTERY_VOLTAGE: AnalogChannel = analog(10);
const BATTERY_CURRENT: AnalogChannel = analog(11);
const PACK_PER_SENSED: u32 = 11;
const MILLIAMPS_PER_MILLIVOLT: u32 = 40;
const MOTOR_TIMER: usize = 1;
const MOTOR_OUTPUTS: [(&str, Channel); 2] = [("PWM1", channel(2)), ("PWM2", channel(3))];
const TIM5: usize = 3;
const TIM5_OUTPUTS: [(&str, Channel); 4] = [
("PWM3", channel(1)),
("PWM4", channel(2)),
("PWM5", channel(3)),
("PWM6", channel(4)),
];
const TIM4: usize = 2;
const TIM4_OUTPUTS: [(&str, Channel); 4] = [
("PWM7", channel(1)),
("PWM8", channel(2)),
("PWM9", channel(3)),
("PWM10", channel(4)),
];
const TIM15_OUTPUTS: [(&str, Channel); 2] = [("PWM11", channel(1)), ("PWM12", channel(2))];
const LED_TIMER: usize = 0;
const LED_OUTPUT: (&str, Channel) = ("PWM13", channel(1));
const SPEED_CONTROLLER: Curve = Curve {
zero: VirtualNanos(1_000_000),
full: VirtualNanos(2_000_000),
};
const ICM42688P_RATE_NOISE: [&str; 3] = [
"icm42688p.rate.noise.x",
"icm42688p.rate.noise.y",
"icm42688p.rate.noise.z",
];
const ICM42688P_ACCELERATION_NOISE: [&str; 3] = [
"icm42688p.acceleration.noise.x",
"icm42688p.acceleration.noise.y",
"icm42688p.acceleration.noise.z",
];
const ICM42605_RATE_NOISE: [&str; 3] = [
"icm42605.rate.noise.x",
"icm42605.rate.noise.y",
"icm42605.rate.noise.z",
];
const ICM42605_ACCELERATION_NOISE: [&str; 3] = [
"icm42605.acceleration.noise.x",
"icm42605.acceleration.noise.y",
"icm42605.acceleration.noise.z",
];
const QMC5883L_FIELD_NOISE: [&str; 3] = [
"qmc5883l.field.noise.x",
"qmc5883l.field.noise.y",
"qmc5883l.field.noise.z",
];
const DPS310_PRESSURE_NOISE: [&str; 1] = ["dps310.pressure.noise"];
const GPS_NOISE_ERRORS: &str = "gps.noise_errors";
const GPS_FRAMING_ERRORS: &str = "gps.framing_errors";
const fn channel(number: u8) -> Channel {
match Channel::new(number) {
Some(channel) => channel,
None => panic!("a timer has six channels"),
}
}
const fn analog(number: u8) -> AnalogChannel {
match AnalogChannel::new(number) {
Some(channel) => channel,
None => panic!("a converter has twenty pin channels"),
}
}
fn turned_yaw_180([x, y, z]: [i32; 3]) -> [i32; 3] {
[x.saturating_neg(), y.saturating_neg(), z]
}
fn turned_yaw_270([x, y, z]: [i32; 3]) -> [i32; 3] {
[y.saturating_neg(), x, z]
}
fn turned_for_the_compass([x, y, z]: [i32; 3]) -> [i32; 3] {
[y, x.saturating_neg(), z]
}
fn actuator(name: &'static str, pulse: Option<Pulse>) -> Actuator {
Actuator::on(name, pulse, SPEED_CONTROLLER)
}
pub type Outputs = flight::Outputs<13>;
#[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 icm42688p_silent: bool,
pub icm42688p_rate: Condition<3>,
pub icm42688p_acceleration: Condition<3>,
pub icm42605_silent: bool,
pub icm42605_rate: Condition<3>,
pub icm42605_acceleration: Condition<3>,
pub dps310_silent: bool,
pub dps310_pressure: Condition<1>,
pub qmc5883l_silent: bool,
pub qmc5883l_field: Condition<3>,
pub gps_silent: bool,
pub gps_noise_errors: u32,
pub gps_framing_errors: u32,
}
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
struct Handed {
icm42688p_rate: Sensed<i32, 3>,
icm42688p_acceleration: Sensed<i32, 3>,
icm42605_rate: Sensed<i32, 3>,
icm42605_acceleration: Sensed<i32, 3>,
qmc5883l_field: Sensed<i32, 3>,
dps310_pressure: Sensed<u32, 1>,
}
const TELEMETRY: usize = 6;
const TELEMETRY_WIRE: HostWire = HostWire {
name: "serial1",
port: 5760,
};
const VECTOR_TABLE: u32 = 0x0800_0000;
const CPUID: u32 = 0x411F_C271;
const CACHES: armv7m::Caches = armv7m::Caches {
clidr: 0x0900_0003,
ctr: 0x8303_C003,
ccsidr: &[0xF00F_E019, 0xF01F_E009],
};
const TCM_EN: FieldSpec = FieldSpec::new("EN", 0, 1, Access::ReadWrite);
const TCM_RMW: FieldSpec = FieldSpec::new("RMW", 1, 1, Access::ReadWrite);
const TCM_RETEN: FieldSpec = FieldSpec::new("RETEN", 2, 1, Access::ReadWrite);
const TCM_SZ: FieldSpec = FieldSpec::new("SZ", 3, 4, Access::ReadOnly);
const AHBP_EN: FieldSpec = FieldSpec::new("EN", 0, 1, Access::ReadWrite);
const CACR_SIWT: FieldSpec = FieldSpec::new("SIWT", 0, 1, Access::ReadWrite);
const CACR_ECCEN: FieldSpec = FieldSpec::new("ECCEN", 1, 1, Access::ReadWrite);
const CACR_FORCEWT: FieldSpec = FieldSpec::new("FORCEWT", 2, 1, Access::ReadWrite);
const AHBS_CTL: FieldSpec = FieldSpec::new("CTL", 0, 2, Access::ReadWrite);
const AHBS_TPRI: FieldSpec = FieldSpec::new("TPRI", 2, 9, Access::ReadWrite);
const AHBS_INITCOUNT: FieldSpec = FieldSpec::new("INITCOUNT", 11, 5, Access::ReadWrite);
const ITCM_64K: u32 = 0b0111;
const DTCM_128K: u32 = 0b1000;
const TCM_WRITABLE: u32 = mask(&TCM_EN) | mask(&TCM_RMW) | mask(&TCM_RETEN);
const ACCESS_CONTROL: [armv7m::ImplementationRegister; 6] = [
register(0xF90, set(&TCM_SZ, 0, ITCM_64K), TCM_WRITABLE),
register(0xF94, set(&TCM_SZ, 0, DTCM_128K), TCM_WRITABLE),
register(0xF98, 0, mask(&AHBP_EN)),
register(
0xF9C,
0,
mask(&CACR_SIWT) | mask(&CACR_ECCEN) | mask(&CACR_FORCEWT),
),
register(
0xFA0,
set(&AHBS_INITCOUNT, 0, 0b01),
mask(&AHBS_CTL) | mask(&AHBS_TPRI) | mask(&AHBS_INITCOUNT),
),
register(0xFA8, 0, 0),
];
const fn register(offset: u64, reset: u32, writable: u32) -> armv7m::ImplementationRegister {
armv7m::ImplementationRegister {
offset,
reset,
writable,
}
}
#[derive(Debug)]
pub struct MatekH743 {
pub mcu: Stm32h743,
conditions: Conditions,
handed: Handed,
sensed: Signals,
}
impl MatekH743 {
pub fn new() -> Result<Self, Error> {
let mut board = Self {
mcu: Stm32h743::new()?,
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(HSE_HZ, 0);
self.mcu.set_supply(SUPPLY_MV);
self.mcu.set_analog_supply(SUPPLY_MV);
for converter in &mut self.mcu.adc {
converter.set_reference(SUPPLY_MV);
}
self.mcu.spi[IMU1_ON].attach_selected(Icm42688p::new(), IMU1_CS);
self.mcu.spi[IMU2_ON].attach_selected(Icm42605::new(), IMU2_CS);
self.mcu.i2c[BARO_ON].attach(Dps310::new(Sdo::Low));
self.mcu.i2c[COMPASS_ON].attach(Qmc5883l::new());
self.mcu.usart[GPS].attach(nmea::Receiver::new());
}
#[must_use]
pub fn config(&self) -> armv7m::Config {
let core = armv7m::Core {
variant: VARIANT,
clock_hz: CLOCK_HZ,
interrupt_lines: NonZeroU32::new(u32::from(
crate::mcu::Operate::cpu(&self.mcu).interrupt_lines,
))
.expect("an MCU with interrupts"),
priority_bits: PRIORITY_BITS,
refclk_divider: REFCLK_DIVIDER,
instructions_per_second: armv7m::INSTRUCTIONS_PER_SECOND,
boot: BOOT,
};
let mut config = armv7m::Config::new(
core,
&crate::cpu::armv7m::windows(crate::mcu::Mcu::bus(&self.mcu)),
);
config.fpu = FPU;
config.mpu_regions = MPU_REGIONS;
config.cpuid = CPUID;
config.caches = Some(CACHES);
config.branch_prediction = armv7m::BranchPrediction::Always;
config.implementation_registers = ACCESS_CONTROL.to_vec();
config.vector_table = Some(VECTOR_TABLE);
config
}
}
store!(MatekH743 {
values: [conditions, handed, sensed],
parts: [mcu],
built: [],
});
impl Board for MatekH743 {
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 = self.config().spec();
vec![Target::new(MCU, &mut self.mcu, core)]
}
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 MatekH743 {
type Signals = Signals;
type Outputs = Outputs;
type Conditions = Conditions;
const OUTPUT_FIELDS: &'static [Field] = Outputs::FIELDS;
const FIELDS: &'static [Field] = SIGNAL_FIELDS;
const CONDITIONS: &'static [Field] = &[
Field::new("icm42688p.silent", "flag", 1, false),
Field::new("icm42688p.rate.stuck", "flag", 1, false),
Field::new("icm42688p.rate.bias.x", "mdeg/s", 4, true),
Field::new("icm42688p.rate.bias.y", "mdeg/s", 4, true),
Field::new("icm42688p.rate.bias.z", "mdeg/s", 4, true),
Field::new(ICM42688P_RATE_NOISE[0], "mdeg/s", 4, false),
Field::new(ICM42688P_RATE_NOISE[1], "mdeg/s", 4, false),
Field::new(ICM42688P_RATE_NOISE[2], "mdeg/s", 4, false),
Field::new("icm42688p.acceleration.stuck", "flag", 1, false),
Field::new("icm42688p.acceleration.bias.x", "1e-6 g", 4, true),
Field::new("icm42688p.acceleration.bias.y", "1e-6 g", 4, true),
Field::new("icm42688p.acceleration.bias.z", "1e-6 g", 4, true),
Field::new(ICM42688P_ACCELERATION_NOISE[0], "1e-6 g", 4, false),
Field::new(ICM42688P_ACCELERATION_NOISE[1], "1e-6 g", 4, false),
Field::new(ICM42688P_ACCELERATION_NOISE[2], "1e-6 g", 4, false),
Field::new("icm42605.silent", "flag", 1, false),
Field::new("icm42605.rate.stuck", "flag", 1, false),
Field::new("icm42605.rate.bias.x", "mdeg/s", 4, true),
Field::new("icm42605.rate.bias.y", "mdeg/s", 4, true),
Field::new("icm42605.rate.bias.z", "mdeg/s", 4, true),
Field::new(ICM42605_RATE_NOISE[0], "mdeg/s", 4, false),
Field::new(ICM42605_RATE_NOISE[1], "mdeg/s", 4, false),
Field::new(ICM42605_RATE_NOISE[2], "mdeg/s", 4, false),
Field::new("icm42605.acceleration.stuck", "flag", 1, false),
Field::new("icm42605.acceleration.bias.x", "1e-6 g", 4, true),
Field::new("icm42605.acceleration.bias.y", "1e-6 g", 4, true),
Field::new("icm42605.acceleration.bias.z", "1e-6 g", 4, true),
Field::new(ICM42605_ACCELERATION_NOISE[0], "1e-6 g", 4, false),
Field::new(ICM42605_ACCELERATION_NOISE[1], "1e-6 g", 4, false),
Field::new(ICM42605_ACCELERATION_NOISE[2], "1e-6 g", 4, false),
Field::new("dps310.silent", "flag", 1, false),
Field::new("dps310.pressure.stuck", "flag", 1, false),
Field::new("dps310.pressure.bias", "Pa", 4, true),
Field::new(DPS310_PRESSURE_NOISE[0], "Pa", 4, false),
Field::new("qmc5883l.silent", "flag", 1, false),
Field::new("qmc5883l.field.stuck", "flag", 1, false),
Field::new("qmc5883l.field.bias.x", "nT", 4, true),
Field::new("qmc5883l.field.bias.y", "nT", 4, true),
Field::new("qmc5883l.field.bias.z", "nT", 4, true),
Field::new(QMC5883L_FIELD_NOISE[0], "nT", 4, false),
Field::new(QMC5883L_FIELD_NOISE[1], "nT", 4, false),
Field::new(QMC5883L_FIELD_NOISE[2], "nT", 4, 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),
];
const REST: Signals = AT_REST;
const CHIPS: &'static [&'static str] = &["icm42688p", "icm42605", "gps"];
fn place(&mut self, chip: usize) -> Option<&mut dyn Placed> {
match *Self::CHIPS.get(chip)? {
"icm42688p" => self.mcu.spi[IMU1_ON].place_mut(ICM42688P_PEER),
"icm42605" => self.mcu.spi[IMU2_ON].place_mut(ICM42605_PEER),
"gps" => self.mcu.usart[GPS].place_mut(),
_ => None,
}
}
fn sense(&mut self, signals: &Signals) {
self.sensed = *signals;
let (conditions, handed) = (&self.conditions, &mut self.handed);
let unit: &mut Icm42688p = self.mcu.spi[IMU1_ON]
.chip_mut(ICM42688P_PEER)
.expect("fit hangs it");
let [x, y, z] = turned_yaw_180(
handed
.icm42688p_rate
.hand(signals.angular_rate, &conditions.icm42688p_rate),
);
unit.set_rate(x, y, z);
let [x, y, z] = turned_yaw_180(
handed
.icm42688p_acceleration
.hand(signals.specific_force, &conditions.icm42688p_acceleration),
);
unit.set_acceleration(x, y, z);
unit.set_temperature(signals.temperature);
let unit: &mut Icm42605 = self.mcu.spi[IMU2_ON]
.chip_mut(ICM42605_PEER)
.expect("fit hangs it");
let [x, y, z] = turned_yaw_270(
handed
.icm42605_rate
.hand(signals.angular_rate, &conditions.icm42605_rate),
);
unit.set_rate(x, y, z);
let [x, y, z] = turned_yaw_270(
handed
.icm42605_acceleration
.hand(signals.specific_force, &conditions.icm42605_acceleration),
);
unit.set_acceleration(x, y, z);
unit.set_temperature(signals.temperature);
let compass: &mut Qmc5883l = self.mcu.i2c[COMPASS_ON]
.chip_mut(QMC5883L_PEER)
.expect("fit hangs it");
let [x, y, z] = turned_for_the_compass(
handed
.qmc5883l_field
.hand(signals.magnetic_field, &conditions.qmc5883l_field),
);
compass.set_field(x, y, z);
compass.set_temperature(signals.temperature);
let barometer: &mut Dps310 = self.mcu.i2c[BARO_ON]
.chip_mut(DPS310_PEER)
.expect("fit hangs it");
let [pressure] = handed
.dps310_pressure
.hand([signals.pressure], &conditions.dps310_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 current = u32::try_from(signals.battery_current).unwrap_or(0) / MILLIAMPS_PER_MILLIVOLT;
for converter in &mut self.mcu.adc {
converter.set_input(BATTERY_VOLTAGE, signals.battery_voltage / PACK_PER_SENSED);
converter.set_input(BATTERY_CURRENT, current);
}
}
fn condition(&mut self, conditions: &Conditions) {
self.conditions = *conditions;
let fitted = self.mcu.spi[IMU1_ON].silence(ICM42688P_PEER, conditions.icm42688p_silent)
& self.mcu.spi[IMU2_ON].silence(ICM42605_PEER, conditions.icm42605_silent)
& self.mcu.i2c[BARO_ON].silence(DPS310_PEER, conditions.dps310_silent)
& self.mcu.i2c[COMPASS_ON].silence(QMC5883L_PEER, conditions.qmc5883l_silent)
& self.mcu.usart[GPS].silence(conditions.gps_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.icm42688p_rate.draw(
&conditions.icm42688p_rate,
&ICM42688P_RATE_NOISE,
seed,
index,
),
handed.icm42688p_acceleration.draw(
&conditions.icm42688p_acceleration,
&ICM42688P_ACCELERATION_NOISE,
seed,
index,
),
handed
.icm42605_rate
.draw(&conditions.icm42605_rate, &ICM42605_RATE_NOISE, seed, index),
handed.icm42605_acceleration.draw(
&conditions.icm42605_acceleration,
&ICM42605_ACCELERATION_NOISE,
seed,
index,
),
handed.qmc5883l_field.draw(
&conditions.qmc5883l_field,
&QMC5883L_FIELD_NOISE,
seed,
index,
),
handed.dps310_pressure.draw(
&conditions.dps310_pressure,
&DPS310_PRESSURE_NOISE,
seed,
index,
),
];
if moved.contains(&true) {
let sensed = self.sensed;
self.sense(&sensed);
}
}
fn drive(&mut self) -> Outputs {
let (motors, registers) = self.mcu.advanced_timer(MOTOR_TIMER);
let [one, two] = MOTOR_OUTPUTS
.map(|(name, channel)| actuator(name, motors.complementary_pulse(channel, registers)));
let (tim5, registers) = self.mcu.timer(TIM5);
let [three, four, five, six] =
TIM5_OUTPUTS.map(|(name, channel)| actuator(name, tim5.pulse(channel, registers)));
let (tim4, registers) = self.mcu.timer(TIM4);
let [seven, eight, nine, ten] =
TIM4_OUTPUTS.map(|(name, channel)| actuator(name, tim4.pulse(channel, registers)));
let (tim15, registers) = self.mcu.timer_15();
let [eleven, twelve] =
TIM15_OUTPUTS.map(|(name, channel)| actuator(name, tim15.pulse(channel, registers)));
let (led, registers) = self.mcu.advanced_timer(LED_TIMER);
let thirteen = actuator(LED_OUTPUT.0, led.pulse(LED_OUTPUT.1, registers));
flight::Outputs([
one, two, three, four, five, six, seven, eight, nine, ten, eleven, twelve, thirteen,
])
}
}
pub fn boxed() -> Result<Box<dyn Board>, Error> {
Ok(Box::new(MatekH743::new()?))
}
wire!(Conditions {
icm42688p_silent with flag,
icm42688p_rate,
icm42688p_acceleration,
icm42605_silent with flag,
icm42605_rate,
icm42605_acceleration,
dps310_silent with flag,
dps310_pressure,
qmc5883l_silent with flag,
qmc5883l_field,
gps_silent with flag,
gps_noise_errors,
gps_framing_errors,
});
#[cfg(test)]
mod tests {
use super::*;
use crate::chip::{Timeline, i2c, spi};
use crate::edge::normal;
use crate::link::serve::deliver;
use crate::link::{Advance, Held, Set, Wire, values_of};
use crate::machine::flight::conformance::{self, wrong};
use crate::mcu::{Operate, Single};
use crate::soc::stm32h743::generated::{RCC, TIM1, TIM4, TIM5, TIM8, TIM15};
use crate::soc::stm32h743::generated::{rcc, tim1, tim2, tim15};
use crate::soc::stm32h743::peripherals::rcc::TimerBus;
use crate::soc::stm32h743::peripherals::spi::{Kind, Spi};
use crate::soc::stm32h743::peripherals::usart;
use crate::soc::{Layout, Peripheral, Width};
const MILLISECOND: u64 = 1_000_000;
const MICROSECOND: u64 = 1_000;
const READ: u8 = 0x80;
const PWR_MGMT0: u8 = 0x4E;
const BOTH_ON: u8 = 0x0F;
const TEMP_DATA1: u8 = 0x1D;
const OUT_X_L: u8 = 0x00;
const TOUT_L: u8 = 0x07;
const CONTROL1: u8 = 0x09;
const CONTINUOUS: u8 = 0x01;
const ODR_200: u8 = 0x0C;
const RNG_8G: u8 = 0x10;
const COMPASS_ADDRESS: u8 = 0x0D;
const MEAS_CFG: u8 = 0x08;
const PRESSURE_ONCE: u8 = 0x01;
const TEMPERATURE_ONCE: u8 = 0x02;
const BARO_ADDRESS: u8 = 0x76;
const PWM_MODE_1: u64 = 0b110;
const PWM_PERIOD_COUNTS: u64 = 20_000;
const BODY: Signals = Signals {
angular_rate: [30_000, -50_000, 70_000],
specific_force: [1_250_000, -2_500_000, 750_000],
magnetic_field: [30_000, -20_000, 45_000],
pressure: 95_000,
..AT_REST
};
fn ardupilot_yaw_90([x, y, z]: [i32; 3]) -> [i32; 3] {
[-y, x, z]
}
fn ardupilot_roll_180([x, y, z]: [i32; 3]) -> [i32; 3] {
[x, -y, -z]
}
fn ardupilot_qmc5883l_driver([x, y, z]: [i32; 3]) -> [i32; 3] {
ardupilot_roll_180(ardupilot_yaw_90([-x, y, -z]))
}
fn ardupilot_yaw_180([x, y, z]: [i32; 3]) -> [i32; 3] {
[-x, -y, z]
}
fn ardupilot_yaw_270([x, y, z]: [i32; 3]) -> [i32; 3] {
[y, -x, z]
}
fn behind_yaw_180([x, y, z]: [i32; 3]) -> [i32; 3] {
[-x, -y, z]
}
fn behind_yaw_270([x, y, z]: [i32; 3]) -> [i32; 3] {
[-y, x, z]
}
fn behind_the_compass([x, y, z]: [i32; 3]) -> [i32; 3] {
[y, -x, z]
}
fn counts(value: i32, counts_per_million: i64) -> i32 {
i32::try_from(crate::chip::round(
i64::from(value) * counts_per_million,
1_000_000,
))
.expect("a count fits")
}
fn counts_of(body: [i32; 3], counts_per_million: i64) -> [i32; 3] {
body.map(|value| counts(value, counts_per_million))
}
fn near(found: [i32; 3], expected: [i32; 3], what: &str) {
for axis in 0..3 {
assert!(
found[axis].abs_diff(expected[axis]) <= 1,
"{what}: {found:?} against {expected:?}"
);
}
}
const ACCEL_COUNTS_PER_MILLION: i64 = 2048;
const RATE_COUNTS_PER_MILLION: i64 = 16_384;
const FIELD_COUNTS_PER_MILLION: i64 = 3_000 * 10;
fn words(bytes: &[u8], big_endian: bool) -> Vec<i32> {
bytes
.chunks(2)
.map(|pair| {
i32::from(if big_endian {
i16::from_be_bytes([pair[0], pair[1]])
} else {
i16::from_le_bytes([pair[0], pair[1]])
})
})
.collect()
}
fn triple(words: &[i32]) -> [i32; 3] {
[words[0], words[1], words[2]]
}
fn bus() -> Single<Spi> {
Single::new(
crate::soc::stm32h743::builder(),
"SPI1",
Spi::new(Kind::FULL),
)
.expect("the bus builds")
}
fn spi_cycle(
bus: &mut Single<Spi>,
at: u64,
chip: &mut dyn spi::Peer,
bytes: &[u8],
) -> Vec<u8> {
bus.drive(VirtualNanos(at), |_, _, context| {
let context = &mut Timeline::new(context);
chip.select(context);
let answers = bytes
.iter()
.map(|&byte| u8::try_from(chip.transfer(u32::from(byte), 8, context)).unwrap())
.collect();
chip.deselect(context);
answers
})
}
fn i2c_write(
bus: &mut Single<Spi>,
at: u64,
chip: &mut dyn i2c::Peer,
address: u8,
bytes: &[u8],
) {
bus.drive(VirtualNanos(at), |_, _, context| {
let context = &mut Timeline::new(context);
assert!(
chip.start(address, false, context),
"{address:#04x} acknowledges"
);
for &byte in bytes {
assert!(chip.write(byte, context));
}
chip.stop(context);
});
}
fn i2c_read(
bus: &mut Single<Spi>,
at: u64,
chip: &mut dyn i2c::Peer,
(address, register): (u8, u8),
count: usize,
) -> Vec<u8> {
bus.drive(VirtualNanos(at), |_, _, context| {
let context = &mut Timeline::new(context);
assert!(
chip.start(address, false, context),
"{address:#04x} acknowledges"
);
assert!(chip.write(register, context));
assert!(chip.start(address, true, context));
let bytes = (0..count)
.map(|index| chip.read(index + 1 == count, context))
.collect();
chip.stop(context);
bytes
})
}
fn inertial_words(unit: &mut dyn spi::Peer) -> Vec<i32> {
let mut bus = bus();
spi_cycle(&mut bus, MILLISECOND, unit, &[PWR_MGMT0, BOTH_ON]);
let mut frame = vec![TEMP_DATA1 | READ];
frame.extend([0; 14]);
let bytes = spi_cycle(&mut bus, 6 * MILLISECOND, unit, &frame);
words(&bytes[1..], true)
}
fn compass_words(compass: &mut dyn i2c::Peer) -> Vec<i32> {
let mut bus = bus();
i2c_write(
&mut bus,
MILLISECOND,
compass,
COMPASS_ADDRESS,
&[CONTROL1, RNG_8G | ODR_200 | CONTINUOUS],
);
let mut bytes = i2c_read(
&mut bus,
30 * MILLISECOND,
compass,
(COMPASS_ADDRESS, OUT_X_L),
6,
);
bytes.extend(i2c_read(
&mut bus,
31 * MILLISECOND,
compass,
(COMPASS_ADDRESS, TOUT_L),
2,
));
words(&bytes, false)
}
fn barometer_bytes(barometer: &mut dyn i2c::Peer) -> Vec<u8> {
let mut bus = bus();
i2c_write(
&mut bus,
50 * MILLISECOND,
barometer,
BARO_ADDRESS,
&[MEAS_CFG, PRESSURE_ONCE],
);
let mut bytes = i2c_read(
&mut bus,
100 * MILLISECOND,
barometer,
(BARO_ADDRESS, 0x00),
3,
);
i2c_write(
&mut bus,
110 * MILLISECOND,
barometer,
BARO_ADDRESS,
&[MEAS_CFG, TEMPERATURE_ONCE],
);
bytes.extend(i2c_read(
&mut bus,
160 * MILLISECOND,
barometer,
(BARO_ADDRESS, 0x03),
3,
));
bytes
}
#[derive(Debug, PartialEq)]
struct Frames {
icm42688p: Vec<i32>,
icm42605: Vec<i32>,
compass: Vec<i32>,
barometer: Vec<u8>,
}
impl Frames {
fn sampled(self) -> Self {
for unit in [&self.icm42688p, &self.icm42605] {
assert!(
unit[1..7].iter().all(|&word| word != i32::from(i16::MIN)),
"{unit:?}"
);
}
assert!(self.compass[0..3].iter().any(|&word| word != 0), "{self:?}");
assert!(self.barometer.iter().any(|&byte| byte != 0), "{self:?}");
self
}
}
fn frames(board: &mut MatekH743) -> Frames {
Frames {
icm42688p: inertial_words(
board.mcu.spi[IMU1_ON]
.chip_mut::<Icm42688p>(ICM42688P_PEER)
.unwrap(),
),
icm42605: inertial_words(
board.mcu.spi[IMU2_ON]
.chip_mut::<Icm42605>(ICM42605_PEER)
.unwrap(),
),
compass: compass_words(
board.mcu.i2c[COMPASS_ON]
.chip_mut::<Qmc5883l>(QMC5883L_PEER)
.unwrap(),
),
barometer: barometer_bytes(
board.mcu.i2c[BARO_ON]
.chip_mut::<Dps310>(DPS310_PEER)
.unwrap(),
),
}
.sampled()
}
struct ByHand {
icm42688p: ([i32; 3], [i32; 3]),
icm42605: ([i32; 3], [i32; 3]),
field: [i32; 3],
pressure: u32,
temperature: i32,
}
fn reference_frames(handed: &ByHand) -> Frames {
let mut icm42688p = Icm42688p::new();
icm42688p.set_rate(
handed.icm42688p.0[0],
handed.icm42688p.0[1],
handed.icm42688p.0[2],
);
icm42688p.set_acceleration(
handed.icm42688p.1[0],
handed.icm42688p.1[1],
handed.icm42688p.1[2],
);
icm42688p.set_temperature(handed.temperature);
let mut icm42605 = Icm42605::new();
icm42605.set_rate(
handed.icm42605.0[0],
handed.icm42605.0[1],
handed.icm42605.0[2],
);
icm42605.set_acceleration(
handed.icm42605.1[0],
handed.icm42605.1[1],
handed.icm42605.1[2],
);
icm42605.set_temperature(handed.temperature);
let mut compass = Qmc5883l::new();
compass.set_field(handed.field[0], handed.field[1], handed.field[2]);
compass.set_temperature(handed.temperature);
let mut barometer = Dps310::new(Sdo::Low);
barometer.set_pressure(handed.pressure);
barometer.set_temperature(handed.temperature);
Frames {
icm42688p: inertial_words(&mut icm42688p),
icm42605: inertial_words(&mut icm42605),
compass: compass_words(&mut compass),
barometer: barometer_bytes(&mut barometer),
}
.sampled()
}
fn handed_clean(signals: &Signals) -> ByHand {
ByHand {
icm42688p: (
behind_yaw_180(signals.angular_rate),
behind_yaw_180(signals.specific_force),
),
icm42605: (
behind_yaw_270(signals.angular_rate),
behind_yaw_270(signals.specific_force),
),
field: behind_the_compass(signals.magnetic_field),
pressure: signals.pressure,
temperature: signals.temperature,
}
}
fn put(board: &mut MatekH743, peripheral: &Peripheral, register: impl Layout, value: u64) {
board
.mcu
.write(
VirtualNanos(0),
peripheral.base + u64::from(register.spec().offset),
Width::W32,
value,
)
.unwrap();
}
#[test]
fn the_board_runs_one_cortex_m7_with_double_precision_and_sixteen_mpu_regions() {
let board = MatekH743::new().unwrap();
let config = board.config();
assert_eq!(config.core.variant, armv7m::Variant::M7);
assert_eq!(config.fpu, armv7m::Fpu::FpV5Dp);
assert_eq!(config.mpu_regions, armv7m::MpuRegions::Sixteen);
}
#[test]
fn the_declared_fields_walk_the_encoded_signals_one_by_one() {
conformance::the_declared_fields_walk_the_encoded_signals_one_by_one::<MatekH743>();
}
#[test]
fn the_declared_conditions_walk_the_encoded_conditions_one_by_one_and_rest_at_zero() {
let conditions = Conditions {
icm42688p_silent: true,
icm42688p_rate: wrong(true, -1),
icm42688p_acceleration: wrong(false, -4),
icm42605_silent: false,
icm42605_rate: wrong(true, -7),
icm42605_acceleration: wrong(false, -10),
dps310_silent: true,
dps310_pressure: Condition {
stuck: false,
bias: [-13],
noise: [13_001],
},
qmc5883l_silent: false,
qmc5883l_field: wrong(true, -16),
gps_silent: true,
gps_noise_errors: 3_000_000_000,
gps_framing_errors: 7,
};
conformance::the_declared_conditions_walk_the_encoded_conditions_one_by_one_and_rest_at_zero::<
MatekH743,
>(
&conditions,
&[
1,
1,
-1,
-2,
-3,
1_001,
1_002,
1_003,
0,
-4,
-5,
-6,
4_001,
4_002,
4_003,
0,
1,
-7,
-8,
-9,
7_001,
7_002,
7_003,
0,
-10,
-11,
-12,
10_001,
10_002,
10_003,
1,
0,
-13,
13_001,
0,
1,
-16,
-17,
-18,
16_001,
16_002,
16_003,
1,
3_000_000_000,
7,
],
);
}
#[test]
fn the_edge_passes_the_links_own_check() {
conformance::the_edge_passes_the_links_own_check(&mut MatekH743::new().unwrap());
}
#[test]
fn the_icm42688p_is_handed_the_body_axes_turned_back_by_yaw_180() {
let mut board = MatekH743::new().unwrap();
board.sense(&BODY);
let found = frames(&mut board).icm42688p;
let mut by_hand = Icm42688p::new();
by_hand.set_rate(-30_000, 50_000, 70_000);
by_hand.set_acceleration(-1_250_000, 2_500_000, 750_000);
by_hand.set_temperature(BODY.temperature);
assert_eq!(found, inertial_words(&mut by_hand));
near(
ardupilot_yaw_180(triple(&found[1..4])),
counts_of(BODY.specific_force, ACCEL_COUNTS_PER_MILLION),
"the force after the firmware's rotation",
);
near(
ardupilot_yaw_180(triple(&found[4..7])),
counts_of(BODY.angular_rate, RATE_COUNTS_PER_MILLION),
"the rate after the firmware's rotation",
);
}
#[test]
fn the_icm42605_is_handed_the_body_axes_turned_back_by_yaw_270() {
let mut board = MatekH743::new().unwrap();
board.sense(&BODY);
let found = frames(&mut board).icm42605;
let mut by_hand = Icm42605::new();
by_hand.set_rate(50_000, 30_000, 70_000);
by_hand.set_acceleration(2_500_000, 1_250_000, 750_000);
by_hand.set_temperature(BODY.temperature);
assert_eq!(found, inertial_words(&mut by_hand));
near(
ardupilot_yaw_270(triple(&found[1..4])),
counts_of(BODY.specific_force, ACCEL_COUNTS_PER_MILLION),
"the force after the firmware's rotation",
);
near(
ardupilot_yaw_270(triple(&found[4..7])),
counts_of(BODY.angular_rate, RATE_COUNTS_PER_MILLION),
"the rate after the firmware's rotation",
);
}
#[test]
fn the_qmc5883l_is_handed_the_body_axes_the_firmware_recovers() {
let mut board = MatekH743::new().unwrap();
board.sense(&BODY);
let found = frames(&mut board).compass;
let mut by_hand = Qmc5883l::new();
by_hand.set_field(-20_000, -30_000, 45_000);
by_hand.set_temperature(BODY.temperature);
assert_eq!(found, compass_words(&mut by_hand));
near(
ardupilot_qmc5883l_driver(triple(&found[0..3])),
counts_of(BODY.magnetic_field, FIELD_COUNTS_PER_MILLION),
"the field after the firmware's driver",
);
}
#[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 before = Signals {
angular_rate: [20_000, -30_000, 40_000],
specific_force: [500_000, -600_000, -700_000],
magnetic_field: [30_000, -20_000, 45_000],
pressure: 95_000,
..AT_REST
};
let after = Signals {
specific_force: [100_000, 200_000, -900_000],
..before
};
for resensed in [false, true] {
let mut board = MatekH743::new().unwrap();
board.sense(&before);
board.condition(&Conditions {
icm42688p_rate: biased(1_000),
icm42605_acceleration: Condition {
stuck: true,
..biased(10_000)
},
qmc5883l_field: biased(4_000),
dps310_pressure: Condition {
bias: [-500],
..Condition::default()
},
..Conditions::default()
});
let now = if resensed {
board.sense(&after);
&after
} else {
&before
};
let expected = ByHand {
icm42688p: (
behind_yaw_180(plus(before.angular_rate, 1_000)),
behind_yaw_180(now.specific_force),
),
icm42605: (
behind_yaw_270(before.angular_rate),
behind_yaw_270(before.specific_force),
),
field: behind_the_compass(plus(before.magnetic_field, 4_000)),
pressure: 94_500,
temperature: before.temperature,
};
assert_eq!(
frames(&mut board),
reference_frames(&expected),
"after another sense: {resensed}"
);
}
}
#[test]
fn each_chips_noise_is_its_own_and_moves_with_each_advance() {
const SEED: u64 = 7;
let noisy = [
(ICM42688P_RATE_NOISE[0], 2_000),
(ICM42605_ACCELERATION_NOISE[0], 30_000),
(QMC5883L_FIELD_NOISE[0], 5_000),
(DPS310_PRESSURE_NOISE[0], 50),
];
for advances in 1..=2_u64 {
let mut board = MatekH743::new().unwrap();
let mut held = Held::at_rest(&board, SEED).unwrap();
for advance in 0..advances {
let mut quiet = Advance::new(VirtualNanos(0));
if advance == 0 {
quiet.conditions = noisy
.iter()
.map(|&(name, deviation)| Set {
index: u16::try_from(
<MatekH743 as Edge>::CONDITIONS
.iter()
.position(|field| field.name == name)
.unwrap(),
)
.unwrap(),
value: deviation,
})
.collect();
}
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()
};
assert_ne!(drawn(noisy[3]), 0, "the pressure's noise is there to see");
assert_ne!(drawn(noisy[2]), 0, "the field's noise is there to see");
let [x, y, z] = AT_REST.angular_rate;
let rate = [x + drawn(noisy[0]), y, z];
let [x, y, z] = AT_REST.specific_force;
let force = [x + drawn(noisy[1]), y, z];
let [x, y, z] = AT_REST.magnetic_field;
let field = [x + drawn(noisy[2]), y, z];
let expected = ByHand {
icm42688p: (behind_yaw_180(rate), behind_yaw_180(AT_REST.specific_force)),
icm42605: (behind_yaw_270(AT_REST.angular_rate), behind_yaw_270(force)),
field: behind_the_compass(field),
pressure: AT_REST.pressure.saturating_add_signed(drawn(noisy[3])),
temperature: AT_REST.temperature,
};
assert_eq!(
frames(&mut board),
reference_frames(&expected),
"after {advances} advances"
);
}
}
#[test]
fn the_board_is_built_at_rest() {
let mut board = MatekH743::new().unwrap();
let found = frames(&mut board);
assert_eq!(found, reference_frames(&handed_clean(&AT_REST)));
for unit in [&found.icm42688p, &found.icm42605] {
assert_eq!(
unit[1..],
[0, 0, -2048, 0, 0, 0],
"a gravity's reaction against z and no turn"
);
}
near(
triple(&found.compass[0..3]),
[0, -660, 1260],
"22 000 nT north and 42 000 nT down through the mounting",
);
conformance::the_board_is_built_at_rest(&mut board);
}
#[test]
fn each_chip_listed_has_a_silence_and_a_place() {
assert_eq!(<MatekH743 as Edge>::CHIPS, ["icm42688p", "icm42605", "gps"]);
conformance::each_chip_listed_has_a_silence_and_a_place(&mut MatekH743::new().unwrap());
for silent in ["dps310.silent", "qmc5883l.silent"] {
assert!(
<MatekH743 as Edge>::CONDITIONS
.iter()
.any(|condition| condition.name == silent),
"{silent} is a condition"
);
}
}
#[test]
fn the_receiver_on_the_gps_port_reports_where_the_board_is() {
assert_eq!(usart::INSTANCES[GPS], "USART2");
conformance::the_receiver_on_the_gps_port_reports_where_the_board_is(
&mut MatekH743::new().unwrap(),
|board| board.mcu.usart[GPS].peer_mut().expect("fit hangs it"),
);
}
fn pulse_counts(pin: u64) -> u64 {
1_000 + 50 * pin
}
fn pwm(mode: &FieldSpec, preload: &FieldSpec) -> u64 {
mode.set(0, PWM_MODE_1) | preload.mask()
}
fn run_general(board: &mut MatekH743, timer: &Peripheral, hz: u64, first_pin: u64) {
put(board, timer, tim2::Register::Psc, hz / 1_000_000 - 1);
put(board, timer, tim2::Register::Arr, PWM_PERIOD_COUNTS - 1);
for (register, pin) in [
(tim2::Register::Ccr1, first_pin),
(tim2::Register::Ccr2, first_pin + 1),
(tim2::Register::Ccr3, first_pin + 2),
(tim2::Register::Ccr4, first_pin + 3),
] {
put(board, timer, register, pulse_counts(pin));
}
put(
board,
timer,
tim2::Register::Ccmr1Output,
pwm(tim2::ccmr1_output::OC1M, tim2::ccmr1_output::OC1PE)
| pwm(tim2::ccmr1_output::OC2M, tim2::ccmr1_output::OC2PE),
);
put(
board,
timer,
tim2::Register::Ccmr2Output,
pwm(tim2::ccmr2_output::OC3M, tim2::ccmr2_output::OC3PE)
| pwm(tim2::ccmr2_output::OC4M, tim2::ccmr2_output::OC4PE),
);
put(
board,
timer,
tim2::Register::Ccer,
tim2::ccer::CC1E.mask()
| tim2::ccer::CC2E.mask()
| tim2::ccer::CC3E.mask()
| tim2::ccer::CC4E.mask(),
);
put(board, timer, tim2::Register::Egr, tim2::egr::UG.mask());
put(board, timer, tim2::Register::Cr1, tim2::cr1::CEN.mask());
}
fn run_tim15(board: &mut MatekH743, hz: u64) {
put(board, &TIM15, tim15::Register::Psc, hz / 1_000_000 - 1);
put(board, &TIM15, tim15::Register::Arr, PWM_PERIOD_COUNTS - 1);
put(board, &TIM15, tim15::Register::Ccr1, pulse_counts(11));
put(board, &TIM15, tim15::Register::Ccr2, pulse_counts(12));
put(
board,
&TIM15,
tim15::Register::Ccmr1Output,
pwm(tim15::ccmr1_output::OC1M, tim15::ccmr1_output::OC1PE)
| pwm(tim15::ccmr1_output::OC2M, tim15::ccmr1_output::OC2PE),
);
put(
board,
&TIM15,
tim15::Register::Ccer,
tim15::ccer::CC1E.mask() | tim15::ccer::CC2E.mask(),
);
put(
board,
&TIM15,
tim15::Register::Bdtr,
tim15::bdtr::MOE.mask(),
);
put(board, &TIM15, tim15::Register::Egr, tim15::egr::UG.mask());
put(board, &TIM15, tim15::Register::Cr1, tim15::cr1::CEN.mask());
}
fn run_tim1(board: &mut MatekH743, hz: u64) {
put(board, &TIM1, tim1::Register::Psc, hz / 1_000_000 - 1);
put(board, &TIM1, tim1::Register::Arr, PWM_PERIOD_COUNTS - 1);
put(board, &TIM1, tim1::Register::Ccr1, pulse_counts(13));
put(
board,
&TIM1,
tim1::Register::Ccmr1Output,
pwm(tim1::ccmr1_output::OC1M, tim1::ccmr1_output::OC1PE),
);
put(board, &TIM1, tim1::Register::Ccer, tim1::ccer::CC1E.mask());
put(board, &TIM1, tim1::Register::Bdtr, tim1::bdtr::MOE.mask());
put(board, &TIM1, tim1::Register::Egr, tim1::egr::UG.mask());
put(board, &TIM1, tim1::Register::Cr1, tim1::cr1::CEN.mask());
}
fn run_tim8(board: &mut MatekH743, hz: u64) {
put(board, &TIM8, tim1::Register::Psc, hz / 1_000_000 - 1);
put(board, &TIM8, tim1::Register::Arr, PWM_PERIOD_COUNTS - 1);
put(board, &TIM8, tim1::Register::Ccr2, pulse_counts(1));
put(board, &TIM8, tim1::Register::Ccr3, pulse_counts(2));
put(
board,
&TIM8,
tim1::Register::Ccmr1Output,
pwm(tim1::ccmr1_output::OC2M, tim1::ccmr1_output::OC2PE),
);
put(
board,
&TIM8,
tim1::Register::Ccmr2Output,
pwm(tim1::ccmr2_output::OC3M, tim1::ccmr2_output::OC3PE),
);
put(
board,
&TIM8,
tim1::Register::Ccer,
tim1::ccer::CC2NE.mask() | tim1::ccer::CC3NE.mask(),
);
put(board, &TIM8, tim1::Register::Egr, tim1::egr::UG.mask());
put(board, &TIM8, tim1::Register::Cr1, tim1::cr1::CEN.mask());
}
#[test]
fn the_output_fields_name_the_thirteen_pins_in_order() {
let named: Vec<String> = (1..=13)
.flat_map(|pin| ["width", "period", "command"].map(|part| format!("PWM{pin}.{part}")))
.collect();
let fields: Vec<&str> = <MatekH743 as Edge>::OUTPUT_FIELDS
.iter()
.map(|field| field.name.as_ref())
.collect();
assert_eq!(fields, named);
assert_eq!(
Outputs::IDLE.0.map(|actuator| actuator.name),
[
"PWM1", "PWM2", "PWM3", "PWM4", "PWM5", "PWM6", "PWM7", "PWM8", "PWM9", "PWM10",
"PWM11", "PWM12", "PWM13"
]
);
}
#[test]
fn the_outputs_are_the_description_s_thirteen_pwm_pins_read_off_their_timers() {
let mut board = MatekH743::new().unwrap();
assert_eq!(
board.drive(),
Outputs::IDLE,
"nothing drives a pin before the firmware"
);
let (clocks, registers) = board.mcu.clocks();
let apb1 = clocks.timer_clock(registers, TimerBus::Apb1);
let apb2 = clocks.timer_clock(registers, TimerBus::Apb2);
assert!(apb1 % 1_000_000 == 0 && apb2 % 1_000_000 == 0);
put(
&mut board,
&RCC,
rcc::Register::Apb1lenr,
rcc::apb1lenr::TIM4EN.mask() | rcc::apb1lenr::TIM5EN.mask(),
);
put(
&mut board,
&RCC,
rcc::Register::Apb2enr,
rcc::apb2enr::TIM1EN.mask()
| rcc::apb2enr::TIM8EN.mask()
| rcc::apb2enr::TIM15EN.mask(),
);
run_general(&mut board, &TIM5, apb1, 3);
run_general(&mut board, &TIM4, apb1, 7);
run_tim15(&mut board, apb2);
run_tim1(&mut board, apb2);
run_tim8(&mut board, apb2);
let driving = |pin: u64| Actuator {
name: Outputs::IDLE.0[usize::try_from(pin).unwrap() - 1].name,
pulse: Some(Pulse {
width: VirtualNanos(pulse_counts(pin) * MICROSECOND),
period: VirtualNanos(PWM_PERIOD_COUNTS * MICROSECOND),
}),
command: Some(u32::try_from(50 * pin).unwrap()),
};
let mut expected = flight::Outputs(std::array::from_fn(|index| {
driving(u64::try_from(index).unwrap() + 1)
}));
expected.0[0] = Outputs::IDLE.0[0];
expected.0[1] = Outputs::IDLE.0[1];
assert_eq!(
board.drive(),
expected,
"TIM8's complementary pins are held low until MOE"
);
put(
&mut board,
&TIM8,
tim1::Register::Bdtr,
tim1::bdtr::MOE.mask(),
);
let outputs = board.drive();
expected.0[0] = driving(1);
expected.0[1] = driving(2);
assert_eq!(outputs, expected);
let mut bytes = Vec::new();
outputs.encode(&mut bytes);
let values = values_of(<MatekH743 as Edge>::OUTPUT_FIELDS, &bytes).unwrap();
let on_the_wire: Vec<i64> = (1..=13)
.flat_map(|pin| {
[
i64::try_from(pulse_counts(pin) * MICROSECOND).unwrap(),
i64::try_from(PWM_PERIOD_COUNTS * MICROSECOND).unwrap(),
i64::try_from(50 * pin).unwrap(),
]
})
.collect();
assert_eq!(values, on_the_wire);
assert_eq!(
Outputs::decode(&mut bytes.as_slice()).unwrap(),
outputs,
"an output with no period comes back with no pulse and no command"
);
}
}