···11+#pragma once
22+33+/*
44+ * Simple PID controller for RPM regulation.
55+ *
66+ * Takes a target RPM (from stick) and current RPM (from EKF/eRPM),
77+ * outputs a throttle value [0, 1]. Used by core 1 to maintain consistent
88+ * spin speed regardless of battery sag or friction changes.
99+ *
1010+ * Tuning: start with P only (Ki=Kd=0), increase until oscillation,
1111+ * then back off ~30%. Add I to eliminate steady-state error. D is
1212+ * rarely needed for RPM control.
1313+ */
1414+1515+typedef struct {
1616+ float kp, ki, kd;
1717+ float integral;
1818+ float prev_error;
1919+ float out_min, out_max;
2020+} pid_t;
2121+2222+void pid_init(pid_t *p, float kp, float ki, float kd,
2323+ float out_min, float out_max);
2424+2525+/* Compute one PID step. dt in seconds. Returns clamped output. */
2626+float pid_compute(pid_t *p, float setpoint, float measurement, float dt);
2727+2828+/* Reset integrator and derivative state (e.g., on mode change or disarm). */
2929+void pid_reset(pid_t *p);
···11+#pragma once
22+33+#include <stdbool.h>
44+55+/*
66+ * Accelerometer nonlinearity correction table.
77+ *
88+ * The H3LIS331DL sensors are not perfectly linear across their ±400G
99+ * range. At high G (near saturation), the reported value deviates from
1010+ * true acceleration. This module stores a piecewise-linear correction
1111+ * curve built during bench calibration (spin at known RPMs, compare
1212+ * measured centrifugal to expected ω²r).
1313+ *
1414+ * The correction is applied as a multiplicative factor:
1515+ * corrected = raw * get_accel_correction(raw_g)
1616+ *
1717+ * Table format: pairs of (raw_g, correction_factor), sorted by raw_g.
1818+ * Linear interpolation between points. Outside the table range, the
1919+ * nearest endpoint factor is used.
2020+ */
2121+2222+#define ACCEL_CAL_MAX_POINTS 8 /* max calibration points */
2323+2424+/* Initialize with an empty table (no correction). */
2525+void accel_cal_init(void);
2626+2727+/* Add a calibration point. Points need not be added in order; the table
2828+ * is kept sorted. Returns false if table is full. */
2929+bool accel_cal_add(float raw_g, float correction_factor);
3030+3131+/* Get the correction factor for a given raw G reading. Returns 1.0 if
3232+ * no calibration data exists. */
3333+float accel_cal_get(float raw_g);
3434+3535+/* Number of calibration points currently stored. */
3636+int accel_cal_count(void);
3737+3838+/* Clear all calibration data. */
3939+void accel_cal_clear(void);
···11+#pragma once
22+33+#include <stdbool.h>
44+#include <stdint.h>
55+66+/*
77+ * Battery voltage monitor with low-voltage alert and safety halt.
88+ *
99+ * Reads the VBAT ADC divider on core 0's sensor tick, filters with an
1010+ * exponential moving average, and exposes the current cell voltage.
1111+ * Core 1 checks battery_ok() before commanding motors; if voltage is
1212+ * critically low, motors are cut regardless of stick input.
1313+ *
1414+ * Thresholds are per-cell for a 2S pack (configurable). The alert level
1515+ * triggers a warning flag the LED/status system can display; the halt
1616+ * level cuts motors immediately.
1717+ */
1818+1919+/* Per-cell voltage thresholds (volts). */
2020+#define BATTERY_WARN_V 3.75f
2121+#define BATTERY_HALT_V 3.60f
2222+#define BATTERY_CELLS 2 /* 2S pack */
2323+2424+/* Initialize ADC and start background sampling. Call from core 0 init. */
2525+void battery_init(void);
2626+2727+/* Sample the ADC and update the filtered voltage. Call once per sensor
2828+ * tick (~1kHz) from core 0. */
2929+void battery_sample(void);
3030+3131+/* Returns true if battery voltage is above the halt threshold.
3232+ * Core 1 should check this before commanding motors. */
3333+bool battery_ok(void);
3434+3535+/* Returns true if battery voltage is below the warning threshold. */
3636+bool battery_low(void);
3737+3838+/* Current filtered pack voltage in volts. */
3939+float battery_voltage(void);
4040+4141+/* Current filtered per-cell voltage in volts. */
4242+float battery_cell_voltage(void);
···11+#pragma once
22+33+#include <stdbool.h>
44+#include <stdint.h>
55+66+/*
77+ * RC signal health monitor. Belt-and-suspenders safety layer on top of
88+ * the receiver's own failsafe. Computes a checksum of all channel values;
99+ * if the checksum hasn't changed for longer than the timeout, the link
1010+ * is considered stale even if the receiver reports "alive."
1111+ *
1212+ * Call rc_health_update() every time new CRSF data arrives. Check
1313+ * rc_health_ok() before trusting stick inputs.
1414+ */
1515+1616+/* Timeout in ms. If no channel change is detected for this long, the
1717+ * link is considered stale. PotatoMelt uses 3000ms. */
1818+#define RC_STALE_TIMEOUT_MS 3000
1919+2020+/* Initialize the health monitor. */
2121+void rc_health_init(void);
2222+2323+/* Call with the current CRSF channel array whenever a frame arrives. */
2424+void rc_health_update(const uint16_t channels[16]);
2525+2626+/* Returns true if the RC link appears healthy (channels changing or
2727+ * recently changed within the timeout). */
2828+bool rc_health_ok(void);
···11+#pragma once
22+33+#include <stdint.h>
44+55+/*
66+ * Hardware watchdog for the RP2350. If the main loop stops feeding the
77+ * watchdog within the timeout period, the chip resets. This prevents a
88+ * firmware hang from leaving motors spinning uncontrolled on a meltybrain.
99+ *
1010+ * The watchdog must be updated from BOTH cores if both are running,
1111+ * since either core hanging is a failure condition. Core 0 updates via
1212+ * watchdog_feed(), core 1 via watchdog_feed_core1().
1313+ */
1414+1515+/* Initialize and start the hardware watchdog with the given timeout in ms.
1616+ * Call once from core 1 (main) before launching core 0. */
1717+void watchdog_start(uint32_t timeout_ms);
1818+1919+/* Feed the watchdog from core 0. Must be called at least once per
2020+ * timeout period. Place in the sensor loop. */
2121+void watchdog_feed(void);
2222+2323+/* Feed the watchdog from core 1. Must be called at least once per
2424+ * timeout period. Place in the control loop. */
2525+void watchdog_feed_core1(void);