Files
CCSModuleSW30Web/Core/Src/psu_control.c
T

1566 lines
45 KiB
C
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "psu_control.h"
#include "board.h"
#include "can.h"
#include "charger_config.h"
#include "charger_control.h"
#include "debug.h"
#include "isr_opt.h"
#include "power_setpoint_executor.h"
#include <stdio.h>
#include <string.h>
PSU_02_t PSU_02;
PSU_04_t PSU_04;
PSU_06_t PSU_06;
PSU_08_t PSU_08;
PSU_09_t PSU_09;
PSU_1A_t PSU_1A;
PSU_1B_t PSU_1B;
PSU_1C_t PSU_1C;
PSU_t PSU0;
#define CAN_DELAY 20
#define PSU_VOLTAGE_THRESHOLD 20 // Порог напряжения для определения состояния (В)
#define PSU_ONLINE_TIMEOUT 500 // Таймаут Niuera (мс)
#define PSU_TONHE_ONLINE_TIMEOUT 3000u /* PF=01 ~0.5–1 с по документу/записи */
#define PSU_STARTUP_DELAY 4000 // Задержка 2 секунды перед включением
#define PSU_DIAG_LOG_MS 2000u
#define PSU_SUDDEN_OFF_DEBOUNCE_MS 200u
/* TonHe V1.2/V1.3: 125 kbit/s already on CAN2, extended 29-bit PDU1.
* Start/stop is group PF=04 then PF=03 with a member bit, not addressed PF=06.
* TONHE_MODULE_ADDR: live SA for the bitmask (4 on the bench). 0 = first SA that answers A0. */
#define TONHE_SA_CTRL 0xA0u
#define TONHE_DA_BROADCAST 0xFFu
#define TONHE_MODULE_ADDR 4u
#define TONHE_PF_STATUS 0x01u
#define TONHE_PF_AC 0x0Bu
#define TONHE_PF_EXT 0x91u
#define TONHE_PF_GROUP_CMD 0x03u
#define TONHE_PF_UI_MASK 0x04u
#define TONHE_PF_HEARTBEAT 0x05u
#define TONHE_P_GROUP_CMD 2u
#define TONHE_P_UI_MASK 4u
#define TONHE_P_HEARTBEAT 6u
#define TONHE_CMD_START 0xAAu
#define TONHE_CMD_STOP 0x55u
#define TONHE_HEARTBEAT_MS 4000u
#define TONHE_CMD_MIN_MS 200u
#define TONHE_STATE_OFF 0x00u
#define TONHE_STATE_ON 0x01u
#define TONHE_STATE_FAULT_OFF 0x11u
#define TONHE_RESTART_RETRY_MS 500u
#define TONHE_RESTART_OFF_MS 10000u
#define TONHE_RESTART_ON_MS 10000u
typedef enum {
TONHE_RESTART_IDLE = 0,
TONHE_RESTART_STOP,
TONHE_RESTART_WAIT_OFF,
TONHE_RESTART_START,
TONHE_RESTART_WAIT_ON
} TonHeRestart_t;
uint32_t can_lastpacket;
extern CAN_HandleTypeDef hcan2;
static uint16_t psu_last_can_v;
static uint16_t psu_last_can_i_0p1A;
static uint16_t psu_cmd_v = PSU_MIN_VOLTAGE;
static uint16_t psu_cmd_i_0p1A;
static uint8_t psu_last_good_s0;
static uint8_t psu_last_good_s1;
static uint8_t psu_last_good_s2;
static PsuType_t psu_type;
static uint16_t psu_max_current_a = PSU_MAX_CURRENT;
static uint8_t psu_output_cmd_on;
static uint8_t tonhe_state;
static uint8_t tonhe_sa;
static uint32_t tonhe_last_cmd_ms;
static uint8_t tonhe_last_sent_start = 0xFFu;
static uint16_t tonhe_last_sent_v;
static uint16_t tonhe_last_sent_i;
static uint16_t tonhe_faults;
static uint8_t tonhe_pfc;
static uint16_t tonhe_ext_x;
static uint8_t tonhe_ext_valid;
static uint32_t tonhe_hb_tick;
static TonHeRestart_t tonhe_restart;
static uint32_t tonhe_restart_tick;
static uint32_t tonhe_restart_cmd_ms;
static uint16_t tonhe_run_v;
static uint8_t tonhe_run_valid;
static void PSU_SendCmd(uint8_t source, uint8_t destination, uint8_t cmd, void *data);
static void PSU_SwitchState(PSU_State_t state);
static void PSU_LogActiveStatusFlags(LogLevel_t level);
static void PSU_MonitorStatusAndUnexpectedOff(void);
static uint8_t PSU_IsTonHe(void);
static void PSU_TonHeSend(uint8_t priority, uint8_t pf, uint8_t da, const uint8_t *data);
static void PSU_TonHeHeartbeat(void);
static void PSU_TonHeSendGroupUi(uint16_t voltage_v, uint16_t current_0p1A, uint8_t force);
static void PSU_TonHeSendGroupOnOff(uint8_t start, uint8_t force);
static void PSU_TonHeRefreshFlags(void);
static void PSU_TonHeRx(uint32_t ext_id, uint32_t dlc, const uint8_t *rx);
static uint8_t PSU_TonHeIsHv(uint16_t voltage_v);
static uint8_t PSU_TonHeNearTarget(uint16_t target_v);
static uint8_t PSU_TonHeLvEstablished(void);
static uint8_t PSU_TonHeCanLvHvRestart(uint16_t target_v);
static void PSU_TonHeRestartReset(void);
static void PSU_TonHeRestartBegin(void);
static void PSU_TonHeRestartTask(void);
static const char *PSU_StateName(PSU_State_t state)
{
switch (state) {
case PSU_UNREADY: return "UNREADY";
case PSU_INITIALIZING: return "INIT";
case PSU_READY: return "READY";
case PSU_WAIT_ACK_ON: return "WAIT_ON";
case PSU_CONT_WAIT_ACK_ON: return "CONT_ON";
case PSU_CONNECTED: return "CONNECTED";
case PSU_FAST_DISCHARGE_OFF: return "FD_OFF";
case PSU_FAST_DISCHARGE_WAIT: return "FD_WAIT";
case PSU_FAST_DISCHARGE_SET: return "FD_SET";
case PSU_FAST_DISCHARGE_ON: return "FD_ON";
case PSU_FAST_DISCHARGE_ON_WAIT: return "FD_ON_WAIT";
case PSU_CURRENT_DROP: return "CUR_DROP";
case PSU_CONT_WAIT_ACK_OFF: return "CONT_OFF";
case PSU_WAIT_ACK_OFF: return "WAIT_OFF";
case PSU_OFF_PAUSE: return "OFF_PAUSE";
default: return "?";
}
}
static uint8_t PSU_IsTonHe(void)
{
return (psu_type == PSU_TYPE_TONHE_30KW) ? 1u : 0u;
}
PsuType_t PSU_GetType(void)
{
return psu_type;
}
static uint8_t PSU_TonHeIsHv(uint16_t voltage_v)
{
return (voltage_v > TONHE_HV_THRESHOLD_V) ? 1u : 0u;
}
/* Same window as Niuera PSD_ON_WAIT: measured U near the commanded setpoint. */
static uint8_t PSU_TonHeNearTarget(uint16_t target_v)
{
uint16_t v_low;
v_low = (target_v > PSD_PRECHARGE_TOLERANCE_V) ?
(uint16_t)(target_v - PSD_PRECHARGE_TOLERANCE_V) : 0u;
return (CONN.MeasuredVoltage >= v_low) ? 1u : 0u;
}
static uint8_t PSU_TonHeLvEstablished(void)
{
if ((tonhe_run_valid == 0u) || PSU_TonHeIsHv(tonhe_run_v) ||
PSU_TonHeIsHv(CONN.MeasuredVoltage)) {
return 0u;
}
return PSU_TonHeNearTarget(tonhe_run_v);
}
static uint8_t PSU_TonHeCanLvHvRestart(uint16_t target_v)
{
if ((tonhe_restart != TONHE_RESTART_IDLE) ||
(PSU0.state != PSU_CONNECTED) ||
(psu_output_cmd_on == 0u) ||
(PSU0.PSU_enabled == 0u) ||
(tonhe_run_valid == 0u) ||
PSU_TonHeIsHv(tonhe_run_v) ||
(PSU_TonHeIsHv(target_v) == 0u)) {
return 0u;
}
return PSU_TonHeLvEstablished();
}
static void PSU_TonHeRestartReset(void)
{
tonhe_restart = TONHE_RESTART_IDLE;
tonhe_run_valid = 0u;
}
static void PSU_TonHeRestartBegin(void)
{
if (tonhe_restart != TONHE_RESTART_IDLE) {
return;
}
log_printf(LOG_INFO, "TonHe LV->HV restart %u->%u\n",
(unsigned)tonhe_run_v, (unsigned)psu_cmd_v);
tonhe_restart = TONHE_RESTART_STOP;
tonhe_restart_tick = HAL_GetTick();
}
static void PSU_TonHeRestartTask(void)
{
uint32_t now;
if (!PSU_IsTonHe() || (tonhe_restart == TONHE_RESTART_IDLE)) {
return;
}
if (!CONN.EnableOutput || !PSU0.ready || PSU0.cont_fault || PSU0.psu_fault) {
log_printf(LOG_WARN, "TonHe LV->HV restart abort\n");
tonhe_restart = TONHE_RESTART_IDLE;
return;
}
now = HAL_GetTick();
switch (tonhe_restart) {
case TONHE_RESTART_STOP:
PSU_TonHeSendGroupOnOff(0u, 1u);
tonhe_restart = TONHE_RESTART_WAIT_OFF;
tonhe_restart_tick = now;
break;
case TONHE_RESTART_WAIT_OFF:
if (PSU0.PSU_enabled == 0u) {
tonhe_restart = TONHE_RESTART_START;
tonhe_restart_tick = now;
} else if ((now - tonhe_restart_tick) > TONHE_RESTART_OFF_MS) {
log_printf(LOG_WARN, "TonHe LV->HV restart off timeout V=%u\n",
(unsigned)CONN.MeasuredVoltage);
PSU0.psu_fault = 1;
CONN.chargingError = CONN_ERR_PSU_FAULT;
tonhe_restart = TONHE_RESTART_IDLE;
PSU_SwitchState(PSU_CURRENT_DROP);
}
break;
case TONHE_RESTART_START:
PSU_TonHeSendGroupUi(psu_cmd_v, psu_cmd_i_0p1A, 1u);
ED_Delay(CAN_DELAY);
PSU_TonHeSendGroupOnOff(1u, 1u);
psu_output_cmd_on = 1u;
tonhe_restart_cmd_ms = now;
tonhe_restart = TONHE_RESTART_WAIT_ON;
tonhe_restart_tick = now;
break;
case TONHE_RESTART_WAIT_ON:
if ((PSU0.PSU_enabled != 0u) && PSU_TonHeNearTarget(psu_cmd_v)) {
tonhe_run_v = psu_cmd_v;
tonhe_run_valid = 1u;
tonhe_restart = TONHE_RESTART_IDLE;
log_printf(LOG_INFO, "TonHe LV->HV restart done V=%u\n",
(unsigned)CONN.MeasuredVoltage);
} else {
if ((now - tonhe_restart_cmd_ms) >= TONHE_RESTART_RETRY_MS) {
PSU_TonHeSendGroupUi(psu_cmd_v, psu_cmd_i_0p1A, 1u);
ED_Delay(CAN_DELAY);
PSU_TonHeSendGroupOnOff(1u, 1u);
tonhe_restart_cmd_ms = now;
}
if ((now - tonhe_restart_tick) > TONHE_RESTART_ON_MS) {
log_printf(LOG_ERR, "TonHe LV->HV restart on timeout V=%u target=%u\n",
(unsigned)CONN.MeasuredVoltage, (unsigned)psu_cmd_v);
PSU0.psu_fault = 1;
CONN.chargingError = CONN_ERR_PSU_FAULT;
tonhe_restart = TONHE_RESTART_IDLE;
PSU_SwitchState(PSU_UNREADY);
}
}
break;
default:
tonhe_restart = TONHE_RESTART_IDLE;
break;
}
}
static uint32_t PSU_TonHeMakeId(uint8_t priority, uint8_t pf, uint8_t da, uint8_t sa)
{
return ((uint32_t)(priority & 7u) << 26) |
((uint32_t)pf << 16) |
((uint32_t)da << 8) |
(uint32_t)sa;
}
static uint16_t PSU_TonHeU16le(const uint8_t *data, uint8_t offset)
{
return (uint16_t)data[offset] | ((uint16_t)data[offset + 1u] << 8);
}
static void PSU_TonHePutU16le(uint8_t *data, uint8_t offset, uint16_t value)
{
data[offset] = (uint8_t)(value & 0xFFu);
data[offset + 1u] = (uint8_t)((value >> 8) & 0xFFu);
}
static void PSU_ApplyOutputTelemetry(uint16_t voltage_v, int16_t current_0p1A, uint8_t temperature)
{
PSU0.outputVoltage = voltage_v;
PSU0.outputCurrent = current_0p1A;
PSU0.temperature = temperature;
if (PSU0.state >= PSU_READY) {
CONN.MeasuredVoltage = PSU0.outputVoltage;
CONN.MeasuredCurrent = (uint16_t)PSU0.outputCurrent;
CONN.Power = CONN.MeasuredCurrent * CONN.MeasuredVoltage / 10u;
CONN.outputEnabled = PSU0.PSU_enabled;
}
}
static void PSU_TonHeRefreshFlags(void)
{
PSU_Status0_t s0;
PSU_Status1_t s1;
PSU_Status2_t s2;
memset(&s0, 0, sizeof(s0));
memset(&s1, 0, sizeof(s1));
memset(&s2, 0, sizeof(s2));
s0.shortCircuitFault = (tonhe_faults & 0x8000u) ? 1u : 0u;
s0.internalCommunicationFault = (tonhe_faults & 0x0200u) ? 1u : 0u;
s0.inputBusLineFault = (tonhe_faults & 0x0100u) ? 1u : 0u;
s0.dischargeFault = (tonhe_faults & 0x0400u) ? 1u : 0u;
s1.dcSideOffStatus = (tonhe_state != TONHE_STATE_ON) ? 1u : 0u;
s1.moduleFaultAlarm = ((tonhe_faults & 0x0080u) || (tonhe_state == TONHE_STATE_FAULT_OFF)) ? 1u : 0u;
s1.fanFaultAlarm = (tonhe_faults & 0x0040u) ? 1u : 0u;
s1.overTempAlarm = (tonhe_faults & 0x0020u) ? 1u : 0u;
s1.outputOverVoltageAlarm = (tonhe_faults & 0x0008u) ? 1u : 0u;
s1.outputOverCurrentAlarm = (tonhe_faults & 0x0010u) ? 1u : 0u;
s2.powerLimitStatus = (tonhe_faults & 0x4000u) ? 1u : 0u;
s2.moduleAddressDuplicate = (tonhe_pfc & 0x10u) ? 1u : 0u;
s2.threePhaseInputPhaseLossAlarm = (tonhe_faults & 0x0002u) ? 1u : 0u;
s2.threePhaseInputUnbalanceAlarm = (tonhe_pfc & 0x04u) ? 1u : 0u;
s2.inputUnderVoltageAlarm = (tonhe_faults & 0x0001u) ? 1u : 0u;
s2.inputOverVoltageAlarm = (tonhe_faults & 0x0004u) ? 1u : 0u;
s2.pfcSideOffStatus = (tonhe_faults & 0x0800u) ? 1u : 0u;
/* PF=91 is optional; missing it must not look like a zero mask. */
if (tonhe_ext_valid) {
if (tonhe_ext_x & 0x0004u) {
s1.canCommunicationInterruptAlarm = 1u;
}
if (tonhe_ext_x & 0x0300u) {
s2.powerLimitStatus = 1u;
}
if (tonhe_ext_x & 0x2000u) {
s1.moduleFaultAlarm = 1u;
}
}
PSU0.status0.bits = s0;
PSU0.status1.bits = s1;
PSU0.status2.bits = s2;
psu_last_good_s0 = PSU0.status0.raw;
psu_last_good_s1 = PSU0.status1.raw;
psu_last_good_s2 = PSU0.status2.raw;
}
static void PSU_TonHeSend(uint8_t priority, uint8_t pf, uint8_t da, const uint8_t *data)
{
int8_t retry_counter = 10;
CAN_TxHeaderTypeDef tx_header;
uint32_t tx_mailbox;
HAL_StatusTypeDef can_result;
uint8_t payload[8];
memcpy(payload, data, 8);
tx_header.ExtId = PSU_TonHeMakeId(priority, pf, da, TONHE_SA_CTRL);
tx_header.RTR = CAN_RTR_DATA;
tx_header.IDE = CAN_ID_EXT;
tx_header.DLC = 8;
while (retry_counter > 0) {
if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan2) > 0) {
can_result = HAL_CAN_AddTxMessage(&hcan2, &tx_header, payload, &tx_mailbox);
if (can_result == HAL_OK) {
return;
}
}
ED_Delay(1);
retry_counter--;
}
}
static void PSU_TonHeHeartbeat(void)
{
uint8_t zeros[8];
uint32_t now = HAL_GetTick();
if ((tonhe_hb_tick != 0u) && ((now - tonhe_hb_tick) < TONHE_HEARTBEAT_MS)) {
return;
}
tonhe_hb_tick = now;
memset(zeros, 0, sizeof(zeros));
PSU_TonHeSend(TONHE_P_HEARTBEAT, TONHE_PF_HEARTBEAT, TONHE_DA_BROADCAST, zeros);
}
static void PSU_TonHeEncodeUi(uint8_t *data, uint8_t u_off, uint8_t i_off,
uint16_t voltage_v, uint16_t current_0p1A)
{
uint32_t u_raw = (uint32_t)voltage_v * 10u;
uint32_t i_raw = (uint32_t)current_0p1A * 10u;
if (u_raw > 10000u) {
u_raw = 10000u;
}
if (i_raw > 50000u) {
i_raw = 50000u;
}
PSU_TonHePutU16le(data, u_off, (uint16_t)u_raw);
PSU_TonHePutU16le(data, i_off, (uint16_t)i_raw);
}
static uint8_t PSU_TonHeLiveSa(void)
{
if (TONHE_MODULE_ADDR != 0u) {
return (uint8_t)TONHE_MODULE_ADDR;
}
return tonhe_sa;
}
static uint8_t PSU_TonHeAcceptSa(uint8_t sa)
{
if ((sa == 0u) || (sa == TONHE_SA_CTRL)) {
return 0u;
}
if (TONHE_MODULE_ADDR != 0u) {
return (sa == (uint8_t)TONHE_MODULE_ADDR) ? 1u : 0u;
}
if (tonhe_sa == 0u) {
tonhe_sa = sa;
}
return (sa == tonhe_sa) ? 1u : 0u;
}
/* PDF group mask: bit (SA-1)%24, bank (SA-1)/24. SA=4 → 08 00 00, bank 0. */
static uint8_t PSU_TonHeFillMask(uint8_t *bytes, uint8_t sa)
{
uint8_t idx;
uint8_t bit;
uint32_t k;
if (sa < 1u) {
sa = 1u;
}
idx = (uint8_t)(sa - 1u);
bit = (uint8_t)(idx % 24u);
k = (1uL << bit);
bytes[0] = (uint8_t)(k & 0xFFu);
bytes[1] = (uint8_t)((k >> 8) & 0xFFu);
bytes[2] = (uint8_t)((k >> 16) & 0xFFu);
return (uint8_t)((idx / 24u) & 0x0Fu);
}
static void PSU_TonHeClampUi(uint16_t *voltage_v, uint16_t *current_0p1A)
{
uint16_t min_i = (uint16_t)PSU_MIN_CURRENT * 10u;
if (*voltage_v < PSU_MIN_VOLTAGE) {
*voltage_v = PSU_MIN_VOLTAGE;
}
if (*current_0p1A < min_i) {
*current_0p1A = min_i;
}
}
static void PSU_TonHeSendGroupUi(uint16_t voltage_v, uint16_t current_0p1A, uint8_t force)
{
uint8_t data[8];
uint8_t sa = PSU_TonHeLiveSa();
uint32_t now;
if (sa == 0u) {
return;
}
PSU_TonHeClampUi(&voltage_v, &current_0p1A);
now = HAL_GetTick();
if (!force &&
(tonhe_last_sent_v == voltage_v) &&
(tonhe_last_sent_i == current_0p1A) &&
((now - tonhe_last_cmd_ms) < TONHE_CMD_MIN_MS)) {
return;
}
memset(data, 0, sizeof(data));
data[3] = PSU_TonHeFillMask(data, sa);
PSU_TonHeEncodeUi(data, 4, 6, voltage_v, current_0p1A);
PSU_TonHeSend(TONHE_P_UI_MASK, TONHE_PF_UI_MASK, TONHE_DA_BROADCAST, data);
if (force || (tonhe_last_sent_v != voltage_v) || (tonhe_last_sent_i != current_0p1A)) {
log_printf(LOG_INFO, "TonHe PF04 U=%u I=%u.%uA sa=%u mask=%02X\n",
(unsigned)voltage_v,
(unsigned)(current_0p1A / 10u),
(unsigned)(current_0p1A % 10u),
(unsigned)sa,
(unsigned)data[0]);
}
tonhe_last_cmd_ms = now;
tonhe_last_sent_v = voltage_v;
tonhe_last_sent_i = current_0p1A;
}
static void PSU_TonHeSendGroupOnOff(uint8_t start, uint8_t force)
{
uint8_t data[8];
uint8_t sa = PSU_TonHeLiveSa();
uint32_t now;
if (sa == 0u) {
return;
}
now = HAL_GetTick();
if (!force &&
(tonhe_last_sent_start == start) &&
((now - tonhe_last_cmd_ms) < TONHE_CMD_MIN_MS)) {
return;
}
memset(data, 0, sizeof(data));
data[4] = PSU_TonHeFillMask(data, sa);
data[3] = start ? TONHE_CMD_START : TONHE_CMD_STOP;
PSU_TonHeSend(TONHE_P_GROUP_CMD, TONHE_PF_GROUP_CMD, TONHE_DA_BROADCAST, data);
if (force || (tonhe_last_sent_start != start)) {
log_printf(LOG_INFO, "TonHe PF03 %s sa=%u mask=%02X\n",
start ? "AA" : "55",
(unsigned)sa,
(unsigned)data[0]);
}
tonhe_last_cmd_ms = now;
tonhe_last_sent_start = start;
}
ISR_FAST static void PSU_TonHeRx(uint32_t ext_id, uint32_t dlc, const uint8_t *rx)
{
uint8_t pf;
uint8_t da;
uint8_t sa;
if (dlc < 8u) {
return;
}
pf = (uint8_t)((ext_id >> 16) & 0xFFu);
da = (uint8_t)((ext_id >> 8) & 0xFFu);
sa = (uint8_t)(ext_id & 0xFFu);
/* Group path still reports from the module SA to controller A0. */
if ((da != TONHE_SA_CTRL) || (PSU_TonHeAcceptSa(sa) == 0u)) {
return;
}
if (pf == TONHE_PF_STATUS) {
uint16_t v_raw = PSU_TonHeU16le(rx, 1);
uint16_t i_raw = PSU_TonHeU16le(rx, 3);
uint16_t v = (uint16_t)(v_raw / 10u);
int16_t i = (int16_t)(i_raw / 10u);
tonhe_state = rx[0];
tonhe_faults = PSU_TonHeU16le(rx, 5);
tonhe_pfc = rx[7];
PSU_TonHeRefreshFlags();
PSU0.online = 1u;
/* Niuera-style: enabled = bus up, not the ON command bit. */
PSU0.PSU_enabled = (v >= PSU_VOLTAGE_THRESHOLD) ? 1u : 0u;
can_lastpacket = HAL_GetTick();
/* Measured only — do not overwrite commanded U/I. */
psu_last_can_v = v;
psu_last_can_i_0p1A = (uint16_t)i;
PSU_ApplyOutputTelemetry(v, i, PSU0.temperature);
} else if (pf == TONHE_PF_AC) {
PSU_06.VAB = PSU_TonHeU16le(rx, 0);
PSU_06.VBC = PSU_TonHeU16le(rx, 2);
PSU_06.VCA = PSU_TonHeU16le(rx, 4);
PSU0.tempAmbient = PSU_TonHeU16le(rx, 6);
PSU0.temperature = (uint8_t)PSU0.tempAmbient;
can_lastpacket = HAL_GetTick();
PSU0.online = 1u;
} else if (pf == TONHE_PF_EXT) {
tonhe_ext_x = PSU_TonHeU16le(rx, 2);
tonhe_ext_valid = 1u;
PSU_TonHeRefreshFlags();
can_lastpacket = HAL_GetTick();
PSU0.online = 1u;
}
}
static void PSU_LogPeriodicDiag(void)
{
static uint32_t last_log_ms;
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;
}
last_log_ms = now;
#if PSD_ENABLE
block_setpoint = PowerSetpoint_HasPending() || PowerSetpoint_IsBusy();
#else
block_setpoint = 0u;
#endif
log_printf(LOG_INFO,
"PSU diag st=%s req=%uV/%u.%uA want=%uV/%u.%uA "
"can=%uV/%u.%uA meas=%uV/%d.%uA "
"enOut=%u out=%u rdy=%u on=%u cont=%u ac=%u hv=%u psd_blk=%u "
"err=%u s0=0x%02x s1=0x%02x s2=0x%02x can_age=%lums\n",
PSU_StateName(PSU0.state),
(unsigned)CONN.RequestedVoltage,
(unsigned)(CONN.RequestedCurrent / 10u),
(unsigned)(CONN.RequestedCurrent % 10u),
(unsigned)CONN.RequestedVoltage,
(unsigned)(CONN.WantedCurrent / 10u),
(unsigned)(CONN.WantedCurrent % 10u),
(unsigned)psu_last_can_v,
(unsigned)(psu_last_can_i_0p1A / 10u),
(unsigned)(psu_last_can_i_0p1A % 10u),
(unsigned)CONN.MeasuredVoltage,
(int)(CONN.MeasuredCurrent / 10),
(unsigned)(((CONN.MeasuredCurrent < 0) ?
-CONN.MeasuredCurrent : CONN.MeasuredCurrent) % 10),
(unsigned)CONN.EnableOutput,
(unsigned)CONN.outputEnabled,
(unsigned)PSU0.ready,
(unsigned)PSU0.PSU_enabled,
(unsigned)PSU0.CONT_enabled,
(unsigned)PSU0.enableAC,
(unsigned)CONN.hv_limit,
(unsigned)block_setpoint,
(unsigned)CONN.chargingError,
(unsigned)PSU0.status0.raw,
(unsigned)PSU0.status1.raw,
(unsigned)PSU0.status2.raw,
(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.
*
* Host Mode::Off (CMD_E2M_SET_OUTPUT_VOLTAGE 0/0) arrives before
* allow_power_on=false during ISO15118 Welding. That is an expected V drop
* while EnableOutput is still 1 — do not treat it as output_lost.
*/
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 host_export_off;
uint8_t expect_on;
in_charging = (CONN.connState == Charging);
/* Everest setMode(Off) → SET_OUTPUT 0 V / 0 A; contactors may stay closed. */
host_export_off = (CONN.WantedCurrent == 0u) || (CONN.RequestedVoltage == 0u);
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) &&
!host_export_off &&
(!PSU_IsTonHe() || (tonhe_restart == TONHE_RESTART_IDLE));
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) ||
host_export_off ||
(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) {
if ((CONN.hv_limit != 0u) || (CONN.hv_limit_count != 0u)) {
log_printf(LOG_INFO, "HV limit reset output off\n");
}
CONN.hv_limit = 0u;
CONN.hv_tick = 0u;
CONN.hv_limit_count = 0u;
return;
}
if (!PSU0.online) {
if ((CONN.hv_limit != 0u) || (CONN.hv_limit_count != 0u)) {
log_printf(LOG_INFO, "HV limit reset no PSU\n");
}
CONN.hv_limit = 0u;
CONN.hv_tick = 0u;
CONN.hv_limit_count = 0u;
return;
}
if (CONN.hv_limit == 0u) {
if ((CONN.hv_limit_count < PSU_HV_LIMIT_MAX_COUNT) &&
(CONN.MeasuredVoltage < PSU_HV_LIMIT_ON_THRESHOLD) &&
(CONN.MeasuredCurrent > PSU_HV_LOAD_CURRENT)) {
if (CONN.hv_tick == 0u) {
CONN.hv_tick = HAL_GetTick();
} else if ((HAL_GetTick() - CONN.hv_tick) >= PSU_HV_SWITCH_DELAY_MS) {
CONN.hv_limit = 1u;
CONN.hv_limit_count++;
CONN.hv_tick = 0u;
log_printf(LOG_WARN, "HV limit ON V=%d I=%d cnt=%d\n",
(int)CONN.MeasuredVoltage,
(int)CONN.MeasuredCurrent,
(int)CONN.hv_limit_count);
if (CONN.hv_limit_count >= PSU_HV_LIMIT_MAX_COUNT) {
log_printf(LOG_WARN, "HV limit locked cnt=%d\n",
(int)CONN.hv_limit_count);
}
}
} else {
CONN.hv_tick = 0u;
}
} else {
if ((CONN.MeasuredVoltage > PSU_HV_LIMIT_OFF_THRESHOLD) ||
(CONN.MeasuredCurrent <= PSU_HV_LOAD_CURRENT)) {
if (CONN.hv_tick == 0u) {
CONN.hv_tick = HAL_GetTick();
} else if ((HAL_GetTick() - CONN.hv_tick) >= PSU_HV_SWITCH_DELAY_MS) {
CONN.hv_limit = 0u;
CONN.hv_tick = 0u;
log_printf(LOG_INFO, "HV limit OFF V=%d I=%d cnt=%d\n",
(int)CONN.MeasuredVoltage,
(int)CONN.MeasuredCurrent,
(int)CONN.hv_limit_count);
}
} else {
CONN.hv_tick = 0u;
}
}
}
static void PSU_SwitchState(PSU_State_t state){
if (PSU0.state != state) {
log_printf(LOG_INFO,
"PSU st %s->%s enOut=%u rdy=%u on=%u cont=%u err=%u\n",
PSU_StateName(PSU0.state),
PSU_StateName(state),
(unsigned)CONN.EnableOutput,
(unsigned)PSU0.ready,
(unsigned)PSU0.PSU_enabled,
(unsigned)PSU0.CONT_enabled,
(unsigned)CONN.chargingError);
}
PSU0.state = state;
PSU0.statetick = HAL_GetTick();
}
static uint32_t PSU_StateTime(void){
return HAL_GetTick() - PSU0.statetick;
}
static void PSU_FastDischarge_PushTarget(void)
{
PowerSetpoint_t fd_sp;
PowerSetpoint_GetFastDischargeTarget(&fd_sp);
PSU_SetVoltageCurrent(0, fd_sp.voltage_V, fd_sp.current_0p1A);
PSU_Enable(0, 1);
}
static void PSU_FastDischarge_AbortToConnected(void)
{
PowerSetpoint_OnFastDischargeAbort();
if (!PSU0.PSU_enabled) {
PSU_FastDischarge_PushTarget();
}
PSU_SwitchState(PSU_CONNECTED);
}
ISR_FAST void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan){
static CAN_RxHeaderTypeDef RxHeader;
static uint8_t RxData[8] = {0,};
CanId_t CanId;
if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO1, &RxHeader, RxData) == HAL_OK)
{
if (PSU_IsTonHe()) {
PSU_TonHeRx(RxHeader.ExtId, RxHeader.DLC, RxData);
return;
}
memcpy(&CanId, &RxHeader.ExtId, sizeof(CanId_t));
/* Для DC30 поддерживается только один силовой модуль (source == 0) */
if(CanId.source != 0) return;
can_lastpacket = HAL_GetTick();
if(CanId.command==0x02){
memcpy(&PSU_02, RxData, 8);
}
if(CanId.command==0x04){
memcpy(&PSU_04, RxData, 8);
PSU0.tempAmbient = PSU_04.moduleTemperature;
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);
PSU_06.VAB = PSU_06.VABLo+(PSU_06.VABHi<<8);
PSU_06.VBC = PSU_06.VBCLo+(PSU_06.VBCHi<<8);
PSU_06.VCA = PSU_06.VCALo+(PSU_06.VCAHi<<8);
}
if(CanId.command==0x08){
memcpy(&PSU_08, RxData, 8);
}
if(CanId.command==0x09){
memcpy(&PSU_09, RxData, 8);
PSU_09.moduleNCurrent = PSU_09.moduleNCurrent_[3];
PSU_09.moduleNCurrent |= PSU_09.moduleNCurrent_[2]<<8;
PSU_09.moduleNCurrent |= PSU_09.moduleNCurrent_[1]<<16;
PSU_09.moduleNCurrent |= PSU_09.moduleNCurrent_[0]<<24;
PSU_09.moduleNVoltage = PSU_09.moduleNVoltage_[3];
PSU_09.moduleNVoltage |= PSU_09.moduleNVoltage_[2]<<8;
PSU_09.moduleNVoltage |= PSU_09.moduleNVoltage_[1]<<16;
PSU_09.moduleNVoltage |= PSU_09.moduleNVoltage_[0]<<24;
// PSU_09 -> PSU -> CONN (один модуль)
{
uint16_t v = PSU_09.moduleNVoltage / 1000;
int16_t i = PSU_09.moduleNCurrent / 100;
PSU0.PSU_enabled = (v >= PSU_VOLTAGE_THRESHOLD);
PSU0.online = 1;
PSU_ApplyOutputTelemetry(v, i, PSU_04.moduleTemperature);
}
}
}
}
void PSU_CAN_FilterInit(){
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterBank = 14;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK;
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterIdHigh = 0x0000;
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0x0000;
sFilterConfig.FilterMaskIdLow = 0x0000;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO1;
sFilterConfig.SlaveStartFilterBank = 14;
if(HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
}
void PSU_Init(){
HAL_CAN_Stop(&hcan2);
MX_CAN2_Init();
PSU_CAN_FilterInit();
HAL_CAN_Start(&hcan2);
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING /* | CAN_IT_ERROR | CAN_IT_BUSOFF | CAN_IT_LAST_ERROR_CODE | CAN_IT_TX_MAILBOX_EMPTY*/);
memset(&PSU0, 0, sizeof(PSU0));
PSU0.state = PSU_UNREADY;
PSU0.statetick = HAL_GetTick();
psu_type = Board_GetPsuType();
psu_max_current_a = Board_GetPsuMaxCurrentA();
PSU0.power_limit = Board_GetPsuMaxPowerW();
psu_output_cmd_on = 0u;
psu_cmd_v = PSU_MIN_VOLTAGE;
psu_cmd_i_0p1A = (uint16_t)PSU_MIN_CURRENT * 10u;
psu_last_can_v = 0u;
psu_last_can_i_0p1A = 0u;
tonhe_sa = (TONHE_MODULE_ADDR != 0u) ? (uint8_t)TONHE_MODULE_ADDR : 0u;
tonhe_last_sent_start = 0xFFu;
tonhe_ext_valid = 0u;
tonhe_hb_tick = 0u;
PSU_TonHeRestartReset();
tonhe_run_v = PSU_MIN_VOLTAGE;
if (PSU_IsTonHe()) {
log_printf(LOG_INFO, "PSU proto: TonHe 30kW group SA=%u (0=discover)\n",
(unsigned)TONHE_MODULE_ADDR);
} else {
log_printf(LOG_INFO, "PSU proto: Niuera type=%u\n", (unsigned)psu_type);
}
PSU_Enable(0, 0);
PowerSetpoint_Init();
}
void PSU_Enable(uint8_t addr, uint8_t enable){
PSU_1A_t data;
memset(&data, 0, sizeof(data));
/* Для DC30 поддерживается только один модуль с адресом 0 */
if(addr != 0) return;
if(PSU0.online == 0) return;
psu_output_cmd_on = enable ? 1u : 0u;
if (PSU_IsTonHe()) {
if (enable) {
PSU_TonHeSendGroupUi(psu_cmd_v, psu_cmd_i_0p1A, 1u);
ED_Delay(CAN_DELAY);
PSU_TonHeSendGroupOnOff(1u, 1u);
} else {
tonhe_restart = TONHE_RESTART_IDLE;
tonhe_run_valid = 0u;
PSU_TonHeSendGroupOnOff(0u, 1u);
}
ED_Delay(CAN_DELAY);
return;
}
data.enable = !enable;
PSU_SendCmd(0xF0, addr, 0x1A, &data);
ED_Delay(CAN_DELAY);
}
void PSU_SetHVMode(uint8_t addr, uint8_t enable){
PSU_1D_t data;
if (PSU_IsTonHe()) {
(void)addr;
(void)enable;
return;
}
memset(&data, 0, sizeof(data));
data.enable = !enable;
if(addr != 0) return;
PSU_SendCmd(0xF0, addr, 0x1D, &data);
}
void PSU_SetVoltageCurrent(uint8_t addr, uint16_t voltage, uint16_t current){
PSU_1C_t data;
memset(&data, 0, sizeof(data));
if(addr != 0) return;
if(voltage<PSU_MIN_VOLTAGE) voltage = PSU_MIN_VOLTAGE;
if((CONN.hv_limit != 0u) && (voltage > PSU_LV_CLAMP_V)){
voltage = PSU_LV_CLAMP_V;
}
psu_cmd_v = voltage;
psu_cmd_i_0p1A = current;
if (PSU_IsTonHe()) {
if (tonhe_restart != TONHE_RESTART_IDLE) {
return;
}
if (psu_output_cmd_on && tonhe_run_valid &&
(PSU_TonHeIsHv(tonhe_run_v) == 0u) && PSU_TonHeIsHv(voltage)) {
if (PSU_TonHeCanLvHvRestart(voltage)) {
PSU_TonHeRestartBegin();
}
/* Keep the LV PF=04 until measured LV is real. */
return;
}
if (psu_output_cmd_on) {
PSU_TonHeSendGroupUi(voltage, current, 0u);
if (PSU0.PSU_enabled) {
tonhe_run_v = voltage;
tonhe_run_valid = 1u;
}
}
return;
}
psu_last_can_v = voltage;
psu_last_can_i_0p1A = current;
uint32_t current_ma = current * 100;
uint32_t voltage_mv = voltage * 1000;
data.moduleCurrentTotal[0] = (current_ma >> 24) & 0xFF;
data.moduleCurrentTotal[1] = (current_ma >> 16) & 0xFF;
data.moduleCurrentTotal[2] = (current_ma >> 8) & 0xFF;
data.moduleCurrentTotal[3] = (current_ma >> 0) & 0xFF;
data.moduleVoltage[0] = (voltage_mv >> 24) & 0xFF;
data.moduleVoltage[1] = (voltage_mv >> 16) & 0xFF;
data.moduleVoltage[2] = (voltage_mv >> 8) & 0xFF;
data.moduleVoltage[3] = (voltage_mv >> 0) & 0xFF;
PSU_SendCmd(0xF0, addr, 0x1C, &data);
}
void PSU_SendCmd(uint8_t source, uint8_t destination, uint8_t cmd, void *data){
CanId_t CanId;
CanId.source = source;
CanId.destination = destination;
CanId.command = cmd;
CanId.device = 0x0A;
int8_t retry_counter = 10;
CAN_TxHeaderTypeDef tx_header;
uint32_t tx_mailbox;
HAL_StatusTypeDef CAN_result;
memcpy(&tx_header.ExtId, &CanId, sizeof(CanId_t));
tx_header.RTR = CAN_RTR_DATA;
tx_header.IDE = CAN_ID_EXT;
tx_header.DLC = 8;
while(retry_counter>0){ //если буфер полон, ждем пока он освободится
if (HAL_CAN_GetTxMailboxesFreeLevel(&hcan2) > 0){
/* отправка сообщения */
CAN_result = HAL_CAN_AddTxMessage(&hcan2, &tx_header, (uint8_t*)data, &tx_mailbox);
/* если отправка удалась, выход */
if(CAN_result == HAL_OK) {
return;
}
}
ED_Delay(1);
retry_counter--;
}
}
void PSU_ReadWrite(){
uint8_t zero_data[8] = {0,0,0,0,0,0,0,0};
if (PSU_IsTonHe()) {
PSU_TonHeHeartbeat();
} else {
PSU_SendCmd(0xF0, 0, 0x04, zero_data);ED_Delay(CAN_DELAY);
PSU_SendCmd(0xF0, 0, 0x06, zero_data);ED_Delay(CAN_DELAY);
// PSU_SendCmd(0xF0, 0, 0x08, zero_data);ED_Delay(CAN_DELAY);
PSU_SendCmd(0xF0, 0, 0x09, zero_data);ED_Delay(CAN_DELAY);
}
// Power Limit
if ((CONN.WantedCurrent/10) * CONN.MeasuredVoltage > PSU0.power_limit){
CONN.RequestedCurrent = PSU0.power_limit * 10 / CONN.MeasuredVoltage;
}else{
CONN.RequestedCurrent = CONN.WantedCurrent;
}
if(CONN.RequestedCurrent > (psu_max_current_a * 10u)){
CONN.RequestedCurrent = psu_max_current_a * 10u;
}
CONN.RequestedPower = CONN.RequestedCurrent * CONN.RequestedVoltage / 10;
#if PSD_ENABLE
{
uint8_t block_setpoint = PowerSetpoint_HasPending() || PowerSetpoint_IsBusy();
if (PSU0.ready && !block_setpoint) {
PSU_SetVoltageCurrent(0, CONN.RequestedVoltage, CONN.RequestedCurrent);
ED_Delay(CAN_DELAY);
}
}
#else
if (PSU0.ready) {
PSU_SetVoltageCurrent(0, CONN.RequestedVoltage, CONN.RequestedCurrent); // Normal mode
ED_Delay(CAN_DELAY);
}
#endif
if (PSU0.ready) {
PSU_HvControl();
}
}
void PSU_Task(void){
static uint32_t psu_on_tick = 0;
static uint32_t cont_ok_tick = 0;
static uint32_t fd_on_last_cmd_ms = 0;
static uint32_t tonhe_on_retry_ms = 0;
PowerSetpointTryResult_t psd_result;
#if PSD_ENABLE
PowerSetpoint_UpdateDeliveryLatch();
if (PowerSetpoint_HasPending()) {
psd_result = PowerSetpoint_TryApply();
if (psd_result == PSD_TRY_START_FAST_DISCHARGE) {
PSU_SwitchState(PSU_FAST_DISCHARGE_OFF);
}
}
#endif
// Обновляем ONLINE/READY по таймауту
if((HAL_GetTick() - can_lastpacket) > (PSU_IsTonHe() ? PSU_TONHE_ONLINE_TIMEOUT : PSU_ONLINE_TIMEOUT)){
if (PSU0.online) {
log_printf(LOG_WARN, "PSU CAN timeout age=%lums -> offline\n",
(unsigned long)(HAL_GetTick() - can_lastpacket));
}
PSU0.online = 0;
PSU0.PSU_enabled = 0;
tonhe_restart = TONHE_RESTART_IDLE;
tonhe_run_valid = 0u;
PSU_04.moduleTemperature = 0;
PSU_04.modularForm0 = 0;
PSU_04.modularForm1 = 0;
PSU_04.modularForm2 = 0;
PSU_06.VAB = 0;
PSU_06.VBC = 0;
PSU_06.VCA = 0;
PSU_09.moduleNCurrent = 0;
PSU_09.moduleNVoltage = 0;
}
if(!PSU0.online || !PSU0.enableAC){
CONN.MeasuredVoltage = 0;
CONN.MeasuredCurrent = 0;
CONN.outputEnabled = 0;
}
// Управление AC-контактором с задержкой отключения 1 минута
if(CONN.EvConnected){
RELAY_Write(RELAY_AC, 1);
psu_on_tick = HAL_GetTick();
PSU0.enableAC = 1;
}else{
if((HAL_GetTick() - psu_on_tick) > 1 * 60000){
RELAY_Write(RELAY_AC, 0);
PSU0.enableAC = 0;
}
}
// Текущее состояние DC-контактора по обратной связи
PSU0.CONT_enabled = IN_ReadInput(IN_CONT_FB_DC);
// Обновляем ready с учётом ошибок
if(PSU0.online && !PSU0.cont_fault && PSU0.enableAC){
// PSU0.ready = 1;
}else{
PSU0.ready = 0;
}
/* Before state transitions so CONNECTED+can_lost is still visible */
PSU_MonitorStatusAndUnexpectedOff();
switch(PSU0.state){
case PSU_UNREADY:
PSU0.enableOutput = 0;
RELAY_Write(RELAY_DC, 0);
if(PSU0.online && PSU0.enableAC && !PSU0.cont_fault){
if (PSU_IsTonHe()) {
PSU_Enable(0, 0);
}
PSU_SwitchState(PSU_INITIALIZING);
}
break;
case PSU_INITIALIZING:
if(PSU_StateTime() > 4000){ // Wait 4s for PSU to initialize
PSU0.ready = 1;
PSU_SwitchState(PSU_READY);
}
break;
case PSU_READY:
// модуль готов, но выключен
CONN.hv_limit = 0u;
CONN.hv_tick = 0u;
CONN.hv_limit_count = 0u;
RELAY_Write(RELAY_DC, 0);
if(!PSU0.ready){
PSU_SwitchState(PSU_UNREADY);
break;
}
if(CONN.EnableOutput){
PSU_Enable(0, 1);
tonhe_on_retry_ms = HAL_GetTick();
PSU_SwitchState(PSU_WAIT_ACK_ON);
}
break;
case PSU_WAIT_ACK_ON:
if(PSU0.PSU_enabled && PSU0.ready){
if (PSU_IsTonHe()) {
tonhe_run_v = tonhe_last_sent_v;
tonhe_run_valid = 1u;
}
PSU_SwitchState(PSU_CONT_WAIT_ACK_ON);
}else if(PSU_StateTime() > 10000){
PSU0.psu_fault = 1;
CONN.chargingError = CONN_ERR_PSU_FAULT;
PSU_SwitchState(PSU_UNREADY);
log_printf(LOG_ERR, "PSU on timeout\n");
}else if (PSU_IsTonHe() &&
((HAL_GetTick() - tonhe_on_retry_ms) >= TONHE_RESTART_RETRY_MS)) {
/* Retry the LV/HV start already sent — do not jump to a later HV cmd. */
PSU_TonHeSendGroupUi(tonhe_last_sent_v, tonhe_last_sent_i, 1u);
ED_Delay(CAN_DELAY);
PSU_TonHeSendGroupOnOff(1u, 1u);
psu_output_cmd_on = 1u;
tonhe_on_retry_ms = HAL_GetTick();
}
break;
case PSU_CONT_WAIT_ACK_ON:
// замыкаем DC-контактор и ждём подтверждение
RELAY_Write(RELAY_DC, 1);
if(PSU0.CONT_enabled){
log_printf(LOG_INFO,
"DC contactor closed (CONT_ON->CONNECTED, fb=%u, t=%lums)\n",
(unsigned)PSU0.CONT_enabled,
(unsigned long)PSU_StateTime());
PSU_SwitchState(PSU_CONNECTED);
}else if(PSU_StateTime() > 1000){
PSU0.cont_fault = 1;
CONN.chargingError = CONN_ERR_CONTACTOR;
PSU_SwitchState(PSU_CURRENT_DROP);
log_printf(LOG_ERR, "Contactor error, stopping...\n");
}
break;
case PSU_CONNECTED:
// Основное рабочее состояние
if(!CONN.EnableOutput || !PSU0.ready){
tonhe_restart = TONHE_RESTART_IDLE;
tonhe_run_valid = 0u;
PSU_SwitchState(PSU_CURRENT_DROP);
break;
}
if (PSU_IsTonHe() && (tonhe_restart == TONHE_RESTART_IDLE) &&
PSU_TonHeCanLvHvRestart(psu_cmd_v)) {
PSU_TonHeRestartBegin();
}
PSU_TonHeRestartTask();
if (PSU0.state != PSU_CONNECTED) {
break;
}
// контроль контактора: 1 c таймаут
if (IN_ReadInput(IN_CONT_FB_DC) != RELAY_Read(RELAY_DC)){
if((HAL_GetTick() - cont_ok_tick) > 1000){
CONN.chargingError = CONN_ERR_CONTACTOR;
PSU0.cont_fault = 1;
PSU_SwitchState(PSU_CURRENT_DROP);
log_printf(LOG_ERR, "Contactor error, stopping...\n");
}
}else{
cont_ok_tick = HAL_GetTick();
}
break;
case PSU_FAST_DISCHARGE_OFF:
if (!CONN.EnableOutput || !PSU0.ready || PSU0.cont_fault || PSU0.psu_fault) {
log_printf(LOG_WARN, "Fast discharge abort -> stop\n");
PowerSetpoint_OnFastDischargeAbort();
PSU_SwitchState(PSU_CURRENT_DROP);
break;
}
PSU_Enable(0, 0);
PSU_SwitchState(PSU_FAST_DISCHARGE_WAIT);
break;
case PSU_FAST_DISCHARGE_WAIT: {
PowerSetpoint_t fd_sp;
if (!CONN.EnableOutput || !PSU0.ready || PSU0.cont_fault || PSU0.psu_fault) {
log_printf(LOG_WARN, "Fast discharge abort -> stop\n");
PowerSetpoint_OnFastDischargeAbort();
PSU_SwitchState(PSU_CURRENT_DROP);
break;
}
/* Как PSU_WAIT_ACK_OFF: 0–20 V на телеметрии = модуль выключен */
if (!PSU0.PSU_enabled) {
PowerSetpoint_GetFastDischargeTarget(&fd_sp);
log_printf(LOG_INFO, "Fast discharge off ack, V=%u -> set %u\n",
(unsigned)CONN.MeasuredVoltage, (unsigned)fd_sp.voltage_V);
PSU_SwitchState(PSU_FAST_DISCHARGE_SET);
} else if (PSU_StateTime() > PSD_OFF_TIMEOUT_MS) {
log_printf(LOG_WARN, "Fast discharge off timeout, V=%u\n",
(unsigned)CONN.MeasuredVoltage);
PSU_FastDischarge_AbortToConnected();
}
break;
}
case PSU_FAST_DISCHARGE_SET: {
PowerSetpoint_t fd_sp;
if (!CONN.EnableOutput || !PSU0.ready || PSU0.cont_fault || PSU0.psu_fault) {
log_printf(LOG_WARN, "Fast discharge abort -> stop\n");
PowerSetpoint_OnFastDischargeAbort();
PSU_SwitchState(PSU_CURRENT_DROP);
break;
}
PowerSetpoint_GetFastDischargeTarget(&fd_sp);
PSU_SetVoltageCurrent(0, fd_sp.voltage_V, fd_sp.current_0p1A);
PSU_SwitchState(PSU_FAST_DISCHARGE_ON);
break;
}
case PSU_FAST_DISCHARGE_ON:
if (!CONN.EnableOutput || !PSU0.ready || PSU0.cont_fault || PSU0.psu_fault) {
log_printf(LOG_WARN, "Fast discharge abort -> stop\n");
PowerSetpoint_OnFastDischargeAbort();
PSU_SwitchState(PSU_CURRENT_DROP);
break;
}
PSU_Enable(0, 1);
fd_on_last_cmd_ms = HAL_GetTick();
PSU_SwitchState(PSU_FAST_DISCHARGE_ON_WAIT);
break;
case PSU_FAST_DISCHARGE_ON_WAIT: {
PowerSetpoint_t fd_sp;
uint16_t v_low;
if (!CONN.EnableOutput || !PSU0.ready || PSU0.cont_fault || PSU0.psu_fault) {
log_printf(LOG_WARN, "Fast discharge abort -> stop\n");
PowerSetpoint_OnFastDischargeAbort();
PSU_SwitchState(PSU_CURRENT_DROP);
break;
}
PowerSetpoint_GetFastDischargeTarget(&fd_sp);
v_low = (fd_sp.voltage_V > PSD_PRECHARGE_TOLERANCE_V) ?
(uint16_t)(fd_sp.voltage_V - PSD_PRECHARGE_TOLERANCE_V) : 0u;
if (PSU0.PSU_enabled && CONN.MeasuredVoltage >= v_low) {
PowerSetpoint_OnFastDischargeComplete();
PSU_SwitchState(PSU_CONNECTED);
} else {
if ((HAL_GetTick() - fd_on_last_cmd_ms) >= PSD_ON_RETRY_MS) {
PSU_FastDischarge_PushTarget();
fd_on_last_cmd_ms = HAL_GetTick();
}
if (PSU_StateTime() > PSD_ON_TIMEOUT_MS) {
log_printf(LOG_WARN, "Fast discharge on timeout, V=%u (target %u)\n",
(unsigned)CONN.MeasuredVoltage, (unsigned)fd_sp.voltage_V);
PSU_FastDischarge_AbortToConnected();
}
}
break;
}
case PSU_CURRENT_DROP:
// снижаем ток до нуля перед отключением DC
CONN.RequestedCurrent = 0;
// если ток действительно упал или вышло время, отключаем DC
if((CONN.MeasuredCurrent < 30) || (PSU_StateTime() > 5000)){
log_printf(LOG_INFO,
"DC contactor open cmd (CUR_DROP->CONT_OFF, I=%u, waited=%lums)\n",
(unsigned)CONN.MeasuredCurrent,
(unsigned long)PSU_StateTime());
PSU_SwitchState(PSU_CONT_WAIT_ACK_OFF);
}
break;
case PSU_CONT_WAIT_ACK_OFF:
RELAY_Write(RELAY_DC, 0);
if(!PSU0.CONT_enabled){
log_printf(LOG_INFO,
"DC contactor opened (fb=0, t=%lums)\n",
(unsigned long)PSU_StateTime());
PSU_Enable(0, 0);
PSU_SwitchState(PSU_WAIT_ACK_OFF);
}else if(PSU_StateTime() > 1000){
PSU0.cont_fault = 1;
CONN.chargingError = CONN_ERR_CONTACTOR;
PSU_Enable(0, 0);
PSU_SwitchState(PSU_WAIT_ACK_OFF);
log_printf(LOG_ERR,
"DC contactor open timeout (relay=%u fb=%u, t=%lums)\n",
(unsigned)RELAY_Read(RELAY_DC),
(unsigned)PSU0.CONT_enabled,
(unsigned long)PSU_StateTime());
}
break;
case PSU_WAIT_ACK_OFF:
if(!PSU0.PSU_enabled){
PSU_SwitchState(PSU_OFF_PAUSE);
}else if(PSU_StateTime() > 10000){
PSU0.psu_fault = 1;
CONN.chargingError = CONN_ERR_PSU_FAULT;
PSU_SwitchState(PSU_UNREADY);
log_printf(LOG_ERR, "PSU off timeout\n");
}
break;
case PSU_OFF_PAUSE:
if(PSU_StateTime() > 4000){
PSU_SwitchState(PSU_READY);
}
break;
default:
PSU_SwitchState(PSU_UNREADY);
break;
}
PSU_LogPeriodicDiag();
}