forked from achamaikin/CCSModuleSW30Web
1566 lines
45 KiB
C
Executable File
1566 lines
45 KiB
C
Executable File
#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, ¤t_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();
|
||
}
|
||
|
||
|