Add charging-phase PSU sudden-off diagnostics (v1.0.28).

Observability only: log can_lost/output_lost and status flags during Charging, without changing PSU recovery behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
raduet
2026-07-12 01:03:13 +02:00
co-authored by Cursor
parent fdc83c5b9f
commit 940e3f4a3d
7 changed files with 8007 additions and 7552 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ extern "C" {
/* USER CODE BEGIN EC */
#define FW_VERSION_MAJOR 1
#define FW_VERSION_MINOR 0
#define FW_VERSION_PATCH 27
#define FW_VERSION_PATCH 28
/* USER CODE END EC */
/* Exported macro ------------------------------------------------------------*/
+1 -1
View File
@@ -105,7 +105,7 @@ void debug_buffer_send(void)
__enable_irq();
}
#define LOG_BUFFER_SIZE 128
#define LOG_BUFFER_SIZE 256
uint8_t log_buffer[LOG_BUFFER_SIZE];
// Кастомный printf с приоритетом лога
+185
View File
@@ -27,6 +27,7 @@ PSU_t PSU0;
#define PSU_ONLINE_TIMEOUT 500 // Таймаут для определения состояния (мс)
#define PSU_STARTUP_DELAY 4000 // Задержка 2 секунды перед включением
#define PSU_DIAG_LOG_MS 2000u
#define PSU_SUDDEN_OFF_DEBOUNCE_MS 200u
uint32_t can_lastpacket;
@@ -34,8 +35,13 @@ extern CAN_HandleTypeDef hcan2;
static uint16_t psu_last_can_v;
static uint16_t psu_last_can_i_0p1A;
static uint8_t psu_last_good_s0;
static uint8_t psu_last_good_s1;
static uint8_t psu_last_good_s2;
static void PSU_SendCmd(uint8_t source, uint8_t destination, uint8_t cmd, void *data);
static void PSU_LogActiveStatusFlags(LogLevel_t level);
static void PSU_MonitorStatusAndUnexpectedOff(void);
static const char *PSU_StateName(PSU_State_t state)
{
@@ -65,6 +71,11 @@ static void PSU_LogPeriodicDiag(void)
uint32_t now = HAL_GetTick();
uint8_t block_setpoint;
/* Periodic PSU telemetry only while session is in Charging. */
if (CONN.connState != Charging) {
return;
}
if ((now - last_log_ms) < PSU_DIAG_LOG_MS) {
return;
}
@@ -110,6 +121,174 @@ static void PSU_LogPeriodicDiag(void)
(unsigned long)(can_lastpacket ? (now - can_lastpacket) : 9999u));
}
static void PSU_LogActiveStatusFlags(LogLevel_t level)
{
PSU_Status0_t s0 = PSU0.status0.bits;
PSU_Status1_t s1 = PSU0.status1.bits;
PSU_Status2_t s2 = PSU0.status2.bits;
log_printf(level,
"PSU flags%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
s0.shortCircuitFault ? " sc" : "",
s0.unevenFlowAlarm ? " uneven" : "",
s0.internalCommunicationFault ? " int_comm" : "",
s0.inputBusLineFault ? " in_bus" : "",
s0.lockProtection ? " lock" : "",
s0.dischargeFault ? " disch" : "",
s0.eepromFault ? " eeprom" : "",
s1.dcSideOffStatus ? " dc_off" : "",
s1.moduleFaultAlarm ? " fault" : "",
s1.moduleProtectionAlarm ? " prot" : "",
s1.fanFaultAlarm ? " fan" : "",
s1.overTempAlarm ? " ot" : "",
s1.outputOverVoltageAlarm ? " oov" : "",
s1.outputOverCurrentAlarm ? " ooc" : "",
s1.canCommunicationInterruptAlarm ? " can_int" : "",
s2.powerLimitStatus ? " plim" : "",
s2.moduleAddressDuplicate ? " addr_dup" : "",
s2.severeUnevenFlowFault ? " uneven_hi" : "",
s2.threePhaseInputPhaseLossAlarm ? " ph_loss" : "",
s2.threePhaseInputUnbalanceAlarm ? " unbal" : "",
s2.inputUnderVoltageAlarm ? " uv" : "",
s2.inputOverVoltageAlarm ? " ov" : "",
s2.pfcSideOffStatus ? " pfc_off" : "");
}
/*
* Observability only: does not change PSU/contactor state machine.
* Armed only in CONN Charging phase:
* - status bit edges while output is requested
* - PSU_enabled 1->0 while CONNECTED + EnableOutput (sudden disable)
*
* Note: PSU_enabled is derived from V >= PSU_VOLTAGE_THRESHOLD, not from
* the module enable command bit.
*/
static void PSU_MonitorStatusAndUnexpectedOff(void)
{
static uint8_t prev_s0;
static uint8_t prev_s1;
static uint8_t prev_s2;
static uint8_t prev_enabled;
static uint8_t inited;
static uint8_t sudden_latched;
static uint32_t sudden_cand_ms;
uint32_t now = HAL_GetTick();
uint8_t in_charging;
uint8_t watch_status;
uint8_t expect_on;
in_charging = (CONN.connState == Charging);
watch_status = in_charging && (CONN.EnableOutput != 0) && (PSU0.online != 0) &&
((PSU0.state == PSU_CONNECTED) ||
(PSU0.state == PSU_WAIT_ACK_ON) ||
(PSU0.state == PSU_CONT_WAIT_ACK_ON));
expect_on = in_charging && (CONN.EnableOutput != 0) && (PSU0.state == PSU_CONNECTED);
if (!inited) {
prev_s0 = PSU0.status0.raw;
prev_s1 = PSU0.status1.raw;
prev_s2 = PSU0.status2.raw;
prev_enabled = PSU0.PSU_enabled;
inited = 1u;
return;
}
if (watch_status) {
if ((PSU0.status0.raw != prev_s0) ||
(PSU0.status1.raw != prev_s1) ||
(PSU0.status2.raw != prev_s2)) {
log_printf(LOG_WARN,
"PSU status change s0=0x%02x->0x%02x s1=0x%02x->0x%02x s2=0x%02x->0x%02x\n",
(unsigned)prev_s0, (unsigned)PSU0.status0.raw,
(unsigned)prev_s1, (unsigned)PSU0.status1.raw,
(unsigned)prev_s2, (unsigned)PSU0.status2.raw);
PSU_LogActiveStatusFlags(LOG_WARN);
}
}
prev_s0 = PSU0.status0.raw;
prev_s1 = PSU0.status1.raw;
prev_s2 = PSU0.status2.raw;
if (!expect_on) {
sudden_cand_ms = 0u;
if ((CONN.EnableOutput == 0) ||
(CONN.connState != Charging) ||
(PSU0.state == PSU_READY) ||
(PSU0.state == PSU_UNREADY) ||
(PSU0.state == PSU_OFF_PAUSE) ||
(PSU0.state == PSU_WAIT_ACK_OFF) ||
(PSU0.state == PSU_CONT_WAIT_ACK_OFF) ||
(PSU0.state == PSU_CURRENT_DROP)) {
sudden_latched = 0u;
}
prev_enabled = PSU0.PSU_enabled;
return;
}
if (PSU0.PSU_enabled) {
sudden_cand_ms = 0u;
sudden_latched = 0u;
prev_enabled = 1u;
return;
}
if (sudden_latched) {
prev_enabled = 0u;
return;
}
if (!prev_enabled) {
sudden_cand_ms = 0u;
return;
}
/* Falling edge of PSU_enabled while still CONNECTED + EnableOutput + Charging */
if (!PSU0.online) {
log_printf(LOG_ERR,
"PSU suddenly disabled while charging reason=can_lost "
"V=%u I=%d cont=%u s0=0x%02x s1=0x%02x s2=0x%02x can_age=%lums\n",
(unsigned)PSU0.outputVoltage,
(int)PSU0.outputCurrent,
(unsigned)PSU0.CONT_enabled,
(unsigned)psu_last_good_s0,
(unsigned)psu_last_good_s1,
(unsigned)psu_last_good_s2,
(unsigned long)(can_lastpacket ? (now - can_lastpacket) : 9999u));
PSU_LogActiveStatusFlags(LOG_ERR);
sudden_latched = 1u;
sudden_cand_ms = 0u;
prev_enabled = 0u;
return;
}
if (sudden_cand_ms == 0u) {
sudden_cand_ms = now;
return;
}
if ((now - sudden_cand_ms) < PSU_SUDDEN_OFF_DEBOUNCE_MS) {
return;
}
log_printf(LOG_ERR,
"PSU suddenly disabled while charging reason=output_lost "
"V=%u I=%d cont=%u s0=0x%02x s1=0x%02x s2=0x%02x can_age=%lums\n",
(unsigned)PSU0.outputVoltage,
(int)PSU0.outputCurrent,
(unsigned)PSU0.CONT_enabled,
(unsigned)PSU0.status0.raw,
(unsigned)PSU0.status1.raw,
(unsigned)PSU0.status2.raw,
(unsigned long)(can_lastpacket ? (now - can_lastpacket) : 9999u));
PSU_LogActiveStatusFlags(LOG_ERR);
sudden_latched = 1u;
sudden_cand_ms = 0u;
prev_enabled = 0u;
}
static void PSU_HvControl(void)
{
if (CONN.EnableOutput == 0u) {
@@ -235,6 +414,9 @@ ISR_FAST void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan){
PSU0.status0.raw = PSU_04.modularForm0;
PSU0.status1.raw = PSU_04.modularForm1;
PSU0.status2.raw = PSU_04.modularForm2;
psu_last_good_s0 = PSU0.status0.raw;
psu_last_good_s1 = PSU0.status1.raw;
psu_last_good_s2 = PSU0.status2.raw;
}
if(CanId.command==0x06){
memcpy(&PSU_06, RxData, 8);
@@ -515,6 +697,9 @@ void PSU_Task(void){
PSU0.ready = 0;
}
/* Before state transitions so CONNECTED+can_lost is still visible */
PSU_MonitorStatusAndUnexpectedOff();
switch(PSU0.state){
case PSU_UNREADY:
PSU0.enableOutput = 0;