Add PSU fast discharge API for DC precharge step-down (v1.0.23).

Policy/executor hooks, event-driven OFF/ON state machine, and release binaries.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
raduet
2026-06-30 16:28:25 +02:00
co-authored by Cursor
parent ecf8e3d2f0
commit a583e12132
13 changed files with 7916 additions and 7128 deletions
+196
View File
@@ -0,0 +1,196 @@
#include "power_setpoint_executor.h"
#include "charger_config.h"
#include "charger_control.h"
#include "debug.h"
#include "psu_control.h"
#include "serial.h"
static PowerSetpoint_t applied;
static PowerSetpoint_t pending;
static PowerSetpoint_t fd_target;
static uint8_t setpoint_dirty;
static uint8_t fd_active;
static uint8_t power_delivery_latched;
static uint8_t fast_discharge_count;
static uint32_t last_fast_discharge_ms;
static uint32_t measured_high_current_since_ms;
static uint8_t session_ended_state(CONN_State_t state)
{
return (state == Unknown) || (state == Unplugged) || (state == Finished) ||
(state == FinishedEV) || (state == FinishedEVSE);
}
void PowerSetpoint_Init(void)
{
applied.voltage_V = PSU_MIN_VOLTAGE;
applied.current_0p1A = 0u;
pending = applied;
fd_target = applied;
setpoint_dirty = 0u;
fd_active = 0u;
power_delivery_latched = 0u;
fast_discharge_count = 0u;
last_fast_discharge_ms = 0u;
measured_high_current_since_ms = 0u;
}
void PowerSetpoint_OnCommand(uint16_t voltage_V, uint16_t current_0p1A)
{
#if PSD_ENABLE
pending.voltage_V = voltage_V;
pending.current_0p1A = current_0p1A;
setpoint_dirty = 1u;
#else
(void)voltage_V;
(void)current_0p1A;
#endif
}
void PowerSetpoint_UpdateDeliveryLatch(void)
{
#if PSD_ENABLE
uint32_t now = HAL_GetTick();
if (!CONN.EnableOutput || !CONN.EvConnected || session_ended_state(CCS_EvseState)) {
power_delivery_latched = 0u;
fast_discharge_count = 0u;
last_fast_discharge_ms = 0u;
measured_high_current_since_ms = 0u;
return;
}
if (CONN.WantedCurrent > PSD_MAX_CMD_CURRENT_0P1A) {
power_delivery_latched = 1u;
}
if (CCS_EvseState == Charging) {
power_delivery_latched = 1u;
}
if (CONN.MeasuredCurrent >= PSD_MAX_MEASURED_CURRENT_0P1A) {
if (measured_high_current_since_ms == 0u) {
measured_high_current_since_ms = now;
} else if ((now - measured_high_current_since_ms) >= PSD_MEASURED_LATCH_MS) {
power_delivery_latched = 1u;
}
} else {
measured_high_current_since_ms = 0u;
}
#endif
}
uint8_t PowerSetpoint_HasPending(void)
{
#if PSD_ENABLE
return setpoint_dirty;
#else
return 0u;
#endif
}
uint8_t PowerSetpoint_IsBusy(void)
{
#if PSD_ENABLE
if (!fd_active) {
return 0u;
}
switch (PSU0.state) {
case PSU_FAST_DISCHARGE_OFF:
case PSU_FAST_DISCHARGE_WAIT:
case PSU_FAST_DISCHARGE_SET:
case PSU_FAST_DISCHARGE_ON:
case PSU_FAST_DISCHARGE_ON_WAIT:
return 1u;
default:
return 0u;
}
#else
return 0u;
#endif
}
static PowerPathContext_t build_context(void)
{
PowerPathContext_t ctx;
ctx.dc_contactor_closed = PSU0.CONT_enabled;
ctx.psu_hv_enabled = PSU0.PSU_enabled;
ctx.measured_voltage_V = CONN.MeasuredVoltage;
ctx.measured_current_0p1A = (uint16_t)CONN.MeasuredCurrent;
ctx.power_delivery_active = power_delivery_latched;
ctx.enable_output = CONN.EnableOutput;
ctx.psu_state_connected = (PSU0.state == PSU_CONNECTED) ? 1u : 0u;
ctx.psu_cont_fault = PSU0.cont_fault;
ctx.psu_fault = PSU0.psu_fault;
ctx.fast_discharge_count = fast_discharge_count;
ctx.last_fast_discharge_ms = last_fast_discharge_ms;
ctx.now_ms = HAL_GetTick();
return ctx;
}
PowerSetpointTryResult_t PowerSetpoint_TryApply(void)
{
#if PSD_ENABLE
PowerPathContext_t ctx;
PowerSetpointAction_t action;
if (!setpoint_dirty || fd_active) {
return PSD_TRY_IDLE;
}
ctx = build_context();
action = PowerSetpoint_Decide(&applied, &pending, &ctx);
if (action == POWER_SETPOINT_FAST_DISCHARGE) {
fd_target = pending;
fd_active = 1u;
setpoint_dirty = 0u;
log_printf(LOG_INFO, "Fast discharge trigger %u->%u I=%u\n",
(unsigned)applied.voltage_V,
(unsigned)fd_target.voltage_V,
(unsigned)fd_target.current_0p1A);
return PSD_TRY_START_FAST_DISCHARGE;
}
if (!PSU0.ready) {
return PSD_TRY_IDLE;
}
PSU_SetVoltageCurrent(0, pending.voltage_V, pending.current_0p1A);
applied = pending;
setpoint_dirty = 0u;
return PSD_TRY_APPLIED_NORMAL;
#else
return PSD_TRY_IDLE;
#endif
}
void PowerSetpoint_GetFastDischargeTarget(PowerSetpoint_t *target)
{
if (target != 0) {
*target = fd_target;
}
}
void PowerSetpoint_OnFastDischargeComplete(void)
{
#if PSD_ENABLE
applied = fd_target;
fd_active = 0u;
fast_discharge_count++;
last_fast_discharge_ms = HAL_GetTick();
log_printf(LOG_INFO, "Fast discharge complete, V=%u\n", (unsigned)CONN.MeasuredVoltage);
#endif
}
void PowerSetpoint_OnFastDischargeAbort(void)
{
#if PSD_ENABLE
fd_active = 0u;
pending = fd_target;
setpoint_dirty = 1u;
log_printf(LOG_WARN, "Fast discharge aborted, V=%u\n", (unsigned)CONN.MeasuredVoltage);
#endif
}
+77
View File
@@ -0,0 +1,77 @@
#include "power_setpoint_policy.h"
PowerSetpointAction_t PowerSetpoint_Decide(
const PowerSetpoint_t *applied,
const PowerSetpoint_t *next,
const PowerPathContext_t *ctx)
{
#if !PSD_ENABLE
(void)applied;
(void)next;
(void)ctx;
return POWER_SETPOINT_NORMAL;
#else
uint16_t delta_v;
if (applied == 0 || next == 0 || ctx == 0) {
return POWER_SETPOINT_NORMAL;
}
if (next->voltage_V >= applied->voltage_V) {
return POWER_SETPOINT_NORMAL;
}
delta_v = (uint16_t)(applied->voltage_V - next->voltage_V);
if (delta_v < PSD_MIN_VOLTAGE_STEP_V) {
return POWER_SETPOINT_NORMAL;
}
if (applied->current_0p1A > PSD_MAX_CMD_CURRENT_0P1A ||
next->current_0p1A > PSD_MAX_CMD_CURRENT_0P1A) {
return POWER_SETPOINT_NORMAL;
}
if (ctx->measured_current_0p1A >= PSD_MAX_MEASURED_CURRENT_0P1A) {
return POWER_SETPOINT_NORMAL;
}
if (ctx->measured_voltage_V <= (uint16_t)(next->voltage_V + PSD_DISCHARGE_MARGIN_V)) {
return POWER_SETPOINT_NORMAL;
}
if (ctx->power_delivery_active) {
return POWER_SETPOINT_NORMAL;
}
if (!ctx->enable_output) {
return POWER_SETPOINT_NORMAL;
}
if (!ctx->psu_state_connected) {
return POWER_SETPOINT_NORMAL;
}
if (!ctx->dc_contactor_closed) {
return POWER_SETPOINT_NORMAL;
}
if (!ctx->psu_hv_enabled) {
return POWER_SETPOINT_NORMAL;
}
if (ctx->psu_cont_fault || ctx->psu_fault) {
return POWER_SETPOINT_NORMAL;
}
if (ctx->fast_discharge_count >= PSD_MAX_PER_SESSION) {
return POWER_SETPOINT_NORMAL;
}
if (ctx->last_fast_discharge_ms != 0u &&
(ctx->now_ms - ctx->last_fast_discharge_ms) < PSD_DEBOUNCE_MS) {
return POWER_SETPOINT_NORMAL;
}
return POWER_SETPOINT_FAST_DISCHARGE;
#endif
}
+148 -9
View File
@@ -6,6 +6,7 @@
#include "charger_control.h"
#include "debug.h"
#include "isr_opt.h"
#include "power_setpoint_executor.h"
#include <stdio.h>
#include <string.h>
@@ -41,6 +42,24 @@ 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;
@@ -154,6 +173,7 @@ void PSU_Init(){
PSU0.hv_tick = 0;
PSU_Enable(0, 0);
PowerSetpoint_Init();
}
void PSU_Enable(uint8_t addr, uint8_t enable){
@@ -258,20 +278,34 @@ void PSU_ReadWrite(){
}
CONN.RequestedPower = CONN.RequestedCurrent * CONN.RequestedVoltage / 10;
if(PSU0.ready){
if (CONN.RequestedVoltage == FAKE_EVREQ_VOLTAGE_V) {
PSU_SetVoltageCurrent(0, (uint16_t)FAKE_PSU_VOLTAGE_V, (uint16_t)FAKE_PSU_CURRENT_0P1A);
}else{
PSU_SetVoltageCurrent(0, CONN.RequestedVoltage, CONN.RequestedCurrent); // Normal mode
#if PSD_ENABLE
{
uint8_t block_setpoint = PowerSetpoint_HasPending() || PowerSetpoint_IsBusy();
if (PSU0.ready && !block_setpoint) {
if (CONN.RequestedVoltage == FAKE_EVREQ_VOLTAGE_V) {
PSU_SetVoltageCurrent(0, (uint16_t)FAKE_PSU_VOLTAGE_V, (uint16_t)FAKE_PSU_CURRENT_0P1A);
} else {
PSU_SetVoltageCurrent(0, CONN.RequestedVoltage, CONN.RequestedCurrent); // Normal mode
}
ED_Delay(CAN_DELAY);
}
}
#else
if (PSU0.ready) {
PSU_SetVoltageCurrent(0, CONN.RequestedVoltage, CONN.RequestedCurrent); // Normal mode
ED_Delay(CAN_DELAY);
if(CONN.MeasuredVoltage > 490){
if(PSU0.hv_tick == 0){
}
#endif
if (PSU0.ready) {
if (CONN.MeasuredVoltage > 490) {
if (PSU0.hv_tick == 0) {
PSU0.hv_tick = HAL_GetTick();
}else if((HAL_GetTick() - PSU0.hv_tick) >= 10000){
} else if ((HAL_GetTick() - PSU0.hv_tick) >= 10000) {
PSU0.hv_mode = 1;
}
}else{
} else {
PSU0.hv_tick = 0;
}
}
@@ -284,6 +318,18 @@ void PSU_ReadWrite(){
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;
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_ONLINE_TIMEOUT){
@@ -403,6 +449,99 @@ void PSU_Task(void){
}
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: 020 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;
+10 -4
View File
@@ -7,6 +7,7 @@
#include "debug.h"
#include "isr_opt.h"
#include "psu_control.h"
#include "power_setpoint_executor.h"
#include "serial_control.h"
#include <stdint.h>
#include <string.h>
@@ -533,15 +534,20 @@ ISR_FAST static void apply_command(uint8_t cmd, const uint8_t* payload, uint16_t
}
case CMD_E2M_SET_OUTPUT_VOLTAGE: {
const e2m_set_output_t* p = (const e2m_set_output_t*)payload;
uint16_t psu_voltage_V;
uint16_t psu_current_0p1A;
if (p->voltage_V == FAKE_EVREQ_VOLTAGE_V) {
fake_500_voltage_mode = 1u;
CONN.RequestedVoltage = FAKE_PSU_VOLTAGE_V;
CONN.WantedCurrent = FAKE_PSU_CURRENT_0P1A;
psu_voltage_V = FAKE_PSU_VOLTAGE_V;
psu_current_0p1A = FAKE_PSU_CURRENT_0P1A;
} else {
fake_500_voltage_mode = 0u;
CONN.RequestedVoltage = p->voltage_V;
CONN.WantedCurrent = p->current_0p1A;
psu_voltage_V = p->voltage_V;
psu_current_0p1A = p->current_0p1A;
}
CONN.RequestedVoltage = psu_voltage_V;
CONN.WantedCurrent = psu_current_0p1A;
PowerSetpoint_OnCommand(psu_voltage_V, psu_current_0p1A);
break;
}
case CMD_E2M_ISOLATION_CONTROL: {