#include <Arduino.h>

// -------------------------------------------------- 
// Estruturas, Enum e Variáveis Globais de Exemplo
// --------------------------------------------------

enum class CanalPWM : uint8_t {
  CANAL_A,
  CANAL_B,
  CANAL_C
};

struct {
  uint16_t posicao; // Setpoint (posição desejada, em valor de pressão)
} modoTrabalho;

struct ValvulaPWMState {
  float erroAnterior = 0.0f;
  float integral = 0.0f;
  uint32_t temporizador = 0;
  uint8_t dutyCycle = 0;
};

ValvulaPWMState valvulaStates[3];

// Parâmetros PWM
const uint8_t dutyMinimo = 0;
const uint8_t dutyMaximo = 255;

// Frequência desejada ~490Hz para Timer3 (pinos 2 e 3)
const uint16_t freqPWMValvula = 490;

// Ajuste do tempo mínimo e máximo de atualização (em ms)
const uint16_t tempoMin = 50;   
const uint16_t tempoMax = 500;  
const uint16_t maxDif = 500;

// Saídas globais para cada canal (para monitorar)
uint8_t dutyCycle1 = 0;
uint8_t dutyCycle2 = 0;
uint8_t dutyCycle3 = 0;

// Configurações do PID
float Kp = 0.2f;
float Ki = 0.3f;
float Kd = 0.01f;

// Função de Constrain (igual a do Arduino)
int16_t constrain_int16(int16_t x, int16_t a, int16_t b) {
  if (x < a) return a;
  if (x > b) return b;
  return x;
}

// --------------------------------------------------
// Configuração do Timer3 para ~490Hz nos pinos 2 e 3
// --------------------------------------------------
// Modo Fast PWM com ICR3 definindo TOP
// Frequência = F_CPU/(Prescaler*(1+ICR3))
// Queremos ~490 Hz: ICR3 ≈ 509 com prescaler = 64
// ICR3 = 509 => Frequência ≈ 490Hz

void configuraTimer3() {
  // Desabilita saída do timer para configurar
  TCCR3A = 0;
  TCCR3B = 0;

  // Modo Fast PWM com TOP em ICR3 (WGM = 14: WGM3=1110)
  // WGM31:0 no TCCR3A, WGM33:2 no TCCR3B
  // WGM33=1, WGM32=1, WGM31=1, WGM30=0 => binário 1110
  TCCR3A = (1 << WGM31);
  TCCR3B = (1 << WGM33) | (1 << WGM32) | (1 << CS31) | (1 << CS30); 
  // CS31 e CS30 => Prescaler = 64 (fórmula: 16MHz/64)
  
  // Define TOP
  ICR3 = 509; 

  // Configura pinos 2 (OC3B) e 3 (OC3C) como saída PWM não-invertido
  // Não-invertido: Clear OC3x on Compare Match, set at BOTTOM
  TCCR3A |= (1 << COM3B1) | (1 << COM3C1);

  // Inicialmente duty = 0
  OCR3B = 0; // pino 2
  OCR3C = 0; // pino 3
}

// --------------------------------------------------
// Função para configurar PWM em cada canal
// --------------------------------------------------
void configuraPWM(CanalPWM canal, uint16_t freq, uint8_t duty) {
  // Ajuste do dutyCycle de acordo com o canal
  // Canal A -> pino 2  (OC3B)
  // Canal B -> pino 3  (OC3C)
  // Canal C -> pino 4  (OC0B, Timer0)

  switch (canal) {
    case CanalPWM::CANAL_A:
      // Converte duty 0-255 para 0-ICR3
      OCR3B = map(duty, 0, 255, 0, ICR3);
      break;
    case CanalPWM::CANAL_B:
      OCR3C = map(duty, 0, 255, 0, ICR3);
      break;
    case CanalPWM::CANAL_C:
      // Para o pino 4 (OC0B), manteremos analogWrite padrão
      // Isso manterá ~490Hz aproximado (Timer0 ~976Hz default, mas pode ser levemente alterado)
      analogWrite(4, duty);
      break;
    default:
      break;
  }
}

