// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The analog-to-digital converters of RM0433 rev 9 chapter 25, written against the `adc3` layout and
//! answering for `ADC1`, `ADC2` and `ADC3` ([`Adc`]), and the two blocks of common registers at
//! `0x300` from the first and third converters ([`AdcCommon`]). Every citation is RM0433 rev 9
//! unless it names another document. This part is revision V, so the revision V text and figures
//! of the chapter are the ones read.
//!
//! **A converter reads a voltage and the board says what is on the pin.** [`Adc::set_input`] puts
//! millivolts on a channel and [`Adc::set_reference`] says what `VREF+` is. The word a conversion
//! answers is the ratio of the two at the full scale of the resolution, `ADC_DATA = VIN x
//! FULL_SCALE / VREF+`, which section 25.4.35 gives with `FULL_SCALE` of `2^N - 1`, rounded down.
//! `ADC3` also converts three internal sources through the channels of Table 209: `VBAT` over four on
//! channel 17, the temperature sensor on 18 and `VREFINT` on 19. They answer once the common block
//! has switched them on, from DS12110 rev 11 Tables 117, 180 and 182.
//!
//! **Time.** The analog block runs at `Fadc_ker_ck`, the clock the common block derives from the
//! kernel clock and `PRESC`, and halves, section 25.4.3 and its figure 136. A conversion lasts `TSMPL + TSAR`, section 25.4.17: the sampling time `SMP` names,
//! 1.5 to 810.5 cycles, then 0.5 + N/2 cycles for an N-bit result, Table 214. The sum of the two is
//! always a whole number of cycles, so the model counts half cycles and carries the remainder of a
//! division from one conversion to the next. A run of conversions is timed from where it began. The
//! calibration, the enable and the regulator take the datasheet's figures from Table 174: 1280 cycles
//! of offset calibration or 165010 with the linearity, one cycle of start-up and 5 microseconds
//! for the regulator, which the table gives as typical with a maximum of 10. The pin is read at the
//! end of the conversion.
//!
//! **What a converter drives.** Request output [`DMA_REQUEST`] is the DMA request, which the request
//! multiplexer's rows for the converters name. Output [`ENABLED_REQUEST`] is `ADEN`, as a level, which
//! the common block reads to tell a write of `ADCx_CCR` made while a converter is enabled. The one
//! interrupt line is position 0 of the instance's table, the converter's own line of Table 147.
//!
//! **The common block is a clock and three request lines.** [`AdcCommon`] runs on the kernel clock
//! the clock controller gates for it, turns it into `Fadc_ker_ck` and drives that to its converters
//! as clock output 0, which is the clock the converters run on. `VREFEN`, `TSEN` and `VBATEN` reach
//! `ADC3` as request outputs 0 to 2.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | `ADC_CR` is `0x2000 0000`, the converter in deep-power-down, and `ADC_CFGR` `0x8000 0000` with the injected queue disabled. `ADC_HTR1` to `ADC_HTR3` are `0x03FF FFFF` | 25.6.3, 25.6.4, 25.6.10, 25.6.22, 25.6.24 | `reset_values_match_the_manual` |
//! | Deep-power-down, `DEEPPWD`, `ADVREGEN`, `LDORDY` | The regulator starts when `ADVREGEN` is set with `DEEPPWD` clear and `LDORDY` rises 5 microseconds later. `DEEPPWD` set clears `ADVREGEN` and `LDORDY` and cancels a start in flight. Clearing `ADVREGEN` drops `LDORDY` at once | 25.4.6, 25.6.1, 25.6.3, DS12110 rev 11 Table 174 | `the_regulator_is_ready_after_the_datasheets_start_up_time`, `deep_power_down_clears_the_regulator_and_cancels_a_start`, `clearing_advregen_drops_ldordy_at_once` |
//! | Calibration, `ADCAL`, `ADCALDIF`, `ADCALLIN` | `ADCAL` stays set for 1280 cycles, or 165010 with `ADCALLIN`, and clears at the end. The linearity words are ready at the end of a linearity calibration. It needs `ADEN` clear | 25.4.8, 25.6.3, DS12110 rev 11 Table 174 | `calibration_takes_the_datasheets_cycles`, `a_linearity_calibration_sets_the_word_ready_bits_at_its_end`, `a_calibration_started_with_the_converter_enabled_is_told_and_not_started`, `a_calibration_before_the_regulator_is_ready_is_told_and_runs` |
//! | `ADEN`, `ADRDY` | `ADRDY` rises one cycle after `ADEN` is set, and a regulator still starting delays it to its end. `DEEPPWD` set always clears `ADVREGEN`. Written zero to a command bit does nothing. `ADRDY` clears by a written one | 25.4.9, 25.6.1, 25.6.3 | `adrdy_rises_one_cycle_after_aden`, `a_written_zero_leaves_a_command_bit_alone`, `enabling_before_the_regulator_is_ready_waits_for_it_and_is_told` |
//! | `ADDIS` | `ADEN` and `ADDIS` clear together | 25.4.9, 25.6.3 | `adddis_disables_the_converter_and_clears_with_aden`, `disabling_while_a_conversion_runs_is_told_and_refused` |
//! | `ADSTART` before `ADRDY` | Held until `ADRDY` rises, then the sequence starts at that instant. Told, since 25.4.9 says the converter is not accurate before its start-up time | 25.4.9, 25.6.3 | `a_start_before_the_converter_is_ready_waits_for_it_and_is_told` |
//! | Constraints on writes | A configuration bit written while the converter is busy, a start with `ADEN` clear, a stop with nothing running, and a conversion setting written during a conversion are told | 25.4.10, 25.6.3 | `configuration_written_while_busy_is_told_and_kept_back`, `a_start_with_the_converter_disabled_is_told_and_refused`, `a_stop_with_nothing_running_is_told`, `conversion_settings_written_during_a_conversion_are_told` |
//! | Channels and the sequence | `SQR1.L` plus one channels, read from `SQ1` to `SQ16` across `SQR1` to `SQR4`. Channels 20 to 31 are reserved and told | 25.4.11, 25.6.11 to 25.6.14 | `a_scan_converts_the_sequence_in_order`, `sixteen_channels_reach_the_fourth_sequence_register`, `reserved_channels_are_told` |
//! | `PCSEL` | A channel in the sequence that `PCSEL` leaves out is told, and converted all the same | 25.4.12 | `a_channel_that_is_not_preselected_is_told` |
//! | `SMPR1`, `SMPR2` | `SMP0`, which the vendor's file leaves out of `SMPR1`, is declared. Channel `x` samples for the cycles its `SMPx` names: 1.5, 2.5, 8.5, 16.5, 32.5, 64.5, 387.5 or 810.5 | 25.4.13, 25.6.6, 25.6.7 | `the_sampling_time_and_the_resolution_set_the_conversion_time`, `channel_zero_samples_for_the_time_smp0_names` |
//! | Resolution, `RES` | 16, 14, 12, 10 or 8 bits, revision V's codes 000, 001 or 101, 010 or 110, 011 and 111, with `TSAR` of 8.5, 7.5, 6.5, 5.5 and 4.5 cycles. Code 100 is revision Y's 8 bits and reserved on V, so it is told and converts as 16 bits | 25.4.23 Table 214, 25.6.4 | `every_resolution_code_gives_its_full_scale`, `a_reserved_resolution_is_told_and_converts_at_sixteen_bits`, `the_sampling_time_and_the_resolution_set_the_conversion_time` |
//! | The word | The pin's share of `VREF+` at the resolution, saturated at full scale | 25.4.35 | `the_word_is_the_pins_share_of_the_reference` |
//! | `ADSTART`, single mode | A software start converts the sequence once and `ADSTART` clears at `EOS` | 25.4.14, 25.4.16, 25.6.3 | `a_single_conversion_ends_at_its_instant_with_the_pins_word` |
//! | Continuous mode, `CONT` | The sequence starts again at `SQ1` as it ends, `ADSTART` stays set, `EOS` rises at each pass | 25.4.15, 25.4.26 | `continuous_mode_runs_the_sequence_again` |
//! | `ADSTP` | A conversion in flight is dropped with its partial result, `ADSTART` and `ADSTP` clear and the sequence restarts at `SQ1` | 25.4.18, 25.6.3 | `adstp_drops_the_conversion_and_resets_the_sequence` |
//! | `EOC`, `EOS` | `EOC` rises with each word and clears by a `DR` read or a written one. `EOS` rises with the last word of the sequence and clears by a written one | 25.4.24, 25.4.25, 25.6.1 | `eoc_clears_by_a_data_read_and_eos_by_a_written_one` |
//! | `EOSMP` | Rises at the end of the sampling time, `SMP` cycles into the conversion, and clears by a written one | 25.4.24, 25.6.1 | `eosmp_rises_at_the_end_of_the_sampling_time`, `a_debugger_sees_the_end_of_sampling_without_a_read_settling_it` |
//! | `DR` and its FIFO | Eight stages, the oldest word first. A read pops one | 25.4.27 | `the_data_register_is_an_eight_stage_fifo` |
//! | `OVR`, `OVRMOD` | With `OVRMOD` clear a word arriving at a full FIFO, or while `OVR` stands, is lost and `OVR` rises. With it set, `DR` is overwritten and a word arriving over an unread one sets `OVR` | 25.4.27, 25.6.1, 25.6.4 | `overrun_keeps_the_old_words_and_drops_the_new_one`, `overrun_mode_overwrites_the_data_register` |
//! | `DMNGT` and the DMA request | With `DMNGT` 01 or 11 the request is high while the FIFO holds a word, and blocked while `OVR` stands | 25.4.27, 25.6.4 | `a_conversion_drives_the_dma_request_until_the_data_is_read`, `overrun_blocks_the_dma_request`, `dmngt_selects_whether_conversions_drive_requests` |
//! | Interrupts | One line. `ADRDY`, `EOSMP`, `EOC`, `EOS`, `OVR`, `JEOC`, `JEOS`, `AWD1`, `AWD2`, `AWD3` and `JQOVF` each raise it under their enable and lower it when cleared | 25.5 Table 221, 25.6.1, 25.6.2 | `each_enable_raises_the_line_for_its_flag`, `an_enabled_adrdy_raises_the_line_and_a_written_one_lowers_it`, `eosmpie_raises_the_line_at_the_end_of_sampling`, `the_three_converters_raise_their_own_lines` |
//! | Internal channels | `ADC3` channels 17, 18 and 19 are `VBAT` over four, the temperature sensor and `VREFINT`, each once its enable stands | 25.4.2 Table 209, 25.4.33 to 25.4.35, DS12110 rev 11 Tables 117, 180, 182 | `adc3_converts_its_internal_channels_once_enabled`, `a_pin_the_converter_has_no_internal_source_for_answers_the_boards_voltage` |
//! | The clock | `Fadc_ker_ck` is the clock output of the converter's common block, `ADC12_Common` for the first two and `ADC3_Common` for the third. A change mid-conversion re-bases it and a clock of zero holds the conversion | 25.4.3 | `each_converter_runs_on_its_common_blocks_analog_clock`, `a_clock_change_mid_conversion_rebases_the_rest`, `a_stopped_clock_holds_the_conversion` |
//! | `ADCx_CCR` | `CKMODE` zero gives the kernel clock over `PRESC`, 1 to 256, then over two. A reserved `PRESC` is told and leaves the clock alone. A write while a converter is enabled is told, since 25.4.10 forbids it | 25.4.3 figure 136, 25.4.10, 25.7.2 | `the_common_block_derives_the_analog_clock`, `a_reserved_prescaler_is_told_and_leaves_the_clock_alone`, `a_write_of_ccr_while_a_converter_is_enabled_is_told` |
//! | The enable output | `ADEN` is request output 1, which the common block reads | 25.4.10 | `the_converter_publishes_its_enable_as_a_request_level` |
//! | `VREFEN`, `TSEN`, `VBATEN` | Drive the three request outputs | 25.7.2 | `the_enables_drive_the_converters_internal_channels` |
//! | Stored registers | The registers the left-out table names are stored and answer what was written, and so are the dual mode fields of `ADC_CCR`, where a nonzero `DUAL` or `DAMDF` is told | 25.6, 25.7 | `registers_without_behavior_store_what_is_written`, `dual_mode_settings_are_stored_and_told` |
//! | Snapshot, reset, determinism | The state serializes, a reset puts it back and two runs of one script are one run | The runtime's snapshot and reset | `the_model_state_round_trips_through_a_snapshot`, `the_common_block_round_trips_through_a_snapshot`, `a_reset_puts_the_converter_back`, `a_reset_puts_the_common_block_back`, `two_runs_of_the_start_up_script_are_the_same_run` |
//! | ChibiOS `adc_lld_start` | The register sequence of the `ADCv4` driver for the first converter reaches ready and then converts the board's six channels continuously with a circular DMA at the instants the datasheet's cycles give. The third converter does the same on its three internal channels | 25.4.6, 25.4.8, 25.4.9, 25.4.27 | `chibios_starts_the_first_converter_and_streams_six_channels`, `chibios_streams_the_third_converters_internal_channels` |
//! | ES0392 rev 15 2.9.7 to 2.9.11 | Table 3 marks each `A` for revisions Y and W and not applicable on V, so there is nothing to model on this part | ES0392 rev 15 Table 3 | none |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | Injected conversions: `JSQR`, `JDRy`, `OFRy`, `JADSTART`, `JADSTP`, `JEOC`, `JEOS`, the context queue, `JQM`, `JQDIS`, `JAUTO` | 25.4.20 to 25.4.22, 25.6.16 to 25.6.18 | ArduPilot never starts one. The registers are stored. A `JADSTART` or `JADSTP` written is told and clears at once, `injected_commands_are_told_and_cleared` |
//! | Analog watchdogs: `AWD1CH`, `AWD1EN`, `JAWD1EN`, `AWD1SGL`, `LTR1`, `LHTR1`, `LTR2`, `HTR2`, `LTR3`, `HTR3`, `AWD2CR`, `AWD3CR` | 25.4.30, 25.6.9 to 25.6.24 | ChibiOS clears the thresholds and never enables a guard. The registers are stored, so no `AWD` flag rises. A start with a guard enabled is told |
//! | The oversampler and the data shifts: `ROVSE`, `JOVSE`, `OSVR`, `OVSS`, `LSHIFT`, `RSHIFT`, the offsets and `SSATE` | 25.4.23, 25.4.27, 25.4.31, 25.6.5 | ArduPilot writes zero to `CFGR2` and the offsets. A word is the converter's, right-aligned, whole. A start with any of them set is told, `an_unmodeled_setting_is_told_at_a_start` |
//! | External triggers: `EXTEN`, `EXTSEL` | 25.4.19 | The timers publish a trigger output and no converter takes one yet. A start with `EXTEN` set arms `ADSTART` and is told, and nothing starts it, `external_triggers_are_told_and_nothing_starts_the_conversion` |
//! | Discontinuous mode, `AUTDLY`, `DISCEN`, `DISCNUM` | 25.4.21, 25.4.29 | ArduPilot leaves both off. The bits are stored, a start with either set is told, and a sequence runs whole |
//! | Differential channels, `DIFSEL` | 25.4.7 | No board puts a differential pair on a pin. A differential channel in the sequence is told and converts single-ended, `a_differential_channel_is_told_and_converted_single_ended`, `the_differential_selection_is_told_when_written_while_enabled` |
//! | `DMNGT` 01 stopping at the end of the controller's transfer | 25.4.27 | The converter would have to be told the stream's count is out, which the runtime doesn't carry back. One shot drives requests as circular does |
//! | `DMNGT` 10 and the transfer to the digital filter | 25.4.28 | The part's filter is not modeled. The mode is told at a start and drives no request |
//! | Dual modes, `DUAL`, `DELAY`, `DAMDF`, `ADCx_CDR`, `ADCx_CDR2`, the slave's `BOOST` following | 25.4.32, 25.7.2 to 25.7.4 | The board lists no `ADC2` pin (`MatekH743`), so the coupled pair has nothing to do together. The fields are stored and a nonzero `DUAL` or `DAMDF` is told. `CDR` and `CDR2` read zero |
//! | `CKMODE` 1 to 3, the synchronous clock `adc_sclk` | 25.4.3 figure 136, 25.7.2 | The converter would run on the system clock over `HPRE` and the divider, and an instance has one clock. The common block runs on the kernel clock and cannot also follow the system clock or the gates of figure 58, so it would hold a rate fixed at construction. The write is told and the clock stays where it was. ChibiOS's default is `ADC_CCR_CKMODE_AHB_DIV4`, which its ArduPilot configuration overrides to `ADC_CCR_CKMODE_ADCCK`, `ckmode_one_to_three_is_told_and_leaves_the_clock_alone` |
//! | `ADCx_CSR` | 25.7.1 | Nothing reads the image. The register is stored and reads zero, so a firmware sees no flag of the other converter there, `the_status_register_is_stored_and_reads_zero` |
//! | The calibration words `VREFIN_CAL`, `TS_CAL1` and `TS_CAL2` at `0x1FF1E860`, `0x1FF1E820` and `0x1FF1E840` | DS12110 rev 11 Tables 118 and 181 | The runtime has no way for a part to give system memory initial words, so they read zero. A firmware that scales the temperature sensor or the supply by them divides by zero or reads a temperature and a `VDDA` of no meaning |
//! | The linearity factors and the offset calibration factor | 25.4.8, 25.6.26, 25.6.27 | Trim data that varies from chip to chip. `CALFACT` and `CALFACT2` keep what is written, and the `LINCALRDYWx` handshake of 25.4.8 completes at the write |
//! | The stabilization time of the regulator's second wake-up and the disable time of the analog block | 25.4.9, DS12110 rev 11 Table 174 | The datasheet gives no figure for `ADDIS`, so the disable takes effect at the write. The datasheet's 1 conversion cycle for `tSTAB` is read as one analog clock cycle |
//! | `tLATR` and `tLATRINJ`, the trigger latency | DS12110 rev 11 Table 174 | They are the latency of a hardware trigger. A software start begins at the write, which 25.4.16 calls immediately |
//! | The sampling instant | 25.4.17 | The pin is read at the end of the conversion, and the model has no event at the end of sampling unless `EOSMPIE` stands |
//! | `SYSCFG_PMCR.BOOSTE` and the analog switch resistance, `BOOST` timing effects | 25.4.13, 25.6.3 | A power and speed setting. `BOOST` is stored and a write during a conversion is told |
//! | The analog accuracy limits: sampling rates, `RAIN`, `VREF+` ranges | DS12110 rev 11 Table 174 | They are what a board is held to. A conversion is exact |
//! | ES0392 rev 15 2.9.1, 2.9.2, 2.9.12, 2.9.13 | ES0392 rev 15 Table 3 | Marked `A` on revision V. They are the injected context queue, which the model leaves out |
//! | ES0392 rev 15 2.9.3 | ES0392 rev 15 Table 3 | Marked `A` on revision V. It is dual interleaved mode, which the model leaves out |
//! | ES0392 rev 15 2.9.4 | ES0392 rev 15 Table 3 | Marked `A` on revision V. It is the analog watchdog output signal, which the model leaves out with the watchdogs |
//! | ES0392 rev 15 2.9.5 | ES0392 rev 15 Table 3 | Marked `A` on revision V. It is `JADSTP` with a high AHB to ADC clock ratio, which needs the injected group |
//! | ES0392 rev 15 2.9.6 | ES0392 rev 15 Table 3 | Marked `A` on revision V. It is dual regular simultaneous mode, with a workaround of `OVRMOD` set, which the model takes |
//!
//! **Where this model departs from the vendor's file.** It leaves `SMP0` out of `ADC_SMPR1`, which the model declares. ST's file resets `ADC_CR` to zero where
//! 25.6.3 says `0x2000 0000`, `ADC_CFGR` to zero where 25.6.4 says `0x8000 0000`, and gives `ADC_LTR1`
//! the reset `0x0FFF 0000` and `ADC_HTR2` and `ADC_HTR3` zero where the sections say zero and
//! `0x03FF FFFF`. It names `ADC_HTR1` `LHTR1` at its right address, which the model keeps. It types the
//! command bits of `ADC_CR` read-write where the section types them `rs`, gives `BOOST` one bit
//! where revision V has two, and leaves out `LDORDY`, which revision V adds, so the part corrects
//! each. The flags of `ADC_ISR` are `rc_w1` and the file has them read-write, so the model holds them
//! and puts them back after a write.
//!
//! **Determinism.** Nothing here reads a clock or a random source. Every instant is an absolute
//! virtual time computed from the access or the event it follows.
use std::collections::VecDeque;
use crate::device::{Device, DeviceContext, Level, RegisterFile, RequestChange};
use crate::snapshot::store;
use crate::soc::stm32h743::generated;
use crate::soc::stm32h743::generated::adc3::{
Register, cfgr, cfgr2, cr, ier, isr, sqr1, sqr2, sqr3, sqr4,
};
use crate::soc::stm32h743::generated::adc3_common::{self, ccr};
use crate::soc::{Access, FieldSpec, Layout, ReadAction, WriteModify};
use crate::time::{EventId, VirtualNanos};
/// The request output that asks for each converted word to be read, which the request multiplexer's
/// rows for the converters name.
pub const DMA_REQUEST: usize = 0;
/// The converter's request output that is `ADEN`, as a level, which the common block reads.
pub const ENABLED_REQUEST: usize = 1;
/// The common block's clock output, `Fadc_ker_ck`, which its converters run on.
pub const ANALOG_CLOCK: usize = 0;
/// The common block's request output that is `VREFEN`.
pub const VREFEN_REQUEST: usize = 0;
/// The common block's request output that is `TSEN`.
pub const VSENSEEN_REQUEST: usize = 1;
/// The common block's request output that is `VBATEN`.
pub const VBATEN_REQUEST: usize = 2;
/// The common block of the first and second converters.
pub const ADC12_COMMON: &str = generated::ADC12_COMMON.name;
/// The common block of the third converter.
pub const ADC3_COMMON: &str = generated::ADC3_COMMON.name;
// The converter's one interrupt line, position 0 of its table, which RM0433 rev 9 Table 147 gives
// as line 18 for the first two and 127 for the third.
const GLOBAL: usize = 0;
const NANOS_PER_SECOND: u128 = 1_000_000_000;
// The input channels of one converter, `VINP[0]` to `VINP[19]`, section 25.4.1.
const CHANNELS: usize = 20;
// DS12110 rev 11 Table 174: the regulator's start-up time, 5 microseconds typical and 10 at most.
const REGULATOR_STARTUP_NANOS: u64 = 5_000;
// DS12110 rev 11 Table 174, `tOFF_CAL`: 1280 cycles of `1/fADC`.
const OFFSET_CALIBRATION_CYCLES: u64 = 1_280;
// DS12110 rev 11 Table 174, `tCAL`: 165010 cycles of `1/fADC`.
const FULL_CALIBRATION_CYCLES: u64 = 165_010;
// DS12110 rev 11 Table 174, `tSTAB` with the regulator already started: one cycle.
const STARTUP_CYCLES: u64 = 1;
// The stages of the data register's FIFO, section 25.4.27.
const FIFO_DEPTH: usize = 8;
// `SMPx`, section 25.6.6, in half cycles: 1.5, 2.5, 8.5, 16.5, 32.5, 64.5, 387.5 and 810.5 cycles.
const SAMPLING_HALF_CYCLES: [u64; 8] = [3, 5, 17, 33, 65, 129, 775, 1621];
// `RES`, section 25.6.4, in bits for revision V, where 100 is revision Y's 8 bits and reserved.
const RESOLUTION_BITS: [Option<u32>; 8] = [
Some(16),
Some(14),
Some(12),
Some(10),
None,
Some(14),
Some(12),
Some(8),
];
// `PRESC`, section 25.7.2, as dividers.
const PRESCALERS: [u64; 12] = [1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256];
// The divider of 2 applied to every clock the analog block gets, section 25.4.3.
const ANALOG_DIVIDER: u64 = 2;
// DS12110 rev 11 Table 117, `VREFINT` typical, in millivolts.
const VREFINT_MILLIVOLTS: u32 = 1216;
// DS12110 rev 11 Table 180, `V30`, the sensor's voltage at 30 degrees, typical, in millivolts.
const V30_MILLIVOLTS: i64 = 620;
// DS12110 rev 11 Table 180, `Avg_Slope`, 2 mV a degree, which is a millivolt every 50 hundredths of
// a degree.
const CENTI_DEGREES_PER_MILLIVOLT: i64 = 50;
const SENSOR_REFERENCE_CENTI_DEGREES: i64 = 3_000;
// DS12110 rev 11 Table 182, the ratio on `VBAT` measurement.
const VBAT_DIVIDER: u32 = 4;
// Table 209: the channels of `ADC3`'s internal sources.
const VBAT_CHANNEL: usize = 17;
const VSENSE_CHANNEL: usize = 18;
const VREFINT_CHANNEL: usize = 19;
// What `ADC_CR` reads out of reset, `DEEPPWD` set, 25.6.3.
const CR_RESET: u64 = cr::DEEPPWD.set(0, 1);
/// Every reset value 25.6 gives that the vendor's file gets wrong, as register and value, for each
/// of the three converters.
pub const RESETS: [(&str, u64); 6] = [
("CR", CR_RESET),
("CFGR", cfgr::JQDIS.set(0, 1)),
("LTR1", 0),
("LHTR1", 0x03FF_FFFF),
("HTR2", 0x03FF_FFFF),
("HTR3", 0x03FF_FFFF),
];
// A field of the vendor's file with the write rule the manual gives.
const fn corrected(base: &FieldSpec, write: WriteModify) -> FieldSpec {
FieldSpec { write, ..*base }
}
// A field the model reads that the vendor's file does not split out.
const fn spec(name: &'static str, bit: u8, width: u8, access: Access) -> FieldSpec {
FieldSpec {
name,
bit,
width,
access,
read: ReadAction::None,
write: WriteModify::None,
}
}
/// The command bits of `ADC_CR`, section 25.6.3, type `rs`: software sets them and hardware clears
/// them, so a written zero does nothing.
pub static CR_COMMANDS: [FieldSpec; 7] = [
corrected(cr::ADEN, WriteModify::OneToSet),
corrected(cr::ADDIS, WriteModify::OneToSet),
corrected(cr::ADSTART, WriteModify::OneToSet),
corrected(cr::JADSTART, WriteModify::OneToSet),
corrected(cr::ADSTP, WriteModify::OneToSet),
corrected(cr::JADSTP, WriteModify::OneToSet),
corrected(cr::ADCAL, WriteModify::OneToSet),
];
/// `BOOST[1:0]`, section 25.6.3 for revision V, two bits where the vendor's file has one.
pub static BOOST: FieldSpec = spec("BOOST", 8, 2, Access::ReadWrite);
/// `SMP0`, bits 2:0 of `ADC_SMPR1`, section 25.6.6, which the vendor's file leaves out.
pub static SMP0: FieldSpec = spec("SMP0", 0, 3, Access::ReadWrite);
// `OFFSETy`, bits 25:0 of `ADC_OFRy`, section 25.6.17.
const OFFSET: FieldSpec = spec("OFFSET", 0, 26, Access::ReadWrite);
/// `LDORDY`, bit 12 of `ADC_ISR`, section 25.6.1: the regulator's output is ready. Hardware's alone.
pub static LDORDY: FieldSpec = spec("LDORDY", 12, 1, Access::ReadOnly);
// One bit of a register with a bit per channel, or three bits of a sampling time.
const fn per_channel<const N: usize>(width: u8) -> [FieldSpec; N] {
let mut fields = [spec("", 0, width, Access::ReadWrite); N];
let mut bit: u8 = 0;
while (bit as usize) < N {
fields[bit as usize] = spec("", bit * width, width, Access::ReadWrite);
bit += 1;
}
fields
}
// Channel `n`'s bit in `ADC_PCSEL` or `ADC_DIFSEL`, sections 25.6.8 and 25.6.25.
static CHANNEL_BITS: [FieldSpec; CHANNELS] = per_channel(1);
// The sampling time of the tenth of a register's channels, 25.6.6 and 25.6.7.
static SAMPLING_TIMES: [FieldSpec; 10] = per_channel(3);
// The eleven flags of `ISR` and the enables of `IER` that raise the line, in bit order.
const FLAGS: [(&FieldSpec, &FieldSpec); 11] = [
(isr::ADRDY, ier::ADRDYIE),
(isr::EOSMP, ier::EOSMPIE),
(isr::EOC, ier::EOCIE),
(isr::EOS, ier::EOSIE),
(isr::OVR, ier::OVRIE),
(isr::JEOC, ier::JEOCIE),
(isr::JEOS, ier::JEOSIE),
(isr::AWD1, ier::AWD1IE),
(isr::AWD2, ier::AWD2IE),
(isr::AWD3, ier::AWD3IE),
(isr::JQOVF, ier::JQOVFIE),
];
// The seven command bits, which say the converter is doing something.
const COMMANDS: [&FieldSpec; 7] = [
cr::ADEN,
cr::ADDIS,
cr::ADSTART,
cr::JADSTART,
cr::ADSTP,
cr::JADSTP,
cr::ADCAL,
];
// The six linearity words' ready bits.
const LINEARITY_READY: [&FieldSpec; 6] = [
cr::LINCALRDYW1,
cr::LINCALRDYW2,
cr::LINCALRDYW3,
cr::LINCALRDYW4,
cr::LINCALRDYW5,
cr::LINCALRDYW6,
];
// `SQ1` to `SQ16`, each with the register it sits in, 25.6.11 to 25.6.14.
const SEQUENCE: [(Register, &FieldSpec); 16] = [
(Register::Sqr1, sqr1::SQ1),
(Register::Sqr1, sqr1::SQ2),
(Register::Sqr1, sqr1::SQ3),
(Register::Sqr1, sqr1::SQ4),
(Register::Sqr2, sqr2::SQ5),
(Register::Sqr2, sqr2::SQ6),
(Register::Sqr2, sqr2::SQ7),
(Register::Sqr2, sqr2::SQ8),
(Register::Sqr2, sqr2::SQ9),
(Register::Sqr3, sqr3::SQ10),
(Register::Sqr3, sqr3::SQ11),
(Register::Sqr3, sqr3::SQ12),
(Register::Sqr3, sqr3::SQ13),
(Register::Sqr3, sqr3::SQ14),
(Register::Sqr4, sqr4::SQ15),
(Register::Sqr4, sqr4::SQ16),
];
const CONFIGURATION_WHILE_BUSY: &str = "RM0433 rev 9 section 25.6.3: this bit may be written only while the converter is disabled and idle, so the write was dropped";
const BOOST_WHILE_CONVERTING: &str =
"RM0433 rev 9 section 25.6.3: BOOST may be written only while no conversion is ongoing";
const CALIBRATION_WHILE_ENABLED: &str = "RM0433 rev 9 section 25.6.3: ADCAL may be set only while ADEN is clear, so no calibration started";
const CALIBRATION_BEFORE_REGULATOR: &str =
"RM0433 rev 9 section 25.4.8: the regulator's start-up time had not elapsed when ADCAL was set";
const ENABLE_WHILE_BUSY: &str = "RM0433 rev 9 section 25.6.3: ADEN may be set only while every other command bit is clear, so the converter was not enabled";
const ENABLE_BEFORE_REGULATOR: &str = "RM0433 rev 9 section 25.4.9: ADEN was set before the regulator's start-up time elapsed, so the converter becomes ready when it has";
const DISABLE_WHILE_BUSY: &str = "RM0433 rev 9 section 25.6.3: ADDIS may be set only while ADEN is set and no conversion is ongoing, so the converter stays enabled";
const START_WITHOUT_ENABLE: &str = "RM0433 rev 9 section 25.6.3: ADSTART may be set only while ADEN is set and ADDIS is clear, so no conversion started";
const START_BEFORE_READY: &str = "RM0433 rev 9 section 25.4.9: ADSTART was set before ADRDY, and the converter converts accurately only after its start-up time, so the conversion waits for ADRDY";
const STOP_WITHOUT_START: &str =
"RM0433 rev 9 section 25.6.3: ADSTP may be set only while ADSTART is set and ADDIS is clear";
const INJECTED_NOT_MODELED: &str = "RM0433 rev 9 section 25.4.20: injected conversions are not modeled, so the command was cleared and nothing converted";
const EXTERNAL_TRIGGER_NOT_MODELED: &str = "RM0433 rev 9 section 25.4.19: no hardware trigger reaches the converter, so ADSTART is armed and nothing starts the conversion";
const CONFIGURATION_WHILE_CONVERTING: &str = "RM0433 rev 9 section 25.4.10: this register may be written only while no regular conversion is ongoing";
const DIFSEL_WHILE_ENABLED: &str =
"RM0433 rev 9 section 25.4.7: DIFSEL may be written only while ADEN is clear";
const RESERVED_CHANNEL: &str = "RM0433 rev 9 section 25.6.11: a sequence names a channel above 19, which is reserved, and it converts nothing";
const CHANNEL_NOT_PRESELECTED: &str = "RM0433 rev 9 section 25.4.12: a channel in the sequence has no PCSEL bit set, so the pad's analog switch is open";
const DIFFERENTIAL_NOT_MODELED: &str = "RM0433 rev 9 section 25.4.7: a differential channel is in the sequence, and the model converts it single-ended";
const FILTER_MODE_NOT_MODELED: &str = "RM0433 rev 9 section 25.4.28: DMNGT selects the digital filter, which the model does not carry, so no request is driven";
const RESERVED_PRESCALER: &str =
"RM0433 rev 9 section 25.7.2: PRESC above 1011 is reserved, so the clock is left as it was";
const RESERVED_RESOLUTION: &str = "RM0433 rev 9 section 25.6.4: RES 100 is 8 bits on revision Y only and reserved on revision V, so the model converts at 16 bits";
const OVERSAMPLING_NOT_MODELED: &str = "RM0433 rev 9 section 25.4.31: oversampling and the data shifts are not modeled, so a word is the converter's, right-aligned";
const OFFSET_NOT_MODELED: &str = "RM0433 rev 9 section 25.4.27: the channel offsets are not modeled, so a word is not reduced by OFFSETy";
const DISCONTINUOUS_NOT_MODELED: &str = "RM0433 rev 9 sections 25.4.21 and 25.4.29: discontinuous and auto-delayed modes are not modeled, so the sequence runs whole";
const WATCHDOG_NOT_MODELED: &str =
"RM0433 rev 9 section 25.4.30: the analog watchdogs are not modeled, so no AWD flag rises";
const SYNCHRONOUS_CLOCK_NOT_MODELED: &str = "RM0433 rev 9 section 25.7.2: the synchronous clock modes follow the system clock, which the common block does not see, so the clock is left as it was";
const CCR_WHILE_ENABLED: &str =
"RM0433 rev 9 section 25.4.10: ADCx_CCR may be written only while the converters are disabled";
const DUAL_NOT_MODELED: &str = "RM0433 rev 9 section 25.4.32: dual ADC mode is not modeled, so the converters run on their own";
/// Which of the three converters a model answers for.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Converter {
/// `ADC1`, the master of the coupled pair.
Adc1,
/// `ADC2`, the slave of the coupled pair.
Adc2,
/// `ADC3`, instantiated on its own, with the internal channels of Table 209.
Adc3,
}
impl Converter {
/// The three, in the manual's order.
pub const ALL: [Self; 3] = [Self::Adc1, Self::Adc2, Self::Adc3];
/// The instance as the vendor's file names it.
#[must_use]
pub const fn instance(self) -> &'static str {
match self {
Self::Adc1 => "ADC1",
Self::Adc2 => "ADC2",
Self::Adc3 => "ADC3",
}
}
/// The block of common registers the converter answers to.
#[must_use]
pub const fn common(self) -> &'static str {
match self {
Self::Adc1 | Self::Adc2 => ADC12_COMMON,
Self::Adc3 => ADC3_COMMON,
}
}
const fn internal_channels(self) -> bool {
matches!(self, Self::Adc3)
}
}
/// Which block of common registers a model answers for.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub enum Block {
/// The block at `0x300` from `ADC1`, for the coupled pair.
Adc12,
/// The block at `0x300` from `ADC3`.
Adc3,
}
impl Block {
/// The two, in address order.
pub const ALL: [Self; 2] = [Self::Adc12, Self::Adc3];
/// The instance as the vendor's file names it.
#[must_use]
pub const fn instance(self) -> &'static str {
match self {
Self::Adc12 => ADC12_COMMON,
Self::Adc3 => ADC3_COMMON,
}
}
/// The converter the block takes as the master.
#[must_use]
pub const fn master(self) -> Converter {
match self {
Self::Adc12 => Converter::Adc1,
Self::Adc3 => Converter::Adc3,
}
}
/// The converter it takes as the slave, where there is one.
#[must_use]
pub const fn slave(self) -> Option<Converter> {
match self {
Self::Adc12 => Some(Converter::Adc2),
Self::Adc3 => None,
}
}
}
/// An input channel, 0 to 19.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Channel(u8);
impl Channel {
/// Channel `number`, 0 to 19.
#[must_use]
pub const fn new(number: u8) -> Option<Self> {
if (number as usize) < CHANNELS {
Some(Self(number))
} else {
None
}
}
}
// What a converter's one timed operation is.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Step {
Calibration,
Startup,
Conversion { channel: usize },
}
// A timed operation in flight, which ends at its event.
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Task {
step: Step,
started: VirtualNanos,
// What was left of it at `started`, in half cycles of the analog clock.
half_cycles: u64,
// How much of `half_cycles` is sampling time.
sample_half: u64,
ends: VirtualNanos,
// `None` while the clock is stopped.
event: Option<EventId>,
}
// How long an operation takes.
#[derive(Debug, Clone, Copy)]
struct Span {
half_cycles: u64,
sample_half: u64,
}
// Where an operation begins: the instant and the fraction of a nanosecond carried to it.
#[derive(Debug, Clone, Copy)]
struct Origin {
at: VirtualNanos,
carry: u64,
}
// What the converter's enable has reached.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Enable {
Off,
WaitingForRegulator,
Starting,
Ready,
}
// The nanoseconds half cycles take at `hz`, and the remainder carried, which is in units of
// `1 / (2 x hz)` of a nanosecond.
fn span_nanos(half_cycles: u64, hz: u64, carry: u64) -> (u64, u64) {
let numerator = u128::from(half_cycles) * NANOS_PER_SECOND + u128::from(carry);
let denominator = 2 * u128::from(hz);
(
u64::try_from(numerator / denominator).expect("an operation fits the clock"),
u64::try_from(numerator % denominator).expect("less than the denominator"),
)
}
/// One converter of an STM32H743, RM0433 rev 9 chapter 25.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Adc {
converter: Converter,
// `Fadc_ker_ck`, which the common block drives. Zero is a stopped clock.
clock_hz: u64,
inputs: [u32; CHANNELS],
reference: u32,
centi_celsius: i32,
backup_battery: u32,
// `VREFEN`, `TSEN` and `VBATEN`, as the common block drives them.
sources: [Level; 3],
status: u64,
// What `ADC_CR` holds, as the model last put it.
shadow_cr: u64,
regulator: bool,
regulator_event: Option<EventId>,
enable: Enable,
task: Option<Task>,
carry: u64,
position: usize,
fifo: VecDeque<u32>,
sampling_ends: Option<VirtualNanos>,
sampling_event: Option<EventId>,
sampling_interrupt: bool,
held_start: bool,
interrupt: Level,
requests: [Level; 2],
}
store!(Adc);
impl Adc {
/// The converter `converter` with its analog clock at `clock_hz` until the common block drives it
/// otherwise, and every pin at zero with `VREF+` at 3.3 V until the board says otherwise.
///
/// A part builds each converter with the clock the common block would derive before the firmware
/// touches the clock tree, which is none: `ADCSEL` selects `pll2_p_ck` out of reset, RM0433 rev 9
/// section 8.7.22, and the PLL is off.
#[must_use]
pub fn new(converter: Converter, clock_hz: u64) -> Self {
Self {
converter,
clock_hz,
inputs: [0; CHANNELS],
reference: 3300,
centi_celsius: 3000,
backup_battery: 0,
sources: [Level::Low; 3],
status: 0,
shadow_cr: CR_RESET,
regulator: false,
regulator_event: None,
enable: Enable::Off,
task: None,
carry: 0,
position: 0,
fifo: VecDeque::new(),
sampling_ends: None,
sampling_event: None,
sampling_interrupt: false,
held_start: false,
interrupt: Level::Low,
requests: [Level::Low; 2],
}
}
/// The voltage on `channel`'s pin from now on, in millivolts.
pub fn set_input(&mut self, channel: Channel, millivolts: u32) {
self.inputs[usize::from(channel.0)] = millivolts;
}
/// `VREF+`, which is `VDDA` where the pin is not brought out or the internal buffer's output
/// where it drives it, in millivolts.
pub fn set_reference(&mut self, millivolts: u32) {
self.reference = millivolts;
}
/// The junction temperature the sensor of section 25.4.33 measures, in hundredths of a degree
/// Celsius.
pub fn set_temperature(&mut self, centi_celsius: i32) {
self.centi_celsius = centi_celsius;
}
/// The voltage on the `VBAT` pin, section 25.4.34, in millivolts.
pub fn set_backup_battery(&mut self, millivolts: u32) {
self.backup_battery = millivolts;
}
// The voltage a channel presents to the converter.
fn millivolts(&self, channel: usize) -> u32 {
if channel >= CHANNELS {
return 0;
}
if !self.converter.internal_channels() {
return self.inputs[channel];
}
let enabled = |request: usize| self.sources[request] == Level::High;
match channel {
VBAT_CHANNEL if enabled(VBATEN_REQUEST) => self.backup_battery / VBAT_DIVIDER,
VSENSE_CHANNEL if enabled(VSENSEEN_REQUEST) => {
let above = i64::from(self.centi_celsius) - SENSOR_REFERENCE_CENTI_DEGREES;
u32::try_from((V30_MILLIVOLTS + above / CENTI_DEGREES_PER_MILLIVOLT).max(0))
.expect("a positive voltage")
}
VREFINT_CHANNEL if enabled(VREFEN_REQUEST) => VREFINT_MILLIVOLTS,
VBAT_CHANNEL | VSENSE_CHANNEL | VREFINT_CHANNEL => 0,
pin => self.inputs[pin],
}
}
// The resolution in bits `RES` selects, 25.6.4, and 16 where the code is reserved.
fn bits(registers: &RegisterFile) -> u32 {
Self::resolution(registers).unwrap_or(16)
}
fn resolution(registers: &RegisterFile) -> Option<u32> {
let code = cfgr::RES.get(registers.get(Register::Cfgr));
RESOLUTION_BITS[usize::try_from(code).expect("three bits")]
}
// The word at the resolution, 25.4.35: the pin's share of the reference, saturated.
fn word(&self, channel: usize, bits: u32) -> u32 {
let full = (1_u64 << bits) - 1;
let word = if self.reference == 0 {
full
} else {
(u64::from(self.millivolts(channel)) * full / u64::from(self.reference)).min(full)
};
u32::try_from(word).expect("at most sixteen bits")
}
// How many channels the regular sequence has, `L` plus one, 25.6.11.
fn length(registers: &RegisterFile) -> usize {
usize::try_from(sqr1::L3.get(registers.get(Register::Sqr1))).expect("four bits") + 1
}
// The `nth` channel of the regular sequence.
fn channel_at(registers: &RegisterFile, nth: usize) -> usize {
let (register, field) = SEQUENCE[nth];
usize::try_from(field.get(registers.get(register))).expect("five bits")
}
// The sampling time code of `channel`, 25.6.6 and 25.6.7.
fn sampling_code(channel: usize, registers: &RegisterFile) -> usize {
let (register, index) = if channel < 10 {
(Register::Smpr1, channel)
} else {
(Register::Smpr2, channel - 10)
};
let index = index.min(SAMPLING_TIMES.len() - 1);
usize::try_from(SAMPLING_TIMES[index].get(registers.get(register))).expect("three bits")
}
fn set_cr(&mut self, registers: &mut RegisterFile, value: u64) {
self.shadow_cr = value;
registers.set(Register::Cr, value);
}
// Puts a timed operation on the analog block, ending after `span` from `origin`.
fn begin(&mut self, step: Step, span: Span, origin: Origin, context: &mut DeviceContext<'_>) {
let mut task = Task {
step,
started: origin.at,
half_cycles: span.half_cycles,
sample_half: span.sample_half,
ends: origin.at,
event: None,
};
self.sampling_ends = None;
self.sampling_event = None;
self.carry = 0;
if self.clock_hz != 0 {
let (nanos, carry) = span_nanos(span.half_cycles, self.clock_hz, origin.carry);
task.ends = VirtualNanos(origin.at.0.saturating_add(nanos));
task.event = Some(context.schedule(task.ends));
self.carry = carry;
if span.sample_half > 0 {
let (sampled, _) = span_nanos(span.sample_half, self.clock_hz, origin.carry);
let ends = VirtualNanos(origin.at.0.saturating_add(sampled));
self.sampling_ends = Some(ends);
if self.sampling_interrupt {
self.sampling_event = Some(context.schedule(ends));
}
}
}
self.task = Some(task);
}
// Starts the conversion at `position`, 25.4.17.
fn begin_conversion(
&mut self,
registers: &RegisterFile,
context: &mut DeviceContext<'_>,
origin: Origin,
) {
let channel = Self::channel_at(registers, self.position);
let sample_half = SAMPLING_HALF_CYCLES[Self::sampling_code(channel, registers)];
let span = Span {
half_cycles: sample_half + u64::from(Self::bits(registers)) + 1,
sample_half,
};
self.begin(Step::Conversion { channel }, span, origin, context);
}
// Sets `EOSMP` once the sampling time of the conversion in flight is past.
fn sync(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let now = context.now();
if self.sampling_ends.is_some_and(|ends| ends <= now) {
self.sampling_ends = None;
self.status = isr::EOSMP.set(self.status, 1);
self.refresh(registers, context);
}
}
// Puts the flags in `ISR`, the head of the FIFO in `DR`, the request outputs and the line where the
// state says they are. The model keeps the level it last drove.
fn refresh(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
registers.set(Register::Isr, self.status);
if let Some(&head) = self.fifo.front() {
registers.set(Register::Dr, u64::from(head));
}
let control = registers.get(Register::Cfgr);
let enables = registers.get(Register::Ier);
let by_dma = matches!(cfgr::DMNGT.get(control), 0b01 | 0b11);
let data = by_dma && !self.fifo.is_empty() && !isr::OVR.is_set(self.status);
let wanted = [data, cr::ADEN.is_set(self.shadow_cr)].map(Level::from);
for (output, level) in wanted.into_iter().enumerate() {
if level != self.requests[output] {
self.requests[output] = level;
context.request(output, level);
}
}
let line = Level::from(
FLAGS
.iter()
.any(|(flag, enable)| flag.is_set(self.status) && enable.is_set(enables)),
);
if line != self.interrupt {
self.interrupt = line;
context.interrupt(GLOBAL, line);
}
}
// `ADC_CR` as written, 25.6.3.
fn write_cr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let old = self.shadow_cr;
let written = registers.get(Register::Cr);
let kept = Self::refuse_configuration(old, written, context);
let powered = self.apply_power(kept, context);
let commanded = self.apply_commands(old, powered, registers, context);
self.set_cr(registers, commanded);
}
// Puts back the configuration bits written while the converter was busy, 25.6.3.
fn refuse_configuration(old: u64, new: u64, context: &mut DeviceContext<'_>) -> u64 {
let busy = COMMANDS.iter().any(|command| command.is_set(old));
let mut kept = new;
for field in [cr::DEEPPWD, cr::ADVREGEN, cr::ADCALDIF, cr::ADCALLIN] {
if busy && field.get(new) != field.get(old) {
kept = field.set(kept, field.get(old));
context.tell("CR", CONFIGURATION_WHILE_BUSY);
}
}
let converting = cr::ADSTART.is_set(old) || cr::JADSTART.is_set(old);
if converting && BOOST.get(new) != BOOST.get(old) {
kept = BOOST.set(kept, BOOST.get(old));
context.tell("CR", BOOST_WHILE_CONVERTING);
}
kept
}
// `DEEPPWD` and `ADVREGEN`, 25.4.6: every write with `DEEPPWD` set clears `ADVREGEN`.
fn apply_power(&mut self, new: u64, context: &mut DeviceContext<'_>) -> u64 {
let mut value = new;
let deep = cr::DEEPPWD.is_set(value);
if deep {
value = cr::ADVREGEN.set(value, 0);
for ready in LINEARITY_READY {
value = ready.set(value, 0);
}
}
let wanted = cr::ADVREGEN.is_set(value) && !deep;
if wanted && !self.regulator {
self.regulator = true;
let at = VirtualNanos(context.now().0.saturating_add(REGULATOR_STARTUP_NANOS));
self.regulator_event = Some(context.schedule(at));
} else if !wanted && self.regulator {
self.regulator = false;
self.regulator_event = None;
self.status = LDORDY.set(self.status, 0);
}
value
}
// The command bits just set, in the order the converter takes them, 25.6.3.
fn apply_commands(
&mut self,
old: u64,
new: u64,
registers: &RegisterFile,
context: &mut DeviceContext<'_>,
) -> u64 {
let newly = |field: &FieldSpec| field.is_set(new) && !field.is_set(old);
let mut value = new;
for command in [cr::JADSTART, cr::JADSTP] {
if newly(command) {
value = command.set(value, 0);
context.tell("CR", INJECTED_NOT_MODELED);
}
}
if newly(cr::ADCAL) {
value = self.calibrate(value, context);
}
if newly(cr::ADEN) {
value = self.enable_converter(value, context);
}
if newly(cr::ADDIS) {
value = self.disable_converter(value, context);
}
if newly(cr::ADSTART) {
value = self.start(value, registers, context);
}
if newly(cr::ADSTP) {
value = self.stop(old, value, context);
}
value
}
// `ADCAL`: 25.4.8.
fn calibrate(&mut self, value: u64, context: &mut DeviceContext<'_>) -> u64 {
if cr::ADEN.is_set(value) {
context.tell("CR", CALIBRATION_WHILE_ENABLED);
return cr::ADCAL.set(value, 0);
}
if !LDORDY.is_set(self.status) {
context.tell("CR", CALIBRATION_BEFORE_REGULATOR);
}
let cycles = if cr::ADCALLIN.is_set(value) {
FULL_CALIBRATION_CYCLES
} else {
OFFSET_CALIBRATION_CYCLES
};
let span = Span {
half_cycles: 2 * cycles,
sample_half: 0,
};
let origin = Origin {
at: context.now(),
carry: 0,
};
self.begin(Step::Calibration, span, origin, context);
value
}
// `ADEN`: 25.4.9.
fn enable_converter(&mut self, value: u64, context: &mut DeviceContext<'_>) -> u64 {
let others = COMMANDS
.iter()
.any(|command| command.name != cr::ADEN.name && command.is_set(value));
if others {
context.tell("CR", ENABLE_WHILE_BUSY);
return cr::ADEN.set(value, 0);
}
if LDORDY.is_set(self.status) {
self.begin_startup(context);
} else {
self.enable = Enable::WaitingForRegulator;
context.tell("CR", ENABLE_BEFORE_REGULATOR);
}
value
}
fn begin_startup(&mut self, context: &mut DeviceContext<'_>) {
self.enable = Enable::Starting;
let span = Span {
half_cycles: 2 * STARTUP_CYCLES,
sample_half: 0,
};
let origin = Origin {
at: context.now(),
carry: 0,
};
self.begin(Step::Startup, span, origin, context);
}
// `ADDIS`: 25.4.9. The datasheet gives the disable no time, so it takes effect at the write.
fn disable_converter(&mut self, value: u64, context: &mut DeviceContext<'_>) -> u64 {
if !cr::ADEN.is_set(value) || cr::ADSTART.is_set(value) || cr::JADSTART.is_set(value) {
context.tell("CR", DISABLE_WHILE_BUSY);
return cr::ADDIS.set(value, 0);
}
self.enable = Enable::Off;
if self
.task
.is_some_and(|task| matches!(task.step, Step::Startup))
{
self.task = None;
}
cr::ADEN.set(cr::ADDIS.set(value, 0), 0)
}
// `ADSTART`: 25.4.16. Before `ADRDY` the start is held, as an enable is held for the regulator,
// since 25.4.9 says only that the converter is not accurate until its start-up time has passed.
fn start(
&mut self,
value: u64,
registers: &RegisterFile,
context: &mut DeviceContext<'_>,
) -> u64 {
if !cr::ADEN.is_set(value) || cr::ADDIS.is_set(value) || self.enable == Enable::Off {
context.tell("CR", START_WITHOUT_ENABLE);
return cr::ADSTART.set(value, 0);
}
if self.enable == Enable::Ready {
self.begin_sequence(registers, context);
} else {
self.held_start = true;
context.tell("CR", START_BEFORE_READY);
}
value
}
// Starts the regular sequence at its first channel, now.
fn begin_sequence(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
if cfgr::EXTEN.get(registers.get(Register::Cfgr)) != 0 {
context.tell("CR", EXTERNAL_TRIGGER_NOT_MODELED);
return;
}
Self::check_sequence(registers, context);
self.position = 0;
let origin = Origin {
at: context.now(),
carry: 0,
};
self.begin_conversion(registers, context, origin);
}
// Tells what in the sequence the model cannot honor, once for the start.
fn check_sequence(registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let preselected = registers.get(Register::Pcsel);
let differential = registers.get(Register::Difsel);
let channels = (0..Self::length(registers)).map(|nth| Self::channel_at(registers, nth));
let (mut reserved, mut closed, mut paired) = (false, false, false);
for channel in channels {
let Some(bit) = CHANNEL_BITS.get(channel) else {
reserved = true;
continue;
};
closed |= !bit.is_set(preselected);
paired |= bit.is_set(differential);
}
if reserved {
context.tell("SQR1", RESERVED_CHANNEL);
}
if closed {
context.tell("PCSEL", CHANNEL_NOT_PRESELECTED);
}
if paired {
context.tell("DIFSEL", DIFFERENTIAL_NOT_MODELED);
}
let configuration = registers.get(Register::Cfgr);
if cfgr::DMNGT.get(configuration) == 0b10 {
context.tell("CFGR", FILTER_MODE_NOT_MODELED);
}
if Self::resolution(registers).is_none() {
context.tell("CFGR", RESERVED_RESOLUTION);
}
Self::check_unmodeled(registers, context);
}
// Tells the settings of the behavior the model leaves out that are stored and nothing more.
fn check_unmodeled(registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let configuration = registers.get(Register::Cfgr);
if cfgr::DISCEN.is_set(configuration) || cfgr::AUTDLY.is_set(configuration) {
context.tell("CFGR", DISCONTINUOUS_NOT_MODELED);
}
let guarded = cfgr::AWD1EN.is_set(configuration)
|| cfgr::JAWD1EN.is_set(configuration)
|| registers.get(Register::Awd2cr) != 0
|| registers.get(Register::Awd3cr) != 0;
if guarded {
context.tell("CFGR", WATCHDOG_NOT_MODELED);
}
let processing = registers.get(Register::Cfgr2);
let shifted = cfgr2::LSHIFT.is_set(processing)
|| [
cfgr2::RSHIFT1,
cfgr2::RSHIFT2,
cfgr2::RSHIFT3,
cfgr2::RSHIFT4,
]
.iter()
.any(|shift| shift.is_set(processing));
if cfgr2::ROVSE.is_set(processing) || cfgr2::JOVSE.is_set(processing) || shifted {
context.tell("CFGR2", OVERSAMPLING_NOT_MODELED);
}
let offset = [
Register::Ofr1,
Register::Ofr2,
Register::Ofr3,
Register::Ofr4,
]
.into_iter()
.any(|register| OFFSET.is_set(registers.get(register)));
if offset {
context.tell("OFR1", OFFSET_NOT_MODELED);
}
}
// `ADSTP`: 25.4.18. A conversion in flight is dropped with its partial result, and the
// datasheet gives the stop no time.
fn stop(&mut self, old: u64, value: u64, context: &mut DeviceContext<'_>) -> u64 {
if !cr::ADSTART.is_set(old) || cr::ADDIS.is_set(value) {
context.tell("CR", STOP_WITHOUT_START);
return cr::ADSTP.set(value, 0);
}
if self
.task
.is_some_and(|task| matches!(task.step, Step::Conversion { .. }))
{
self.task = None;
}
self.sampling_ends = None;
self.sampling_event = None;
self.held_start = false;
self.position = 0;
cr::ADSTART.set(cr::ADSTP.set(value, 0), 0)
}
// A timed operation has ended.
fn finish(
&mut self,
task: Task,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
match task.step {
Step::Calibration => {
let mut value = cr::ADCAL.set(self.shadow_cr, 0);
if cr::ADCALLIN.is_set(value) {
for ready in LINEARITY_READY {
value = ready.set(value, 1);
}
}
self.set_cr(registers, value);
}
Step::Startup => {
self.enable = Enable::Ready;
self.status = isr::ADRDY.set(self.status, 1);
if std::mem::take(&mut self.held_start) {
self.begin_sequence(registers, context);
}
}
Step::Conversion { channel } => {
self.finish_conversion(channel, task.ends, registers, context);
}
}
}
// A conversion has ended: the word, the flags and the next conversion.
fn finish_conversion(
&mut self,
channel: usize,
ends: VirtualNanos,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let word = self.word(channel, Self::bits(registers));
self.store(word, registers);
self.position += 1;
let done = self.position >= Self::length(registers);
if done {
self.status = isr::EOS.set(self.status, 1);
self.position = 0;
}
let control = registers.get(Register::Cfgr);
if !done || cfgr::CONT.is_set(control) {
let origin = Origin {
at: ends,
carry: self.carry,
};
self.begin_conversion(registers, context, origin);
} else if cfgr::EXTEN.get(control) == 0 {
let value = cr::ADSTART.set(self.shadow_cr, 0);
self.set_cr(registers, value);
}
}
// Puts a word in the data register, or drops it, 25.4.27.
fn store(&mut self, word: u32, registers: &RegisterFile) {
if cfgr::OVRMOD.is_set(registers.get(Register::Cfgr)) {
if !self.fifo.is_empty() {
self.status = isr::OVR.set(self.status, 1);
}
self.fifo.clear();
} else if isr::OVR.is_set(self.status) || self.fifo.len() >= FIFO_DEPTH {
self.status = isr::OVR.set(self.status, 1);
return;
}
self.fifo.push_back(word);
self.status = isr::EOC.set(self.status, 1);
}
fn told_while_converting(&self, register: Register, context: &mut DeviceContext<'_>) {
if cr::ADSTART.is_set(self.shadow_cr) {
context.tell(register.spec().name, CONFIGURATION_WHILE_CONVERTING);
}
}
// How many half cycles of `task` have gone by at `hz` when `now` comes, never past its end.
fn elapsed_half_cycles(task: &Task, hz: u64, now: VirtualNanos) -> u64 {
let nanos = u128::from(now.0.saturating_sub(task.started.0));
let half_cycles = nanos * 2 * u128::from(hz) / NANOS_PER_SECOND;
u64::try_from(half_cycles).map_or(task.half_cycles, |elapsed| elapsed.min(task.half_cycles))
}
// The flags that a written one clears.
fn flag_mask() -> u64 {
FLAGS.iter().fold(0, |mask, (flag, _)| mask | flag.mask())
}
}
impl Device for Adc {
type Register = Register;
/// A new `Fadc_ker_ck`: the operation in flight keeps what it had done and does the rest at the
/// new rate, and a stopped clock holds it.
fn on_clock(&mut self, hz: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
if hz == self.clock_hz {
return;
}
self.sync(registers, context);
let old = self.clock_hz;
self.clock_hz = hz;
self.carry = 0;
let Some(mut task) = self.task.take() else {
return;
};
let now = context.now();
let remaining = task.half_cycles - Self::elapsed_half_cycles(&task, old, now);
task.sample_half = task.sample_half.min(remaining);
let span = Span {
half_cycles: remaining,
sample_half: task.sample_half,
};
let origin = Origin { at: now, carry: 0 };
self.begin(task.step, span, origin, context);
}
/// The common block's `VREFEN`, `TSEN` and `VBATEN`, 25.7.2, which reach this converter when
/// it is the third.
fn on_request(
&mut self,
change: RequestChange,
_registers: &mut RegisterFile,
_context: &mut DeviceContext<'_>,
) {
if change.instance != self.converter.common() {
return;
}
if let Some(source) = self.sources.get_mut(change.request) {
*source = change.level;
}
}
/// `ADC_DR` pops the FIFO and clears `EOC`, 25.4.24 and 25.4.27, and a read of `ISR` first
/// settles the end of sampling.
fn read(
&mut self,
register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> Option<u64> {
self.sync(registers, context);
match register {
Register::Dr => {
let value = self
.fifo
.pop_front()
.map_or_else(|| registers.get(Register::Dr), u64::from);
self.status = isr::EOC.set(self.status, 0);
self.refresh(registers, context);
Some(value)
}
Register::Isr
| Register::Ier
| Register::Cr
| Register::Cfgr
| Register::Cfgr2
| Register::Smpr1
| Register::Smpr2
| Register::Pcsel
| Register::Ltr1
| Register::Lhtr1
| Register::Sqr1
| Register::Sqr2
| Register::Sqr3
| Register::Sqr4
| Register::Jsqr
| Register::Ofr1
| Register::Ofr2
| Register::Ofr3
| Register::Ofr4
| Register::Jdr1
| Register::Jdr2
| Register::Jdr3
| Register::Jdr4
| Register::Awd2cr
| Register::Awd3cr
| Register::Ltr2
| Register::Htr2
| Register::Ltr3
| Register::Htr3
| Register::Difsel
| Register::Calfact
| Register::Calfact2 => None,
}
}
/// `ISR` as a read at `now` would find it, with the end of sampling in.
fn peek(
&self,
register: Register,
_registers: &RegisterFile,
now: VirtualNanos,
) -> Option<u64> {
if register != Register::Isr {
return None;
}
let sampled = self.sampling_ends.is_some_and(|ends| ends <= now);
Some(isr::EOSMP.set(
self.status,
u64::from(sampled) | isr::EOSMP.get(self.status),
))
}
fn write(
&mut self,
register: Register,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
self.sync(registers, context);
match register {
// 25.6.1: every flag is `rc_w1`, and `LDORDY` is hardware's.
Register::Isr => self.status &= !(value & Self::flag_mask()),
// 25.6.2, and 25.4.24 for the end of sampling that wants an event when it can interrupt.
Register::Ier => {
self.sampling_interrupt = ier::EOSMPIE.is_set(value);
if let (true, Some(ends), None) = (
self.sampling_interrupt,
self.sampling_ends,
self.sampling_event,
) {
self.sampling_event = Some(context.schedule(ends));
}
}
// 25.6.3 and 25.4.6 to 25.4.18.
Register::Cr => self.write_cr(registers, context),
// 25.4.10: regular configuration is written with no conversion ongoing.
Register::Cfgr
| Register::Cfgr2
| Register::Smpr1
| Register::Smpr2
| Register::Sqr1
| Register::Sqr2
| Register::Sqr3
| Register::Sqr4 => self.told_while_converting(register, context),
// 25.4.7: the differential selection is written with the converter disabled.
Register::Difsel => {
if cr::ADEN.is_set(self.shadow_cr) {
context.tell("DIFSEL", DIFSEL_WHILE_ENABLED);
}
}
// Stored: 25.4.12, 25.4.20, 25.4.30, 25.6.8 to 25.6.10, 25.6.16 to 25.6.27. Read-only,
// so the file drops the write: 25.6.15 and 25.6.18.
Register::Pcsel
| Register::Ltr1
| Register::Lhtr1
| Register::Jsqr
| Register::Ofr1
| Register::Ofr2
| Register::Ofr3
| Register::Ofr4
| Register::Awd2cr
| Register::Awd3cr
| Register::Ltr2
| Register::Htr2
| Register::Ltr3
| Register::Htr3
| Register::Calfact
| Register::Calfact2
| Register::Dr
| Register::Jdr1
| Register::Jdr2
| Register::Jdr3
| Register::Jdr4 => {}
}
self.refresh(registers, context);
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if self.regulator_event == Some(event) {
self.regulator_event = None;
self.status = LDORDY.set(self.status, 1);
if self.enable == Enable::WaitingForRegulator {
self.begin_startup(context);
}
}
if self.sampling_event == Some(event) {
self.sampling_event = None;
}
self.sync(registers, context);
if let Some(task) = self.task.filter(|task| task.event == Some(event)) {
self.task = None;
self.finish(task, registers, context);
}
self.refresh(registers, context);
}
fn reset(&mut self) {
let clock_hz = self.clock_hz;
let mut again = Self::new(self.converter, clock_hz);
again.inputs = self.inputs;
again.reference = self.reference;
again.centi_celsius = self.centi_celsius;
again.backup_battery = self.backup_battery;
*self = again;
}
}
/// The common block of the converters, 25.7: the clock that runs them and the enables of the
/// internal channels.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct AdcCommon {
block: Block,
kernel_hz: u64,
driven: Option<u64>,
// `ADEN` of the master and the slave, as they drive it.
enabled: [Level; 2],
sources: [Level; 3],
}
store!(AdcCommon);
impl AdcCommon {
/// The block `block` with `kernel_hz` of `adc_ker_ck_input` until the tree drives it otherwise.
///
/// The block runs on the kernel clock `ADCSEL` selects, RM0433 rev 9 section 8.7.22, through
/// the enable of its converters, which the clock controller gates for it. The part gives none
/// out of reset, since `ADCSEL` selects `pll2_p_ck` and the PLL is off.
#[must_use]
pub fn new(block: Block, kernel_hz: u64) -> Self {
Self {
block,
kernel_hz,
driven: None,
enabled: [Level::Low; 2],
sources: [Level::Low; 3],
}
}
// `Fadc_ker_ck`, 25.4.3 figure 136, for the settings of `ADC_CCR`, or `None` for a reserved
// `PRESC` or a synchronous clock mode.
fn analog_hz(&self, control: u64) -> Option<u64> {
if ccr::CKMODE.get(control) != 0 {
return None;
}
let code = usize::try_from(ccr::PRESC.get(control)).expect("four bits");
Some(self.kernel_hz / (*PRESCALERS.get(code)? * ANALOG_DIVIDER))
}
fn refresh(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let control = registers.get(adc3_common::Register::Ccr);
match self.analog_hz(control) {
Some(rate) if self.driven != Some(rate) => {
self.driven = Some(rate);
context.clock(ANALOG_CLOCK, rate);
}
Some(_) | None => {}
}
for (output, enable) in [
(VREFEN_REQUEST, ccr::VREFEN),
(VSENSEEN_REQUEST, ccr::VSENSEEN),
(VBATEN_REQUEST, ccr::VBATEN),
] {
let level = Level::from(enable.is_set(control));
if level != self.sources[output] {
self.sources[output] = level;
context.request(output, level);
}
}
}
}
impl Device for AdcCommon {
type Register = adc3_common::Register;
/// A new `adc_ker_ck_input`, the converters' gated kernel clock.
fn on_clock(&mut self, hz: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
self.kernel_hz = hz;
self.refresh(registers, context);
}
// A converter's `ADEN`, as it drives it, which 25.4.10 says `ADCx_CCR` waits for.
fn on_request(
&mut self,
change: RequestChange,
_registers: &mut RegisterFile,
_context: &mut DeviceContext<'_>,
) {
if change.request != ENABLED_REQUEST {
return;
}
let place = if change.instance == self.block.master().instance() {
0
} else if self
.block
.slave()
.is_some_and(|slave| change.instance == slave.instance())
{
1
} else {
return;
};
self.enabled[place] = change.level;
}
fn write(
&mut self,
register: adc3_common::Register,
_value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
match register {
// 25.7.2.
adc3_common::Register::Ccr => {
let control = registers.get(adc3_common::Register::Ccr);
if ccr::DUAL.get(control) != 0 || ccr::DAMDF.get(control) != 0 {
context.tell("CCR", DUAL_NOT_MODELED);
}
if ccr::CKMODE.get(control) != 0 {
context.tell("CCR", SYNCHRONOUS_CLOCK_NOT_MODELED);
} else if self.analog_hz(control).is_none() {
context.tell("CCR", RESERVED_PRESCALER);
}
if self.enabled.contains(&Level::High) {
context.tell("CCR", CCR_WHILE_ENABLED);
}
self.refresh(registers, context);
}
// Stored: the status image of 25.7.1 is read-only and reads zero. 25.7.3 and 25.7.4
// are read-only.
adc3_common::Register::Csr
| adc3_common::Register::Cdr
| adc3_common::Register::Cdr2 => {}
}
}
fn reset(&mut self) {
self.driven = None;
self.enabled = [Level::Low; 2];
self.sources = [Level::Low; 3];
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::{InterruptChange, ResetCause, Told};
use crate::mcu::{Mcu, Operate, Single};
use crate::snapshot;
use crate::soc::stm32h743::builder;
use crate::soc::stm32h743::generated::{ADC1, ADC2, ADC3, ADC3_COMMON, ADC12_COMMON};
use crate::soc::{Layout, Peripheral, Width};
const START_AT: VirtualNanos = VirtualNanos(10_000);
// 50 ns to an analog cycle, so every instant below is whole nanoseconds.
const ANALOG_HZ: u64 = 20_000_000;
const CYCLE_NANOS: u64 = 50;
// 1.5 + 0.5 + 8 cycles: the shortest sampling time at 16 bits.
const SHORT_CONVERSION: u64 = 10 * CYCLE_NANOS;
// ArduPilot's kernel clock, `pll3_r_ck` at 32 MHz on an 8 MHz crystal, over the converter's 2.
const ARDUPILOT_ANALOG_HZ: u64 = 16_000_000;
// 387.5 + 0.5 + 8 cycles at 16 MHz, which is whole nanoseconds.
const ARDUPILOT_CONVERSION: u64 = 24_750;
fn at(base: VirtualNanos, offset: u64) -> VirtualNanos {
VirtualNanos(base.0 + offset)
}
// What a pin at `millivolts` converts to at 16 bits against 3.3 V, 25.4.35.
fn word16(millivolts: u64) -> u64 {
millivolts * 65_535 / 3_300
}
fn control(resolution: u64, dmngt: u64, extra: u64) -> u64 {
cfgr::DMNGT.set(cfgr::RES.set(0, resolution), dmngt) | extra
}
// The converter on a bus of its own, and the instant the test has reached.
struct Rig {
soc: Single<Adc>,
peripheral: Peripheral,
now: VirtualNanos,
}
impl Rig {
fn on(converter: Converter, hz: u64) -> Self {
let peripheral = match converter {
Converter::Adc1 => ADC1,
Converter::Adc2 => ADC2,
Converter::Adc3 => ADC3,
};
Self {
soc: Single::new(builder(), peripheral.name, Adc::new(converter, hz))
.expect("the bus builds"),
peripheral,
now: START_AT,
}
}
fn new() -> Self {
Self::on(Converter::Adc1, ANALOG_HZ)
}
// The regulator on and the converter enabled and ready, as ChibiOS's `adc_lld_vreg_on` and
// `adc_lld_analog_on` leave it.
fn ready() -> Self {
Self::ready_on(Converter::Adc1, ANALOG_HZ)
}
fn ready_on(converter: Converter, hz: u64) -> Self {
let mut rig = Self::on(converter, hz);
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
rig.write(Register::Isr, isr::ADRDY.mask());
rig.or_into(Register::Cr, cr::ADEN.mask());
rig.after(CYCLE_NANOS);
rig.soc.drain_requests();
rig.soc.drain_interrupts();
rig
}
fn location(&self, register: Register) -> u64 {
self.peripheral.base + u64::from(register.spec().offset)
}
fn read(&mut self, register: Register) -> u64 {
let address = self.location(register);
self.soc
.read(self.now, address, Width::W32)
.expect("the register answers")
}
fn write(&mut self, register: Register, value: u64) {
let address = self.location(register);
self.soc
.write(self.now, address, Width::W32, value)
.expect("the register takes the write");
}
// `register |= bits`, as a driver writes it.
fn or_into(&mut self, register: Register, bits: u64) {
let value = self.read(register);
self.write(register, value | bits);
}
fn go_to(&mut self, instant: VirtualNanos) {
self.now = instant;
self.soc.advance_to(instant);
}
fn after(&mut self, nanos: u64) {
self.go_to(at(self.now, nanos));
}
fn flag(&mut self, field: &FieldSpec) -> bool {
field.is_set(self.read(Register::Isr))
}
fn command(&mut self, field: &FieldSpec) -> bool {
field.is_set(self.read(Register::Cr))
}
fn told(&mut self) -> Vec<Told> {
self.soc.drain_told()
}
fn lines(&mut self) -> Vec<(u16, Level)> {
self.soc
.drain_interrupts()
.into_iter()
.map(
|InterruptChange {
interrupt, level, ..
}| (interrupt, level),
)
.collect()
}
// The levels the data request took since the last look.
fn data_requests(&mut self) -> Vec<Level> {
self.soc
.drain_requests()
.into_iter()
.filter(|change| change.request == DMA_REQUEST)
.map(|change| change.level)
.collect()
}
fn input(&mut self, channel: u8, millivolts: u32) {
self.soc
.model
.set_input(Channel::new(channel).expect("a channel"), millivolts);
}
// Writes the sequence, the preselection, one sampling time for every channel and the
// configuration, the registers a driver writes before `ADSTART`.
fn configure(&mut self, channels: &[u8], sampling: u64, configuration: u64) {
let mut sequence = [0_u64; 4];
let mut times = [0_u64; 2];
let mut preselected = 0;
for (nth, &channel) in channels.iter().enumerate() {
let (register, field) = SEQUENCE[nth];
let slot = match register {
Register::Sqr1 => 0,
Register::Sqr2 => 1,
Register::Sqr3 => 2,
_ => 3,
};
sequence[slot] = field.set(sequence[slot], u64::from(channel));
preselected |= CHANNEL_BITS[usize::from(channel)].mask();
let (slot, index) = if channel < 10 {
(0, usize::from(channel))
} else {
(1, usize::from(channel) - 10)
};
times[slot] = SAMPLING_TIMES[index].set(times[slot], sampling);
}
let length = u64::try_from(channels.len() - 1).expect("a short sequence");
sequence[0] = sqr1::L3.set(sequence[0], length);
self.write(Register::Pcsel, preselected);
self.write(Register::Smpr1, times[0]);
self.write(Register::Smpr2, times[1]);
for (register, value) in [
Register::Sqr1,
Register::Sqr2,
Register::Sqr3,
Register::Sqr4,
]
.into_iter()
.zip(sequence)
{
self.write(register, value);
}
self.write(Register::Cfgr, configuration);
}
// Configures and starts, answering the instant of `ADSTART`.
fn started(&mut self, channels: &[u8], sampling: u64, configuration: u64) -> VirtualNanos {
self.configure(channels, sampling, configuration);
self.or_into(Register::Cr, cr::ADSTART.mask());
self.now
}
// Moves the model's clock as the common block would.
fn clock(&mut self, hz: u64) {
self.soc.drive(self.now, |model, registers, context| {
model.on_clock(hz, registers, context);
});
}
// The common block's enables of the internal channels, which reach the third converter.
fn enable_source(&mut self, request: usize, level: Level) {
self.soc.drive(self.now, |model, registers, context| {
model.on_request(
RequestChange {
instance: ADC3_COMMON.name,
request,
level,
},
registers,
context,
);
});
}
}
#[test]
fn reset_values_match_the_manual() {
for converter in Converter::ALL {
let mut rig = Rig::on(converter, ANALOG_HZ);
for register in Register::ALL {
let expected = match register {
Register::Cr => 0x2000_0000,
Register::Cfgr => 0x8000_0000,
Register::Lhtr1 | Register::Htr2 | Register::Htr3 => 0x03FF_FFFF,
_ => 0,
};
assert_eq!(rig.read(register), expected, "{converter:?} {register:?}");
}
}
}
#[test]
fn the_regulator_is_ready_after_the_datasheets_start_up_time() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
assert_eq!(
rig.read(Register::Cr),
0x1000_0000,
"the write leaves deep-power-down, which ChibiOS asserts before it calibrates"
);
rig.after(REGULATOR_STARTUP_NANOS - 1);
assert!(!rig.flag(&LDORDY));
rig.after(1);
assert!(rig.flag(&LDORDY));
assert_eq!(rig.soc.next_event(), None);
rig.write(Register::Isr, LDORDY.mask());
assert!(
rig.flag(&LDORDY),
"the flag is hardware's, a written one cannot clear it"
);
}
#[test]
fn deep_power_down_clears_the_regulator_and_cancels_a_start() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
assert!(rig.flag(&LDORDY));
rig.write(Register::Cr, cr::DEEPPWD.mask());
assert_eq!(rig.read(Register::Cr), cr::DEEPPWD.mask());
assert!(!rig.flag(&LDORDY), "the regulator went with the write");
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(2_000);
rig.write(Register::Cr, cr::DEEPPWD.mask());
rig.after(REGULATOR_STARTUP_NANOS);
assert!(!rig.flag(&LDORDY), "the start in flight was canceled");
rig.write(Register::Cr, cr::DEEPPWD.mask() | cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
assert!(
!rig.flag(&LDORDY),
"a regulator enabled in deep-power-down never starts"
);
assert!(
!cr::ADVREGEN.is_set(rig.read(Register::Cr)),
"every write with DEEPPWD set clears ADVREGEN"
);
assert!(rig.told().is_empty());
}
#[test]
fn clearing_advregen_drops_ldordy_at_once() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
rig.write(Register::Cr, 0);
assert!(!rig.flag(&LDORDY));
}
#[test]
fn calibration_takes_the_datasheets_cycles() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
let started = rig.now;
rig.or_into(Register::Cr, cr::ADCAL.mask());
rig.go_to(at(started, 1_280 * CYCLE_NANOS - 1));
assert!(
rig.command(cr::ADCAL),
"an offset calibration is 1280 cycles"
);
rig.go_to(at(started, 1_280 * CYCLE_NANOS));
assert!(!rig.command(cr::ADCAL));
assert!(
!LINEARITY_READY.iter().any(|ready| rig.command(ready)),
"an offset calibration makes no linearity word"
);
assert!(rig.told().is_empty());
}
#[test]
fn a_linearity_calibration_sets_the_word_ready_bits_at_its_end() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
rig.or_into(Register::Cr, cr::ADCALLIN.mask());
let started = rig.now;
rig.or_into(Register::Cr, cr::ADCAL.mask());
rig.go_to(at(started, 165_010 * CYCLE_NANOS - 1));
assert!(rig.command(cr::ADCAL), "165010 cycles with the linearity");
assert!(!rig.command(cr::LINCALRDYW6));
rig.go_to(at(started, 165_010 * CYCLE_NANOS));
assert!(!rig.command(cr::ADCAL));
assert!(LINEARITY_READY.iter().all(|ready| rig.command(ready)));
}
#[test]
fn a_calibration_started_with_the_converter_enabled_is_told_and_not_started() {
let mut rig = Rig::ready();
rig.or_into(Register::Cr, cr::ADCAL.mask());
assert!(!rig.command(cr::ADCAL));
assert_eq!(rig.soc.next_event(), None);
let told = rig.told();
assert_eq!(told.len(), 1);
assert_eq!(told[0].reason, CALIBRATION_WHILE_ENABLED);
}
#[test]
fn a_calibration_before_the_regulator_is_ready_is_told_and_runs() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS - 1);
rig.or_into(Register::Cr, cr::ADCAL.mask());
assert!(rig.command(cr::ADCAL));
assert_eq!(rig.told()[0].reason, CALIBRATION_BEFORE_REGULATOR);
}
#[test]
fn adrdy_rises_one_cycle_after_aden() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
rig.write(Register::Isr, isr::ADRDY.mask());
let enabled = rig.now;
rig.or_into(Register::Cr, cr::ADEN.mask());
rig.go_to(at(enabled, CYCLE_NANOS - 1));
assert!(!rig.flag(isr::ADRDY));
rig.go_to(at(enabled, CYCLE_NANOS));
assert!(rig.flag(isr::ADRDY));
assert!(rig.command(cr::ADEN));
rig.write(Register::Isr, isr::ADRDY.mask());
assert!(!rig.flag(isr::ADRDY), "a written one clears it");
assert!(rig.command(cr::ADEN), "and the converter stays enabled");
}
#[test]
fn a_written_zero_leaves_a_command_bit_alone() {
let mut rig = Rig::ready();
rig.write(Register::Cr, cr::ADVREGEN.mask());
assert!(rig.command(cr::ADEN), "ADEN is rs: only hardware clears it");
}
#[test]
fn enabling_before_the_regulator_is_ready_waits_for_it_and_is_told() {
let mut rig = Rig::new();
let started = rig.now;
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(1_000);
rig.or_into(Register::Cr, cr::ADEN.mask());
assert_eq!(rig.told()[0].reason, ENABLE_BEFORE_REGULATOR);
rig.go_to(at(started, REGULATOR_STARTUP_NANOS + CYCLE_NANOS - 1));
assert!(!rig.flag(isr::ADRDY));
rig.go_to(at(started, REGULATOR_STARTUP_NANOS + CYCLE_NANOS));
assert!(
rig.flag(isr::ADRDY),
"one cycle after the regulator is ready"
);
}
#[test]
fn adddis_disables_the_converter_and_clears_with_aden() {
let mut rig = Rig::ready();
rig.or_into(Register::Cr, cr::ADDIS.mask());
assert!(!rig.command(cr::ADEN));
assert!(!rig.command(cr::ADDIS));
assert!(rig.flag(isr::ADRDY), "only software clears the flag");
}
#[test]
fn disabling_while_a_conversion_runs_is_told_and_refused() {
let mut rig = Rig::ready();
rig.started(&[1], 0, control(0, 0, cfgr::CONT.mask()));
rig.or_into(Register::Cr, cr::ADDIS.mask());
assert!(rig.command(cr::ADEN));
assert!(!rig.command(cr::ADDIS));
assert_eq!(rig.told()[0].reason, DISABLE_WHILE_BUSY);
}
#[test]
fn configuration_written_while_busy_is_told_and_kept_back() {
let mut rig = Rig::ready();
rig.write(
Register::Cr,
cr::ADEN.mask() | cr::ADVREGEN.mask() | cr::DEEPPWD.mask(),
);
assert!(!rig.command(cr::DEEPPWD));
assert_eq!(rig.told()[0].reason, CONFIGURATION_WHILE_BUSY);
rig.started(&[1], 0, control(0, 0, cfgr::CONT.mask()));
rig.told();
rig.or_into(Register::Cr, BOOST.set(0, 3));
assert_eq!(BOOST.get(rig.read(Register::Cr)), 0);
assert_eq!(rig.told()[0].reason, BOOST_WHILE_CONVERTING);
}
#[test]
fn a_start_before_the_converter_is_ready_waits_for_it_and_is_told() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
rig.input(1, 1_650);
rig.configure(&[1], 0, control(0, 0, 0));
let enabled = rig.now;
rig.or_into(Register::Cr, cr::ADEN.mask());
rig.after(CYCLE_NANOS / 2);
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert!(rig.command(cr::ADSTART), "the start is held, not refused");
assert_eq!(rig.told()[0].reason, START_BEFORE_READY);
rig.go_to(at(enabled, CYCLE_NANOS + SHORT_CONVERSION - 1));
assert!(!rig.flag(isr::EOC));
rig.go_to(at(enabled, CYCLE_NANOS + SHORT_CONVERSION));
assert!(rig.flag(isr::EOC), "the conversion began at ADRDY");
assert_eq!(rig.read(Register::Dr), 32_767);
}
#[test]
fn a_start_with_the_converter_disabled_is_told_and_refused() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert!(!rig.command(cr::ADSTART));
assert_eq!(rig.told()[0].reason, START_WITHOUT_ENABLE);
}
#[test]
fn a_stop_with_nothing_running_is_told() {
let mut rig = Rig::ready();
rig.or_into(Register::Cr, cr::ADSTP.mask());
assert!(!rig.command(cr::ADSTP));
assert_eq!(rig.told()[0].reason, STOP_WITHOUT_START);
}
#[test]
fn conversion_settings_written_during_a_conversion_are_told() {
let mut rig = Rig::ready();
rig.started(&[1], 0, control(0, 0, cfgr::CONT.mask()));
rig.told();
rig.write(Register::Sqr1, 0);
rig.write(Register::Cfgr, 0);
let told = rig.told();
assert_eq!(
told.iter().map(|told| told.register).collect::<Vec<_>>(),
["SQR1", "CFGR"]
);
assert!(
told.iter()
.all(|told| told.reason == CONFIGURATION_WHILE_CONVERTING)
);
}
#[test]
fn a_single_conversion_ends_at_its_instant_with_the_pins_word() {
let mut rig = Rig::ready();
rig.input(1, 1_650);
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION - 1));
assert!(!rig.flag(isr::EOC));
assert!(rig.command(cr::ADSTART));
rig.go_to(at(started, SHORT_CONVERSION));
assert!(rig.flag(isr::EOC));
assert!(rig.flag(isr::EOS), "a sequence of one ends with its word");
assert!(
!rig.command(cr::ADSTART),
"a single software start clears ADSTART at EOS"
);
assert_eq!(rig.read(Register::Dr), 32_767);
assert_eq!(rig.soc.next_event(), None);
}
#[test]
fn the_sampling_time_and_the_resolution_set_the_conversion_time() {
let by_sampling = [
(0, 10),
(1, 11),
(2, 17),
(3, 25),
(4, 41),
(5, 73),
(6, 396),
(7, 819),
];
let by_resolution = [(0, 10), (1, 9), (2, 8), (3, 7), (7, 6)];
let cases = by_sampling
.iter()
.map(|&(sampling, cycles)| (sampling, 0, cycles))
.chain(
by_resolution
.iter()
.map(|&(resolution, cycles)| (0, resolution, cycles)),
);
for (sampling, resolution, cycles) in cases {
let mut rig = Rig::ready();
let started = rig.started(&[1], sampling, control(resolution, 0, 0));
let nanos = cycles * CYCLE_NANOS;
rig.go_to(at(started, nanos - 1));
assert!(!rig.flag(isr::EOC), "SMP {sampling} RES {resolution}");
rig.go_to(at(started, nanos));
assert!(rig.flag(isr::EOC), "SMP {sampling} RES {resolution}");
}
}
#[test]
fn every_resolution_code_gives_its_full_scale() {
let full_scales = [65_535, 16_383, 4_095, 1_023, 65_535, 16_383, 4_095, 255];
for (code, full) in (0..).zip(full_scales) {
let mut rig = Rig::ready();
rig.input(1, 3_300);
let started = rig.started(&[1], 0, control(code, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION));
assert_eq!(rig.read(Register::Dr), full, "RES {code}");
}
}
#[test]
fn a_reserved_resolution_is_told_and_converts_at_sixteen_bits() {
let mut rig = Rig::ready();
rig.input(1, 3_300);
rig.configure(&[1], 0, control(4, 0, 0));
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert_eq!(rig.told()[0].reason, RESERVED_RESOLUTION);
rig.after(SHORT_CONVERSION);
assert_eq!(rig.read(Register::Dr), 65_535);
}
#[test]
fn channel_zero_samples_for_the_time_smp0_names() {
let mut rig = Rig::ready();
let started = rig.started(&[0], 7, control(0, 0, 0));
rig.go_to(at(started, 819 * CYCLE_NANOS - 1));
assert!(!rig.flag(isr::EOC), "SMP0 is 810.5 cycles of sampling");
rig.go_to(at(started, 819 * CYCLE_NANOS));
assert!(rig.flag(isr::EOC));
}
#[test]
fn an_unmodeled_setting_is_told_at_a_start() {
let cases: [(Register, u64, &str); 5] = [
(
Register::Cfgr2,
cfgr2::ROVSE.mask(),
OVERSAMPLING_NOT_MODELED,
),
(
Register::Cfgr2,
cfgr2::LSHIFT.set(0, 1),
OVERSAMPLING_NOT_MODELED,
),
(Register::Ofr2, 0x10, OFFSET_NOT_MODELED),
(Register::Awd2cr, 1, WATCHDOG_NOT_MODELED),
(
Register::Cfgr,
cfgr::DISCEN.mask(),
DISCONTINUOUS_NOT_MODELED,
),
];
for (register, value, reason) in cases {
let mut rig = Rig::ready();
rig.configure(&[1], 0, control(0, 0, 0));
rig.write(register, value);
rig.told();
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert!(
rig.told().iter().any(|told| told.reason == reason),
"{register:?}"
);
}
let mut rig = Rig::ready();
rig.configure(&[1], 0, cfgr::AWD1EN.mask());
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert!(
rig.told()
.iter()
.any(|told| told.reason == WATCHDOG_NOT_MODELED)
);
}
#[test]
fn the_word_is_the_pins_share_of_the_reference() {
let mut rig = Rig::ready();
rig.input(1, 825);
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION));
assert_eq!(rig.read(Register::Dr), 16_383, "rounded down");
rig.input(1, 4_000);
rig.or_into(Register::Cr, cr::ADSTART.mask());
rig.after(SHORT_CONVERSION);
assert_eq!(
rig.read(Register::Dr),
65_535,
"a pin over the reference saturates"
);
rig.soc.model.set_reference(2_500);
rig.input(1, 1_250);
rig.or_into(Register::Cr, cr::ADSTART.mask());
rig.after(SHORT_CONVERSION);
assert_eq!(
rig.read(Register::Dr),
32_767,
"against the reference the board set"
);
rig.soc.model.set_reference(0);
rig.or_into(Register::Cr, cr::ADSTART.mask());
rig.after(SHORT_CONVERSION);
assert_eq!(
rig.read(Register::Dr),
65_535,
"no reference reads full scale"
);
}
#[test]
fn a_scan_converts_the_sequence_in_order() {
let channels = [3, 5, 7, 9, 11, 13, 15, 17, 19, 2, 4];
let mut rig = Rig::ready();
for channel in 0..20_u8 {
rig.input(channel, u32::from(channel) * 100);
}
let started = rig.started(&channels, 0, control(0, 0, 0));
for (nth, channel) in (1..).zip(channels) {
rig.go_to(at(started, nth * SHORT_CONVERSION - 1));
assert!(!rig.flag(isr::EOC), "conversion {nth}");
rig.go_to(at(started, nth * SHORT_CONVERSION));
assert!(rig.flag(isr::EOC), "conversion {nth}");
assert_eq!(
rig.read(Register::Dr),
word16(u64::from(channel) * 100),
"channel {channel}"
);
assert_eq!(rig.flag(isr::EOS), nth == 11, "EOS only with the last");
}
assert!(!rig.command(cr::ADSTART));
}
#[test]
fn sixteen_channels_reach_the_fourth_sequence_register() {
let channels: Vec<u8> = (0..16).collect();
let mut rig = Rig::ready();
for &channel in &channels {
rig.input(channel, 100 + 100 * u32::from(channel));
}
let started = rig.started(&channels, 0, control(0, 0, 0));
let mut words = Vec::new();
for nth in 1..=15 {
rig.go_to(at(started, nth * SHORT_CONVERSION));
words.push(rig.read(Register::Dr));
}
assert!(!rig.flag(isr::EOS));
rig.go_to(at(started, 16 * SHORT_CONVERSION));
assert!(rig.flag(isr::EOS));
assert_eq!(rig.read(Register::Dr), word16(1_600), "SQ16 is channel 15");
assert_eq!(words[14], word16(1_500), "and SQ15 is channel 14");
}
#[test]
fn reserved_channels_are_told() {
let mut rig = Rig::ready();
rig.write(Register::Sqr1, sqr1::SQ1.set(0, 25));
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert_eq!(rig.told()[0].reason, RESERVED_CHANNEL);
}
#[test]
fn a_channel_that_is_not_preselected_is_told() {
let mut rig = Rig::ready();
rig.input(1, 1_650);
rig.write(Register::Sqr1, sqr1::SQ1.set(0, 1));
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert_eq!(rig.told()[0].reason, CHANNEL_NOT_PRESELECTED);
rig.after(SHORT_CONVERSION);
assert_eq!(rig.read(Register::Dr), 32_767, "and converted all the same");
}
#[test]
fn a_differential_channel_is_told_and_converted_single_ended() {
let mut rig = Rig::ready();
rig.write(Register::Difsel, CHANNEL_BITS[1].mask());
rig.told();
rig.input(1, 1_650);
rig.configure(&[1], 0, 0);
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert_eq!(rig.told()[0].reason, DIFFERENTIAL_NOT_MODELED);
}
#[test]
fn the_differential_selection_is_told_when_written_while_enabled() {
let mut rig = Rig::ready();
rig.write(Register::Difsel, CHANNEL_BITS[1].mask());
assert_eq!(rig.told()[0].reason, DIFSEL_WHILE_ENABLED);
}
#[test]
fn continuous_mode_runs_the_sequence_again() {
let mut rig = Rig::ready();
rig.input(4, 1_000);
rig.input(7, 2_000);
let started = rig.started(&[4, 7], 0, control(0, 0, cfgr::CONT.mask()));
let expected = [1_000, 2_000, 1_000, 2_000, 1_000];
for (nth, millivolts) in (1..).zip(expected) {
rig.go_to(at(started, nth * SHORT_CONVERSION));
assert_eq!(
rig.read(Register::Dr),
word16(millivolts),
"conversion {nth}"
);
assert_eq!(rig.flag(isr::EOS), nth % 2 == 0, "conversion {nth}");
rig.write(Register::Isr, isr::EOS.mask());
assert!(rig.command(cr::ADSTART), "continuous mode keeps ADSTART");
}
}
#[test]
fn adstp_drops_the_conversion_and_resets_the_sequence() {
let mut rig = Rig::ready();
rig.input(4, 1_000);
rig.input(7, 2_000);
rig.input(8, 3_000);
let started = rig.started(&[4, 7, 8], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION + SHORT_CONVERSION / 2));
rig.or_into(Register::Cr, cr::ADSTP.mask());
assert!(!rig.command(cr::ADSTP), "the stop is done at once");
assert!(!rig.command(cr::ADSTART));
rig.go_to(at(started, 4 * SHORT_CONVERSION));
assert_eq!(
rig.soc.next_event(),
None,
"the old end left nothing behind"
);
assert_eq!(
rig.read(Register::Dr),
word16(1_000),
"the first word is all there is"
);
assert_eq!(
rig.read(Register::Dr),
word16(1_000),
"the dropped one wrote nothing"
);
rig.input(4, 500);
let again = rig.now;
rig.or_into(Register::Cr, cr::ADSTART.mask());
rig.go_to(at(again, SHORT_CONVERSION));
assert_eq!(
rig.read(Register::Dr),
word16(500),
"the sequence starts again at SQ1"
);
}
#[test]
fn eoc_clears_by_a_data_read_and_eos_by_a_written_one() {
let mut rig = Rig::ready();
rig.input(1, 1_000);
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION));
assert!(rig.flag(isr::EOC) && rig.flag(isr::EOS));
rig.read(Register::Dr);
assert!(!rig.flag(isr::EOC), "a read of DR clears EOC");
assert!(rig.flag(isr::EOS), "and leaves EOS");
rig.write(Register::Isr, 0);
assert!(rig.flag(isr::EOS), "a written zero leaves a flag");
rig.write(Register::Isr, isr::EOS.mask());
assert!(!rig.flag(isr::EOS));
rig.or_into(Register::Cr, cr::ADSTART.mask());
rig.after(SHORT_CONVERSION);
assert!(rig.flag(isr::EOC));
rig.write(Register::Isr, isr::EOC.mask());
assert!(!rig.flag(isr::EOC), "a written one clears EOC too");
}
#[test]
fn eosmp_rises_at_the_end_of_the_sampling_time() {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, 75 - 1));
assert!(!rig.flag(isr::EOSMP), "1.5 cycles of sampling is 75 ns");
rig.go_to(at(started, 75));
assert!(rig.flag(isr::EOSMP));
assert!(!rig.flag(isr::EOC));
rig.write(Register::Isr, isr::EOSMP.mask());
assert!(!rig.flag(isr::EOSMP));
}
#[test]
fn a_debugger_sees_the_end_of_sampling_without_a_read_settling_it() {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0, 0));
let address = rig.location(Register::Isr);
let peeked = |rig: &mut Rig, offset: u64| {
rig.soc
.peek(at(started, offset), address, Width::W32)
.expect("the register answers")
};
assert!(!isr::EOSMP.is_set(peeked(&mut rig, 74)));
assert!(isr::EOSMP.is_set(peeked(&mut rig, 75)));
}
#[test]
fn the_data_register_is_an_eight_stage_fifo() {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0, cfgr::CONT.mask()));
for nth in 1..=8_u64 {
rig.input(1, u32::try_from(100 * nth).expect("small"));
rig.go_to(at(started, nth * SHORT_CONVERSION));
}
assert!(!rig.flag(isr::OVR), "eight words fit");
for nth in 1..=8_u64 {
assert_eq!(rig.read(Register::Dr), word16(100 * nth), "word {nth}");
}
assert_eq!(
rig.read(Register::Dr),
word16(800),
"an empty FIFO answers the last word"
);
}
#[test]
fn overrun_keeps_the_old_words_and_drops_the_new_one() {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0, cfgr::CONT.mask()));
for nth in 1..=9_u64 {
rig.input(1, u32::try_from(100 * nth).expect("small"));
rig.go_to(at(started, nth * SHORT_CONVERSION - 1));
assert!(!rig.flag(isr::OVR), "before the ninth word");
rig.go_to(at(started, nth * SHORT_CONVERSION));
}
assert!(rig.flag(isr::OVR), "the ninth word met a full FIFO");
for nth in 1..=8_u64 {
assert_eq!(rig.read(Register::Dr), word16(100 * nth), "word {nth}");
}
rig.input(1, 1_100);
rig.go_to(at(started, 10 * SHORT_CONVERSION));
assert_eq!(
rig.read(Register::Dr),
word16(800),
"while OVR stands a new word is dropped even with room"
);
assert!(!rig.flag(isr::EOC));
rig.write(Register::Isr, isr::OVR.mask());
rig.go_to(at(started, 11 * SHORT_CONVERSION));
assert_eq!(
rig.read(Register::Dr),
word16(1_100),
"and taken after OVR is cleared"
);
}
#[test]
fn overrun_mode_overwrites_the_data_register() {
let mut rig = Rig::ready();
let started = rig.started(
&[1],
0,
control(0, 0, cfgr::CONT.mask() | cfgr::OVRMOD.mask()),
);
for nth in 1..=3_u64 {
rig.input(1, u32::try_from(100 * nth).expect("small"));
rig.go_to(at(started, nth * SHORT_CONVERSION));
assert_eq!(
rig.flag(isr::OVR),
nth > 1,
"OVR when a word is overwritten"
);
}
assert_eq!(rig.read(Register::Dr), word16(300), "the last word wins");
assert_eq!(
rig.read(Register::Dr),
word16(300),
"and nothing is queued behind it"
);
}
#[test]
fn a_conversion_drives_the_dma_request_until_the_data_is_read() {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0b11, cfgr::CONT.mask()));
rig.go_to(at(started, SHORT_CONVERSION - 1));
assert_eq!(rig.data_requests(), []);
rig.go_to(at(started, SHORT_CONVERSION));
assert_eq!(rig.data_requests(), [Level::High]);
rig.read(Register::Dr);
assert_eq!(
rig.data_requests(),
[Level::Low],
"the transfer takes the word"
);
rig.go_to(at(started, 2 * SHORT_CONVERSION));
assert_eq!(rig.data_requests(), [Level::High]);
}
#[test]
fn overrun_blocks_the_dma_request() {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0b11, cfgr::CONT.mask()));
rig.go_to(at(started, 8 * SHORT_CONVERSION));
assert_eq!(
rig.data_requests(),
[Level::High],
"high from the first word"
);
rig.go_to(at(started, 9 * SHORT_CONVERSION));
assert_eq!(rig.data_requests(), [Level::Low], "blocked by the overrun");
rig.write(Register::Isr, isr::OVR.mask());
assert_eq!(
rig.data_requests(),
[Level::High],
"released with it, words still waiting"
);
}
#[test]
fn dmngt_selects_whether_conversions_drive_requests() {
for (dmngt, driven) in [(0, false), (1, true), (2, false), (3, true)] {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, dmngt, 0));
rig.go_to(at(started, SHORT_CONVERSION));
assert_eq!(
rig.data_requests().contains(&Level::High),
driven,
"DMNGT {dmngt}"
);
let told = rig.told();
assert_eq!(
told.iter()
.any(|told| told.reason == FILTER_MODE_NOT_MODELED),
dmngt == 2,
"DMNGT {dmngt}"
);
}
}
#[test]
fn the_converter_publishes_its_enable_as_a_request_level() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
let enable = |rig: &mut Rig| -> Vec<Level> {
rig.soc
.drain_requests()
.into_iter()
.filter(|change| change.request == ENABLED_REQUEST)
.map(|change| change.level)
.collect()
};
assert_eq!(enable(&mut rig), []);
rig.or_into(Register::Cr, cr::ADEN.mask());
assert_eq!(enable(&mut rig), [Level::High]);
rig.after(CYCLE_NANOS);
rig.or_into(Register::Cr, cr::ADDIS.mask());
assert_eq!(enable(&mut rig), [Level::Low]);
}
#[test]
fn an_enabled_adrdy_raises_the_line_and_a_written_one_lowers_it() {
let mut rig = Rig::new();
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
rig.write(Register::Ier, ier::ADRDYIE.mask());
rig.or_into(Register::Cr, cr::ADEN.mask());
rig.after(CYCLE_NANOS - 1);
assert_eq!(rig.lines(), []);
rig.after(1);
assert_eq!(rig.lines(), [(18, Level::High)]);
rig.write(Register::Isr, isr::ADRDY.mask());
assert_eq!(rig.lines(), [(18, Level::Low)]);
}
#[test]
fn each_enable_raises_the_line_for_its_flag() {
let mut rig = Rig::ready();
rig.write(Register::Ier, ier::EOCIE.mask());
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION));
assert_eq!(rig.lines(), [(18, Level::High)], "EOC under EOCIE");
rig.read(Register::Dr);
assert_eq!(rig.lines(), [(18, Level::Low)], "a read of DR lowers it");
let mut rig = Rig::ready();
rig.write(Register::Ier, ier::EOSIE.mask());
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION));
assert_eq!(rig.lines(), [(18, Level::High)], "EOS under EOSIE");
rig.read(Register::Dr);
assert_eq!(rig.lines(), [], "EOS holds the line past a read of DR");
rig.write(Register::Isr, isr::EOS.mask());
assert_eq!(rig.lines(), [(18, Level::Low)]);
let mut rig = Rig::ready();
rig.write(Register::Ier, ier::OVRIE.mask());
let started = rig.started(&[1], 0, control(0, 0, cfgr::CONT.mask()));
rig.go_to(at(started, 9 * SHORT_CONVERSION));
assert_eq!(rig.lines(), [(18, Level::High)], "OVR under OVRIE");
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION));
assert_eq!(rig.lines(), [], "no enable, no line");
}
#[test]
fn eosmpie_raises_the_line_at_the_end_of_sampling() {
let mut rig = Rig::ready();
rig.write(Register::Ier, ier::EOSMPIE.mask());
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, 75 - 1));
assert_eq!(rig.lines(), []);
rig.go_to(at(started, 75));
assert_eq!(rig.lines(), [(18, Level::High)]);
rig.write(Register::Isr, isr::EOSMP.mask());
assert_eq!(rig.lines(), [(18, Level::Low)]);
}
#[test]
fn the_three_converters_raise_their_own_lines() {
for (converter, line) in [
(Converter::Adc1, 18),
(Converter::Adc2, 18),
(Converter::Adc3, 127),
] {
let mut rig = Rig::on(converter, ANALOG_HZ);
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(REGULATOR_STARTUP_NANOS);
rig.write(Register::Ier, ier::ADRDYIE.mask());
rig.or_into(Register::Cr, cr::ADEN.mask());
rig.after(CYCLE_NANOS);
assert_eq!(rig.lines(), [(line, Level::High)], "{converter:?}");
}
}
#[test]
fn a_pin_the_converter_has_no_internal_source_for_answers_the_boards_voltage() {
let mut rig = Rig::ready();
rig.input(19, 1_000);
let started = rig.started(&[19], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION));
assert_eq!(rig.read(Register::Dr), word16(1_000));
}
#[test]
fn adc3_converts_its_internal_channels_once_enabled() {
let mut rig = Rig::ready_on(Converter::Adc3, ANALOG_HZ);
rig.soc.model.set_backup_battery(4_000);
rig.soc.model.set_temperature(8_000);
let started = rig.started(&[17, 18, 19], 0, control(0, 0, 0));
for nth in 1..=3 {
rig.go_to(at(started, nth * SHORT_CONVERSION));
assert_eq!(rig.read(Register::Dr), 0, "a disabled source reads zero");
}
rig.enable_source(VBATEN_REQUEST, Level::High);
rig.enable_source(VSENSEEN_REQUEST, Level::High);
rig.enable_source(VREFEN_REQUEST, Level::High);
let again = rig.now;
rig.or_into(Register::Cr, cr::ADSTART.mask());
let expected = [word16(1_000), word16(720), word16(1_216)];
for (nth, word) in (1..).zip(expected) {
rig.go_to(at(again, nth * SHORT_CONVERSION));
assert_eq!(rig.read(Register::Dr), word, "channel {}", 16 + nth);
}
}
#[test]
fn a_clock_change_mid_conversion_rebases_the_rest() {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION / 2));
rig.clock(ANALOG_HZ / 2);
rig.go_to(at(started, SHORT_CONVERSION / 2 + SHORT_CONVERSION - 1));
assert!(!rig.flag(isr::EOC), "half done at 20 MHz, half left at 10");
rig.go_to(at(started, SHORT_CONVERSION / 2 + SHORT_CONVERSION));
assert!(rig.flag(isr::EOC));
}
#[test]
fn a_stopped_clock_holds_the_conversion() {
let mut rig = Rig::ready();
let started = rig.started(&[1], 0, control(0, 0, 0));
rig.go_to(at(started, SHORT_CONVERSION / 2));
rig.clock(0);
rig.go_to(at(started, 100 * SHORT_CONVERSION));
assert!(!rig.flag(isr::EOC));
assert_eq!(rig.soc.next_event(), None);
let resumed = rig.now;
rig.clock(ANALOG_HZ);
rig.go_to(at(resumed, SHORT_CONVERSION / 2 - 1));
assert!(!rig.flag(isr::EOC));
rig.go_to(at(resumed, SHORT_CONVERSION / 2));
assert!(rig.flag(isr::EOC), "the half that was left");
}
#[test]
fn external_triggers_are_told_and_nothing_starts_the_conversion() {
let mut rig = Rig::ready();
rig.configure(&[1], 0, cfgr::EXTEN.set(0, 1));
rig.or_into(Register::Cr, cr::ADSTART.mask());
assert_eq!(rig.told()[0].reason, EXTERNAL_TRIGGER_NOT_MODELED);
rig.after(100 * SHORT_CONVERSION);
assert!(rig.command(cr::ADSTART), "armed");
assert!(!rig.flag(isr::EOC));
}
#[test]
fn injected_commands_are_told_and_cleared() {
let mut rig = Rig::ready();
rig.or_into(Register::Cr, cr::JADSTART.mask());
assert!(!rig.command(cr::JADSTART));
assert_eq!(rig.told()[0].reason, INJECTED_NOT_MODELED);
}
#[test]
fn registers_without_behavior_store_what_is_written() {
let mut rig = Rig::new();
for register in [
Register::Cfgr2,
Register::Pcsel,
Register::Ltr1,
Register::Lhtr1,
Register::Jsqr,
Register::Ofr1,
Register::Ofr2,
Register::Ofr3,
Register::Ofr4,
Register::Awd2cr,
Register::Awd3cr,
Register::Ltr2,
Register::Htr2,
Register::Ltr3,
Register::Htr3,
Register::Difsel,
Register::Calfact,
Register::Calfact2,
] {
rig.write(register, 0x0000_0155);
assert_eq!(rig.read(register), 0x0000_0155, "{register:?}");
}
rig.write(Register::Jdr1, 0x55);
assert_eq!(
rig.read(Register::Jdr1),
0,
"the injected data registers are read-only"
);
}
// The registers one common-block test touches, on the block's own bus.
struct CommonRig {
soc: Single<AdcCommon>,
peripheral: Peripheral,
now: VirtualNanos,
}
impl CommonRig {
fn on(block: Block, kernel_hz: u64) -> Self {
let peripheral = match block {
Block::Adc12 => ADC12_COMMON,
Block::Adc3 => ADC3_COMMON,
};
Self {
soc: Single::new(builder(), peripheral.name, AdcCommon::new(block, kernel_hz))
.expect("the bus builds"),
peripheral,
now: START_AT,
}
}
fn read(&mut self, register: adc3_common::Register) -> u64 {
let address = self.peripheral.base + u64::from(register.spec().offset);
self.soc
.read(self.now, address, Width::W32)
.expect("the register answers")
}
fn write(&mut self, register: adc3_common::Register, value: u64) {
let address = self.peripheral.base + u64::from(register.spec().offset);
self.soc
.write(self.now, address, Width::W32, value)
.expect("the register takes the write");
}
fn kernel_clock(&mut self, hz: u64) {
self.soc.drive(self.now, |model, registers, context| {
model.on_clock(hz, registers, context);
});
}
// A converter's `ADEN` as the converter drives it.
fn enabled(&mut self, converter: Converter, level: Level) {
self.soc.drive(self.now, |model, registers, context| {
model.on_request(
RequestChange {
instance: converter.instance(),
request: ENABLED_REQUEST,
level,
},
registers,
context,
);
});
}
fn rates(&mut self) -> Vec<u64> {
self.soc
.drain_clocks()
.into_iter()
.map(|change| {
assert_eq!(change.output, ANALOG_CLOCK);
change.hz
})
.collect()
}
}
#[test]
fn the_common_block_derives_the_analog_clock() {
let mut rig = CommonRig::on(Block::Adc12, 32_000_000);
rig.kernel_clock(32_000_000);
assert_eq!(
rig.rates(),
[16_000_000],
"the kernel clock over 1, then over 2"
);
let dividers = [1, 2, 4, 6, 8, 10, 12, 16, 32, 64, 128, 256];
for (code, divider) in (0..).zip(dividers).skip(1) {
rig.write(adc3_common::Register::Ccr, ccr::PRESC.set(0, code));
assert_eq!(rig.rates(), [32_000_000 / divider / 2], "PRESC {code}");
}
rig.write(adc3_common::Register::Ccr, 0);
assert_eq!(rig.rates(), [16_000_000], "back on the kernel clock");
rig.kernel_clock(0);
assert_eq!(
rig.rates(),
[0],
"a gated kernel clock stops the converters"
);
}
#[test]
fn a_reserved_prescaler_is_told_and_leaves_the_clock_alone() {
let mut rig = CommonRig::on(Block::Adc12, 32_000_000);
rig.kernel_clock(32_000_000);
rig.rates();
rig.write(adc3_common::Register::Ccr, ccr::PRESC.set(0, 12));
assert_eq!(rig.rates(), []);
let told = rig.soc.drain_told();
assert_eq!(told.len(), 1);
assert_eq!(told[0].reason, RESERVED_PRESCALER);
}
#[test]
fn the_enables_drive_the_converters_internal_channels() {
let mut rig = CommonRig::on(Block::Adc3, 32_000_000);
rig.write(adc3_common::Register::Ccr, ccr::VREFEN.mask());
let requests = |rig: &mut CommonRig| -> Vec<(usize, Level)> {
rig.soc
.drain_requests()
.into_iter()
.map(|change| (change.request, change.level))
.collect()
};
assert_eq!(requests(&mut rig), [(VREFEN_REQUEST, Level::High)]);
rig.write(
adc3_common::Register::Ccr,
ccr::VREFEN.mask() | ccr::VSENSEEN.mask() | ccr::VBATEN.mask(),
);
assert_eq!(
requests(&mut rig),
[
(VSENSEEN_REQUEST, Level::High),
(VBATEN_REQUEST, Level::High)
]
);
rig.write(adc3_common::Register::Ccr, 0);
assert_eq!(
requests(&mut rig),
[
(VREFEN_REQUEST, Level::Low),
(VSENSEEN_REQUEST, Level::Low),
(VBATEN_REQUEST, Level::Low)
]
);
}
#[test]
fn ckmode_one_to_three_is_told_and_leaves_the_clock_alone() {
let mut rig = CommonRig::on(Block::Adc12, 32_000_000);
rig.kernel_clock(32_000_000);
rig.rates();
for mode in 1..=3 {
rig.write(adc3_common::Register::Ccr, ccr::CKMODE.set(0, mode));
assert_eq!(rig.rates(), [], "CKMODE {mode}");
assert_eq!(
rig.soc.drain_told()[0].reason,
SYNCHRONOUS_CLOCK_NOT_MODELED
);
}
}
#[test]
fn a_write_of_ccr_while_a_converter_is_enabled_is_told() {
let mut rig = CommonRig::on(Block::Adc12, 32_000_000);
rig.write(adc3_common::Register::Ccr, ccr::VREFEN.mask());
assert!(
rig.soc.drain_told().is_empty(),
"disabled converters, no tell"
);
rig.enabled(Converter::Adc2, Level::High);
rig.write(adc3_common::Register::Ccr, 0);
assert_eq!(rig.soc.drain_told()[0].reason, CCR_WHILE_ENABLED);
rig.enabled(Converter::Adc2, Level::Low);
rig.enabled(Converter::Adc3, Level::High);
rig.write(adc3_common::Register::Ccr, ccr::VREFEN.mask());
assert!(
rig.soc.drain_told().is_empty(),
"the third converter is not this block's"
);
}
#[test]
fn the_status_register_is_stored_and_reads_zero() {
let mut rig = CommonRig::on(Block::Adc12, 32_000_000);
rig.enabled(Converter::Adc1, Level::High);
rig.write(adc3_common::Register::Csr, 0xFFFF_FFFF);
assert_eq!(rig.read(adc3_common::Register::Csr), 0);
}
#[test]
fn the_common_block_round_trips_through_a_snapshot() {
let mut rig = CommonRig::on(Block::Adc12, 32_000_000);
rig.kernel_clock(32_000_000);
rig.enabled(Converter::Adc1, Level::High);
rig.write(adc3_common::Register::Ccr, ccr::VBATEN.mask());
let bytes = snapshot::stored(&rig.soc.model).expect("the model stores itself");
let mut restored = AdcCommon::new(Block::Adc12, 0);
snapshot::load(&mut restored, snapshot::keep(bytes)).expect("and reads itself back");
assert_eq!(format!("{restored:?}"), format!("{:?}", rig.soc.model));
}
#[test]
fn a_reset_puts_the_common_block_back() {
let mut rig = CommonRig::on(Block::Adc12, 32_000_000);
rig.kernel_clock(32_000_000);
rig.enabled(Converter::Adc1, Level::High);
rig.write(adc3_common::Register::Ccr, ccr::VBATEN.mask());
Mcu::reset(&mut rig.soc, rig.now, ResetCause::Core);
assert_eq!(rig.read(adc3_common::Register::Ccr), 0);
let fresh = AdcCommon::new(Block::Adc12, 32_000_000);
assert_eq!(format!("{:?}", rig.soc.model), format!("{fresh:?}"));
rig.kernel_clock(32_000_000);
assert_eq!(
rig.rates(),
[16_000_000],
"the clock is driven again after a reset"
);
}
#[test]
fn each_converter_runs_on_its_common_blocks_analog_clock() {
let bus = builder().build().expect("the bus builds");
for (converter, block) in [
(Converter::Adc1, Block::Adc12),
(Converter::Adc2, Block::Adc12),
(Converter::Adc3, Block::Adc3),
] {
let clock = bus.clock_of(converter.instance()).expect("a clock");
assert_eq!(clock.instance, block.instance(), "{converter:?}");
assert_eq!(clock.output, ANALOG_CLOCK, "{converter:?}");
}
}
#[test]
fn dual_mode_settings_are_stored_and_told() {
let mut rig = CommonRig::on(Block::Adc12, 32_000_000);
let value = ccr::DUAL.set(ccr::DELAY.set(ccr::DAMDF.set(0, 2), 3), 1);
rig.write(adc3_common::Register::Ccr, value);
assert_eq!(rig.read(adc3_common::Register::Ccr), value);
assert_eq!(rig.soc.drain_told()[0].reason, DUAL_NOT_MODELED);
assert_eq!(rig.read(adc3_common::Register::Cdr), 0);
assert_eq!(rig.read(adc3_common::Register::Cdr2), 0);
}
// What ChibiOS's `adc_lld_start` does to a converter, in its register order, with the waits it
// polls and the instants they end at: the instant calibration started and the one it ended.
fn chibios_start(rig: &mut Rig) -> (VirtualNanos, VirtualNanos) {
rig.write(Register::Difsel, 0);
rig.write(Register::Cr, cr::ADVREGEN.mask());
rig.after(10_000);
assert_eq!(
rig.read(Register::Cr),
cr::ADVREGEN.mask(),
"the driver's own assert before it calibrates"
);
let value = rig.read(Register::Cr);
rig.write(
Register::Cr,
value & !(cr::ADCALDIF.mask() | cr::ADCALLIN.mask()),
);
rig.or_into(Register::Cr, 0);
let calibrating = rig.now;
rig.or_into(Register::Cr, cr::ADCAL.mask());
let mut waited = 0;
while rig.command(cr::ADCAL) {
rig.after(1_000);
waited += 1;
assert!(waited < 200, "the calibration never ended");
}
let calibrated = rig.now;
rig.or_into(Register::Cr, BOOST.set(0, 1));
assert_eq!(
BOOST.get(rig.read(Register::Cr)),
1,
"BOOST is two bits on this revision"
);
rig.write(Register::Isr, isr::ADRDY.mask());
let enabled = rig.now;
rig.or_into(Register::Cr, cr::ADEN.mask());
let mut waited = 0;
while !rig.flag(isr::ADRDY) {
rig.after(1);
waited += 1;
assert!(waited < 200, "the converter never became ready");
}
assert_eq!(
rig.now,
at(enabled, 62),
"tSTAB is one cycle, 62.5 ns rounded down"
);
(calibrating, calibrated)
}
// `adc_lld_start_conversion` for a circular group of `channels` at 16 bits, 387.5 cycles of
// sampling and continuous conversion, answering the instant of `ADSTART`.
fn chibios_start_conversion(rig: &mut Rig, channels: &[u8]) -> VirtualNanos {
let status = rig.read(Register::Isr);
rig.write(Register::Isr, status);
rig.write(Register::Cfgr2, 0);
let mut preselected = 0;
let mut times = [0_u64; 2];
let mut sequence = [0_u64; 4];
for (nth, &channel) in channels.iter().enumerate() {
preselected |= CHANNEL_BITS[usize::from(channel)].mask();
let (slot, index) = if channel < 10 {
(0, usize::from(channel))
} else {
(1, usize::from(channel) - 10)
};
times[slot] = SAMPLING_TIMES[index].set(times[slot], 6);
let (register, field) = SEQUENCE[nth];
let place = match register {
Register::Sqr1 => 0,
Register::Sqr2 => 1,
Register::Sqr3 => 2,
_ => 3,
};
sequence[place] = field.set(sequence[place], u64::from(channel));
}
rig.write(Register::Pcsel, preselected);
for register in [
Register::Ltr1,
Register::Lhtr1,
Register::Ltr2,
Register::Htr2,
Register::Ltr3,
Register::Htr3,
Register::Awd2cr,
Register::Awd3cr,
] {
rig.write(register, 0);
}
rig.write(Register::Smpr1, times[0]);
rig.write(Register::Smpr2, times[1]);
let count = u64::try_from(channels.len() - 1).expect("a short group");
rig.write(Register::Sqr1, sequence[0] | sqr1::L3.set(0, count));
rig.write(Register::Sqr2, sequence[1]);
rig.write(Register::Sqr3, sequence[2]);
rig.write(Register::Sqr4, sequence[3]);
rig.write(Register::Cfgr, control(0, 0b11, cfgr::CONT.mask()));
rig.or_into(Register::Cr, cr::ADSTART.mask());
rig.now
}
// The circular DMA's side of the stream: at each conversion's end the request rises, the
// transfer reads the word and the request falls.
fn stream(rig: &mut Rig, started: VirtualNanos, expected: &[u64], passes: u64) -> Vec<u64> {
let count = u64::try_from(expected.len()).expect("a few channels");
let mut words = Vec::new();
rig.data_requests();
for nth in 1..=passes * count {
rig.go_to(at(started, nth * ARDUPILOT_CONVERSION - 1));
assert_eq!(
rig.data_requests(),
[],
"no request before conversion {nth} ends"
);
rig.go_to(at(started, nth * ARDUPILOT_CONVERSION));
assert_eq!(rig.data_requests(), [Level::High], "conversion {nth} ends");
let word = rig.read(Register::Dr);
let index = usize::try_from((nth - 1) % count).expect("a small index");
assert_eq!(word, expected[index], "conversion {nth}");
assert_eq!(
rig.data_requests(),
[Level::Low],
"the transfer took word {nth}"
);
assert_eq!(
rig.flag(isr::EOS),
nth >= count,
"EOS comes with the last channel and stays until it is cleared, conversion {nth}"
);
words.push(word);
}
words
}
#[test]
fn chibios_starts_the_first_converter_and_streams_six_channels() {
let mut rig = Rig::on(Converter::Adc1, ARDUPILOT_ANALOG_HZ);
let (calibrating, calibrated) = chibios_start(&mut rig);
assert_eq!(
calibrated,
at(calibrating, 80_000),
"1280 cycles at 16 MHz, which the 1 microsecond poll sees at its own instant"
);
let channels = [4, 7, 8, 10, 11, 18];
let millivolts = [100, 700, 1_500, 2_200, 2_900, 3_300];
for (channel, millivolts) in channels.into_iter().zip(millivolts) {
rig.input(channel, millivolts);
}
let started = chibios_start_conversion(&mut rig, &channels);
let expected = millivolts.map(u64::from).map(word16);
let words = stream(&mut rig, started, &expected, 3);
assert_eq!(words.len(), 18);
assert_eq!(
rig.lines(),
[],
"the driver enables no interrupt without an error callback"
);
assert!(
rig.told().is_empty(),
"the firmware's own sequence is told nothing"
);
}
#[test]
fn chibios_streams_the_third_converters_internal_channels() {
let mut rig = Rig::on(Converter::Adc3, ARDUPILOT_ANALOG_HZ);
chibios_start(&mut rig);
rig.soc.model.set_backup_battery(4_100);
rig.soc.model.set_temperature(2_500);
for request in [VREFEN_REQUEST, VSENSEEN_REQUEST, VBATEN_REQUEST] {
rig.enable_source(request, Level::High);
}
let started = chibios_start_conversion(&mut rig, &[17, 18, 19]);
let words = stream(&mut rig, started, &[20_355, 12_114, 24_148], 2);
assert_eq!(words, [20_355, 12_114, 24_148, 20_355, 12_114, 24_148]);
assert!(rig.told().is_empty());
}
// What a script leaves: the request levels at each instant, the words and the model.
type StartUp = (Vec<(VirtualNanos, Vec<Level>)>, Vec<u64>, String);
// A start-up script run on a fresh converter: every request level, every word and the model.
fn start_up_script() -> StartUp {
let mut rig = Rig::on(Converter::Adc1, ARDUPILOT_ANALOG_HZ);
chibios_start(&mut rig);
let channels = [4, 7, 8];
for (channel, millivolts) in channels.into_iter().zip([300, 1_300, 2_300]) {
rig.input(channel, millivolts);
}
let started = chibios_start_conversion(&mut rig, &channels);
let mut levels = Vec::new();
let mut words = Vec::new();
for nth in 1..=6 {
rig.go_to(at(started, nth * ARDUPILOT_CONVERSION));
levels.push((rig.now, rig.data_requests()));
words.push(rig.read(Register::Dr));
}
(levels, words, format!("{:?}", rig.soc.model))
}
#[test]
fn two_runs_of_the_start_up_script_are_the_same_run() {
assert_eq!(start_up_script(), start_up_script());
}
#[test]
fn the_model_state_round_trips_through_a_snapshot() {
let mut rig = Rig::ready();
rig.input(1, 700);
let started = rig.started(&[1, 2], 6, control(0, 0b11, cfgr::CONT.mask()));
rig.go_to(at(started, 3 * SHORT_CONVERSION));
let bytes = snapshot::stored(&rig.soc.model).expect("the model stores itself");
let mut restored = Adc::new(Converter::Adc1, ANALOG_HZ);
snapshot::load(&mut restored, snapshot::keep(bytes)).expect("and reads itself back");
assert_eq!(format!("{restored:?}"), format!("{:?}", rig.soc.model));
}
#[test]
fn a_reset_puts_the_converter_back() {
let mut rig = Rig::ready();
rig.input(1, 700);
rig.started(&[1], 6, control(0, 0b11, cfgr::CONT.mask()));
rig.after(SHORT_CONVERSION);
Mcu::reset(&mut rig.soc, rig.now, ResetCause::Core);
assert_eq!(
rig.soc.next_event(),
None,
"the conversion went with the reset"
);
assert_eq!(rig.read(Register::Cr), 0x2000_0000);
rig.after(1_000_000);
assert!(!rig.flag(isr::EOC));
assert_eq!(rig.read(Register::Dr), 0);
let mut again = Rig::on(Converter::Adc1, ANALOG_HZ);
again.input(1, 700);
assert_eq!(
format!("{:?}", rig.soc.model),
format!("{:?}", again.soc.model),
"a reset keeps what the board put on the pins and nothing else"
);
}
}