#include "serial.h" #include "cp.h" #include "connector.h" #include "board.h" #include "debug.h" #include "isr_opt.h" #include #include #include "charger_config.h" #include "psu_control.h" #include "serial_control.h" extern UART_HandleTypeDef huart3; extern uint8_t config_initialized; static void send_state(void); static void CCS_SendResetReason(void); CCS_MaxLoad_t CCS_MaxLoad; uint32_t CCS_Power = 0; uint32_t CCS_EnergyWs = 0; uint32_t CCS_Energy = 0; uint32_t last_cmd_sent = 0; uint32_t last_stop_sent = 0; CONN_Control_t last_cmd = CMD_NONE; uint8_t ev_enable_output = 0; #define CMD_INTERVAL 10 #define MAX_TX_BUFFER_SIZE 256 #define MAX_RX_BUFFER_SIZE 256 #define EVEREST_TIMEOUT_WARN_MS 5000u #define EVEREST_TIMEOUT_STOP_MS 10000u #define UART3_REINIT_TIMEOUT_MS 1500u /* Everest requests 500 V -> PSU really gets 300 V / 1 A, state still reports 500 V. */ #define FAKE_EVREQ_VOLTAGE_V 500u #define FAKE_PSU_VOLTAGE_V 300u #define FAKE_PSU_CURRENT_0P1A 10u static uint8_t rx_buffer[MAX_RX_BUFFER_SIZE]; static uint8_t tx_buffer[MAX_TX_BUFFER_SIZE]; static uint8_t tx_pending_buffer[MAX_TX_BUFFER_SIZE]; static uint16_t tx_pending_len = 0; static uint8_t uart3_tx_busy = 0; uint8_t ESTOP = 0; uint8_t REPLUG = 0; static uint8_t enabled = 0; static uint8_t pwm_duty_percent = 100; uint8_t isolation_enable = 0; static uint32_t last_host_seen = 0; static uint8_t fake_500_voltage_mode = 0; static uint8_t everest_timed_out = 0; static uint8_t everest_timeout_warn_latched = 0; static uint8_t everest_timeout_stop_latched = 0; static uint32_t uart3_last_packet_tick = 0; static uint32_t uart3_last_reinit_tick = 0; CCS_State_t CCS_State; CCS_EvInfo_t CCS_EvInfo; CONN_State_t CCS_EvseState; CCS_ConnectorState_t CCS_ConnectorState = CCS_UNKNOWN; ISR_FAST static uint8_t process_received_packet(const uint8_t* packet, uint16_t packet_len); static void CCS_UART3_Watchdog(void); static void CCS_LogUart3Error(const char *tag); ISR_FAST static void uart3_log_hal_error(uint8_t uart_num, uint32_t err) { if (err == HAL_UART_ERROR_NONE) { log_printf(LOG_ERR, "UART%u HAL error decode: NONE\n", uart_num); return; } log_printf(LOG_ERR, "UART%u HAL error decode: %s%s%s%s%s%s raw=0x%08lx\n", uart_num, (err & HAL_UART_ERROR_PE) ? "PE " : "", (err & HAL_UART_ERROR_NE) ? "NE " : "", (err & HAL_UART_ERROR_FE) ? "FE " : "", (err & HAL_UART_ERROR_ORE) ? "ORE " : "", (err & HAL_UART_ERROR_DMA) ? "DMA " : "", #ifdef HAL_UART_ERROR_INVALID_CALLBACK (err & HAL_UART_ERROR_INVALID_CALLBACK) ? "INV_CB " : "", #else "", #endif (unsigned long)err); } ISR_FAST static void uart3_arm_rx_or_log(const char *where) { HAL_StatusTypeDef st = HAL_UARTEx_ReceiveToIdle_DMA(&huart3, rx_buffer, sizeof(rx_buffer)); if (st == HAL_OK) { return; } uint32_t err_after = HAL_UART_GetError(&huart3); log_printf(LOG_ERR, "UART3 RX arm failed (%s): HAL_Status=%d err_after=0x%08lx\n", where, (int)st, (unsigned long)err_after); uart3_log_hal_error(3u, err_after); CCS_LogUart3Error("UART3 RX arm failed details"); if (err_after != HAL_UART_ERROR_NONE) { (void)HAL_UART_AbortReceive(&huart3); } } ISR_FAST void CCS_RxEventCallback(UART_HandleTypeDef *huart, uint16_t size) { if (huart != &huart3) { log_printf(LOG_WARN, "UART3 RX drop: wrong huart in RxEventCallback (size=%u)\n", (unsigned)size); return; } if (size == 0u) { log_printf(LOG_WARN, "UART3 RX drop: RxEvent size=0 (idle, no payload)\n"); uart3_arm_rx_or_log("RxEventCallback"); return; } if (size > sizeof(rx_buffer)) { log_printf(LOG_ERR, "UART3 RX drop: size=%u > rx_buffer %u (overflow, not parsed)\n", (unsigned)size, (unsigned)sizeof(rx_buffer)); uart3_arm_rx_or_log("RxEventCallback"); return; } uart3_last_packet_tick = HAL_GetTick(); uart3_last_reinit_tick = uart3_last_packet_tick; process_received_packet(rx_buffer, size); uart3_arm_rx_or_log("RxEventCallback"); } ISR_FAST void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart) { uint32_t error = HAL_UART_GetError(huart); uint8_t uart_num = (huart == &huart2) ? 2 : (huart == &huart3) ? 3 : (huart == &huart5) ? 5 : 0; log_printf(LOG_ERR, "UART%u HAL error (ISR): raw=0x%08lx — RX may be corrupted until re-arm\n", uart_num, (unsigned long)error); uart3_log_hal_error(uart_num, error); (void)HAL_UART_AbortReceive(huart); (void)HAL_UART_AbortTransmit(huart); if (huart == &huart3) { uart3_tx_busy = 0; uart3_arm_rx_or_log("ErrorCallback"); } else { SC_RecoverUartDma(huart); } } void CCS_TxCpltCallback(UART_HandleTypeDef *huart) { if (huart != &huart3) { return; } uart3_tx_busy = 0; if (tx_pending_len > 0) { memcpy(tx_buffer, tx_pending_buffer, tx_pending_len); uart3_tx_busy = 1; if (HAL_UART_Transmit_DMA(&huart3, tx_buffer, tx_pending_len) != HAL_OK) { uart3_tx_busy = 0; CCS_LogUart3Error("UART3 TX DMA resend failed"); } tx_pending_len = 0; } } void CCS_SerialLoop(void) { static uint32_t tick; if ((int32_t)(HAL_GetTick() - tick) < 1) return; tick = HAL_GetTick(); static uint32_t replug_tick = 0; static uint32_t replug_watchdog_tick = 0; static uint32_t replug_watchdog1_tick = 0; static uint32_t last_state_sent = 0; static uint32_t force_unlock_tick = 0; static uint32_t stop_tick = 0; CCS_UART3_Watchdog(); if (CONN.connControl != CMD_NONE) { last_cmd = CONN.connControl; } if (CONN.connControl == CMD_FORCE_UNLOCK) { if (force_unlock_tick == 0) { force_unlock_tick = HAL_GetTick(); } else if ((int32_t)(HAL_GetTick() - force_unlock_tick) >= 10000) { CONN.connControl = CMD_NONE; force_unlock_tick = 0; } } else { force_unlock_tick = 0; } if (CONN.connControl == CMD_STOP) { if (stop_tick == 0) { stop_tick = HAL_GetTick(); } else if ((int32_t)(HAL_GetTick() - stop_tick) >= 1000) { CONN.connControl = CMD_NONE; stop_tick = 0; } } else { stop_tick = 0; } if((int32_t)(HAL_GetTick() - last_cmd_sent) > (int32_t)CMD_INTERVAL){ if ((int32_t)(HAL_GetTick() - last_state_sent) >= 200) { send_state(); last_state_sent = HAL_GetTick(); } if (ESTOP) { log_printf(LOG_ERR, "ESTOP triggered\n"); CCS_SendEmergencyStop(); ESTOP = 0; } if (((CONN.connControl == CMD_STOP) || (CONN.connControl == CMD_FORCE_UNLOCK) || (CONN.chargingError != CONN_NO_ERROR)) && ((int32_t)(HAL_GetTick() - last_stop_sent) > 1000)) { last_stop_sent = HAL_GetTick(); log_printf(LOG_WARN, "Stopping charging...\n"); if (CONN.connControl == CMD_FORCE_UNLOCK) { CP_SetDuty(100); } CCS_SendEmergencyStop(); } if (((CCS_EvseState == FinishedEV) || (CCS_EvseState == FinishedEVSE)) && ((int32_t)(HAL_GetTick() - last_stop_sent) > 1000)) { last_stop_sent = HAL_GetTick(); log_printf(LOG_WARN, "FinishedEV, stopping...\n"); CCS_SendEmergencyStop(); } } (void)replug_watchdog_tick; (void)replug_watchdog1_tick; uint8_t host_timeout_warn = (last_host_seen > 0u) && ((int32_t)(HAL_GetTick() - last_host_seen) > (int32_t)EVEREST_TIMEOUT_WARN_MS); uint8_t host_timeout_stop = (last_host_seen > 0u) && ((int32_t)(HAL_GetTick() - last_host_seen) > (int32_t)EVEREST_TIMEOUT_STOP_MS); uint8_t host_timed_out = host_timeout_stop; if (host_timeout_warn && !everest_timeout_warn_latched) { log_printf(LOG_ERR, "Everest timeout\n"); everest_timeout_warn_latched = 1; } if (host_timeout_stop && !everest_timeout_stop_latched) { log_printf(LOG_ERR, "Everest timeout, stopping charging...\n"); everest_timeout_stop_latched = 1; } if (!host_timeout_warn) { everest_timeout_warn_latched = 0; everest_timeout_stop_latched = 0; } everest_timed_out = host_timeout_stop; switch(CCS_ConnectorState){ case CCS_UNKNOWN: RELAY_Write(RELAY_CP, 0); CONN_SetState(Unknown); if (config_initialized && !host_timed_out) { CCS_ConnectorState = CCS_UNPLUGGED; } break; case CCS_DISABLED: RELAY_Write(RELAY_CP, 0); CONN_SetState(Disabled); if ((CONN.chargingError == CONN_NO_ERROR) && !host_timed_out){ CCS_ConnectorState = CCS_UNPLUGGED; } break; case CCS_UNPLUGGED: RELAY_Write(RELAY_CP, 1); CONN_SetState(Unplugged); if ((cp_state_buffer == EV_STATE_B_CONN_PREP) || (cp_state_buffer == EV_STATE_C_CONN_ACTIVE)){ CCS_ConnectorState = CCS_AUTH_REQUIRED; } if (CONN.chargingError != CONN_NO_ERROR){ log_printf(LOG_ERR, "Charging error %d, state -> disabled\n", CONN.chargingError); CCS_ConnectorState = CCS_DISABLED; } break; case CCS_AUTH_REQUIRED: RELAY_Write(RELAY_CP, 1); CONN_SetState(AuthRequired); if(CONN.connControl == CMD_START){ log_printf(LOG_INFO, "Charging permitted, start charging\n"); CCS_ConnectorState = CCS_CONNECTED; } if (cp_state_buffer == EV_STATE_A_IDLE){ log_printf(LOG_INFO, "Car unplugged\n"); CCS_ConnectorState = CCS_UNPLUGGED; } break; case CCS_CONNECTED: RELAY_Write(RELAY_CP, 1); if((CCS_EvseState < Preparing) || (CCS_EvseState == AuthRequired)) { CONN_SetState(Preparing); } else { CONN_SetState(CCS_EvseState); } if (cp_state_buffer == EV_STATE_A_IDLE){ log_printf(LOG_INFO, "Car unplugged\n"); CCS_ConnectorState = CCS_UNPLUGGED; } if(REPLUG > 0){ log_printf(LOG_INFO, "Replugging...\n"); CCS_ConnectorState = CCS_REPLUGGING; } break; case CCS_REPLUGGING: RELAY_Write(RELAY_CP, 0); CONN_SetState(Replugging); if((int32_t)(HAL_GetTick() - replug_tick) > 1000){ replug_tick = HAL_GetTick(); if(REPLUG > 0){ if (REPLUG != 0xFF) REPLUG--; } else { log_printf(LOG_INFO, "Replugging finished, but car unplugged\n"); CCS_ConnectorState = CCS_UNPLUGGED; } } if(REPLUG == 0){ if(cp_state_buffer == EV_STATE_B_CONN_PREP){ log_printf(LOG_INFO, "Replugging finished, car plugged, state -> auth required\n"); CCS_ConnectorState = CCS_AUTH_REQUIRED; } } break; } // 10s timeout: enforce safe-state until host communication recovers. if (host_timeout_stop) { CONN.EnableOutput = 0; CCS_EvseState = Unknown; CP_SetDuty(100); if (CCS_ConnectorState != CCS_DISABLED && CCS_ConnectorState != CCS_UNKNOWN) { CCS_ConnectorState = CCS_DISABLED; } } else { if (last_cmd == CMD_STOP) { CONN.EnableOutput = 0; } else { CONN.EnableOutput = ev_enable_output ? 1 : 0; if((CONN.EnableOutput == 0) && (CONN.connState == Preparing)){ CONN.EnableOutput = 0; } } } if ((cp_state_buffer == EV_STATE_B_CONN_PREP) || (cp_state_buffer == EV_STATE_C_CONN_ACTIVE) || (cp_state_buffer == EV_STATE_D_CONN_ACT_VENT)) { CONN.EvConnected = 1; } else { CONN.EvConnected = 0; } } void CCS_Init(void){ CP_Init(); CP_SetDuty(100); CCS_MaxLoad.maxVoltage = PSU_MAX_VOLTAGE; // 1000V CCS_MaxLoad.minVoltage = PSU_MIN_VOLTAGE; //150V CCS_MaxLoad.maxCurrent = PSU_MAX_CURRENT*10; //100A CCS_MaxLoad.minCurrent = PSU_MIN_CURRENT*10; //1A CCS_MaxLoad.maxPower = PSU_MAX_POWER; //30000W uart3_last_packet_tick = HAL_GetTick(); uart3_last_reinit_tick = uart3_last_packet_tick; uart3_arm_rx_or_log("Init"); CCS_SendResetReason(); log_printf(LOG_INFO, "CCS init\n"); } ISR_FAST static uint16_t crc16_ibm(const uint8_t* data, uint16_t length) { uint16_t crc = 0xFFFFu; for (uint16_t i = 0; i < length; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 1u) { crc = (crc >> 1) ^ 0xA001u; } else { crc >>= 1; } } } return crc; } static uint16_t CCS_BuildPacket(uint8_t cmd, const void* payload, uint16_t payload_len, uint8_t* out, uint16_t out_max) { uint16_t total_len = (uint16_t)(1u + payload_len + 2u); if (total_len > out_max) return 0; out[0] = cmd; if (payload_len && payload != NULL) { memcpy(&out[1], payload, payload_len); } uint16_t crc = crc16_ibm(out, (uint16_t)(1u + payload_len)); out[1u + payload_len] = (uint8_t)(crc & 0xFFu); out[1u + payload_len + 1u] = (uint8_t)((crc >> 8) & 0xFFu); return total_len; } static void CCS_SendPacket(uint8_t cmd, const void* payload, uint16_t payload_len) { uint16_t len = CCS_BuildPacket(cmd, payload, payload_len, tx_buffer, sizeof(tx_buffer)); if (len > 0) { if (uart3_tx_busy) { memcpy(tx_pending_buffer, tx_buffer, len); tx_pending_len = len; } else { uart3_tx_busy = 1; if (HAL_UART_Transmit_DMA(&huart3, tx_buffer, len) != HAL_OK) { uart3_tx_busy = 0; CCS_LogUart3Error("UART3 TX DMA start failed"); } } } last_cmd_sent = HAL_GetTick(); } static void CCS_SendResetReason(void) { CCS_SendPacket(CMD_M2E_RESET, NULL, 0); } void CCS_SendEmergencyStop(void) { CCS_SendPacket(CMD_M2E_ESTOP, NULL, 0); } void CCS_SendStart(void) { CCS_SendPacket(CMD_M2E_START, NULL, 0); } static void CCS_CalculateEnergy(void) { static uint32_t lastTick = 0; uint32_t currentTick = HAL_GetTick(); uint32_t elapsedTimeMs = currentTick - lastTick; lastTick = currentTick; CCS_Power = CONN.MeasuredVoltage * CONN.MeasuredCurrent / 10; CCS_EnergyWs += (CCS_Power * elapsedTimeMs) / 1000; if(CCS_EvseState == Unplugged) { CCS_EnergyWs = 0; } CCS_Energy = CCS_EnergyWs / 3600; } static void send_state(void) { CCS_CalculateEnergy(); CCS_State.DutyCycle = CP_GetDuty(); CCS_State.OutputEnabled = PSU0.CONT_enabled; CCS_State.MeasuredVoltage = (uint16_t)CONN.MeasuredVoltage; if (fake_500_voltage_mode) { CCS_State.MeasuredVoltage = FAKE_EVREQ_VOLTAGE_V; } CCS_State.MeasuredCurrent = (uint16_t)CONN.MeasuredCurrent; CCS_State.Power = CCS_Power; CCS_State.Energy = CCS_Energy; if(CCS_ConnectorState == CCS_CONNECTED){ CCS_State.CpState = cp_state_buffer; } else { CCS_State.CpState = EV_STATE_A_IDLE; } CCS_State.MaxVoltage = CCS_MaxLoad.maxVoltage; CCS_State.MinVoltage = CCS_MaxLoad.minVoltage; CCS_State.MaxCurrent = CCS_MaxLoad.maxCurrent; CCS_State.MinCurrent = CCS_MaxLoad.minCurrent; CCS_State.MaxPower = CCS_MaxLoad.maxPower; CCS_State.IsolationValid = isolation_enable; CCS_State.IsolationResistance = 900000; CCS_SendPacket(CMD_M2E_STATE, &CCS_State, sizeof(CCS_State)); } ISR_FAST static uint16_t expected_payload_len(uint8_t cmd) { switch (cmd) { case CMD_E2M_PWM_DUTY: return sizeof(e2m_pwm_duty_t); case CMD_E2M_ENABLE_OUTPUT: return sizeof(e2m_enable_output_t); case CMD_E2M_RESET: return sizeof(e2m_reset_t); case CMD_E2M_ENABLE: return sizeof(e2m_enable_t); case CMD_E2M_REPLUG: return sizeof(e2m_replug_t); case CMD_E2M_SET_OUTPUT_VOLTAGE: return sizeof(e2m_set_output_t); case CMD_E2M_ISOLATION_CONTROL: return sizeof(e2m_isolation_control_t); case CMD_E2M_EV_INFO: return sizeof(CCS_EvInfo_t); case CMD_E2M_EVSE_STATE: return sizeof(CONN_State_t); case CMD_E2M_KEEP_ALIVE: return 0; default: return 0xFFFFu; } } ISR_FAST static void apply_command(uint8_t cmd, const uint8_t* payload, uint16_t payload_len) { (void)payload_len; last_host_seen = HAL_GetTick(); everest_timed_out = 0; everest_timeout_warn_latched = 0; everest_timeout_stop_latched = 0; switch (cmd) { case CMD_E2M_PWM_DUTY: { const e2m_pwm_duty_t* p = (const e2m_pwm_duty_t*)payload; uint8_t duty = p->pwm_duty_percent; if (duty > 100) duty = 100; pwm_duty_percent = duty; if (CONN.connControl != CMD_FORCE_UNLOCK) { CP_SetDuty(duty); } break; } case CMD_E2M_ENABLE_OUTPUT: { const e2m_enable_output_t* p = (const e2m_enable_output_t*)payload; ev_enable_output = (p->enable_output != 0); break; } case CMD_E2M_RESET: { const e2m_reset_t* p = (const e2m_reset_t*)payload; if (p->reset) { log_printf(LOG_WARN, "Everest reset command\n"); // CCS_SendResetReason(); // HAL_Delay(10); // NVIC_SystemReset(); } break; } case CMD_E2M_ENABLE: { const e2m_enable_t* p = (const e2m_enable_t*)payload; enabled = (p->enable != 0); (void)enabled; break; } case CMD_E2M_SET_OUTPUT_VOLTAGE: { const e2m_set_output_t* p = (const e2m_set_output_t*)payload; 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; } else { fake_500_voltage_mode = 0u; CONN.RequestedVoltage = p->voltage_V; CONN.WantedCurrent = p->current_0p1A; } break; } case CMD_E2M_ISOLATION_CONTROL: { const e2m_isolation_control_t* p = (const e2m_isolation_control_t*)payload; isolation_enable = p->command; break; } case CMD_E2M_EV_INFO: { memcpy(&CCS_EvInfo, payload, sizeof(CCS_EvInfo_t)); CONN.SOC = (uint8_t)(CCS_EvInfo.soc / 10); break; } case CMD_E2M_EVSE_STATE: { CCS_EvseState = (CONN_State_t)payload[0]; break; } case CMD_E2M_REPLUG: { (void)payload; CP_SetDuty(pwm_duty_percent); break; } case CMD_E2M_KEEP_ALIVE: { last_host_seen = HAL_GetTick(); break; } default: log_printf(LOG_WARN, "UART3 RX warn: cmd 0x%02x CRC/len OK but no switch case (expected_payload vs apply_command)\n", cmd); break; } } ISR_FAST static uint8_t process_received_packet(const uint8_t* packet, uint16_t packet_len) { if (packet_len < 3u) { if (packet_len == 0u) { log_printf(LOG_WARN, "UART3 RX drop: too_short len=0 (empty chunk)\n"); } else if (packet_len == 1u) { log_printf(LOG_WARN, "UART3 RX drop: too_short len=1 b0=0x%02x\n", packet[0]); } else { log_printf(LOG_WARN, "UART3 RX drop: too_short len=2 b0=0x%02x b1=0x%02x\n", packet[0], packet[1]); } return 0; } uint8_t cmd = packet[0]; uint16_t payload_len = (uint16_t)(packet_len - 3u); uint16_t received_crc = (uint16_t)packet[packet_len - 2u] | (uint16_t)packet[packet_len - 1u] << 8; uint16_t calculated_crc = crc16_ibm(packet, (uint16_t)(1u + payload_len)); if (received_crc != calculated_crc) { log_printf(LOG_ERR, "UART3 RX drop: crc_mismatch cmd=0x%02x total_len=%u payload_len=%u " "crc_rx=0x%04x crc_calc=0x%04x\n", cmd, (unsigned)packet_len, (unsigned)payload_len, (unsigned)received_crc, (unsigned)calculated_crc); return 0; } uint16_t expected_len = expected_payload_len(cmd); if (expected_len == 0xFFFFu) { log_printf(LOG_WARN, "UART3 RX drop: unknown_cmd cmd=0x%02x total_len=%u payload_len=%u\n", cmd, (unsigned)packet_len, (unsigned)payload_len); return 0; } if (expected_len != payload_len) { log_printf(LOG_ERR, "UART3 RX drop: len_mismatch cmd=0x%02x expected_payload=%u got_payload=%u " "total_len=%u\n", cmd, (unsigned)expected_len, (unsigned)payload_len, (unsigned)packet_len); return 0; } if (payload_len > 0) { apply_command(cmd, &packet[1], payload_len); } else { apply_command(cmd, NULL, 0); } return 1; } static void CCS_UART3_Watchdog(void) { const int32_t since_last_packet = (int32_t)(HAL_GetTick() - uart3_last_packet_tick); const int32_t since_last_reinit = (int32_t)(HAL_GetTick() - uart3_last_reinit_tick); if ((since_last_packet >= (int32_t)UART3_REINIT_TIMEOUT_MS) && (since_last_reinit >= (int32_t)UART3_REINIT_TIMEOUT_MS) && (huart3.RxState == HAL_UART_STATE_READY)) { uart3_arm_rx_or_log("Watchdog"); CCS_LogUart3Error("UART3 watchdog rearm"); uart3_last_reinit_tick = HAL_GetTick(); } } static void CCS_LogUart3Error(const char *tag) { log_printf(LOG_ERR, "%s: err=0x%08lx g=%lu rx=%lu tx_busy=%u\n", tag, (unsigned long)HAL_UART_GetError(&huart3), (unsigned long)huart3.gState, (unsigned long)huart3.RxState, (unsigned)uart3_tx_busy); }