// --------------------------------------------------
// Rotina de controleValvulaPWM_PID
// --------------------------------------------------
void controleValvulaPWM_PID(CanalPWM canal, uint16_t pressao)
{
    ValvulaPWMState &state = valvulaStates[(uint8_t)canal];

    // Garante que o dutyCycle inicial esteja dentro dos limites
    state.dutyCycle = constrain(state.dutyCycle, dutyMinimo, dutyMaximo);

    // Calcula a diferença absoluta entre a pressão atual e a posição desejada
    float prop = fminf((float)abs((int)pressao - (int)modoTrabalho.posicao) / (float)maxDif, 1.0f);

    // Calcula o tempo de atualização com base na diferença
    uint16_t tempoAtualizacao = (uint16_t)(tempoMax - (tempoMax - tempoMin) * prop);

    // Atualiza o PWM com base no temporizador dinâmico
    uint32_t agora = millis();
    if ((agora - state.temporizador) >= tempoAtualizacao) {
        float deltaTime = (agora - state.temporizador) / 1000.0f;
        state.temporizador = agora;

        float erro = (float)modoTrabalho.posicao - (float)pressao;

        // Integral com anti-windup
        if (state.dutyCycle > dutyMinimo && state.dutyCycle < dutyMaximo) {
            state.integral += erro * deltaTime;
            if (state.integral > 50.0f) state.integral = 50.0f;
            if (state.integral < -50.0f) state.integral = -50.0f;
        }

        float derivativo = (erro - state.erroAnterior) / (deltaTime > 0.0f ? deltaTime : 1.0f);
        state.erroAnterior = erro;

        float outputPID = (Kp * erro) + (Ki * state.integral) + (Kd * derivativo);

        int16_t novoDutyCycle = (int16_t)state.dutyCycle + (int16_t)(outputPID);
        novoDutyCycle = constrain_int16(novoDutyCycle, dutyMinimo, dutyMaximo);

        if (novoDutyCycle == dutyMinimo || novoDutyCycle == dutyMaximo) {
            state.integral = 0;
        }

        state.dutyCycle = (uint8_t)novoDutyCycle;

        switch (canal) {
            case CanalPWM::CANAL_A:
                dutyCycle1 = state.dutyCycle;
                break;
            case CanalPWM::CANAL_B:
                dutyCycle2 = state.dutyCycle;
                break;
            case CanalPWM::CANAL_C:
                dutyCycle3 = state.dutyCycle;
                break;
            default:
                break;
        }

        configuraPWM(canal, freqPWMValvula, state.dutyCycle);
    }
}

// --------------------------------------------------
// Setup e Loop Principal para Teste
// --------------------------------------------------

void setup() {
  Serial.begin(115200);

  // Setpoint
  modoTrabalho.posicao = 500;

  // Configura pinos de saída
  pinMode(2, OUTPUT);  // Canal A (OC3B)
  pinMode(3, OUTPUT);  // Canal B (OC3C)
  pinMode(4, OUTPUT);  // Canal C (OC0B)
  pinMode(5, OUTPUT);  // Dir
  pinMode(8, OUTPUT);  // Enable

  digitalWrite(5, HIGH);
  digitalWrite(8, HIGH);

  // Configura Timer3 para ~490Hz nos pinos 2 e 3
  configuraTimer3();

  // Inicia o dutyCycle em 50% (por exemplo)
  valvulaStates[(uint8_t)CanalPWM::CANAL_A].dutyCycle = 128;
  valvulaStates[(uint8_t)CanalPWM::CANAL_A].temporizador = millis();
}

void loop() {
  // Simulação de pressão (exemplo: onda senoidal)
  static uint32_t tempo = 0;
  tempo += 10;
  delay(10);

  float simPressao = 500.0f + 250.0f * sin(tempo / 1000.0f);

  // Chama a rotina PID para canal A
  controleValvulaPWM_PID(CanalPWM::CANAL_A, (uint16_t)simPressao);

  // Imprime valores para análise
  Serial.print("Tempo: ");
  Serial.print(tempo);
  Serial.print(" ms | Pressao: ");
  Serial.print(simPressao, 2);
  Serial.print(" | Setpoint: ");
  Serial.print(modoTrabalho.posicao);
  Serial.print(" | DutyCycle (A): ");
  Serial.println(dutyCycle1);
}
A4988