Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Inc/debug_uart.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
* on targets without USE_DEBUG_UART (stubs are no-ops). */
enum {
DBG_EVT_BOOT = 1,
DBG_EVT_NFAULT, /* gate-driver nFAULT, unclassified (GDF etc.) */
DBG_EVT_NFAULT_UVLO, /* nFAULT + low bus → undervoltage class */
DBG_EVT_NFAULT_OCP, /* nFAULT + high current/drive → overcurrent class */
DBG_EVT_NFAULT_OTW, /* nFAULT + high temp → overtemperature class */
DBG_EVT_NFAULT, /* gate-driver nFAULT, unclassified (GDF etc.) */
DBG_EVT_NFAULT_UVLO, /* nFAULT held, low bus → UVLO */
DBG_EVT_NFAULT_OCP, /* retry budget exceeded (persistent VDS) */
DBG_EVT_NFAULT_OTW, /* nFAULT held, bridge live → warning (OTW) */
DBG_EVT_NFAULT_OTSD, /* nFAULT held, bridge dead, MCU thermal label */
DBG_EVT_NFAULT_RETRY, /* short nFAULT pulse (VDS auto-retry) */
DBG_EVT_STUCK,
DBG_EVT_LVC,
DBG_EVT_SIGNAL,
Expand Down
70 changes: 49 additions & 21 deletions Inc/faults.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,34 @@ typedef enum {
FAULT_SIGNAL_TIMEOUT,
FAULT_BEMF_STALL,
/*
* Gate-driver nFAULT classes (DRV8350H / DRV8328). The pin is a single
* open-drain OR of UVLO/OCP/OTW/GDF — no SPI status on hardware-interface
* parts — so these are ADC guesses at the latch edge (V / I / temp).
* Gate-driver nFAULT classes. Not DRV status bits (H-interface has no
* SPI). On DRV8350H they come from pin duration + bridge conduction;
* on DRV8328 every nFAULT is a latched cut (UVLO vs other is a V hint).
*/
FAULT_GD_UVLO, /* low bus voltage guess */
FAULT_GD_OCP, /* high current / was-driving guess */
FAULT_GD_OTW, /* high temperature guess */
FAULT_GD_UNKNOWN, /* nFAULT with no clear ADC signature */
FAULT_GD_UVLO, /* bus below useful pack voltage */
FAULT_GD_OCP, /* VDS retry pulse, or retry budget exceeded */
FAULT_GD_OTW, /* nFAULT held, bridge still driving (DRV OTW) */
FAULT_GD_OTSD, /* held + dead, MCU die in thermal band (log label) */
FAULT_GD_UNKNOWN, /* held + dead, no UVLO/thermal signature (GDF etc.) */
} fault_id_t;

/*
* Latched gate-driver fault cause while nFAULT is active or sticky.
* Classified cause while nFAULT warning/Hi-Z/latch is active.
* FAULT_NONE when healthy. Best-effort only — not a DRV status register.
*/
fault_id_t faultGateDriverCause(void);

/* Short name for logs ("UVLO", "OCP", "OTW", "nFAULT", or ""). */
/* Short name for logs ("UVLO", "OCP", "OTW", "OTSD", "nFAULT", or ""). */
const char *faultGateDriverCauseName(fault_id_t cause);

/* Consume a one-shot gate-driver log queued by faultPollGateDriver.
* Returns 0 if none, FAULT_GD_LOG_WARNING, or FAULT_GD_LOG_ERROR.
* *cause is FAULT_NONE when there is nothing pending. */
#define FAULT_GD_LOG_NONE 0
#define FAULT_GD_LOG_WARNING 1
#define FAULT_GD_LOG_ERROR 2
uint8_t faultGateDriverConsumeLog(fault_id_t *cause);

/*
* Stuck-rotor protection (was the top of setInput after throttle map).
* If bemf_timeout_happened has exceeded the threshold, cut drive and latch.
Expand Down Expand Up @@ -205,23 +214,42 @@ uint32_t faultErrorCount(void);
void faultErrorCountReset(void);

/*
* Gate-driver nFAULT poll (DRV8350H FAULT_N on ARK_G431_CAN, DRV8328 on
* ARK_4IN1_F051 when the pin is defined).
*
* The DRV pin is a single open-drain OR of VDS OCP, UVLO, OTW, and GDF —
* hardware interface parts (…H) do not expose SPI status. On assert we
* classify a best-effort cause from MCU bus voltage / current / temp
* (faultGateDriverCause), cut PWM, latch ESC_FAULT_STUCK, log the specific
* cause, and at zero throttle pulse DRV ENABLE (or clear the software latch
* after ENABLE-low sleep) so latched trips can clear without reboot.
* Gate-driver nFAULT poll. Two chip policies (see faults.c):
*
* DRV8350H (ARK 12S CAN): pin duration + bridge conducting *while driven*.
* pulse ~8 ms : VDS auto-retry — keep PWM, count pulses if seen
* held + live : OTW warning — keep PWM, log WARNING (rate-limited)
* held + dead : Hi-Z until pin releases; MCU temp is a log label only
* (not HIZ vs latch). Persistent pin-low at zero throttle
* ENABLE tRST (consecutive failed pulses; pin-high
* recovery resets), then latch.
* held + commanded, never confirmed live, ~250 ms : Hi-Z (dwell backstop;
* commanded time accumulates across idle blips below
* the DRV sleep threshold; a sleep re-classifies)
*
* DRV8328 (ARK 4IN1): every nFAULT already Hi-Z's the FETs (no OTW-only,
* no 8 ms retry). Cut PWM, latch stuck until zero throttle; nSLEEP sleep
* clears the DRV latch.
*
* Call from the main loop (not the 20 kHz path). No-op without the pin.
*/
void faultPollGateDriver(void);

/* 1 while a real gate-driver trip is latched, or nFAULT is low while the
* driver is awake and past post-wake settle. Sleep (ENABLE/nSLEEP low) does
* not count — the pin is asserted by VCP UVLO then. */
/* 1 kHz timebase for nFAULT duration (called from tenKhzRoutine's 1 kHz
* branch). No-op without the pin. */
void faultGateDriverTick1kHz(void);

/* 1 while PWM must stay off (DRV Hi-Z wait or latched trip). Not set for
* OTW warning or the classify window — those keep driving. Sleep
* (ENABLE/nSLEEP low) does not count: the pin is asserted by VCP UVLO then. */
uint8_t faultGateDriverFaultActive(void);

/* 1 while nFAULT is held and the bridge is still driving (OTW class). */
uint8_t faultGateDriverWarningActive(void);

/* 1 while a Hi-Z wait needs ENABLE high so the pin can auto-clear and so
* a zero-throttle ENABLE tRST can unlatch GDF. LATCH does not set this:
* sleep-on-idle is that state's reset. */
uint8_t faultGateDriverKeepAwake(void);

#endif /* FAULTS_H_ */
5 changes: 5 additions & 0 deletions Inc/gate_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ extern volatile uint8_t gate_driver_awake;
void gateDriverInit(void);
void gateDriverWakeBlocking(void);
void gateDriverSleep(void);
# if defined(USE_DRV_ENABLE)
void gateDriverFaultResetPulse(void);
# else
/* DRV8328 never calls this (nSLEEP sleep-on-idle is the latch reset). */
static inline void gateDriverFaultResetPulse(void) {}
# endif
void gateDriverPoll(void);
/* 1 when ENABLE/nSLEEP is high and nFAULT is past the post-wake settle.
* Pin is asserted in sleep (VCP UVLO) and can glitch on the first PWM. */
Expand Down
13 changes: 9 additions & 4 deletions Inc/targets.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,11 @@
* awake for beeps, drive, and brake. Wake: PWM inactive →
* ENABLE high → ~3 ms settle (charge pump) → then PWM.
* nFAULT = PA12 (FAULT_N) — open-drain, external 20k to 3.3V;
* asserts on VDS OCP (resistor-set on VDS pin), UVLO, OTW, GDF
* (single wire OR — no SPI status). Firmware guesses cause
* from bus V / current / temp ADC at latch (faults.c).
* asserts on VDS OCP (resistor-set on VDS pin), UVLO, OTW,
* OTSD, GDF (single wire OR — no SPI status). Firmware
* classifies from pin duration + whether the bridge is
* still conducting (faults.c): OTW and 8 ms VDS retries
* keep PWM; Hi-Z faults cut drive.
* Mode / IDRIVE / VDS thresholds are hardwired on the board; firmware
* only enables the driver and reacts to FAULT_N.
*/
Expand Down Expand Up @@ -252,7 +254,10 @@
# define CURRENT_ADC_CHANNEL LL_ADC_CHANNEL_3
# define CURRENT_ADC_PIN LL_GPIO_PIN_3
# define USE_SERIAL_TELEMETRY
/* DRV8328 nSLEEP (PA15): high = awake. Sleep when idle via gate_driver.c. */
/* DRV8328 nSLEEP (PA15): high = awake. Sleep when idle via gate_driver.c.
* nFAULT (PB5): every assert Hi-Z's the FETs (no OTW-only / no VDS
* auto-retry). Firmware cuts PWM and latches until zero throttle;
* nSLEEP-low sleep is the fault reset. */
# define USE_DRV8328_NSLEEP
# define NSLEEP_PORT GPIOA
# define NSLEEP_PIN LL_GPIO_PIN_15
Expand Down
36 changes: 19 additions & 17 deletions Src/DroneCAN/DroneCAN.c
Original file line number Diff line number Diff line change
Expand Up @@ -579,30 +579,33 @@ static void can_printf(const char *fmt, ...)
}

