//==========================Bibliothèque====================================================
#include "driver/gpio.h"
#include "stdio.h"
#include "driver/ledc.h"
#include "driver/pcnt.h"
#include "soc/pcnt_struct.h"
#include <math.h>
#include <Keypad.h>  
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

//=============================================================================================
//====================================Partie Déclaration=======================================
// Configuration de l'afficheur LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);
#define LEDC_HS_CH1_GPIO GPIO_NUM_4 // GPIO 33 comme sortie pour le générateur de pulses (LEDC)
uint32_t RapportCycliqSortie = 0;   // Valeur calculée du cycle de travail
uint32_t ResolutionSortie = 0;      // Valeur calculée de la résolution
// GPIO pour le clavier matriciel
const byte ROWS = 4; // 4 lignes
const byte COLS = 4; // 4 colonnes
char keys[ROWS][COLS] = {
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};
byte rowPins[ROWS] = {5, 18, 19, 12}; // Connexion des lignes du clavier aux GPIO
byte colPins[COLS] = {14, 23, 25, 26}; // Connexion des colonnes du clavier aux GPIO
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
#define LEDC_HS_CH0_GPIO GPIO_NUM_33 // GPIO 33 comme sortie pour le générateur de pulses (LEDC)
#define PCNT_COUNT_UNIT       PCNT_UNIT_0       // PCNT Compteur d'impulsions Unité 0
#define PCNT_COUNT_CHANNEL    PCNT_CHANNEL_0    // Canal 0 du compteur d'impulsions PCNT de l'ESP32
#define PCNT_INPUT_SIG_IO     GPIO_NUM_34  //GPIO 34 entrée du diviseur
#define LEDC_HS_CH0_GPIO      GPIO_NUM_33 // GPIO_33 sortie du LEDC
#define PCNT_INPUT_CTRL_IO    GPIO_NUM_35  // Pin de contrôle PCNT - HIGH = compte à rebours, LOW =  non compte à rebours 
#define OUTPUT_CONTROL_GPIO   GPIO_NUM_32  // GPIO_32 sortie de la timer
#define PCNT_H_LIM_VAL        overflow  // Debordement du compteur
#define IN_BOARD_LED          GPIO_NUM_2  // GPIO 2
bool            flag          = true; // le flag de survéllance pour le debordement
uint32_t        overflow      = 20000;// Valeur max pour que le PCNT deborde
int16_t         pulses        = 0;// Nombre d'impulsions comptées
uint32_t        multPulses    = 0; // Nombre de dépassements du compteur PCNT
uint32_t        InterValTemps = 1000000;// Intervalle 1S = 1000000µs pour le comptage
uint32_t        FreqVoulue     = 10000;// Frequence- 10000 Hz
uint32_t        RapportCycliq  = 0; // Rapport cyclique
uint32_t        Resolution     = 0; // Resolution de service
float           Frequence    = 0; // Variable pour calculer la fréquence
String freqInput = ""; // Stocke la fréquence entrée par l'utilisateur
String divInput = ""; // Stocke l'entrée du rapport de division
int inputInt = 1000;   // Valeur par défaut de la fréquence
uint32_t n = 2; // Variable du rapprot de division du diviseur

esp_timer_create_args_t create_args; // Arguments de la Timer
esp_timer_handle_t timer_handle;  // Instance de Timer
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED; // variable portMUX_TYPE pour synchronisation

//===============================================================================================================
//===============================================================================================================
void setup() {
  Serial.begin(115200);                                                   
  Serial.println(" Digite uma frequencia - 1 a 40 MHz");                 
  lcd.init(); // Initialiser l'afficheur
  lcd.backlight(); // Activer le rétroéclairage
  lcd.clear(); // Effacer l'écran
  lcd.setCursor(0, 0); // Placer le curseur à la position (0, 0)
  lcd.print("Freq Diviseur");  // Afficher "Freq Diviseur" à l'écran

  // Initialiser les broches du clavier
  for (int i = 0; i < ROWS; i++) {
    pinMode(rowPins[i], OUTPUT);
  }
  for (int i = 0; i < COLS; i++) {
    pinMode(colPins[i], INPUT_PULLUP);
  }

  GenerateurDeSignaux(); // Initialiser l'oscillateur LEDC
  Compteurd1Impulsion(); // Initialisation du PCNT
  // Configurer GPIO en mode OUTPUT pour le contrôle de la fréquence
  pinMode(OUTPUT_CONTROL_GPIO, OUTPUT);
  // Configurer GPIO en mode INPUT pour le signal d'entrée de fréquence
  pinMode(PCNT_INPUT_SIG_IO, INPUT);
  // Configurer GPIO en mode OUTPUT pour l'LED
  pinMode(IN_BOARD_LED, OUTPUT);
  // Créer un timer ESP32 pour contrôler la fréquence
  create_args.callback = ControleurDeTemps; // Instances du temps de contrôle
  esp_timer_create(&create_args, &timer_handle); // Création des paramètres de la minuterie
  // Configurer l'LED intégrée pour afficher la fréquence
  gpio_set_direction(IN_BOARD_LED, GPIO_MODE_OUTPUT); 
}
//=====================================================================================================
//=====================================================================================================
void loop() {

  ClaviMatricielle () ; // Fonction pour le clavier matricielle
  FrequenceDeSortie(); // Fonction pour calculer la fréquence divisée
}

