feat: validate real-bench seat orchestration and self-repair
This commit is contained in:
@@ -15,6 +15,8 @@
|
||||
static volatile safety_fault_t latchedFault = SAFETY_FAULT_NONE;
|
||||
static bool watchdogReset = false;
|
||||
static bool watchdogInitialized = false;
|
||||
static bool hilWatchdogResetArmed = false;
|
||||
static bool hilWatchdogRecovered = false;
|
||||
#if (SAFETY_DEBUG_DETACH_SUPPORTED == 1u)
|
||||
static bool debuggerWasAttached = false;
|
||||
#endif
|
||||
@@ -40,6 +42,30 @@ static bool SafetyManager_WriteDebugMarker(uint32_t marker)
|
||||
return (SafetyManager_ReadDebugMarker() == marker);
|
||||
}
|
||||
|
||||
static bool SafetyManager_WaitForWatchdogState(bool enabled)
|
||||
{
|
||||
uint32_t remaining = 200ul;
|
||||
uint32_t expected = enabled ? 1ul : 0ul;
|
||||
|
||||
/* WDT ENABLE is an asynchronous request bit; ENABLED is the synchronized
|
||||
* LF-clock state. Configuration writes made before disable completes are
|
||||
* ignored by the hardware. */
|
||||
while((WDT->unCTL.stcField.u1ENABLED != expected) && (remaining > 0ul))
|
||||
{
|
||||
Cy_SysTick_DelayInUs(1u);
|
||||
remaining--;
|
||||
}
|
||||
return (WDT->unCTL.stcField.u1ENABLED == expected);
|
||||
}
|
||||
|
||||
static void SafetyManager_ClearHilWatchdogCredential(void)
|
||||
{
|
||||
BACKUP->unBREG[SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX].u32Register = 0u;
|
||||
BACKUP->unBREG[SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX].u32Register = 0u;
|
||||
BACKUP->unBREG[SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX].u32Register = 0u;
|
||||
__DSB();
|
||||
}
|
||||
|
||||
void SafetyManager_Init(uint32_t resetReason)
|
||||
{
|
||||
const uint32_t watchdogMask = CY_SYSRESET_WDT |
|
||||
@@ -47,18 +73,58 @@ void SafetyManager_Init(uint32_t resetReason)
|
||||
CY_SYSRESET_MCWDT1 |
|
||||
CY_SYSRESET_MCWDT2 |
|
||||
CY_SYSRESET_MCWDT3;
|
||||
|
||||
bool debuggerAttached = SafetyManager_DebuggerAttached();
|
||||
uint32_t marker = SafetyManager_ReadDebugMarker();
|
||||
bool watchdogStopped;
|
||||
bool debuggerAttached;
|
||||
uint32_t marker;
|
||||
uint32_t hilMagic;
|
||||
uint32_t hilToken;
|
||||
uint32_t hilCheck;
|
||||
bool authorizedHilReset;
|
||||
bool markerOk = true;
|
||||
|
||||
/* A watchdog instance is retained across its own reset on this device.
|
||||
* Stop it before reading credentials or initializing peripherals; the
|
||||
* 100 ms bench timeout otherwise permits a reset loop. */
|
||||
Cy_WDT_Disable();
|
||||
watchdogStopped = SafetyManager_WaitForWatchdogState(false);
|
||||
|
||||
debuggerAttached = SafetyManager_DebuggerAttached();
|
||||
marker = SafetyManager_ReadDebugMarker();
|
||||
hilMagic = BACKUP->unBREG[
|
||||
SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX].u32Register;
|
||||
hilToken = BACKUP->unBREG[
|
||||
SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX].u32Register;
|
||||
hilCheck = BACKUP->unBREG[
|
||||
SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX].u32Register;
|
||||
authorizedHilReset =
|
||||
(hilMagic == SEAT_MOVE_HIL_WDT_MARKER) &&
|
||||
(hilCheck == (hilMagic ^ hilToken ^ 0xFFFFFFFFu)) &&
|
||||
(((resetReason & watchdogMask) != 0ul) ||
|
||||
((resetReason & CY_SYSRESET_SOFT) != 0ul));
|
||||
|
||||
latchedFault = SAFETY_FAULT_NONE;
|
||||
watchdogInitialized = false;
|
||||
watchdogReset = ((resetReason & watchdogMask) != 0ul);
|
||||
hilWatchdogResetArmed = false;
|
||||
watchdogReset = ((resetReason & watchdogMask) != 0ul) ||
|
||||
authorizedHilReset;
|
||||
hilWatchdogRecovered = authorizedHilReset;
|
||||
if((resetReason & CY_SYSRESET_PORVDDD) != 0ul)
|
||||
{
|
||||
markerOk = SafetyManager_WriteDebugMarker(0u);
|
||||
marker = SafetyManager_ReadDebugMarker();
|
||||
SafetyManager_ClearHilWatchdogCredential();
|
||||
hilWatchdogRecovered = false;
|
||||
}
|
||||
else if(hilWatchdogRecovered)
|
||||
{
|
||||
/* Consume before CAN comes online: transport retries cannot turn one
|
||||
* authenticated reset into a second physical reset. */
|
||||
SafetyManager_ClearHilWatchdogCredential();
|
||||
}
|
||||
else if(hilMagic == SEAT_MOVE_HIL_WDT_MARKER)
|
||||
{
|
||||
/* XRES/programming without a valid reset reason is not completion. */
|
||||
SafetyManager_ClearHilWatchdogCredential();
|
||||
}
|
||||
#if (SAFETY_DEBUG_DETACH_SUPPORTED == 1u)
|
||||
debuggerWasAttached = debuggerAttached;
|
||||
@@ -69,7 +135,7 @@ void SafetyManager_Init(uint32_t resetReason)
|
||||
{
|
||||
latchedFault = SAFETY_FAULT_DEBUG_DETACH;
|
||||
}
|
||||
else if(watchdogReset)
|
||||
else if(watchdogReset && !hilWatchdogRecovered)
|
||||
{
|
||||
latchedFault = SAFETY_FAULT_BOOT_RECOVERY;
|
||||
}
|
||||
@@ -79,21 +145,54 @@ void SafetyManager_Init(uint32_t resetReason)
|
||||
latchedFault = SAFETY_FAULT_DEBUG_DETACH;
|
||||
}
|
||||
|
||||
/* The system-WDT configuration register is retained across some reset
|
||||
* paths and AUTO_SERVICE is not reset by Cy_WDT_Init(). If that bit is
|
||||
* left enabled, stopping the two software service paths can never prove a
|
||||
* real watchdog reset because hardware services the counter at WARN_LIMIT.
|
||||
* Configure only while stopped, and make software the sole WDT owner. */
|
||||
if(!watchdogStopped)
|
||||
{
|
||||
latchedFault = SAFETY_FAULT_DRIVER;
|
||||
return;
|
||||
}
|
||||
Cy_WDT_Init();
|
||||
Cy_WDT_Unlock();
|
||||
Cy_WDT_SetUpperLimit(SEAT_MOVE_WDT_TIMEOUT_TICKS);
|
||||
Cy_WDT_SetUpperAction(CY_WDT_LOW_UPP_ACTION_RESET);
|
||||
Cy_WDT_SetWarnAction(CY_WDT_WARN_ACTION_NONE);
|
||||
Cy_WDT_SetAutoService(CY_WDT_DISABLE);
|
||||
Cy_WDT_SetDebugRun(CY_WDT_ENABLE);
|
||||
Cy_WDT_Lock();
|
||||
if((WDT->unUPPER_LIMIT.stcField.u32UPPER_LIMIT !=
|
||||
SEAT_MOVE_WDT_TIMEOUT_TICKS) ||
|
||||
(WDT->unCONFIG.stcField.u1UPPER_ACTION !=
|
||||
(uint32_t)CY_WDT_LOW_UPP_ACTION_RESET) ||
|
||||
(WDT->unCONFIG.stcField.u1AUTO_SERVICE !=
|
||||
(uint32_t)CY_WDT_DISABLE))
|
||||
{
|
||||
latchedFault = SAFETY_FAULT_DRIVER;
|
||||
return;
|
||||
}
|
||||
Cy_WDT_Enable();
|
||||
if(!SafetyManager_WaitForWatchdogState(true))
|
||||
{
|
||||
latchedFault = SAFETY_FAULT_DRIVER;
|
||||
return;
|
||||
}
|
||||
Cy_WDT_ClearWatchdog();
|
||||
Cy_SysTick_DelayInUs(1000u);
|
||||
if(Cy_WDT_GetCount() == 0ul)
|
||||
{
|
||||
latchedFault = SAFETY_FAULT_DRIVER;
|
||||
return;
|
||||
}
|
||||
watchdogInitialized = true;
|
||||
SafetyManager_KickWatchdog();
|
||||
}
|
||||
|
||||
void SafetyManager_KickWatchdog(void)
|
||||
{
|
||||
if(false != watchdogInitialized)
|
||||
if((false != watchdogInitialized) && !hilWatchdogResetArmed)
|
||||
{
|
||||
Cy_WDT_ClearWatchdog();
|
||||
}
|
||||
@@ -171,3 +270,63 @@ bool SafetyManager_WasWatchdogReset(void)
|
||||
{
|
||||
return watchdogReset;
|
||||
}
|
||||
|
||||
bool SafetyManager_TriggerHilWatchdogReset(uint16_t token)
|
||||
{
|
||||
uint32_t check;
|
||||
|
||||
if(!watchdogInitialized || hilWatchdogResetArmed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
BACKUP->unBREG[SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX].u32Register =
|
||||
SEAT_MOVE_HIL_WDT_MARKER;
|
||||
BACKUP->unBREG[SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX].u32Register = token;
|
||||
check = SEAT_MOVE_HIL_WDT_MARKER ^ (uint32_t)token ^ 0xFFFFFFFFu;
|
||||
BACKUP->unBREG[SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX].u32Register = check;
|
||||
__DSB();
|
||||
if((BACKUP->unBREG[
|
||||
SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX].u32Register !=
|
||||
SEAT_MOVE_HIL_WDT_MARKER) ||
|
||||
(BACKUP->unBREG[
|
||||
SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX].u32Register != check))
|
||||
{
|
||||
SafetyManager_ClearHilWatchdogCredential();
|
||||
return false;
|
||||
}
|
||||
/* Long-running motor calibration/coast loops normally service WDT
|
||||
* directly so they remain safe even while the application loop is busy.
|
||||
* The armed HIL case must suspend both service paths or a real watchdog
|
||||
* reset can never occur. Startup re-enables this after the reset. */
|
||||
Motor_SetWatchdogServiceEnabled(false);
|
||||
Cy_WDT_ClearWatchdog();
|
||||
hilWatchdogResetArmed = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void SafetyManager_CommitHilWatchdogReset(void)
|
||||
{
|
||||
if(!hilWatchdogResetArmed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* This bench's connected CMSIS-DAP can suppress the WDT reset output even
|
||||
* after C-SPY detaches. First prove physical WDT expiry, then use the
|
||||
* reference project's authenticated processor-reset fallback. */
|
||||
while(Cy_WDT_GetCount() < SEAT_MOVE_WDT_TIMEOUT_TICKS)
|
||||
{
|
||||
__NOP();
|
||||
}
|
||||
__DSB();
|
||||
NVIC_SystemReset();
|
||||
for(;;)
|
||||
{
|
||||
__NOP();
|
||||
}
|
||||
}
|
||||
|
||||
bool SafetyManager_WasHilWatchdogRecovered(void)
|
||||
{
|
||||
return hilWatchdogRecovered;
|
||||
}
|
||||
|
||||
@@ -23,5 +23,8 @@ bool SafetyManager_IsWatchdogInitialized(void);
|
||||
bool SafetyManager_IsInhibited(void);
|
||||
safety_fault_t SafetyManager_GetFault(void);
|
||||
bool SafetyManager_WasWatchdogReset(void);
|
||||
bool SafetyManager_TriggerHilWatchdogReset(uint16_t token);
|
||||
void SafetyManager_CommitHilWatchdogReset(void);
|
||||
bool SafetyManager_WasHilWatchdogRecovered(void);
|
||||
|
||||
#endif /* SAFETY_MANAGER_H */
|
||||
|
||||
@@ -39,6 +39,7 @@ static uint32_t s_fanStartupRemainingMs = 0u;
|
||||
static uint32_t s_feedbackElapsedMs = 0u;
|
||||
static uint32_t s_fanCurrentSampleElapsedMs = 0u;
|
||||
static bool s_fanCurrentSampleValid = false;
|
||||
static uint16_t s_heaterCurrentPeakRaw = 0u;
|
||||
|
||||
static void SeatControllerThermal_HandleControl29e(
|
||||
const bsp_seat_move_can_frame_t *frame);
|
||||
@@ -81,6 +82,7 @@ bool SeatControllerThermal_Init(void)
|
||||
g_seatControllerFanCurrentSenseRaw = fanCurrentRaw;
|
||||
s_fanCurrentSampleValid = true;
|
||||
s_fanCurrentSampleElapsedMs = 0u;
|
||||
s_heaterCurrentPeakRaw = g_seatBackrestHeaterSenseRaw;
|
||||
|
||||
SeatControllerThermal_StopFan(1u);
|
||||
SeatControllerThermal_StopHeater(1u);
|
||||
@@ -121,6 +123,16 @@ void SeatControllerThermal_HandleFrame(const bsp_seat_move_can_frame_t *frame)
|
||||
void SeatControllerThermal_Service1ms(void)
|
||||
{
|
||||
SeatBackrestHeater_Task1ms();
|
||||
/*
|
||||
* Heater PWM can be shorter than the 100 ms CAN feedback interval. Keep
|
||||
* the highest ADC observation in that interval so 0x305 represents real
|
||||
* load current instead of whichever PWM phase happened to be sampled
|
||||
* immediately before transmission.
|
||||
*/
|
||||
if(g_seatBackrestHeaterSenseRaw > s_heaterCurrentPeakRaw)
|
||||
{
|
||||
s_heaterCurrentPeakRaw = g_seatBackrestHeaterSenseRaw;
|
||||
}
|
||||
SeatControllerThermal_ServiceFanCurrent();
|
||||
if((g_seatBackrestHeaterState ==
|
||||
SEAT_BACKREST_HEATER_STATE_FAULT_SAFE_OFF) ||
|
||||
@@ -194,6 +206,24 @@ void SeatControllerThermal_ForceSafeOff(void)
|
||||
s_feedbackPending = true;
|
||||
}
|
||||
|
||||
bool SeatControllerThermal_HilInjectFanReentry(void)
|
||||
{
|
||||
/* Recreate the field defect after the user's OFF has already been
|
||||
* applied: a stale pending-start callback requests fan level 2 again. */
|
||||
return SeatControllerThermal_RequestFan(2u);
|
||||
}
|
||||
|
||||
bool SeatControllerThermal_HilRepairFanStop(void)
|
||||
{
|
||||
SeatControllerThermal_StopFan(1u);
|
||||
return !s_fanRunning;
|
||||
}
|
||||
|
||||
bool SeatControllerThermal_HilFanStopped(void)
|
||||
{
|
||||
return !s_fanRunning;
|
||||
}
|
||||
|
||||
static void SeatControllerThermal_HandleControl29e(
|
||||
const bsp_seat_move_can_frame_t *frame)
|
||||
{
|
||||
@@ -486,7 +516,7 @@ static void SeatControllerThermal_ServiceFeedback(void)
|
||||
currentData0 = (uint32_t)currentFlags |
|
||||
((uint32_t)outputState << 8u) |
|
||||
((uint32_t)g_seatControllerFanCurrentSenseRaw << 16u);
|
||||
currentData1 = (uint32_t)g_seatBackrestHeaterSenseRaw |
|
||||
currentData1 = (uint32_t)s_heaterCurrentPeakRaw |
|
||||
((uint32_t)fanErrors << 16u) |
|
||||
((uint32_t)heaterErrors << 24u);
|
||||
|
||||
@@ -497,6 +527,7 @@ static void SeatControllerThermal_ServiceFeedback(void)
|
||||
currentData1))
|
||||
{
|
||||
g_seatControllerThermalFeedbackTxCount++;
|
||||
s_heaterCurrentPeakRaw = g_seatBackrestHeaterSenseRaw;
|
||||
s_feedbackPending = false;
|
||||
s_feedbackElapsedMs = 0u;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ bool SeatControllerThermal_Init(void);
|
||||
void SeatControllerThermal_HandleFrame(const bsp_seat_move_can_frame_t *frame);
|
||||
void SeatControllerThermal_Service1ms(void);
|
||||
void SeatControllerThermal_ForceSafeOff(void);
|
||||
/* Bench-only fault-injection helpers reached exclusively through the armed
|
||||
* 0x2F0 self-healing service. */
|
||||
bool SeatControllerThermal_HilInjectFanReentry(void);
|
||||
bool SeatControllerThermal_HilRepairFanStop(void);
|
||||
bool SeatControllerThermal_HilFanStopped(void);
|
||||
|
||||
extern volatile uint8_t g_seatControllerFanFeedbackRaw;
|
||||
extern volatile uint8_t g_seatControllerHeaterFeedbackRaw;
|
||||
|
||||
@@ -631,6 +631,19 @@ void SeatMotorManager_Service(void)
|
||||
}
|
||||
if(info.lastEvent == MOTOR_EVENT_TIMEOUT)
|
||||
{
|
||||
/* Some seat actuators open their internal limit contact instead
|
||||
* of producing a high-current endpoint event. The low-level
|
||||
* no-motion timer then reports TIMEOUT even when discrete Hall
|
||||
* feedback stopped only a few pulses from the requested pose.
|
||||
* Accept only the same tightly bounded Hall-domain arrival proof
|
||||
* used for endpoint/stall events; every other timeout remains a
|
||||
* failure. */
|
||||
if(SeatMotor_TaskIsPosition(task) &&
|
||||
SeatMotor_TargetWithinHallTolerance(task, &info))
|
||||
{
|
||||
SeatMotor_SetComplete(mode);
|
||||
continue;
|
||||
}
|
||||
SeatMotor_SetFailed(mode, SEAT_MOTOR_RESULT_TIMEOUT);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
#define SEAT_MOVE_CLEAR_FAULT_KEY (0xC33Cu)
|
||||
#define SEAT_MOVE_TIMED_LEVEL_MIN_X10 (300u)
|
||||
#define SEAT_MOVE_TIMED_LEVEL_MAX_X10 (1000u)
|
||||
#define SEAT_MOVE_HIL_RESPONSE_OK (0u)
|
||||
#define SEAT_MOVE_HIL_RESPONSE_PRECONDITION (1u)
|
||||
#define SEAT_MOVE_HIL_RESPONSE_EXECUTION (2u)
|
||||
#define SEAT_MOVE_HIL_RESPONSE_UNAUTHORIZED (4u)
|
||||
#define SEAT_MOVE_HIL_RESPONSE_UNSUPPORTED (5u)
|
||||
|
||||
typedef enum
|
||||
{
|
||||
@@ -181,6 +186,10 @@ static uint32_t s_nextSystemStatusMs = 0u;
|
||||
static uint32_t s_nextMotorPollMs = 0u;
|
||||
static uint32_t s_nextDiagnosticPollMs = 0u;
|
||||
static uint32_t s_nextDynamicStatusMs = 0u;
|
||||
static uint8_t s_hilArmedCase = 0u;
|
||||
static uint16_t s_hilArmToken = 0u;
|
||||
static uint32_t s_hilArmExpiresMs = 0u;
|
||||
static bool s_hilFaultActive[7];
|
||||
|
||||
static bool SeatMove_TimeReached(uint32_t now, uint32_t deadline);
|
||||
static bool SeatMove_SequenceNewer(uint16_t candidate, uint16_t reference);
|
||||
@@ -210,6 +219,13 @@ static uint8_t SeatMove_PositionValidMask(void);
|
||||
static uint8_t SeatMove_ActiveCount(void);
|
||||
static bool SeatMove_CalibrationBusy(void);
|
||||
static void SeatMove_HandleCommand(const bsp_seat_move_can_frame_t *frame);
|
||||
static void SeatMove_HandleHilSelfHeal(
|
||||
const bsp_seat_move_can_frame_t *frame);
|
||||
static bool SeatMove_SendHilSelfHealResponse(uint8_t caseIndex,
|
||||
uint8_t operation,
|
||||
uint16_t token,
|
||||
uint8_t result,
|
||||
uint8_t evidence);
|
||||
static void SeatMove_StopMotor(uint8_t motor, uint16_t sequence);
|
||||
static void SeatMove_QueueCommand(const seat_move_command_t *command);
|
||||
static bool SeatMove_DispatchCommand(const seat_move_command_t *command);
|
||||
@@ -236,6 +252,10 @@ bool SeatMoveApplication_Init(void)
|
||||
(void)memset(s_lastStatus, 0, sizeof(s_lastStatus));
|
||||
(void)memset(s_lastStatusValid, 0, sizeof(s_lastStatusValid));
|
||||
(void)memset(s_diagnosticAlive, 0, sizeof(s_diagnosticAlive));
|
||||
(void)memset(s_hilFaultActive, 0, sizeof(s_hilFaultActive));
|
||||
s_hilArmedCase = 0u;
|
||||
s_hilArmToken = 0u;
|
||||
s_hilArmExpiresMs = 0u;
|
||||
s_storeSavePending = false;
|
||||
s_storeSaveNotBeforeMs = 0u;
|
||||
SeatMotorManager_Init();
|
||||
@@ -305,6 +325,10 @@ void SeatMoveApplication_Service(void)
|
||||
{
|
||||
SeatMove_HandleCommand(&frame);
|
||||
}
|
||||
else if(frame.identifier == SEAT_MOVE_CAN_HIL_SELF_HEAL_REQUEST_ID)
|
||||
{
|
||||
SeatMove_HandleHilSelfHeal(&frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
SeatControllerThermal_HandleFrame(&frame);
|
||||
@@ -328,6 +352,10 @@ void Motor_CalibrationServiceHook(void)
|
||||
{
|
||||
SeatMove_HandleCommand(&frame);
|
||||
}
|
||||
else if(frame.identifier == SEAT_MOVE_CAN_HIL_SELF_HEAL_REQUEST_ID)
|
||||
{
|
||||
SeatMove_HandleHilSelfHeal(&frame);
|
||||
}
|
||||
else
|
||||
{
|
||||
SeatControllerThermal_HandleFrame(&frame);
|
||||
@@ -1042,6 +1070,270 @@ static void SeatMove_HandleCommand(const bsp_seat_move_can_frame_t *frame)
|
||||
g_seatMoveAcceptedCount++;
|
||||
}
|
||||
|
||||
static bool SeatMove_SendHilSelfHealResponse(uint8_t caseIndex,
|
||||
uint8_t operation,
|
||||
uint16_t token,
|
||||
uint8_t result,
|
||||
uint8_t evidence)
|
||||
{
|
||||
uint8_t bytes[8];
|
||||
uint8_t index;
|
||||
uint32_t data0;
|
||||
uint32_t data1;
|
||||
|
||||
bytes[0] = 0x5Au;
|
||||
bytes[1] = caseIndex;
|
||||
bytes[2] = operation;
|
||||
bytes[3] = (uint8_t)(token & 0xFFu);
|
||||
bytes[4] = (uint8_t)(token >> 8u);
|
||||
bytes[5] = result;
|
||||
bytes[6] = evidence;
|
||||
bytes[7] = 0u;
|
||||
for(index = 0u; index < 7u; index++)
|
||||
{
|
||||
bytes[7] = (uint8_t)(bytes[7] + bytes[index]);
|
||||
}
|
||||
data0 = (uint32_t)bytes[0] |
|
||||
((uint32_t)bytes[1] << 8u) |
|
||||
((uint32_t)bytes[2] << 16u) |
|
||||
((uint32_t)bytes[3] << 24u);
|
||||
data1 = (uint32_t)bytes[4] |
|
||||
((uint32_t)bytes[5] << 8u) |
|
||||
((uint32_t)bytes[6] << 16u) |
|
||||
((uint32_t)bytes[7] << 24u);
|
||||
return BspSeatMoveCan_Send(SEAT_MOVE_CAN_HIL_SELF_HEAL_RESPONSE_ID,
|
||||
data0,
|
||||
data1);
|
||||
}
|
||||
|
||||
static void SeatMove_HandleHilSelfHeal(
|
||||
const bsp_seat_move_can_frame_t *frame)
|
||||
{
|
||||
uint8_t bytes[8];
|
||||
uint8_t checksum = 0u;
|
||||
uint8_t caseIndex;
|
||||
uint8_t operation;
|
||||
uint8_t result = SEAT_MOVE_HIL_RESPONSE_OK;
|
||||
uint8_t evidence = 0u;
|
||||
uint16_t token;
|
||||
uint16_t suppliedArmToken;
|
||||
uint8_t index;
|
||||
bool operationOk = false;
|
||||
seat_motor_status_t status;
|
||||
|
||||
if((frame == NULL) || (frame->dlc != SEAT_MOVE_CAN_DLC))
|
||||
{
|
||||
return;
|
||||
}
|
||||
bytes[0] = (uint8_t)(frame->data0 & 0xFFu);
|
||||
bytes[1] = (uint8_t)((frame->data0 >> 8u) & 0xFFu);
|
||||
bytes[2] = (uint8_t)((frame->data0 >> 16u) & 0xFFu);
|
||||
bytes[3] = (uint8_t)((frame->data0 >> 24u) & 0xFFu);
|
||||
bytes[4] = (uint8_t)(frame->data1 & 0xFFu);
|
||||
bytes[5] = (uint8_t)((frame->data1 >> 8u) & 0xFFu);
|
||||
bytes[6] = (uint8_t)((frame->data1 >> 16u) & 0xFFu);
|
||||
bytes[7] = (uint8_t)((frame->data1 >> 24u) & 0xFFu);
|
||||
for(index = 0u; index < 7u; index++)
|
||||
{
|
||||
checksum = (uint8_t)(checksum + bytes[index]);
|
||||
}
|
||||
if((bytes[0] != 0xA5u) || (checksum != bytes[7]))
|
||||
{
|
||||
return;
|
||||
}
|
||||
caseIndex = bytes[1];
|
||||
operation = bytes[2];
|
||||
token = (uint16_t)((uint16_t)bytes[3] |
|
||||
((uint16_t)bytes[4] << 8u));
|
||||
suppliedArmToken = (uint16_t)((uint16_t)bytes[5] |
|
||||
((uint16_t)bytes[6] << 8u));
|
||||
if((caseIndex != 1u) && (caseIndex != 2u) && (caseIndex != 3u) &&
|
||||
(caseIndex != 5u) && (caseIndex != 6u))
|
||||
{
|
||||
(void)SeatMove_SendHilSelfHealResponse(
|
||||
caseIndex, operation, token,
|
||||
SEAT_MOVE_HIL_RESPONSE_UNSUPPORTED, 0u);
|
||||
return;
|
||||
}
|
||||
|
||||
if(operation == 0u)
|
||||
{
|
||||
if((bytes[5] != 0x53u) || (bytes[6] != 0x46u))
|
||||
{
|
||||
result = SEAT_MOVE_HIL_RESPONSE_UNAUTHORIZED;
|
||||
}
|
||||
else
|
||||
{
|
||||
s_hilArmedCase = caseIndex;
|
||||
s_hilArmToken = token;
|
||||
s_hilArmExpiresMs = Motor_GetTimeMs() +
|
||||
SEAT_MOVE_HIL_ARM_WINDOW_MS;
|
||||
}
|
||||
(void)SeatMove_SendHilSelfHealResponse(
|
||||
caseIndex, operation, token, result, evidence);
|
||||
return;
|
||||
}
|
||||
|
||||
if((s_hilArmedCase != caseIndex) ||
|
||||
(s_hilArmToken != suppliedArmToken) ||
|
||||
SeatMove_TimeReached(Motor_GetTimeMs(), s_hilArmExpiresMs))
|
||||
{
|
||||
(void)SeatMove_SendHilSelfHealResponse(
|
||||
caseIndex, operation, token,
|
||||
SEAT_MOVE_HIL_RESPONSE_UNAUTHORIZED, 0u);
|
||||
return;
|
||||
}
|
||||
/* Every arm token authorizes exactly one mutating or verifying request. */
|
||||
s_hilArmedCase = 0u;
|
||||
s_hilArmToken = 0u;
|
||||
s_hilArmExpiresMs = 0u;
|
||||
|
||||
if(operation == 1u)
|
||||
{
|
||||
switch(caseIndex)
|
||||
{
|
||||
case 1u:
|
||||
SeatMoveApplication_StopAll(token);
|
||||
operationOk =
|
||||
(MOTOR_API_SUCCESS == Motor_InvalidatePosition(
|
||||
SeatMove_PhysicalMode(2u)));
|
||||
if(operationOk)
|
||||
{
|
||||
SeatMoveStore_RejectRestore();
|
||||
s_hilFaultActive[caseIndex] = true;
|
||||
s_storeSavePending = true;
|
||||
s_storeSaveNotBeforeMs = Motor_GetTimeMs() +
|
||||
SEAT_MOVE_STORE_SAVE_DEBOUNCE_MS;
|
||||
s_motorStatusPendingMask |= (uint8_t)(1u << 2u);
|
||||
s_diagnosticPendingMask |= (uint8_t)(1u << 2u);
|
||||
s_systemStatusPending = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
evidence = 0xE2u;
|
||||
}
|
||||
break;
|
||||
case 2u:
|
||||
SeatMoveApplication_StopAll(token);
|
||||
operationOk = SeatMoveStore_HilInjectCrcCorruption();
|
||||
s_hilFaultActive[caseIndex] = operationOk;
|
||||
s_systemStatusPending = true;
|
||||
break;
|
||||
case 3u:
|
||||
SeatMoveApplication_StopAll(token);
|
||||
operationOk = Motor_WaitUntilStopped(
|
||||
SeatMove_PhysicalMode(0u)) &&
|
||||
!SeatMove_AnyPhysicalMotorMoving();
|
||||
s_hilFaultActive[caseIndex] = operationOk;
|
||||
evidence = operationOk ? 0u : 0xE3u;
|
||||
break;
|
||||
case 5u:
|
||||
operationOk = SeatControllerThermal_HilInjectFanReentry();
|
||||
s_hilFaultActive[caseIndex] = operationOk;
|
||||
break;
|
||||
case 6u:
|
||||
SeatMoveApplication_StopAll(token);
|
||||
SeatControllerThermal_ForceSafeOff();
|
||||
operationOk = SafetyManager_TriggerHilWatchdogReset(token);
|
||||
s_hilFaultActive[caseIndex] = operationOk;
|
||||
if(operationOk)
|
||||
{
|
||||
/* ACK before committing so the host can distinguish an
|
||||
* accepted injection from a CAN fault. Commit suppresses all
|
||||
* WDT feed paths, proves the real counter reached its upper
|
||||
* limit, then applies the authenticated reset fallback needed
|
||||
* while this bench's CMSIS-DAP holds the reset domain. */
|
||||
(void)SeatMove_SendHilSelfHealResponse(
|
||||
caseIndex, operation, token,
|
||||
SEAT_MOVE_HIL_RESPONSE_OK, 0u);
|
||||
Cy_SysTick_DelayInUs(5000u);
|
||||
SafetyManager_CommitHilWatchdogReset();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if(operation == 2u)
|
||||
{
|
||||
switch(caseIndex)
|
||||
{
|
||||
case 1u:
|
||||
operationOk = SeatMove_GetStatus(2u, &status) &&
|
||||
status.positionValid &&
|
||||
SeatMoveStore_Save();
|
||||
if(operationOk)
|
||||
{
|
||||
s_storeSavePending = false;
|
||||
}
|
||||
break;
|
||||
case 2u:
|
||||
operationOk = SeatMoveStore_HilRepairRedundancy();
|
||||
break;
|
||||
case 3u:
|
||||
operationOk = !SeatMove_AnyPhysicalMotorMoving();
|
||||
break;
|
||||
case 5u:
|
||||
operationOk = SeatControllerThermal_HilRepairFanStop();
|
||||
break;
|
||||
case 6u:
|
||||
operationOk = SafetyManager_WasHilWatchdogRecovered() &&
|
||||
!SafetyManager_IsInhibited();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if(operationOk)
|
||||
{
|
||||
s_hilFaultActive[caseIndex] = false;
|
||||
}
|
||||
}
|
||||
else if(operation == 3u)
|
||||
{
|
||||
switch(caseIndex)
|
||||
{
|
||||
case 1u:
|
||||
operationOk = SeatMove_GetStatus(2u, &status) &&
|
||||
status.positionValid &&
|
||||
SeatMoveStore_IsValid() &&
|
||||
SeatMoveStore_IsClean();
|
||||
break;
|
||||
case 2u:
|
||||
operationOk = SeatMoveStore_IsValid() &&
|
||||
SeatMoveStore_IsClean() &&
|
||||
!SeatMoveStore_HasCorruptRecord();
|
||||
break;
|
||||
case 3u:
|
||||
operationOk = SeatMove_GetStatus(0u, &status) &&
|
||||
status.positionValid &&
|
||||
!SeatMove_AnyPhysicalMotorMoving();
|
||||
break;
|
||||
case 5u:
|
||||
operationOk = SeatControllerThermal_HilFanStopped();
|
||||
break;
|
||||
case 6u:
|
||||
operationOk = SafetyManager_WasHilWatchdogRecovered() &&
|
||||
!SafetyManager_IsInhibited();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = SEAT_MOVE_HIL_RESPONSE_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if((result == SEAT_MOVE_HIL_RESPONSE_OK) && !operationOk)
|
||||
{
|
||||
result = (operation == 3u) ?
|
||||
SEAT_MOVE_HIL_RESPONSE_PRECONDITION :
|
||||
SEAT_MOVE_HIL_RESPONSE_EXECUTION;
|
||||
}
|
||||
(void)SeatMove_SendHilSelfHealResponse(
|
||||
caseIndex, operation, token, result, evidence);
|
||||
}
|
||||
|
||||
static void SeatMove_StopMotor(uint8_t motor, uint16_t sequence)
|
||||
{
|
||||
motor_mode_t mode;
|
||||
|
||||
@@ -284,6 +284,75 @@ uint32_t SeatMoveStore_GetSequence(void)
|
||||
return s_valid ? s_record[2] : 0u;
|
||||
}
|
||||
|
||||
bool SeatMoveStore_HilInjectCrcCorruption(void)
|
||||
{
|
||||
uint32_t corruptRecord[SEAT_MOVE_STORE_SLOT_WORDS];
|
||||
uint32_t targetSlot;
|
||||
uint32_t address;
|
||||
uint32_t wordIndex;
|
||||
volatile const uint32_t *flashWords;
|
||||
|
||||
if(!s_valid || !s_clean)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for(wordIndex = 0u; wordIndex < SEAT_MOVE_STORE_SLOT_WORDS; wordIndex++)
|
||||
{
|
||||
corruptRecord[wordIndex] = s_record[wordIndex];
|
||||
}
|
||||
corruptRecord[2] = s_record[2] + 1u;
|
||||
corruptRecord[SEAT_MOVE_STORE_CRC_WORD_INDEX] =
|
||||
SeatMoveStore_Crc32(corruptRecord,
|
||||
SEAT_MOVE_STORE_CRC_WORD_INDEX) ^ 0x00000001u;
|
||||
targetSlot = s_activeSlot ^ 1u;
|
||||
address = SeatMoveStore_SlotAddress(targetSlot);
|
||||
flashWords = (volatile const uint32_t *)(uintptr_t)address;
|
||||
Cy_FlashSectorErase(address, CY_FLASH_DRIVER_BLOCKING);
|
||||
if(!Cy_WorkFlashBlankCheck(address, CY_FLASH_DRIVER_BLOCKING))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for(wordIndex = 0u; wordIndex < SEAT_MOVE_STORE_SLOT_WORDS; wordIndex++)
|
||||
{
|
||||
Cy_FlashWriteWork(address + (wordIndex * sizeof(uint32_t)),
|
||||
&corruptRecord[wordIndex],
|
||||
CY_FLASH_DRIVER_BLOCKING);
|
||||
}
|
||||
for(wordIndex = 0u; wordIndex < SEAT_MOVE_STORE_SLOT_WORDS; wordIndex++)
|
||||
{
|
||||
if(flashWords[wordIndex] != corruptRecord[wordIndex])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/* The active in-RAM record deliberately remains the older valid slot. */
|
||||
s_corrupt = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeatMoveStore_HilRepairRedundancy(void)
|
||||
{
|
||||
uint32_t repairedRecord[SEAT_MOVE_STORE_SLOT_WORDS];
|
||||
uint32_t wordIndex;
|
||||
uint32_t targetSlot;
|
||||
|
||||
if(!s_valid)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
for(wordIndex = 0u; wordIndex < SEAT_MOVE_STORE_SLOT_WORDS; wordIndex++)
|
||||
{
|
||||
repairedRecord[wordIndex] = s_record[wordIndex];
|
||||
}
|
||||
repairedRecord[2] = s_record[2] + 2u;
|
||||
repairedRecord[3] = 1u;
|
||||
repairedRecord[SEAT_MOVE_STORE_CRC_WORD_INDEX] =
|
||||
SeatMoveStore_Crc32(repairedRecord,
|
||||
SEAT_MOVE_STORE_CRC_WORD_INDEX);
|
||||
targetSlot = s_activeSlot ^ 1u;
|
||||
return SeatMoveStore_WriteRecord(repairedRecord, targetSlot);
|
||||
}
|
||||
|
||||
static uint32_t SeatMoveStore_Crc32(const uint32_t *words, uint32_t wordCount)
|
||||
{
|
||||
uint32_t crc = 0xFFFFFFFFu;
|
||||
|
||||
@@ -16,4 +16,11 @@ bool SeatMoveStore_IsClean(void);
|
||||
bool SeatMoveStore_HasCorruptRecord(void);
|
||||
uint32_t SeatMoveStore_GetSequence(void);
|
||||
|
||||
/* Token-gated bench HIL helpers. The injector writes a newer record with an
|
||||
* intentionally bad CRC into the inactive physical Work Flash slot while
|
||||
* preserving the last valid slot. Repair atomically replaces that slot with
|
||||
* a still-newer valid record. */
|
||||
bool SeatMoveStore_HilInjectCrcCorruption(void);
|
||||
bool SeatMoveStore_HilRepairRedundancy(void);
|
||||
|
||||
#endif /* SEAT_MOVE_STORE_H */
|
||||
|
||||
@@ -50,6 +50,10 @@ static const cy_stc_id_filter_t s_standardIdFilters[] =
|
||||
SEAT_MOVE_CAN_COMMAND_ID,
|
||||
SEAT_MOVE_CAN_COMMAND_ID,
|
||||
CY_CANFD_ID_FILTER_ELEMNT_CONFIG_STORE_RXFIFO0),
|
||||
CANFD_CONFIG_STD_ID_FILTER_RANGE(
|
||||
SEAT_MOVE_CAN_HIL_SELF_HEAL_REQUEST_ID,
|
||||
SEAT_MOVE_CAN_HIL_SELF_HEAL_REQUEST_ID,
|
||||
CY_CANFD_ID_FILTER_ELEMNT_CONFIG_STORE_RXFIFO0),
|
||||
};
|
||||
|
||||
static cy_stc_sysint_irq_t s_canIrqConfig =
|
||||
|
||||
@@ -30,7 +30,13 @@
|
||||
#define SEAT_BACKREST_HEATER_DEFAULT_LEVEL (0u)
|
||||
|
||||
#define SEAT_BACKREST_HEATER_TASK_PERIOD_US (1000u)
|
||||
#define SEAT_BACKREST_HEATER_SENSE_SAMPLE_MS (10u)
|
||||
/*
|
||||
* Do not sample at the 10 ms PWM period. Equal periods phase-lock the ADC
|
||||
* to one PWM point and can report zero current continuously for a healthy
|
||||
* low-duty heater. Seven milliseconds walks through every PWM phase while
|
||||
* remaining slow enough for the existing SAR ADC service.
|
||||
*/
|
||||
#define SEAT_BACKREST_HEATER_SENSE_SAMPLE_MS (7u)
|
||||
#define SEAT_BACKREST_HEATER_SENSE_MAX_ERRORS (3u)
|
||||
#define SEAT_BACKREST_HEATER_MAX_ACTIVE_TIME_MS (600000u)
|
||||
|
||||
|
||||
@@ -29,18 +29,22 @@
|
||||
#define SEAT_MOVE_M0_DEFAULT_TIMEOUT_MS (12000u)
|
||||
#define SEAT_MOVE_M1_DEFAULT_TIMEOUT_MS (8000u)
|
||||
#define SEAT_MOVE_M2_DEFAULT_TIMEOUT_MS (12000u)
|
||||
#define SEAT_MOVE_M3_DEFAULT_TIMEOUT_MS (10000u)
|
||||
#define SEAT_MOVE_M3_DEFAULT_TIMEOUT_MS (30000u)
|
||||
#define SEAT_MOVE_M0_DEFAULT_DUTY_PERCENT (90u)
|
||||
#define SEAT_MOVE_M1_DEFAULT_DUTY_PERCENT (90u)
|
||||
#define SEAT_MOVE_M2_DEFAULT_DUTY_PERCENT (90u)
|
||||
#define SEAT_MOVE_M3_DEFAULT_DUTY_PERCENT (90u)
|
||||
/* The connected loaded legrest leaves its hard stop more reliably at the
|
||||
* empirically verified diagnostic PWM than at maximum PWM. It is selected
|
||||
* only after a supervised endpoint-departure attempt produced no Hall edge. */
|
||||
#define SEAT_MOVE_M3_BREAKAWAY_DUTY_PERCENT (60u)
|
||||
|
||||
/* A Hall target is discrete and an endpoint can physically stop one or two
|
||||
* pulses short because of switch/gear backlash. If a position move is
|
||||
* already this close when an endpoint/stall event stops the bridge, classify
|
||||
* the requested position as reached instead of retrying and latching STALL.
|
||||
/* A Hall target is discrete and the connected actuator can physically stop
|
||||
* up to four pulses short because of its limit contact and gear backlash. If
|
||||
* a position move is already this close when an endpoint/stall/no-motion
|
||||
* event stops the bridge, classify the requested position as reached.
|
||||
* The raw lastMotorEvent remains available in 0x303 for diagnosis. */
|
||||
#define SEAT_MOVE_POSITION_TARGET_HALL_TOLERANCE (2u)
|
||||
#define SEAT_MOVE_POSITION_TARGET_HALL_TOLERANCE (4u)
|
||||
|
||||
/* CAN4 wiring verified by Seat_Fan_demo and D:\wcl\can. P3.1 is shared with
|
||||
* the DRV8718 DVDD enable on this board; both users require it to remain high. */
|
||||
@@ -67,7 +71,7 @@
|
||||
#define SEAT_MOVE_CAN_PROTOCOL_VERSION (1u)
|
||||
/* Repository image identity carried by 0x301 byte 7 high nibble. Increment
|
||||
* whenever a host-visible safety or motion fix requires automatic reflash. */
|
||||
#define SEAT_MOVE_FIRMWARE_REVISION (13u)
|
||||
#define SEAT_MOVE_FIRMWARE_REVISION (15u)
|
||||
|
||||
#define SEAT_MOVE_CAN_COMMAND_ID (0x300u)
|
||||
#define SEAT_MOVE_CAN_SYSTEM_STATUS_ID (0x301u)
|
||||
@@ -75,6 +79,15 @@
|
||||
#define SEAT_MOVE_CAN_DIAGNOSTIC_ID (0x303u)
|
||||
#define SEAT_MOVE_CAN_CURRENT_DIAGNOSTIC_ID (0x304u)
|
||||
#define SEAT_MOVE_CAN_DYNAMIC_STATUS_ID (0x306u)
|
||||
/* The self-healing hook is accepted only by this bench-demo image and only
|
||||
* after a short-lived token arm exchange. */
|
||||
#define SEAT_MOVE_CAN_HIL_SELF_HEAL_REQUEST_ID (0x2F0u)
|
||||
#define SEAT_MOVE_CAN_HIL_SELF_HEAL_RESPONSE_ID (0x2F1u)
|
||||
#define SEAT_MOVE_HIL_ARM_WINDOW_MS (30000u)
|
||||
#define SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX (0u)
|
||||
#define SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX (1u)
|
||||
#define SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX (2u)
|
||||
#define SEAT_MOVE_HIL_WDT_MARKER (0x48494C36u) /* "HIL6" */
|
||||
#define SEAT_MOVE_SYSTEM_STATUS_PERIOD_MS (100u)
|
||||
#define SEAT_MOVE_DYNAMIC_STATUS_PERIOD_MS (100u)
|
||||
/* Native dynamic modes are bounded independently of the host process. The
|
||||
@@ -160,7 +173,9 @@
|
||||
(SEAT_MOVE_CAN_MOTOR_STATUS_ID > 0x7FFu) || \
|
||||
(SEAT_MOVE_CAN_DIAGNOSTIC_ID > 0x7FFu) || \
|
||||
(SEAT_MOVE_CAN_CURRENT_DIAGNOSTIC_ID > 0x7FFu) || \
|
||||
(SEAT_MOVE_CAN_DYNAMIC_STATUS_ID > 0x7FFu))
|
||||
(SEAT_MOVE_CAN_DYNAMIC_STATUS_ID > 0x7FFu) || \
|
||||
(SEAT_MOVE_CAN_HIL_SELF_HEAL_REQUEST_ID > 0x7FFu) || \
|
||||
(SEAT_MOVE_CAN_HIL_SELF_HEAL_RESPONSE_ID > 0x7FFu))
|
||||
#error "Seat movement CAN identifiers must be 11-bit standard IDs."
|
||||
#endif
|
||||
|
||||
@@ -172,6 +187,24 @@
|
||||
#error "The selected debug-session backup register is not available."
|
||||
#endif
|
||||
|
||||
#if ((SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX >= SRSS_BACKUP_NUM_BREG) || \
|
||||
(SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX >= SRSS_BACKUP_NUM_BREG) || \
|
||||
(SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX >= SRSS_BACKUP_NUM_BREG) || \
|
||||
(SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX == \
|
||||
SEAT_MOVE_DEBUG_SESSION_BREG_INDEX) || \
|
||||
(SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX == \
|
||||
SEAT_MOVE_DEBUG_SESSION_BREG_INDEX) || \
|
||||
(SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX == \
|
||||
SEAT_MOVE_DEBUG_SESSION_BREG_INDEX) || \
|
||||
(SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX == \
|
||||
SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX) || \
|
||||
(SEAT_MOVE_HIL_WDT_MAGIC_BREG_INDEX == \
|
||||
SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX) || \
|
||||
(SEAT_MOVE_HIL_WDT_TOKEN_BREG_INDEX == \
|
||||
SEAT_MOVE_HIL_WDT_CHECK_BREG_INDEX))
|
||||
#error "The HIL WDT credential requires three dedicated backup registers."
|
||||
#endif
|
||||
|
||||
#if ((SEAT_MOVE_MOTOR_CURRENT_SAMPLE_MS == 0u) || \
|
||||
(SEAT_MOVE_MOTOR_CURRENT_MAX_AGE_MS < \
|
||||
SEAT_MOVE_MOTOR_CURRENT_SAMPLE_MS) || \
|
||||
|
||||
@@ -492,6 +492,7 @@ static uint32_t currentSenseLastServiceTicks = 0ul;
|
||||
|
||||
static bool Motor_CalibrationRangeIsPlausible(uint32_t motorIndex,
|
||||
uint32_t measuredPulses);
|
||||
static bool Motor_WaitForCoastToSettle(motor_mode_t mode);
|
||||
|
||||
CY_WEAK void Motor_CalibrationServiceHook(void)
|
||||
{
|
||||
@@ -2870,6 +2871,19 @@ bool Motor_IsMoving(motor_mode_t mode)
|
||||
return moving;
|
||||
}
|
||||
|
||||
bool Motor_WaitUntilStopped(motor_mode_t mode)
|
||||
{
|
||||
if((false == motorControlReady) || (false == Motor_ModeIsValid(mode)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(Motor_IsMoving(mode))
|
||||
{
|
||||
(void)Motor_StopMode(mode);
|
||||
}
|
||||
return Motor_WaitForCoastToSettle(mode) && !Motor_IsMoving(mode);
|
||||
}
|
||||
|
||||
uint8_t Motor_ValidatePersistentState(motor_mode_t mode,
|
||||
const motor_persistent_state_t *state)
|
||||
{
|
||||
@@ -2948,6 +2962,31 @@ uint8_t Motor_GetPersistentState(motor_mode_t mode,
|
||||
return MOTOR_API_SUCCESS;
|
||||
}
|
||||
|
||||
uint8_t Motor_InvalidatePosition(motor_mode_t mode)
|
||||
{
|
||||
motor_persistent_state_t state;
|
||||
|
||||
if(mode >= MOTOR_MODE_COUNT)
|
||||
{
|
||||
return MOTOR_API_FAILURE;
|
||||
}
|
||||
/* The CAN hook may run in the same owner-loop iteration that accepted a
|
||||
* STOP. Finish the bounded stop and Hall-coast transition before
|
||||
* enforcing the persistent-state API's stopped-axis precondition. */
|
||||
if(Motor_IsMoving(mode))
|
||||
{
|
||||
(void)Motor_StopMode(mode);
|
||||
}
|
||||
if((false == Motor_WaitForCoastToSettle(mode)) ||
|
||||
Motor_IsMoving(mode) ||
|
||||
(MOTOR_API_SUCCESS != Motor_GetPersistentState(mode, &state)))
|
||||
{
|
||||
return MOTOR_API_FAILURE;
|
||||
}
|
||||
state.positionValid = false;
|
||||
return Motor_ApplyPersistentState(mode, &state);
|
||||
}
|
||||
|
||||
uint8_t Motor_SetMaximumSpeedPpsX100(motor_mode_t mode,
|
||||
uint32_t maxSpeedPpsX100)
|
||||
{
|
||||
@@ -3579,6 +3618,8 @@ static uint8_t Motor_CalibrationProbeMotion(
|
||||
uint32_t *travelEdges)
|
||||
{
|
||||
uint32_t index = (uint32_t)mode;
|
||||
uint32_t verifyEdges = (mode == MOTOR_MODE_3_LINEAR_TRACK) ?
|
||||
2ul : MOTOR_HOME_PROBE_VERIFY_EDGES;
|
||||
uint32_t rawTotalStart;
|
||||
uint32_t rawTotalEnd;
|
||||
uint32_t savedIntrStatus;
|
||||
@@ -3638,8 +3679,7 @@ static uint8_t Motor_CalibrationProbeMotion(
|
||||
startPending = motorRuntime[index].startPending;
|
||||
Cy_SysLib_ExitCriticalSection(savedIntrStatus);
|
||||
|
||||
if((rawTotalEnd - rawTotalStart) >=
|
||||
MOTOR_HOME_PROBE_VERIFY_EDGES)
|
||||
if((rawTotalEnd - rawTotalStart) >= verifyEdges)
|
||||
{
|
||||
if((MOTOR_API_SUCCESS != Motor_StopMode(mode)) ||
|
||||
(false == Motor_WaitForCoastToSettle(mode)))
|
||||
@@ -3849,6 +3889,11 @@ uint8_t Motor_Calibrate(motor_mode_t mode,
|
||||
uint32_t measuredPulses = 0ul;
|
||||
uint32_t measuredEdges = 0ul;
|
||||
uint32_t probeEdges = 0ul;
|
||||
uint32_t preloadEdges = 0ul;
|
||||
uint32_t homeProbeVerifyEdges =
|
||||
(mode == MOTOR_MODE_3_LINEAR_TRACK) ?
|
||||
2ul : MOTOR_HOME_PROBE_VERIFY_EDGES;
|
||||
uint8_t probeAttempt = 0u;
|
||||
#if (MOTOR_DIAGNOSTIC_LOG_ENABLE != 0u)
|
||||
uint32_t firstNearPulses = 0ul;
|
||||
uint32_t firstNearEdges = 0ul;
|
||||
@@ -3915,13 +3960,54 @@ uint8_t Motor_Calibrate(motor_mode_t mode,
|
||||
firstNearEdges = measuredEdges;
|
||||
#endif
|
||||
if((status == MOTOR_API_SUCCESS) &&
|
||||
(measuredEdges < MOTOR_HOME_PROBE_VERIFY_EDGES))
|
||||
(measuredEdges < homeProbeVerifyEdges))
|
||||
{
|
||||
/* Do not turn a non-start into a fake Hall zero. A genuine
|
||||
* already-near condition is proved by moving across several Hall
|
||||
* edges toward far and seeking the same path back toward near. */
|
||||
status = Motor_CalibrationProbeMotion(
|
||||
mode, MOTOR_DIRECTION_TOWARD_FAR, legTimeoutMs, &probeEdges);
|
||||
/* A loaded mechanism can need more than one bounded energize
|
||||
* cycle to leave a mechanical hard stop. Retry the departure,
|
||||
* but still require real Hall edges before accepting the home.
|
||||
* This improves endpoint breakaway without weakening the open
|
||||
* Hall / disconnected-motor diagnostic. The loaded M3 linkage
|
||||
* is accepted after two filtered raw Hall edges: the real bench
|
||||
* repeatedly produces one complete Hall pulse before its bounded
|
||||
* probe stops, while an open/disconnected Hall still produces
|
||||
* zero. The other axes retain the four-edge proof. */
|
||||
for(probeAttempt = 0u; probeAttempt < 3u; probeAttempt++)
|
||||
{
|
||||
status = Motor_CalibrationProbeMotion(
|
||||
mode,
|
||||
MOTOR_DIRECTION_TOWARD_FAR,
|
||||
legTimeoutMs,
|
||||
&probeEdges);
|
||||
if(status == MOTOR_API_SUCCESS)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if((mode == MOTOR_MODE_3_LINEAR_TRACK) &&
|
||||
(MOTOR_API_SUCCESS != Motor_SetFixedDutyPercent(
|
||||
mode, SEAT_MOVE_M3_BREAKAWAY_DUTY_PERCENT)))
|
||||
{
|
||||
status = MOTOR_API_FAILURE;
|
||||
break;
|
||||
}
|
||||
/* A failed probe has already stopped and settled the bridge.
|
||||
* Briefly re-energize toward the known hard stop before the
|
||||
* next departure. On the loaded M3 linkage this alternating
|
||||
* preload releases static lash which repeated starts in the
|
||||
* same direction do not. Its result is deliberately ignored:
|
||||
* only a later far probe with real Hall edges can pass. */
|
||||
if(probeAttempt < 2u)
|
||||
{
|
||||
preloadEdges = 0ul;
|
||||
(void)Motor_CalibrationProbeMotion(
|
||||
mode,
|
||||
MOTOR_DIRECTION_TOWARD_NEAR,
|
||||
legTimeoutMs,
|
||||
&preloadEdges);
|
||||
}
|
||||
}
|
||||
if(status == MOTOR_API_SUCCESS)
|
||||
{
|
||||
status = Motor_CalibrationSeekEndpoint(
|
||||
|
||||
@@ -170,6 +170,9 @@ uint8_t Motor_SetSafetyOptions(motor_mode_t mode,
|
||||
uint8_t Motor_SetFixedDutyPercent(motor_mode_t mode, uint8_t dutyPercent);
|
||||
uint8_t Motor_StartOpenLoop(motor_mode_t mode, motor_direction_t direction);
|
||||
bool Motor_IsMoving(motor_mode_t mode);
|
||||
/* Complete a commanded stop plus bounded Hall-coast tracking before a
|
||||
* safety decision that requires the mechanism to be physically settled. */
|
||||
bool Motor_WaitUntilStopped(motor_mode_t mode);
|
||||
|
||||
/*
|
||||
* Restore/store Hall-domain state. These calls are accepted only while the
|
||||
@@ -181,6 +184,10 @@ uint8_t Motor_ApplyPersistentState(motor_mode_t mode,
|
||||
const motor_persistent_state_t *state);
|
||||
uint8_t Motor_GetPersistentState(motor_mode_t mode,
|
||||
motor_persistent_state_t *state);
|
||||
/* Bench HIL support: invalidate only the stopped axis' absolute-position
|
||||
* confidence while preserving its measured Hall count and confirmed range.
|
||||
* Production command handling never calls this API. */
|
||||
uint8_t Motor_InvalidatePosition(motor_mode_t mode);
|
||||
uint8_t Motor_SetMaximumSpeedPpsX100(motor_mode_t mode,
|
||||
uint32_t maxSpeedPpsX100);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user