/*
* One-shot fault LogMessages: rising edge of nFAULT latch or stuck-rotor
* latch. nFAULT also forces ESC_FAULT_STUCK — prefer the classified
* gate-driver cause ("UVLO"/"OCP"/"OTW"/"nFAULT") when both rise together.
* One-shot fault LogMessages: gate-driver consume queue, or stuck-rotor
* latch. Prefer the gate-driver line when both rise together — nFAULT
* latch also forces ESC_FAULT_STUCK.
*/
static void DroneCAN_pollFaultLogMessages(void)
{
static uint8_t prev_nfault;
static uint8_t prev_stuck;

const uint8_t nfault = faultGateDriverFaultActive();
fault_id_t cause = FAULT_NONE;
const uint8_t gd_level = faultGateDriverConsumeLog(&cause);
const uint8_t stuck = (uint8_t)(escGetState() == ESC_FAULT_STUCK);

if (nfault && !prev_nfault) {
/* ADC-guessed class (DRV pin is OR-only on hardware-interface parts). */
const fault_id_t cause = faultGateDriverCause();
if (gd_level == FAULT_GD_LOG_WARNING) {
if (cause == FAULT_GD_OCP) {
can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_WARNING, "nFAULT retry");
} else {
can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_WARNING, "nFAULT OTW");
}
} else if (gd_level == FAULT_GD_LOG_ERROR) {
switch (cause) {
case FAULT_GD_UVLO:
can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT UVLO");
break;
case FAULT_GD_OCP:
can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT OCP");
break;
case FAULT_GD_OTW:
can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT OTW");
case FAULT_GD_OTSD:
can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT OTSD");
break;
default:
can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "nFAULT");
Expand All @@ -612,7 +615,6 @@ static void DroneCAN_pollFaultLogMessages(void)
can_log(UAVCAN_PROTOCOL_DEBUG_LOGLEVEL_ERROR, "stuck");
}

