WIP - New mcu work and refactor - #189
Merged
Merged
Conversation
- Stubbed out PWR - Various RCC functionality - Added python script(s) for auto-generating register data headers.
- UART print example now works.
- Update CRC module to use generated headers - add more headers for other used chips
- Change DMA printfs to debug
- Fix bitbanding masking ADC peripheral space
- fix MPU region count - Add extension board machine
- Add bridge variant for c1 - add example launch script
- initial 2d dashboard for extension - Misc implementations for thermistors and fan on extension
- Comment timer trace that's missing
- Add attempt at xdirect movement
- Fix incorrect pin assignments for PWM/step - Add CXY motor handling
- Support Timer input capture - Fix USART IRQ bugs - Fix default timer ARR on reset - First working XBE boot! - XBE fan test passes - Fix missing display invalidations if no motors exist
Vintagepc/reggen refactor
#5) * prusa-mk4: set OTP BOM ID so the FW boots buddy::hw::Configuration::check_bom_compatible() accepts board_bom_id >= 37 (plus legacy 12/14, and >= 1 on iX). The OTP_v4 initializer left .bomID at 0, so every xBuddy machine -- MK4, MK3.x, CORE One, iX -- hit bsod("BOM ID not compatible") on current firmware. The BSOD fires before usb_device_init(), so nothing reaches the USB CDC syslog and the failure presents as "CDC logging is broken" rather than "the firmware stopped": the GUI still renders, cdc.log and itm.log stay empty, and the USB DCTL register is never written. MINI404 has carried this fix for a while; MINI418 never got it. 38 is a shipped customer board. Note bomID is a dedicated OTP byte -- the 27/34 digits in the datamatrix are the board revision, a different field. * c1-bridge: route ANFC (0xdd) to the extension-board socket CORE One polls an ANFC reader at modbus unit 221 every ~170 ms. The bridge had the case scaffolded but the forwarding call commented out, so the frames went nowhere: the master logged Failed to read input register 221:0x8000@63 Puppies: Communication error, going to recovery puppies on every poll -- 1146 times in a 100 s boot. Not fatal in the sim (the FW still reaches the marlin loop) but on real hardware those retries saturate Modbus and take MMU communication down with them. The commented-out target resolves to C1_DEV_EXT, i.e. the same socket as the xBE, so fake_puppies/xbe_dummy.py answers for both units. It can be told not to (--no-anfc), which restores the unanswered-poll behaviour for reproducing the saturation bug. * prusa-mk4: name the thermistor and heater instances Every qdev_new("thermistor") in the tree was anonymous, and on the xBuddy machines the heaters were too, so there was no QOM path to address one with. That blocks qtests (which is why there is no thermistor test) and makes per-instance configuration impossible: -global applies to every instance of a type, and a machine has five thermistors. Names the five thermistors by sensor role and the two heaters as heater-E / heater-B, matching what prusa-mini.c already does for its heaters, pinda and ir-sensor. /machine/peripheral/thermistor-{noz,bed,brk,brd,case} /machine/peripheral/heater-{E,B} * prusa-mk4: add ht-hotend machine property (PT1000 nozzle) The HT hotend swaps the nozzle NTC for a PT1000, which the firmware detects at boot by reading the raw nozzle ADC (hotend_detect.hpp). Simulating it needs the nozzle thermistor -- and only that one -- to follow the PT1000 curve. -machine prusa-core-one,ht-hotend=on Adds temptable_1010_ht to thermistortables.h, generated from the firmware's thermistor_1010.h and reordered descending-by-temperature like the other tables here. PT1000 is the reversed case (ADC rises with temperature); the existing lookup in thermistor.c handles that unchanged, since it keys on temperature rather than ADC. Verified against the firmware table: exact at every knot, worst round-trip error 0.71 C, which is ADC quantisation (3.13 C/count at 400 C). Named _ht because thermistortables.h already carries an inactive, PtLine-generated upstream Marlin temptable_1010 guarded by THERMISTORHEATER_*. Refuses ht-hotend on non-CORE-One machines (no HAS_HT_HOTEND firmware support) and alongside the machine-level has_sock (the HT hotend takes no sock). Note the latter guard does not see -global heater.has_sock=on, since -global is applied at realize; that combination still produces a PT1000 nozzle with sock-reduced thermal mass. Property name uses a dash: -machine normalises `_` to `-`, so the neighbouring has_sock machine property is in fact unreachable from the command line and only settable via QMP qom-set. Left as-is. * prusa-mk4: register the has_sock machine property as has-sock -machine rewrites underscores to dashes in option keys before the QOM lookup (keyval_dashify(), system/vl.c), so a machine property registered with an underscore can never be set from the command line: $ qemu-system-buddy -machine prusa-mk4-027c,has_sock=on qemu-system-buddy: Property 'prusa-mk4-027c-machine.has-sock' not found prusa-mini.c already registers "has-sock", and this file's own "qtest-force-mmu" uses dashes; the xBuddy has_sock is the odd one out. It was only ever reachable via QMP qom-set. Not a breaking change for -machine users: the spelling that fails today starts working, since both has_sock= and has-sock= now resolve to the same property. QMP callers using the exact old name would need updating. Device properties are unaffected -- -global heater.has_sock=on goes through a different path and keeps its underscore. * prusa-mk4: settable ambient and power-on nozzle temperature The simulated hotend always started at a hardcoded 18 C and could only cool. Boot-time hotend detection is about what the nozzle ADC does in the first few seconds after power-on, so the bench needs to choose that starting point -- a warm restart, a cold room, or a cold-soaked printer in hot air. -machine prusa-core-one,ambient-temp-c=45,nozzle-start-adc10=512 ambient-temp-c (default 25) is what the heaters cool towards; on CORE One that is the chamber, which is effectively constant over a detection window (it takes 20+ min to move, against 3-8 s of observation). nozzle-start-adc10 is the reading at power-on, in the 10-bit units every firmware threshold is expressed in. It is resolved to a temperature through whichever sensor curve the nozzle is configured with, so the same raw value means 150 C on the NTC and 95.8 C on the PT1000 -- it describes the electrical reality rather than assuming a sensor. Unset means start at ambient, which is the only default that is correct for both curves. Resolving it to a temperature (rather than pinning the ADC output) is what makes the reading *evolve*: the model then cools or warms from there, and the slope is the signal detection strategies actually read. heater.c: adds ambient_temp_x10 / start_temp_x10, and makes the tick condition two-sided. It was `currentTemp > ambientTemp + 0.3`, so a hotend below ambient took the else branch on the very first tick and teleported to ambient with no warming transient -- which made the cold-soak case unrepresentable. thermistor.c: adds thermistor_temp_for_adc10(), the reverse of the sensor curve. thermistor_select_table() is split out side-effect free so the lookup can probe a table without driving IRQs. * prusa-mk4: don't disturb machines that opt out, and drop the table probe Two review findings. ambient-temp-c defaulted to 25 and was applied unconditionally, so every xBuddy machine's nozzle start temperature moved from its own 18 C baseline to 25 C whether or not anything used the feature. It now defaults to "unset" and falls back to the machine's own value; the baseline only moves when ambient-temp-c or nozzle-start-adc10 is actually given. Verified: MK4 and CORE One both still report 18 C untouched, 45 C when asked for 45, 150 C for nozzle-start-adc10=626. thermistor_table_for() resolved a table number by constructing a throwaway ThermistorState on the stack and running the selection switch over it. That was safe only while thermistor_select_table() stayed side-effect free -- one added line driving an IRQ would have dereferenced GPIO pointers on a never-realized device. Turned the switch into a plain (table_no) -> (table, len) lookup that holds no device state, with thermistor_select_table() as a thin wrapper, so the hazard is gone structurally rather than by comment. * heater: use the measured cool-down constant on CORE One The cooling branch decayed towards ambient with a hardcoded 0.005/s, i.e. a 200 s time constant, identically on every machine. Boot-time hotend detection reads the *slope* of the nozzle ADC in the seconds after power-on, so that constant is the signal, not a detail. Measured on a CORE One over Grafana/Influx (temp_noz + ttemp_noz at 1 Hz, five heat-and-cool runs; see sim/docs/HotendThermalMeasurement.md): 210 s standard and 230 s HT in still air. Wired per machine, defaulting to the previous 200 s so unmeasured machines behave exactly as before. Also replaces pow(2.7183, ...) with expf(). The sim now reproduces the measurement to about 2%: -0.565 vs -0.553 degC/s (standard) and -0.516 vs -0.502 (HT) at 145 degC, the middle of the ambiguous detection band. That is 3.5 ADC counts/s against 3.4 measured. Deliberately NOT changed: the heating gain. Applying the measured constants to heat-up drops the MK4 nozzle selftest from 227 to 190 degC, outside the firmware's [195,245]. That is not a bad measurement -- the captures ramp from cold, while the selftest starts from a *stabilised* 80 degC, and a single-node lumped model has no notion of the block's internal gradient, so it cannot fit both. Cool-down has no such ambiguity: it was measured directly, and it is the half detection depends on. As a cross-check the measured HT constants put the CORE One HT selftest at 238.8 degC, comfortably inside its [170,260] window. Fan-dependent cooling (~160 s heatbreak fan, ~136 s print fan) is measured and recorded in the doc but not wired: both fans are off at boot, which is the state detection observes. cool_tau_s is a device property, so a scenario that needs a fan-running rate can set it without new plumbing. * thermistor: allow pinning the raw ADC reading The sensor curve can only express readings a working probe could produce, so a scenario could not until now describe a short, an open circuit, or a DMA buffer that has not been filled yet -- all of which the firmware's boot hotend detection classifies, and all of which sit outside every table. -machine prusa-core-one,nozzle-raw-adc10=0 # short / not-yet-valid -machine prusa-core-one,nozzle-raw-adc10=1023 # open circuit Pins the nozzle channel for the whole run, bypassing the curve and the thermal model. Note this is the opposite of nozzle-start-adc10, which is resolved *through* the curve into a starting temperature and then evolves. Also adds thermistor::HoldRaw(adc10) / ::ReleaseRaw script actions, so a probe can fail during a run rather than only at boot -- an intermittent connection is a different failure from a permanently open one. This makes the two silent-demotion hypotheses testable. Both confirmed on a CORE One with an HT hotend fitted: ADC 0 -> clear-NTC -> selected standard ADC 1023 -> clear-NTC -> selected standard i.e. one bad reading makes the firmware classify an HT printer as standard with no dialog; with the stored type already high_temp that silently rewrites EEPROM and invalidates the nozzle selftest, and the dialog then appears on the *next* boot. That is a strong candidate for the reported "dialog fires too often". It also unlocks the MINTEMP/MAXTEMP RSOD scenarios: the same rail means opposite things on the two curves, so the errors are expected to look swapped between a standard and an HT hotend. See docs/HTHotendSimPlan.md 3c. * heater: model fan-dependent cooling, sensor lag and transport delay Three things the previous model could not express, all measured on a CORE One (sim/docs/HotendThermalMeasurement.md 4c-4e): Fan-dependent cool-down. A new "cooling-fan-in" GPIO array carries the print and heatbreak fan duties, and the cool-down constant follows them. The two fans do not add: the print fan blows directly under the nozzle tip while the heatbreak fan only cools the heatsink above the thermal choke, so once the print fan runs the heatbreak path stops mattering. Summing conductances over-predicts cooling by ~25% against measurement; the dominant-fan form reproduces all four measured states to within 8% (18% in the one state whose measurement is itself uncertain). Airflow enters as duty^0.6, which is the Nusselt-vs-Reynolds form and is what the heatbreak fan's two measured duty points give. Block -> thermistor lag and heater -> block dead time. The thermistor sits beside the cartridge in the block rather than in it, so after PWM is applied the reading does not move for ~1-2 s and needs several more to reach its asymptotic rate. A one-node model is 6.7-21x worse than this on every cold ramp measured, on two machines and both hotends. This matters because boot-time hotend detection is being evaluated on pulses of exactly that length; without the lag the sim answers such a question optimistically. All four new properties default to "not characterised", which reproduces the previous behaviour exactly, so no existing machine changes. * prusa-mk4: apply the measured CORE One hotend thermals Wires both cooling fans to the hotend heater and fills in the constants measured on a CORE One (sim/docs/HotendThermalMeasurement.md 4d/4e). Machines without measurements pass zeroes and are unaffected; the wiring is harmless for them. The still-air cool-down was previously 210 s (standard) / 230 s (HT), taken from fits whose captures had the heatbreak fan running at 51/255 throughout. With both fans genuinely off it is 324 s / ~510 s. That is the state boot-time hotend detection observes, because the firmware does not start the heatbreak fan until 45 degC, so the figure in use was wrong in exactly the regime the detection bench runs in. thermal_mass_x10 is left alone for the stock hotend: with the sock fitted -- the shipping configuration, and the one that was captured -- 45 * 0.85 gives 3.83 degC/s against a measured 3.41, so touching it would only risk the selftest window. The HT hotend cannot inherit that value, as it is heavier and takes no sock; 33 is what its measured step response gives. Checked against the captures with harness/heater_model_check.py: the step response now tracks the real printer to within a few percent from 5 s onward for both hotends, where before it was 1.4-2.5x too fast. * prusa-mk4: drive the hotend heater from the timer's duty ratio TIM3 emits a duty ratio 0..255 (f2xx_tim_calc_pwm_ratio), but every xBuddy machine except iX fed it into the heater's "pwm_in" port -- the soft-PWM *edge* input, which reads any nonzero value as "on" and then latches full power through its 500 ms timeout. A proportional heater was therefore behaving as a bang-bang one, and a sustained command was reaching the block half a second late and leaving a spurious residual duty (tOn & 0xFF) behind on release. iX was moved to "raw-pwm-in" when it was added; MK4, MK3.5 and CORE One were left on the old port. This routes them all the same way and drops the branch. Found while checking whether an 8 s open-loop heat pulse would be faithfully reproduced for the boot-time hotend detection work; it is not, on the old path. MK4 CDC smoke test still passes. * fan: separate commanded duty from actual airflow, and split the fault modes "pwm-out" carries what the firmware commanded, and dashboards consume it as such. Nothing carried what the fan is actually moving, so the heater's cooling model had to use the commanded duty -- meaning a stalled fan still cooled the hotend at full rate. New "airflow-out" carries the real thing, and the hotend uses that. The fault modes were also welded together. They are physically distinct and each stages a different failure: is_stalled seized rotor: no tach pulses and no airflow tach_failed broken tach wire: the fan spins and cools, but reports nothing airflow_scale obstructed duct: cools less than commanded, and the firmware cannot see it at all Stall/Resume keep their previous both-at-once meaning, so existing scripts are unaffected; TachFail/TachOK and SetAirflow are new. The tach-only fault is the common field failure and could not be staged before without also changing the thermals and confounding whatever was being tested. Requested for staging a fan selftest failure on a thermally healthy printer. * thermistor: optional deterministic ADC noise The model's ADC reading is exact, so any detection threshold tuned against the sim looks more comfortable than it is. -machine nozzle-noise-adc10=N adds a bounded, seeded perturbation to the nozzle reading so a classifier's *margin* can be exercised. It cannot say what the real noise floor is; that still has to be measured on hardware. Off by default, and never applied to a pinned raw_adc10 reading -- a hard short reads a steady zero, not a noisy one. xorshift32 seeded from a property, so a scenario replays identically. The output is otherwise refreshed only when the heater pushes a new temperature, and the heater stops ticking once settled -- which is exactly the state boot-time detection observes -- so a 10 ms timer re-emits while noise is enabled. Without it the "noise" would be a single frozen offset. Note this means the jitter updates at 100 Hz rather than per ADC conversion, so firmware-side oversampling will not attenuate it the way it would real noise; the knob is pessimistic, which is the safe direction for a margin test. * stm32f4xx_rcc: stop returning uninitialised stack data from RCC->CSR The CSR read handler built a fresh local, set only LSION/LSIRDY on it and returned that, so bits 24..31 -- the reset-cause flags -- were whatever happened to be on the stack. Any firmware branching on the reset cause was reading nondeterministic data, and the stored reset value (0x0E000000: BOR, PAD, POR) was never visible. Read the stored register and overlay only the synthesised LSI status. Writes now honour RMVF, which clears the flags as on hardware; previously it was ignored. Note what this does NOT do: nothing sets IWDGRSTF, so an IWDG reset is still indistinguishable from a power-on. The watchdog itself is modelled and does request a real system reset (stm32_iwdg.c), so a reboot loop reproduces -- only the cause is unreadable. Wiring the watchdog to latch a cause across the reset needs a link from the IWDG to the RCC that no machine currently has, and is left for whoever needs it. * prusa-mk4: stop inverting fan PWM the firmware does not invert The firmware inverts fan PWM only when Configuration::has_inverted_fans() is true, which is `!iX && board bom_id < 37`. Every machine in this file shares the one OTP_v4 initializer in mk4_init(), and that has carried .bomID = 38 since 674710b0b5 -- so the firmware stopped inverting, while five of the six configs here kept f_inverted = true. iX was wrong independently: the firmware never inverts it. The effect is silent and total: a commanded 100% arrives as 0% and a commanded 0% arrives as 100%. Confirmed by instrumenting the heater's fan input with guest timestamps -- during an 8 s window in which the firmware held the print fan at full, the model saw duty 0, and the fan only went to 255 once the firmware turned it off. Caught while validating the BFW-9282 hotend probe, where the fan phase cooled as though no fan were running. With this fixed the same scenario's ADC delta goes from 72 to 142 counts. Note what this means for anything fan-related done in this sim since 674710b0b5: fan behaviour has been inverted on prusa-mk4-*, prusa-mk3v9, prusa-core-one* and prusa-iX-*. mk3v5 was already correct. * prusa-mk4: let ambient-temp-c reach the chamber limit The setter capped ambient at 60 degC. A CORE One's own chamber limit is 65, which is also where boot-time hotend detection puts its warm-nozzle interlock -- so the one scenario aimed squarely at that interlock could not be expressed, and failed with a zero-byte log rather than anything that pointed at the cause. Widened to 0..100: generous for a test knob, still rejects a typo'd 1000. * heater: reject out-of-range dead_time_ms and a zero cool_tau_s at realize Two limits that were implicit. dead_time_ms is a uint16 but the PWM history holds only 31 slots of 250 ms, so anything above 7750 ms was silently truncated to it. cool_tau_s is a divisor and a zero would have produced an infinity. Both now fail at realize with a message naming the actual bound. A property whose range is really set by a constant somewhere else, with nothing linking the two, is how a knob ends up quietly doing something other than what it says -- the same shape as the two bugs this branch already fixes. * fixup! thermistor: allow pinning the raw ADC reading * fixup! thermistor: optional deterministic ADC noise * fixup! heater: model fan-dependent cooling, sensor lag and transport delay * fixup! prusa-mk4: add ht-hotend machine property (PT1000 nozzle) * fixup! heater: model fan-dependent cooling, sensor lag and transport delay * fixup! prusa-mk4: apply the measured CORE One hotend thermals * fixup! prusa-mk4: drive the hotend heater from the timer's duty ratio * Update fan.h Clean Claude drivel * Update prusa-mk4.c Delete claude drivel that's self evident or not relevant to the current state of the code * - Delete claude drivel/novellas/tangents - Collapse pointless single-use functions - Simplify inefficient constructs/patterns * heater: restore the vmstate version bump for the sensor-node fields The cleanup set .version_id back to 1 but left four fields tagged _V(...,2): sensor_x100, fan_duty, pwm_hist and pwm_hist_idx. vmstate_field_exists() skips any field whose version_id exceeds the description's, so all four are silently dropped from the stream -- a restored heater loses the sensor node, the per-fan duty the cooling model reads, and the dead-time ring buffer. post_load then read sensor_x100 unguarded, so sensorTemp came back from a value that was never loaded. Either the fields or the bump had to go; keeping the bump preserves loading of a v1 stream, which minimum_version_id = 1 still promises. --------- Co-authored-by: D.R.racer <drracer@drracer.eu> Co-authored-by: vintagepc <53943260+vintagepc@users.noreply.github.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## MINI404 #189 +/- ##
==========================================
Coverage ? 53.01%
==========================================
Files ? 139
Lines ? 14195
Branches ? 2655
==========================================
Hits ? 7526
Misses ? 6090
Partials ? 579 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…longer gated by gcov
vintagepc
force-pushed
the
merge-new-MCU-work
branch
from
September 5, 2026 17:01
2a39a86 to
19d1c86
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wholesale merge of a feature/development tree for the C1 and new MCUs involved in the XBE (H503, C092).
Largely just a sync, the work is by no means complete and still rough around the edges.