//===================================================================================================
//============================Calcul de fréquence de sortie==========================================
void FrequenceDeSortie() {
    if (flag == true) // Si le décompte est terminé
  {
    flag = false;  // Empêche la réimpression
    Frequence = ((pulses + (multPulses * overflow)) / 2) / n  ; // Calcule la somme des impulsions comptées dans le PCNT
    Serial.print("Frequencia : ");
    Serial.print(Frequence);
    Serial.println(" Hz ");                                                    

    lcd.setCursor(0, 1);
    lcd.print("SORTIE: ");
    lcd.print(Frequence);
    lcd.print(" Hz");
    
    multPulses = 0;  // Remise à zéro du compteur de débordement
    delay (100);     // Délai 100 ms
    pcnt_counter_clear(PCNT_COUNT_UNIT); // Remise à zéro du compteur PCNT
    esp_timer_start_once(timer_handle, InterValTemps);// Lance le compteur de temps d'une seconde
    gpio_set_level(OUTPUT_CONTROL_GPIO, 1);// Porte de contrôle - permet le comptage des impulsions
  }
}
//===================================================================================================
//==============================Clavier Matricielle==================================================
void ClaviMatricielle () {
  char key = keypad.getKey();
  
  // Si une touche est appuyée
  if (key) {
    Serial.println(key);

    switch (key) {
      case '0' ... '9':    // Pour les touches numériques
        freqInput += key;
        Serial.print("Fréquence en entrée: ");
        Serial.println(freqInput);

        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("ENTREE: ");
        lcd.print(freqInput);
        lcd.print(" "); // Effacer les anciens caractères
        break;
      FreqVoulue = 0;

      case '#':  // Valider la fréquence entrée
        if (freqInput.length() > 0) {
          inputInt = freqInput.toInt();  // Convertir l'entrée en entier
          //freqInput = "";
          FreqVoulue = inputInt;  // Mettre à jour la fréquence souhaitée
          freqInput = "";
          if (FreqVoulue != 0)    // Si une valeur a été introduite
          {
             GenerateurDeSignaux ();   // Reconfigure la fréquence de l'oscillateur
          }
          //GenerateurDeSignaux();
          Serial.print("Nouvelle fréquence: ");
          Serial.println(FreqVoulue);

          // Réinitialiser et appliquer la nouvelle fréquence
          
        }
        break;

      case 'A': // Valider la modification de rapport de division
        if(freqInput.length() > 0) {
          n = freqInput.toInt();
          FrequenceDeSortie();
          freqInput = "";
        }
        break;


      case '*':  // Effacer l'entrée actuelle
        freqInput = "";
        Serial.println("Effacement de l'entrée.");

        lcd.clear();
        lcd.setCursor(0, 0);
        lcd.print("ENTREE:  "); // Effacer l'affichage de l'entrée
        break;

      default:
        break;
    }
  }
}
//================================================================================================================
//===============================LEDC=============================================================================
void GenerateurDeSignaux ()  // Fonction de l'oscillateur
{
  Resolution = (log (80000000 / FreqVoulue)  / log(2)) / 2 ; // Calcule de la résolution de service
  if (Resolution < 1) Resolution = 1; // Résolution minimale fixée = 1 
  Serial.println(Resolution);                                 
  RapportCycliq = (pow(2, Resolution)) / 2; // Calcul de rapport cyclique
  Serial.println(RapportCycliq);                                      
  ledc_timer_config_t ledc_timer = {};    // Instance de Configuration du timer de LEDC
  ledc_timer.duty_resolution =  ledc_timer_bit_t(Resolution); // Configuration de la résolution
  ledc_timer.freq_hz    = FreqVoulue;    // Configuration de la fréquence
  ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE; // Mode haute vitesse
  ledc_timer.timer_num = LEDC_TIMER_0; // Utilisation du timer0 du LEDC
  ledc_timer_config(&ledc_timer);  // Configuration du timer de LEDC
  ledc_channel_config_t ledc_channel = {}; //Instance de configuration du canal de LEDC
  ledc_channel.channel    = LEDC_CHANNEL_0; // Configuration canal 0 
  ledc_channel.duty       = RapportCycliq;  // Configuration de rapport cyclique
  ledc_channel.gpio_num   = LEDC_HS_CH0_GPIO; // Configure le GPIO de la sortie LEDC 
  ledc_channel.intr_type  = LEDC_INTR_DISABLE; // Désactive l'interruption LEDC
  ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE; // Mode de fonctionnement du canal à grande vitesse
  ledc_channel.timer_sel  = LEDC_TIMER_0; // Selecionne de timer 0 de LEDC
  ledc_channel_config(&ledc_channel); // Configuration du canal de LEDC
}