prev_nfault = nfault;
prev_stuck = stuck;
}

Expand All @@ -621,7 +623,7 @@ static uint8_t DroneCAN_nodeHealth(void)
{
const esc_state_t st = escGetState();

/* Cannot drive: stuck latch, gate-driver trip, or LVC. */
/* Cannot drive: stuck latch, gate-driver Hi-Z/latch, or LVC. */
if (st == ESC_FAULT_STUCK || faultGateDriverFaultActive() || st == ESC_FAULT_LVC) {
return UAVCAN_PROTOCOL_NODESTATUS_HEALTH_CRITICAL;
}
Expand All @@ -630,11 +632,11 @@ static uint8_t DroneCAN_nodeHealth(void)
return UAVCAN_PROTOCOL_NODESTATUS_HEALTH_ERROR;
}
/*
* WARNING only for established hard trips (faultErrorCount: stall +
* established jump desync) or active post-desync holdoff. Acquisition
* roughness and commanded-stop coast (PX4 disarm) must stay OK.
* WARNING: nFAULT OTW (bridge still driving), established hard trips
* (faultErrorCount: stall + established jump desync), or post-desync
* holdoff. Acquisition roughness and commanded-stop coast stay OK.
*/
if (faultErrorCount() > 0 || faultDesyncRestartHoldoffActive()) {
if (faultGateDriverWarningActive() || faultErrorCount() > 0 || faultDesyncRestartHoldoffActive()) {
return UAVCAN_PROTOCOL_NODESTATUS_HEALTH_WARNING;
}
return UAVCAN_PROTOCOL_NODESTATUS_HEALTH_OK;
Expand Down
21 changes: 18 additions & 3 deletions Src/control_loop.c
Original file line number Diff line number Diff line change
Expand Up @@ -513,9 +513,11 @@ void setInput()
}
// Missed-ZC power cut (BLHeli-style): while commutating blind the
// rotor position is unknown - bound the energy driven into a possibly
// wrong phase. On DRV8328 (F051 4IN1) there is no VDS trip; on
// DRV8350H (ARK_G431_CAN) the board has a resistor-set VDS limit and
// FAULT_N is polled in faultPollGateDriver. Applied here in setInput
// wrong phase. On DRV8328 (F051 4IN1) VDS is latched until nSLEEP
// reset; on DRV8350H (ARK_G431_CAN) the board has a resistor-set VDS
// limit that auto-retries in 8 ms. FAULT_N is polled in
// faultPollGateDriver (OTW / short VDS pulses do not cut PWM).
// Applied here in setInput
// (on F051 that is the DShot EXTI IRQ, not the main loop) rather than
// inside the O3 RAM_FUNC 20 kHz body (bench bisect showed adding code
// there disturbs F051 startup). Pulling last_duty_cycle down as well
Expand Down Expand Up @@ -609,6 +611,19 @@ RAM_FUNC void tenKhzRoutine()
ledcounter++;
ramp_count++;
one_khz_loop_counter++;
#if defined(USE_DRV_NFAULT)
{
/* nFAULT classify timebase must run during sine start too. The
* PID 1 kHz block is inside !escInSineStart() and would freeze gd_ms.
* >= PID_LOOP_DIVIDER is 20 ticks at 20 kHz = 1 kHz; the PID
* block still uses `>` (21 ticks, ~952 Hz). */
static uint16_t gd_khz_div;
if (++gd_khz_div >= PID_LOOP_DIVIDER) {
gd_khz_div = 0;
faultGateDriverTick1kHz();
}
}
#endif
if (!escIsArmed()) {
if (cell_count == 0) {
if (inputSet) {
Expand Down
8 changes: 7 additions & 1 deletion Src/debug_uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ static const char *evt_name(uint8_t e)
return "nFAULT OCP";
case DBG_EVT_NFAULT_OTW:
return "nFAULT OTW";
case DBG_EVT_NFAULT_OTSD:
return "nFAULT OTSD";
case DBG_EVT_NFAULT_RETRY:
return "nFAULT retry";
case DBG_EVT_STUCK:
return "stuck";
case DBG_EVT_LVC:
Expand Down Expand Up @@ -202,7 +206,9 @@ void debugUartService(void)
const char *to = escStateName((esc_state_t)rec.b);
debugUartPrintf("esc: %s -> %s\r\n", from, to);
} else if (rec.kind == 2) {
debugUartPrintf("fault: %s\r\n", evt_name(rec.a));
/* OTW / VDS-retry are warnings: HWCI aborts on "fault: nFAULT". */
const char *pfx = (rec.a == DBG_EVT_NFAULT_OTW || rec.a == DBG_EVT_NFAULT_RETRY) ? "warn" : "fault";
debugUartPrintf("%s: %s\r\n", pfx, evt_name(rec.a));
}
}
}
Expand Down
Loading
Loading