Files
chargeflow/components/evse/evse_pilot.c

319 lines
8.7 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// components/evse/evse_pilot.c
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#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 "adc.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
// Referência usada pelo ADC121S021 externo.
// No ADC interno, o valor já é convertido para mV por adc_cali_raw_to_voltage().
#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;
static bool s_internal_adc_configured = false;
// ---------------------
// Helpers internos
// ---------------------
static int adc121_raw_to_mv(uint16_t raw)
{
return (int)((raw * ADC121_VREF_MV) / ADC121_MAX);
}
static int compare_int(const void *a, const void *b)
{
int va = *(const int *)a;
int vb = *(const int *)b;
if (va < vb) return -1;
if (va > vb) return 1;
return 0;
}
static bool pilot_adc_read_mv(int *mv)
{
if (!mv)
{
return false;
}
if (board_config.pilot_adc_source == BOARD_CONFIG_PILOT_ADC_INTERNAL_ESP32)
{
if (!adc_handle || !adc_cali_handle)
{
ESP_LOGE(TAG, "ADC interno não inicializado/calibrado");
return false;
}
int raw = 0;
esp_err_t err;
adc_lock();
err = adc_oneshot_read(adc_handle, board_config.pilot_adc_channel, &raw);
if (err == ESP_OK)
{
err = adc_cali_raw_to_voltage(adc_cali_handle, raw, mv);
}
adc_unlock();
if (err != ESP_OK)
{
ESP_LOGE(TAG, "Erro ao ler/converter ADC interno do pilot: %s", esp_err_to_name(err));
return false;
}
return true;
}
uint16_t raw = 0;
if (!adc121s021_dma_get_sample(&raw))
{
return false;
}
*mv = adc121_raw_to_mv(raw);
return true;
}
static void pilot_adc_init(void)
{
if (board_config.pilot_adc_source == BOARD_CONFIG_PILOT_ADC_INTERNAL_ESP32)
{
if (!adc_handle)
{
adc_init();
}
if (!s_internal_adc_configured)
{
adc_oneshot_chan_cfg_t config = {
.bitwidth = ADC_BITWIDTH_DEFAULT,
.atten = ADC_ATTEN_DB_12
};
ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle,
board_config.pilot_adc_channel,
&config));
s_internal_adc_configured = true;
}
ESP_LOGI(TAG, "Pilot ADC: interno ESP32 ADC1_CH%d",
(int)board_config.pilot_adc_channel);
return;
}
ESP_LOGI(TAG, "Pilot ADC: ADC121S021 externo via SPI");
adc121s021_dma_init();
}
// ---------------------
// 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 o backend de leitura do pilot configurado na board.
pilot_adc_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: 680 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");
int samples_mv[NUM_PILOT_SAMPLES];
int collected = 0;
int attempts = 0;
while (collected < NUM_PILOT_SAMPLES && attempts < MAX_SAMPLE_ATTEMPTS)
{
int sample_mv = 0;
if (pilot_adc_read_mv(&sample_mv))
{
samples_mv[collected++] = sample_mv;
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_mv, collected, sizeof(int), compare_int);
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;
int low_mv = samples_mv[low_index];
int high_mv = samples_mv[high_index];
// 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);
}