Merge pull request 'feat(safety): IWDG watchdog, arm hold interlock, tilt alert (bd-3qh)' (#4) from sl-controls/bd-3qh-safety-systems into main
This commit is contained in:
commit
909763494f
51
include/safety.h
Normal file
51
include/safety.h
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
#ifndef SAFETY_H
|
||||||
|
#define SAFETY_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SaltyLab Safety Systems
|
||||||
|
*
|
||||||
|
* Covers:
|
||||||
|
* - IWDG hardware watchdog (MCU reset if main loop hangs)
|
||||||
|
* - RC signal timeout monitoring
|
||||||
|
* - Tilt fault alert via buzzer
|
||||||
|
* - Arm hold interlock (must hold arm for ARMING_HOLD_MS)
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* safety_init() — call once in main() after HAL_Init().
|
||||||
|
* Starts IWDG with WATCHDOG_TIMEOUT_MS timeout from config.h.
|
||||||
|
* Starts ARMING_HOLD_MS countdown from config.h.
|
||||||
|
*/
|
||||||
|
void safety_init(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* safety_refresh() — call every main loop iteration.
|
||||||
|
* Resets IWDG counter. If not called within WATCHDOG_TIMEOUT_MS,
|
||||||
|
* the MCU will reset (independent of software — cannot be disabled).
|
||||||
|
*/
|
||||||
|
void safety_refresh(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* safety_rc_alive() — returns true if RC receiver has sent a frame
|
||||||
|
* within RC_TIMEOUT_MS. Call from the balance loop.
|
||||||
|
*/
|
||||||
|
bool safety_rc_alive(uint32_t now);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* safety_alert_tilt_fault() — one-shot buzzer beep for tilt fault.
|
||||||
|
* Safe to call repeatedly; only fires once per fault.
|
||||||
|
*/
|
||||||
|
void safety_alert_tilt_fault(bool faulted);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* safety_arm_interlock() — returns true once arm button has been
|
||||||
|
* held for ARMING_HOLD_MS from the moment safety_arm_start() was called.
|
||||||
|
*/
|
||||||
|
void safety_arm_start(uint32_t now); /* Call when arm requested */
|
||||||
|
bool safety_arm_ready(uint32_t now); /* Poll until true, then arm */
|
||||||
|
void safety_arm_cancel(void); /* Cancel pending arm */
|
||||||
|
|
||||||
|
#endif /* SAFETY_H */
|
||||||
34
src/main.c
34
src/main.c
@ -8,6 +8,9 @@
|
|||||||
#include "hoverboard.h"
|
#include "hoverboard.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "status.h"
|
#include "status.h"
|
||||||
|
#include "safety.h"
|
||||||
|
#include "crsf.h"
|
||||||
|
#include <math.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
|
||||||
@ -101,6 +104,14 @@ int main(void) {
|
|||||||
status_init();
|
status_init();
|
||||||
HAL_Delay(3000); /* Wait for USB host to enumerate */
|
HAL_Delay(3000); /* Wait for USB host to enumerate */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IWDG starts after the USB enumeration delay — avoids spurious reset
|
||||||
|
* during the 3s startup hold. Once started, safety_refresh() MUST be
|
||||||
|
* called at least every WATCHDOG_TIMEOUT_MS (50ms) or MCU resets.
|
||||||
|
* IWDG cannot be stopped once started (hardware enforced).
|
||||||
|
*/
|
||||||
|
safety_init();
|
||||||
|
|
||||||
/* Init IMU (MPU6000 via SPI1 — mpu6000.c wraps icm42688 + complementary filter) */
|
/* Init IMU (MPU6000 via SPI1 — mpu6000.c wraps icm42688 + complementary filter) */
|
||||||
int imu_ret = mpu6000_init() ? 0 : -1;
|
int imu_ret = mpu6000_init() ? 0 : -1;
|
||||||
|
|
||||||
@ -125,13 +136,34 @@ int main(void) {
|
|||||||
|
|
||||||
uint32_t now = HAL_GetTick();
|
uint32_t now = HAL_GetTick();
|
||||||
|
|
||||||
/* Handle arm/disarm requests from USB */
|
/* Feed hardware watchdog — must happen every WATCHDOG_TIMEOUT_MS */
|
||||||
|
safety_refresh();
|
||||||
|
|
||||||
|
/* RC timeout: disarm if signal lost while armed */
|
||||||
|
if (bal.state == BALANCE_ARMED && !safety_rc_alive(now)) {
|
||||||
|
/* USB-only mode: no RC receiver expected yet — skip RC timeout.
|
||||||
|
* Uncomment when CRSF is wired: balance_disarm(&bal); */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tilt fault buzzer alert (one-shot on fault edge) */
|
||||||
|
safety_alert_tilt_fault(bal.state == BALANCE_TILT_FAULT);
|
||||||
|
|
||||||
|
/* Handle arm/disarm requests from USB with arm-hold interlock */
|
||||||
if (cdc_arm_request) {
|
if (cdc_arm_request) {
|
||||||
cdc_arm_request = 0;
|
cdc_arm_request = 0;
|
||||||
|
/* Start the arm hold timer — motors don't enable until ARMING_HOLD_MS */
|
||||||
|
if (bal.state == BALANCE_DISARMED && fabsf(bal.pitch_deg) < 10.0f) {
|
||||||
|
safety_arm_start(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* Complete arming once hold timer expires */
|
||||||
|
if (safety_arm_ready(now) && bal.state == BALANCE_DISARMED) {
|
||||||
|
safety_arm_cancel();
|
||||||
balance_arm(&bal);
|
balance_arm(&bal);
|
||||||
}
|
}
|
||||||
if (cdc_disarm_request) {
|
if (cdc_disarm_request) {
|
||||||
cdc_disarm_request = 0;
|
cdc_disarm_request = 0;
|
||||||
|
safety_arm_cancel();
|
||||||
balance_disarm(&bal);
|
balance_disarm(&bal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
77
src/safety.c
Normal file
77
src/safety.c
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* safety.c — SaltyLab Safety Systems
|
||||||
|
*
|
||||||
|
* IWDG: 40kHz LSI, prescaler 32 → tick = 0.8ms.
|
||||||
|
* WATCHDOG_TIMEOUT_MS from config.h (default 50ms → reload = 62).
|
||||||
|
* Formula: reload = (timeout_ms / (prescaler / 40000)) - 1
|
||||||
|
* reload = (50 * 40000 / 32) - 1 = 62499 → clamp to 4095 (12-bit max)
|
||||||
|
* At PSC=256: tick = 6.4ms, reload = ceil(50/6.4)-1 = 7 → ~57.6ms
|
||||||
|
* Using PSC=32: tick = 0.8ms, reload = ceil(50/0.8)-1 = 62 → 50ms ✓
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "safety.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "crsf.h"
|
||||||
|
#include "stm32f7xx_hal.h"
|
||||||
|
|
||||||
|
/* IWDG prescaler 32 → LSI(40kHz)/32 = 1250 ticks/sec → 0.8ms/tick */
|
||||||
|
#define IWDG_PRESCALER IWDG_PRESCALER_32
|
||||||
|
#define IWDG_TICK_MS (32.0f / 40.0f) /* 0.8ms per tick */
|
||||||
|
#define IWDG_RELOAD ((uint32_t)(WATCHDOG_TIMEOUT_MS / IWDG_TICK_MS))
|
||||||
|
|
||||||
|
#if IWDG_RELOAD > 4095
|
||||||
|
# error "WATCHDOG_TIMEOUT_MS too large for IWDG_PRESCALER_32 — increase prescaler"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static IWDG_HandleTypeDef hiwdg;
|
||||||
|
|
||||||
|
/* Arm interlock */
|
||||||
|
static uint32_t s_arm_start_ms = 0;
|
||||||
|
static bool s_arm_pending = false;
|
||||||
|
|
||||||
|
/* Tilt fault alert state — edge-detect to fire buzzer once */
|
||||||
|
static bool s_was_faulted = false;
|
||||||
|
|
||||||
|
void safety_init(void) {
|
||||||
|
hiwdg.Instance = IWDG;
|
||||||
|
hiwdg.Init.Prescaler = IWDG_PRESCALER;
|
||||||
|
hiwdg.Init.Reload = IWDG_RELOAD;
|
||||||
|
hiwdg.Init.Window = IWDG_WINDOW_DISABLE;
|
||||||
|
HAL_IWDG_Init(&hiwdg); /* Starts watchdog immediately */
|
||||||
|
}
|
||||||
|
|
||||||
|
void safety_refresh(void) {
|
||||||
|
HAL_IWDG_Refresh(&hiwdg);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool safety_rc_alive(uint32_t now) {
|
||||||
|
/* If crsf_state has never received a frame, last_rx_ms == 0 */
|
||||||
|
if (crsf_state.last_rx_ms == 0) return false;
|
||||||
|
return (now - crsf_state.last_rx_ms) < RC_TIMEOUT_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void safety_alert_tilt_fault(bool faulted) {
|
||||||
|
if (faulted && !s_was_faulted) {
|
||||||
|
/* Rising edge: single buzzer burst to alert rider */
|
||||||
|
HAL_GPIO_WritePin(BEEPER_PORT, BEEPER_PIN, GPIO_PIN_SET);
|
||||||
|
HAL_Delay(200);
|
||||||
|
HAL_GPIO_WritePin(BEEPER_PORT, BEEPER_PIN, GPIO_PIN_RESET);
|
||||||
|
}
|
||||||
|
s_was_faulted = faulted;
|
||||||
|
}
|
||||||
|
|
||||||
|
void safety_arm_start(uint32_t now) {
|
||||||
|
if (!s_arm_pending) {
|
||||||
|
s_arm_start_ms = now;
|
||||||
|
s_arm_pending = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool safety_arm_ready(uint32_t now) {
|
||||||
|
if (!s_arm_pending) return false;
|
||||||
|
return (now - s_arm_start_ms) >= ARMING_HOLD_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void safety_arm_cancel(void) {
|
||||||
|
s_arm_pending = false;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user