forked from achamaikin/CCSModuleSW30Web
448 lines
14 KiB
C
448 lines
14 KiB
C
#include "serial.h"
|
|
#include "cp.h"
|
|
#include "connector.h"
|
|
#include "board.h"
|
|
#include "debug.h"
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include "charger_config.h"
|
|
#include "psu_control.h"
|
|
|
|
extern UART_HandleTypeDef huart3;
|
|
|
|
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_MS 2000
|
|
|
|
static uint8_t rx_buffer[MAX_RX_BUFFER_SIZE];
|
|
static uint8_t tx_buffer[MAX_TX_BUFFER_SIZE];
|
|
static uint8_t rx_armed = 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 everest_timed_out = 0;
|
|
static uint32_t last_everest_timeout_log_tick = 0;
|
|
static CP_State_t cp_state_buffer = EV_STATE_ACQUIRING;
|
|
|
|
CCS_State_t CCS_State;
|
|
CCS_EvInfo_t CCS_EvInfo;
|
|
CONN_State_t CCS_EvseState;
|
|
CCS_ConnectorState_t CCS_ConnectorState = CCS_UNPLUGGED;
|
|
|
|
static uint8_t process_received_packet(const uint8_t* packet, uint16_t packet_len);
|
|
|
|
void CCS_RxEventCallback(UART_HandleTypeDef *huart, uint16_t size) {
|
|
if (huart != &huart3) {
|
|
return;
|
|
}
|
|
rx_armed = 0;
|
|
if (size > 0 && size <= sizeof(rx_buffer)) {
|
|
process_received_packet(rx_buffer, size);
|
|
}
|
|
}
|
|
|
|
void CCS_SerialLoop(void) {
|
|
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;
|
|
|
|
if (!rx_armed && HAL_UART_GetState(&huart3) == HAL_UART_STATE_READY) {
|
|
if (HAL_UARTEx_ReceiveToIdle_IT(&huart3, rx_buffer, sizeof(rx_buffer)) == HAL_OK) {
|
|
rx_armed = 1;
|
|
}
|
|
}
|
|
|
|
/* Read CP once per loop and use buffered value below. */
|
|
cp_state_buffer = CP_GetState();
|
|
|
|
if (CONN.connControl != CMD_NONE) {
|
|
last_cmd = CONN.connControl;
|
|
}
|
|
|
|
if((HAL_GetTick() - last_cmd_sent) > CMD_INTERVAL){
|
|
if ((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.chargingError != CONN_NO_ERROR)) &&
|
|
((HAL_GetTick() - last_stop_sent) > 1000)) {
|
|
last_stop_sent = HAL_GetTick();
|
|
log_printf(LOG_WARN, "Stopping charging...\n");
|
|
CCS_SendEmergencyStop();
|
|
}
|
|
|
|
if (((CCS_EvseState == FinishedEV) || (CCS_EvseState == FinishedEVSE)) &&
|
|
((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;
|
|
|
|
switch(CCS_ConnectorState){
|
|
case CCS_DISABLED:
|
|
RELAY_Write(RELAY_CP, 0);
|
|
CONN_SetState(Disabled);
|
|
if (CONN.chargingError == CONN_NO_ERROR){
|
|
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) {
|
|
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((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;
|
|
}
|
|
|
|
// If Everest timeout happened, keep safe-state and limit log frequency.
|
|
// The safe-state must remain until we receive a valid packet from the host.
|
|
if (everest_timed_out) {
|
|
if (last_everest_timeout_log_tick == 0 ||
|
|
(HAL_GetTick() - last_everest_timeout_log_tick) >= EVEREST_TIMEOUT_MS) {
|
|
log_printf(LOG_ERR, "Everest timeout\n");
|
|
last_everest_timeout_log_tick = HAL_GetTick();
|
|
}
|
|
CONN.EnableOutput = 0;
|
|
CCS_EvseState = Unknown;
|
|
CP_SetDuty(100);
|
|
} else if (last_host_seen > 0 && (HAL_GetTick() - last_host_seen) > EVEREST_TIMEOUT_MS) {
|
|
log_printf(LOG_ERR, "Everest timeout\n");
|
|
everest_timed_out = 1;
|
|
last_host_seen = HAL_GetTick(); // reset after the first timeout
|
|
last_everest_timeout_log_tick = HAL_GetTick();
|
|
|
|
CONN.EnableOutput = 0;
|
|
CCS_EvseState = Unknown;
|
|
CP_SetDuty(100);
|
|
} 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
|
|
CCS_SendResetReason();
|
|
log_printf(LOG_INFO, "CCS init\n");
|
|
}
|
|
|
|
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) {
|
|
HAL_UART_Transmit(&huart3, tx_buffer, len, 1000);
|
|
}
|
|
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;
|
|
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));
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
last_everest_timeout_log_tick = 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;
|
|
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;
|
|
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:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static uint8_t process_received_packet(const uint8_t* packet, uint16_t packet_len) {
|
|
if (packet_len < 3) return 0;
|
|
|
|
uint8_t cmd = packet[0];
|
|
uint16_t payload_len = (uint16_t)(packet_len - 3);
|
|
|
|
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)(1 + payload_len));
|
|
if (received_crc != calculated_crc) {
|
|
log_printf(LOG_ERR, "Packet CRC error\n");
|
|
return 0;
|
|
}
|
|
|
|
uint16_t expected_len = expected_payload_len(cmd);
|
|
if (expected_len == 0xFFFF) {
|
|
log_printf(LOG_WARN, "Unknown cmd 0x%02x\n", cmd);
|
|
return 0;
|
|
}
|
|
if (expected_len != payload_len) {
|
|
log_printf(LOG_ERR, "Packet len mismatch cmd=0x%02x\n", cmd);
|
|
return 0;
|
|
}
|
|
|
|
if (payload_len > 0) {
|
|
apply_command(cmd, &packet[1], payload_len);
|
|
} else {
|
|
apply_command(cmd, NULL, 0);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|