#include "cp.h" #include "adc.h" #include "board.h" #include "tim.h" #include "debug.h" #include #define MAX_DUTY 450 #define CP_EMA_ALPHA_Q8 38 #define CP_DEBOUNCE_MS_DEFAULT 10 #define CP_DEBOUNCE_MS_F 60 #define CP_DEBOUNCE_MS_F_LOW_DUTY 100 #define CP_LOW_DUTY_THRESHOLD_PERCENT 10 #define CP_A_ENTER_MV 11000 #define CP_A_EXIT_MV 10000 #define CP_B_ENTER_LOW_MV 8000 #define CP_B_ENTER_HIGH_MV 10000 #define CP_B_EXIT_LOW_MV 7500 #define CP_B_EXIT_HIGH_MV 10500 #define CP_C_ENTER_LOW_MV 5000 #define CP_C_ENTER_HIGH_MV 7000 #define CP_C_EXIT_LOW_MV 4500 #define CP_C_EXIT_HIGH_MV 7500 #define CP_D_ENTER_LOW_MV 2000 #define CP_D_ENTER_HIGH_MV 4000 #define CP_D_EXIT_LOW_MV 1500 #define CP_D_EXIT_HIGH_MV 4500 #define CP_E_ENTER_LOW_MV -1000 #define CP_E_ENTER_HIGH_MV 2000 #define CP_E_EXIT_LOW_MV -1500 #define CP_E_EXIT_HIGH_MV 2500 #define CP_F_ENTER_MV -11500 #define CP_F_EXIT_MV -10500 static int32_t cp_voltage_mv = 0; static int32_t cp_voltage_filt_mv = 0; static uint8_t cp_filter_initialized = 0; static uint8_t cp_duty = 0; CP_State_t fake_cp_state = EV_STATE_ACQUIRING; static CP_State_t cp_stable_state = EV_STATE_ACQUIRING; static CP_State_t cp_candidate_state = EV_STATE_ACQUIRING; static uint32_t cp_candidate_since_ms = 0; static uint32_t CP_ReadAdcChannel(uint32_t ch) { uint32_t adc = 0; ADC_Select_Channel(ch); HAL_ADC_Start(&hadc1); HAL_ADC_PollForConversion(&hadc1, 10); adc = HAL_ADC_GetValue(&hadc1); HAL_ADC_Stop(&hadc1); return adc; } #define VREFINT_CAL_ADDR ((uint16_t*)0x1FFFF7BA) // для STM32F1! static uint8_t CP_IsInRange(int32_t v, int32_t lo, int32_t hi) { return (v >= lo && v <= hi) ? 1u : 0u; } static int32_t CP_ApplyEma(int32_t raw_mv) { if (!cp_filter_initialized) { cp_voltage_filt_mv = raw_mv; cp_filter_initialized = 1; return cp_voltage_filt_mv; } cp_voltage_filt_mv += ((raw_mv - cp_voltage_filt_mv) * CP_EMA_ALPHA_Q8) / 256; return cp_voltage_filt_mv; } static CP_State_t CP_ClassifyWithHysteresis(int32_t v, CP_State_t prev) { switch (prev) { case EV_STATE_A_IDLE: if (v >= CP_A_EXIT_MV) return EV_STATE_A_IDLE; break; case EV_STATE_B_CONN_PREP: if (CP_IsInRange(v, CP_B_EXIT_LOW_MV, CP_B_EXIT_HIGH_MV)) return EV_STATE_B_CONN_PREP; break; case EV_STATE_C_CONN_ACTIVE: if (CP_IsInRange(v, CP_C_EXIT_LOW_MV, CP_C_EXIT_HIGH_MV)) return EV_STATE_C_CONN_ACTIVE; break; case EV_STATE_D_CONN_ACT_VENT: if (CP_IsInRange(v, CP_D_EXIT_LOW_MV, CP_D_EXIT_HIGH_MV)) return EV_STATE_D_CONN_ACT_VENT; break; case EV_STATE_E_NO_POWER: if (CP_IsInRange(v, CP_E_EXIT_LOW_MV, CP_E_EXIT_HIGH_MV)) return EV_STATE_E_NO_POWER; break; case EV_STATE_F_ERROR: if (v <= CP_F_EXIT_MV) return EV_STATE_F_ERROR; break; default: break; } if (v >= CP_A_ENTER_MV) return EV_STATE_A_IDLE; if (CP_IsInRange(v, CP_B_ENTER_LOW_MV, CP_B_ENTER_HIGH_MV)) return EV_STATE_B_CONN_PREP; if (CP_IsInRange(v, CP_C_ENTER_LOW_MV, CP_C_ENTER_HIGH_MV)) return EV_STATE_C_CONN_ACTIVE; if (CP_IsInRange(v, CP_D_ENTER_LOW_MV, CP_D_ENTER_HIGH_MV)) return EV_STATE_D_CONN_ACT_VENT; if (CP_IsInRange(v, CP_E_ENTER_LOW_MV, CP_E_ENTER_HIGH_MV)) return EV_STATE_E_NO_POWER; if (v <= CP_F_ENTER_MV) return EV_STATE_F_ERROR; return EV_STATE_ACQUIRING; } static uint32_t CP_GetDebounceMs(CP_State_t next_state) { if (next_state == EV_STATE_F_ERROR) { if (cp_duty <= CP_LOW_DUTY_THRESHOLD_PERCENT) { return CP_DEBOUNCE_MS_F_LOW_DUTY; } return CP_DEBOUNCE_MS_F; } return CP_DEBOUNCE_MS_DEFAULT; } static int32_t CP_ReadVoltageMv(void) { uint32_t adc = 0; int32_t v_adc_mv = 0; int32_t v_out_mv = 0; adc = CP_ReadAdcChannel((uint32_t)4u); v_adc_mv = (int32_t)((adc * 3300u) / 4095u); v_out_mv = ((v_adc_mv - 1723) * 1000) / 130; return v_out_mv; } void CP_Init(void) { /* TIM3_CH2 (PA7): set 1kHz PWM like original CCS logic. */ htim3.Instance->PSC = 160 - 1; htim3.Instance->ARR = MAX_DUTY - 1; #if DUTY_INVERT == 0 htim3.Instance->CCR2 = MAX_DUTY; htim3.Instance->CCR1 = MAX_DUTY + 5; #else htim3.Instance->CCR2 = 0; htim3.Instance->CCR1 = 0; #endif HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_2); HAL_TIM_OC_Start_IT(&htim3, TIM_CHANNEL_1); } void CP_SetDuty(uint8_t percentage) { uint32_t pwmduty = MAX_DUTY * percentage / 100; cp_duty = percentage; #if DUTY_INVERT == 0 htim3.Instance->CCR2 = pwmduty; htim3.Instance->CCR1 = 0 + 1; #else htim3.Instance->CCR2 = MAX_DUTY - pwmduty; htim3.Instance->CCR1 = MAX_DUTY - pwmduty + 5; #endif } uint8_t CP_GetDuty(void) { return cp_duty; } int32_t CP_GetVoltage(void) { return cp_voltage_mv; } CP_State_t CP_GetState(void) { int32_t voltage_real = cp_voltage_filt_mv; uint32_t now = HAL_GetTick(); if(fake_cp_state != EV_STATE_ACQUIRING) { return fake_cp_state; } CP_State_t instant_state = CP_ClassifyWithHysteresis(voltage_real, cp_stable_state); if (instant_state == cp_stable_state) { cp_candidate_state = cp_stable_state; cp_candidate_since_ms = now; } else { if (cp_candidate_state != instant_state) { cp_candidate_state = instant_state; cp_candidate_since_ms = now; } else if ((now - cp_candidate_since_ms) >= CP_GetDebounceMs(cp_candidate_state)) { cp_stable_state = cp_candidate_state; } } return cp_stable_state; } void CP_Loop(void) { (void)CP_GetState(); } void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) { if (ADC_TryLock() == 0u) { return; } cp_voltage_mv = CP_ReadVoltageMv(); (void)CP_ApplyEma(cp_voltage_mv); ADC_Unlock(); } }