//===============================================================================================================
//===============================Fonction d'interruption=========================================================
static void IRAM_ATTR pcnt_intr_handler(void *arg) // Configuration de la timer pour contrôler le debordement
{
  portENTER_CRITICAL_ISR(&timerMux); // Bloquer une nouvelle interruption
  multPulses++;                      // Conteneur de débordement incrémentiel
  PCNT.int_clr.val = BIT(PCNT_COUNT_UNIT); // Indicateur d'interruption 
  portEXIT_CRITICAL_ISR(&timerMux); // Nouvelle interruption
}
//==================================================================================================================
//==================================================================================================================
void Compteurd1Impulsion () // Fonction de PCNT
{
  pcnt_config_t pcnt_config = { }; // Instance de configuration du PCNT
  pcnt_config.pulse_gpio_num = PCNT_INPUT_SIG_IO; // Configuration du GPIO d'entrée du PCNT
  pcnt_config.ctrl_gpio_num = PCNT_INPUT_CTRL_IO; // Configuration du GPIO d'entrée de contrôle
  pcnt_config.unit = PCNT_COUNT_UNIT;// Unité 0 de comptage PCNT
  pcnt_config.channel = PCNT_COUNT_CHANNEL;  // Canal 0 PCNT 
  pcnt_config.counter_h_lim = PCNT_H_LIM_VAL; // Limite maximale de comptage = 20000
  pcnt_config.pos_mode = PCNT_COUNT_INC; // Augmente le comptage lors de la montée de l'impulsion
  pcnt_config.neg_mode = PCNT_COUNT_INC; // Augmente le comptage lors de la descente de l'impulsion
  pcnt_config.lctrl_mode = PCNT_MODE_DISABLE; // PCNT - mode lctrl désactivé
  pcnt_config.hctrl_mode = PCNT_MODE_KEEP; // PCNT - mode hctrl - si HIGH comptage incrémenté
  pcnt_unit_config(&pcnt_config); // Configure le compteur PCNT
  pcnt_counter_pause(PCNT_COUNT_UNIT);// Mise en pause du compteur PCNT
  pcnt_counter_clear(PCNT_COUNT_UNIT); // Remise à zéro du compteur PCNT
  pcnt_event_enable(PCNT_COUNT_UNIT, PCNT_EVT_H_LIM);// Fixe la limite supérieure de comptage
  pcnt_isr_register(pcnt_intr_handler, NULL, 0, NULL);// Configure la routine d'interruption PCNT
  pcnt_intr_enable(PCNT_COUNT_UNIT); // Activer les interruptions PCNT
  pcnt_counter_resume(PCNT_COUNT_UNIT);// Remise à zéro du compteur PCNT
}
//=========================================================================================================================
//=========================================================================================================================
void ControleurDeTemps (void*) // Fonction de la fin du temps de lecture de l'impulsion
{
  gpio_set_level(OUTPUT_CONTROL_GPIO, 0);// Contrôle du PCNT - pour le comptable
  pcnt_get_counter_value(PCNT_COUNT_UNIT, &pulses); // Obtient la valeur comptée dans le PCNT
  flag = true; // Informe qu'il y a eu une interruption du contrôle
}













D0D1D2D3D4D5D6D7GNDLOGIC