// components/evse/evse_pilot.c #include #include #include #include "driver/ledc.h" #include "esp_err.h" #include "esp_log.h" #include "esp_rom_sys.h" #include "evse_pilot.h" #include "adc121s021_dma.h" #include "board_config.h" #define PILOT_PWM_TIMER LEDC_TIMER_0 #define PILOT_PWM_CHANNEL LEDC_CHANNEL_0 #define PILOT_PWM_SPEED_MODE LEDC_LOW_SPEED_MODE #define PILOT_PWM_DUTY_RES LEDC_TIMER_10_BIT #define PILOT_PWM_MAX_DUTY 1023 // --- Configuração de amostragem do Pilot --- #define NUM_PILOT_SAMPLES 100 #define MAX_SAMPLE_ATTEMPTS 1000 #define PILOT_SAMPLE_DELAY_US 10 // Percentagem para descartar extremos superior/inferior (ruído) #define PILOT_EXTREME_PERCENT 10 // 10% superior e inferior // ADC referência #define ADC121_VREF_MV 3300 #define ADC121_MAX 4095 static const char *TAG = "evse_pilot"; typedef enum { PILOT_MODE_DC_HIGH = 0, // +12V (nível alto) PILOT_MODE_DC_LOW, // nível baixo / pilot desligado (dependente do hardware) PILOT_MODE_PWM // PWM ativo } pilot_mode_t; static pilot_mode_t s_mode = PILOT_MODE_DC_LOW; static uint32_t last_pwm_duty = 0; // --------------------- // Helpers internos // --------------------- static int adc_raw_to_mv(uint16_t raw) { return (int)((raw * ADC121_VREF_MV) / ADC121_MAX); } static int compare_uint16(const void *a, const void *b) { uint16_t va = *(const uint16_t *)a; uint16_t vb = *(const uint16_t *)b; if (va < vb) return -1; if (va > vb) return 1; return 0; } // --------------------- // Inicialização PWM + ADC // --------------------- void pilot_init(void) { // Configura timer do PWM do Pilot (1 kHz) ledc_timer_config_t ledc_timer = { .speed_mode = PILOT_PWM_SPEED_MODE, .timer_num = PILOT_PWM_TIMER, .duty_resolution = PILOT_PWM_DUTY_RES, .freq_hz = 1000, // 1 kHz (IEC 61851) .clk_cfg = LEDC_AUTO_CLK }; ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); // Canal do PWM no pino configurado em board_config ledc_channel_config_t ledc_channel = { .speed_mode = PILOT_PWM_SPEED_MODE, .channel = PILOT_PWM_CHANNEL, .timer_sel = PILOT_PWM_TIMER, .intr_type = LEDC_INTR_DISABLE, .gpio_num = board_config.pilot_pwm_gpio, .duty = 0, .hpoint = 0 }; ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); // Garante que começa parado e em idle baixo (pilot off) ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, 0)); s_mode = PILOT_MODE_DC_LOW; last_pwm_duty = 0; // Inicializa driver do ADC121S021 adc121s021_dma_init(); } // --------------------- // Controlo do modo do Pilot // --------------------- void pilot_set_level(bool high) { pilot_mode_t target = high ? PILOT_MODE_DC_HIGH : PILOT_MODE_DC_LOW; // Se já estiver no modo DC desejado e sem PWM ativo, ignora if (s_mode == target && last_pwm_duty == 0) { return; } ESP_LOGI(TAG, "Pilot set DC level: %s", high ? "HIGH(+12V)" : "LOW/OFF"); // Para PWM e fixa o nível idle do GPIO ESP_ERROR_CHECK(ledc_stop(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, high ? 1 : 0)); s_mode = target; last_pwm_duty = 0; } void pilot_set_amps(uint16_t amps) { if (amps < 6 || amps > 80) { ESP_LOGE(TAG, "Invalid ampere value: %d A (valid: 6–80 A)", amps); return; } uint32_t duty_percent; if (amps <= 51) { duty_percent = (amps * 10) / 6; } else { duty_percent = (amps * 10) / 25 + 64; } if (duty_percent > 100) duty_percent = 100; uint32_t duty = (PILOT_PWM_MAX_DUTY * duty_percent) / 100; // Se já estiver em PWM com o mesmo duty, ignora if (s_mode == PILOT_MODE_PWM && last_pwm_duty == duty) { return; } s_mode = PILOT_MODE_PWM; last_pwm_duty = duty; ESP_LOGI(TAG, "Pilot set PWM: %d A → %d/%d (≈ %d%% duty)", amps, (int)duty, PILOT_PWM_MAX_DUTY, (int)duty_percent); ESP_ERROR_CHECK(ledc_set_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL, duty)); ESP_ERROR_CHECK(ledc_update_duty(PILOT_PWM_SPEED_MODE, PILOT_PWM_CHANNEL)); } bool pilot_get_state(void) { // "Alto" significa DC +12V (estado A). PWM não conta como DC high. return (s_mode == PILOT_MODE_DC_HIGH); } // --------------------- // Medição do sinal de Pilot (PWM 1 kHz J1772) // --------------------- void pilot_measure(pilot_voltage_t *up_voltage, bool *down_voltage_n12) { ESP_LOGD(TAG, "pilot_measure"); uint16_t samples[NUM_PILOT_SAMPLES]; int collected = 0; int attempts = 0; while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS) { uint16_t adc_sample; if (adc121s021_dma_get_sample(&adc_sample)) { samples[collected++] = adc_sample; esp_rom_delay_us(PILOT_SAMPLE_DELAY_US); } else { esp_rom_delay_us(100); attempts++; } } if (collected < NUM_PILOT_SAMPLES) { ESP_LOGW(TAG, "Timeout on sample read (%d/%d)", collected, NUM_PILOT_SAMPLES); *up_voltage = PILOT_VOLTAGE_1; *down_voltage_n12 = false; return; } // Ordena as amostras para eliminar extremos (ruído/espúrios) qsort(samples, collected, sizeof(uint16_t), compare_uint16); int k = (collected * PILOT_EXTREME_PERCENT) / 100; if (k < 2) k = 2; // garante margem mínima // descarta k/2 em cada lado (aprox. 10% total, mantendo simetria) int low_index = k / 2; int high_index = collected - 1 - (k / 2); if (low_index < 0) low_index = 0; if (high_index >= collected) high_index = collected - 1; if (high_index <= low_index) high_index = low_index; uint16_t low_raw = samples[low_index]; uint16_t high_raw = samples[high_index]; int high_mv = adc_raw_to_mv(high_raw); int low_mv = adc_raw_to_mv(low_raw); // Determina o nível positivo (+12, +9, +6, +3 ou <3 V) if (high_mv >= board_config.pilot_down_threshold_12) { *up_voltage = PILOT_VOLTAGE_12; } else if (high_mv >= board_config.pilot_down_threshold_9) { *up_voltage = PILOT_VOLTAGE_9; } else if (high_mv >= board_config.pilot_down_threshold_6) { *up_voltage = PILOT_VOLTAGE_6; } else if (high_mv >= board_config.pilot_down_threshold_3) { *up_voltage = PILOT_VOLTAGE_3; } else { *up_voltage = PILOT_VOLTAGE_1; } // Verifica se o nível negativo atinge -12 V (diodo presente, C/D válidos) *down_voltage_n12 = (low_mv <= board_config.pilot_down_threshold_n12); ESP_LOGD(TAG, "Final: up_voltage=%d, down_voltage_n12=%d (high=%d mV, low=%d mV)", *up_voltage, *down_voltage_n12, high_mv, low_mv); }