saltylab-firmware/include/motor_current.h
sl-controls 2b06161cb4 feat: Motor current monitoring and overload protection (Issue #584)
Adds ADC-based motor current sensing with configurable overload threshold,
soft PWM limiting, hard cutoff on sustained overload, and auto-recovery.

Changes:
- include/motor_current.h: MotorCurrentState enum (NORMAL/SOFT_LIMIT/COOLDOWN),
  thresholds (5A hard, 4A soft, 2s overload, 10s cooldown), full API
- src/motor_current.c: reads battery_adc_get_current_ma() each tick (reuses
  existing ADC3 IN13/PC3 DMA sampling); linear PWM scale in soft-limit zone
  (scale256 fixed-point); fault counter + one-tick fault_pending flag for
  main-loop fault log integration; telemetry at MOTOR_CURR_TLM_HZ (5 Hz)
- include/pid_flash.h: add pid_sched_entry_t (16 bytes), pid_sched_flash_t
  (128 bytes at 0x0807FF40), PID_SCHED_MAX_BANDS=6, pid_flash_load_schedule(),
  pid_flash_save_all() — fixes missing types needed by jlink.h (Issue #550)
- src/pid_flash.c: implement flash_write_words() helper, pid_flash_load_schedule(),
  pid_flash_save_all() — single sector-7 erase covers both schedule and PID records
- include/jlink.h: add JLINK_TLM_MOTOR_CURRENT (0x86), jlink_tlm_motor_current_t
  (8 bytes: current_ma, limit_pct, state, fault_count), jlink_send_motor_current_tlm()
- src/jlink.c: implement jlink_send_motor_current_tlm() (14-byte frame)

Motor overload state machine:
  MC_NORMAL     : current_ma < 4000 mA — full PWM authority
  MC_SOFT_LIMIT : 4000-5000 mA — linear reduction (0% at 4A → 100% at 5A)
  MC_COOLDOWN   : >5A sustained 2s → zero output for 10s then NORMAL

Main-loop integration:
  motor_current_tick(now_ms);
  if (motor_current_fault_pending()) fault_log_append(FAULT_MOTOR_OVERCURRENT);
  cmd = motor_current_apply_limit(balance_pid_output());
  motor_current_send_tlm(now_ms);

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 12:25:29 -04:00

122 lines
3.9 KiB
C

#ifndef MOTOR_CURRENT_H
#define MOTOR_CURRENT_H
#include <stdint.h>
#include <stdbool.h>
/*
* motor_current — ADC-based motor current monitoring and overload protection
* for Issue #584.
*
* Hardware:
* ADC3 IN13 (PC3, ADC_CURR_PIN) is already sampled by battery_adc.c via
* DMA2_Stream0 circular. This module reads battery_adc_get_current_ma()
* each tick rather than running a second ADC, since total discharge current
* on this single-motor balance bot equals motor current plus ~30 mA overhead.
*
* Behaviour:
* MC_NORMAL : current_ma < MOTOR_CURR_SOFT_MA — full output
* MC_SOFT_LIMIT : current_ma in [SOFT_MA, HARD_MA) — linear PWM reduction
* MC_COOLDOWN : hard cutoff latched after HARD_MA sustained for
* MOTOR_CURR_OVERLOAD_MS (2 s) — zero output for
* MOTOR_CURR_COOLDOWN_MS (10 s), then MC_NORMAL
*
* Soft limit formula (MC_SOFT_LIMIT):
* scale = (HARD_MA - current_ma) / (HARD_MA - SOFT_MA) [0..1]
* limited_cmd = (int16_t)(cmd * scale)
*
* Fault event:
* On each hard-cutoff trip, s_fault_count is incremented (saturates at 255)
* and motor_current_fault_pending() returns true for one main-loop tick so
* the caller can append a fault log entry.
*
* Main-loop integration (pseudo-code):
*
* void main_loop_tick(uint32_t now_ms) {
* battery_adc_tick(now_ms);
* motor_current_tick(now_ms);
*
* if (motor_current_fault_pending())
* fault_log_append(FAULT_MOTOR_OVERCURRENT);
*
* int16_t cmd = balance_pid_output();
* cmd = motor_current_apply_limit(cmd);
* motor_driver_update(&g_motor, cmd, steer, now_ms);
*
* motor_current_send_tlm(now_ms); // rate-limited to MOTOR_CURR_TLM_HZ
* }
*/
/* ---- Thresholds ---- */
#define MOTOR_CURR_HARD_MA 5000u /* 5 A — hard cutoff level */
#define MOTOR_CURR_SOFT_MA 4000u /* 4 A — soft-limit onset (80% of hard) */
#define MOTOR_CURR_OVERLOAD_MS 2000u /* sustained over HARD_MA before fault */
#define MOTOR_CURR_COOLDOWN_MS 10000u /* zero-output recovery period (ms) */
#define MOTOR_CURR_TLM_HZ 5u /* JLINK_TLM_MOTOR_CURRENT publish rate */
/* ---- State enum ---- */
typedef enum {
MC_NORMAL = 0,
MC_SOFT_LIMIT = 1,
MC_COOLDOWN = 2,
} MotorCurrentState;
/* ---- API ---- */
/*
* motor_current_init() — reset all state.
* Call once during system init, after battery_adc_init().
*/
void motor_current_init(void);
/*
* motor_current_tick(now_ms) — evaluate ADC reading, update state machine.
* Call from main loop after battery_adc_tick(), at any rate ≥ 10 Hz.
* Non-blocking (<1 µs).
*/
void motor_current_tick(uint32_t now_ms);
/*
* motor_current_apply_limit(cmd) — scale motor command by current-limit factor.
* MC_NORMAL: returns cmd unchanged.
* MC_SOFT_LIMIT: returns cmd scaled down linearly.
* MC_COOLDOWN: returns 0.
* Call after motor_current_tick() each loop iteration.
*/
int16_t motor_current_apply_limit(int16_t cmd);
/*
* motor_current_is_faulted() — true while in MC_COOLDOWN (output zeroed).
*/
bool motor_current_is_faulted(void);
/*
* motor_current_state() — current state machine state.
*/
MotorCurrentState motor_current_state(void);
/*
* motor_current_ma() — most recent ADC reading used by the state machine (mA).
*/
int32_t motor_current_ma(void);
/*
* motor_current_fault_count() — lifetime hard-cutoff trip counter (0..255).
*/
uint8_t motor_current_fault_count(void);
/*
* motor_current_fault_pending() — true for exactly one tick after a hard
* cutoff trip fires. Main loop should append a fault log entry and then the
* flag clears automatically on the next call.
*/
bool motor_current_fault_pending(void);
/*
* motor_current_send_tlm(now_ms) — transmit JLINK_TLM_MOTOR_CURRENT (0x86)
* frame to Jetson. Rate-limited to MOTOR_CURR_TLM_HZ; safe to call every tick.
*/
void motor_current_send_tlm(uint32_t now_ms);
#endif /* MOTOR_CURRENT_H */