
// BLOG Eletrogate
// ESP32 Frequencimetro
// ESP32 DevKit 38 pinos + LCD 
// https://blog.eletrogate.com/esp32-frequencimetro-de-precisao
// Rui Viana e Gustavo Murta agosto/2020

#include "stdio.h"                                                        // Biblioteca STDIO
#include "driver/ledc.h"                                                  // Biblioteca ESP32 LEDC
#include "driver/pcnt.h"                                                  // Biblioteca ESP32 PCNT

#define LCD_OFF                                                           // Defina LCD_ON, para usar LCD, se não, defina LCD_OFF
#define LCD_I2C_OFF                                                       // Defina LCD_I2C_ON, para usar LCD I2C, se não, defina LCD_I2C_OFF

#ifdef LCD_I2C_ON                                                         // Se habilitar LCD I2C
#define I2C_SDA 21                                                        // LCD I2C SDA - GPIO_21
#define I2C_SCL 22                                                        // LCD I2C SCL - GPIO_22
#include <Wire.h>                                                         // Biblioteca para I2C
#include <LiquidCrystal_PCF8574.h>                                        // Biblioteca para LCD com PCF8574
LiquidCrystal_PCF8574 lcd(0x3F);                                          // Instancia LCD I2C com endereço x3F
#endif                                                                    // LCD I2C

#ifdef LCD_ON                                                             // Se habilitar LCD com interface 4 bits
#include <LiquidCrystal.h>                                                // Biblioteca para LCD
LiquidCrystal lcd(4, 16, 17, 5, 18, 19);                                  // Instancia e define os ports
#endif                                                                    // LCD

#define PCNT_COUNT_UNIT       PCNT_UNIT_0                                 // Unidade 0 do Contador de pulso PCNT do ESP32
#define PCNT_COUNT_CHANNEL    PCNT_CHANNEL_0                              // Canal 0 do Contador de pulso PCNT do ESP32

#define PCNT_INPUT_SIG_IO     GPIO_NUM_34                                 // Entrada do Frequencimetro -  GPIO 34
#define LEDC_HS_CH0_GPIO      GPIO_NUM_33                                 // Saida do LEDC - gerador de pulsos - GPIO_33
#define PCNT_INPUT_CTRL_IO    GPIO_NUM_35                                 // Pino de controle do PCNT - HIGH = count up, LOW = count down 
#define OUTPUT_CONTROL_GPIO   GPIO_NUM_32                                 // Saida do timer - Controla a contagem - GPIO_32
#define PCNT_H_LIM_VAL        overflow                                    // Limite superior de contagem

#define IN_BOARD_LED          GPIO_NUM_2                                  // LED nativo ESP32 - GPIO 2

bool            flag          = true;                                     // Indicador de fim de contagem - libera impressão
uint32_t        overflow      = 20000;                                    // Valor maximo para overflow do contador PCNT
int16_t         pulses        = 0;                                        // Quantidade de pulsos contados
uint32_t        multPulses    = 0;                                        // Quantidade de overflows do contador PCNT
uint32_t        janela        = 1000000;                                  // Tempo de amostragem  de 1 segundo para a contagem de pulsos 999990
uint32_t        oscilador     = 12543;                                    // Frequencia inicial do oscilador - 12543 Hz
uint32_t        mDuty         = 0;                                        // Valor calculado do ciclo de carga
uint32_t        resolucao     = 0;                                        // Valor calculado da resolucao
float           frequencia    = 0;                                        // Variavel para calculo de frequencia
char            buf[32];                                                  // Buffer para guardar a pontuacao

esp_timer_create_args_t create_args;                                      // Argumentos do ESP-Timer
esp_timer_handle_t timer_handle;                                          // Instancia de ESP-Timer

portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;                     // variavel tipo portMUX_TYPE para sincronismo

//----------------------------------------------------------------------------------------
void setup()
{
  Serial.begin(115200);                                                   // Inicializa a serial 115200 Bps
  Serial.println(" Digite uma frequencia - 1 a 40 MHz");                  // Print na console

#ifdef LCD_I2C_ON                                                         // Se estiver usando LCD I2C 
  Wire.begin(I2C_SDA, I2C_SCL);                                           // Inicializa Interface I2C
  lcd.setBacklight(255);                                                  // Ativa leds do backlight do LCD
#endif

#if defined LCD_ON || defined LCD_I2C_ON                                  // Se estiver usando LCD ou LCD I2C      
  lcd.begin(16, 2);                                                       // Inicializa LCD 16 colunas 2 linhas
  lcd.print("  Frequencia:");                                             // Print no LCD
#endif

  inicializa_frequencimetro();                                            // Inicializa o frequencimetro
}

//----------------------------------------------------------------------------
void inicializa_oscilador ()                                              // Inicializa gerador de pulsos
{
  resolucao = (log (80000000 / oscilador)  / log(2)) / 2 ;                // Calculo da resolucao para o oscilador
  if (resolucao < 1) resolucao = 1;                                       // Resoluçao mínima 
  // Serial.println(resolucao);                                           // Print
  mDuty = (pow(2, resolucao)) / 2;                                        // Calculo do ciclo de carga 50% do pulso
  // Serial.println(mDuty);                                               // Print

  ledc_timer_config_t ledc_timer = {};                                    // Instancia a configuracao do timer do LEDC

  ledc_timer.duty_resolution =  ledc_timer_bit_t(resolucao);              // Configura resolucao
  ledc_timer.freq_hz    = oscilador;                                      // Configura a frequencia do oscilador
  ledc_timer.speed_mode = LEDC_HIGH_SPEED_MODE;                           // Modo de operacao em alta velocidade
  ledc_timer.timer_num = LEDC_TIMER_0;                                    // Usar timer0 do LEDC
  ledc_timer_config(&ledc_timer);                                         // Configura o timer do LEDC

  ledc_channel_config_t ledc_channel = {};                                // Instancia a configuracao canal do LEDC

  ledc_channel.channel    = LEDC_CHANNEL_0;                               // Configura canal 0 
  ledc_channel.duty       = mDuty;                                        // Configura o ciclo de carga
  ledc_channel.gpio_num   = LEDC_HS_CH0_GPIO;                             // Configura GPIO da saida do LEDC - oscilador
  ledc_channel.intr_type  = LEDC_INTR_DISABLE;                            // Desabilita interrupção do LEDC
  ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE;                         // Modo de operacao do canal em alta velocidade
  ledc_channel.timer_sel  = LEDC_TIMER_0;                                 // Seleciona timer 0 do LEDC
  ledc_channel_config(&ledc_channel);                                     // Configura o canal do LEDC
}

//----------------------------------------------------------------------------------
static void IRAM_ATTR pcnt_intr_handler(void *arg)                        // Contagem do contador de Overflow
{
  portENTER_CRITICAL_ISR(&timerMux);                                      // Bloqueia nova interrupção
  multPulses++;                                                           // Incrementa contador de overflow
  PCNT.int_clr.val = BIT(PCNT_COUNT_UNIT);                                // Limpa indicador de interrupção
  portEXIT_CRITICAL_ISR(&timerMux);                                       // Libera nova interrupção
}

//----------------------------------------------------------------------------------
void inicializa_contador(void)                                            // Inicializacao do contador de pulsos
{
  pcnt_config_t pcnt_config = { };                                        // Instancia PCNT config

  pcnt_config.pulse_gpio_num = PCNT_INPUT_SIG_IO;                         // Configura GPIO para entrada dos pulsos
  pcnt_config.ctrl_gpio_num = PCNT_INPUT_CTRL_IO;                         // Configura GPIO para controle da contagem
  pcnt_config.unit = PCNT_COUNT_UNIT;                                     // Unidade de contagem PCNT - 0
  pcnt_config.channel = PCNT_COUNT_CHANNEL;                               // Canal de contagem PCNT - 0
  pcnt_config.counter_h_lim = PCNT_H_LIM_VAL;                             // Limite maximo de contagem - 20000
  pcnt_config.pos_mode = PCNT_COUNT_INC;                                  // Incrementa contagem na subida do pulso
  pcnt_config.neg_mode = PCNT_COUNT_INC;                                  // Incrementa contagem na descida do pulso
  pcnt_config.lctrl_mode = PCNT_MODE_DISABLE;                             // PCNT - modo lctrl desabilitado
  pcnt_config.hctrl_mode = PCNT_MODE_KEEP;                                // PCNT - modo hctrl - se HIGH conta incrementando
  pcnt_unit_config(&pcnt_config);                                         // Configura o contador PCNT

  pcnt_counter_pause(PCNT_COUNT_UNIT);                                    // Pausa o contador PCNT
  pcnt_counter_clear(PCNT_COUNT_UNIT);                                    // Zera o contador PCNT

  pcnt_event_enable(PCNT_COUNT_UNIT, PCNT_EVT_H_LIM);                     // Configura limite superior de contagem
  pcnt_isr_register(pcnt_intr_handler, NULL, 0, NULL);                    // Conigura rotina de interrupção do PCNT
  pcnt_intr_enable(PCNT_COUNT_UNIT);                                      // Habilita interrupções do PCNT

  pcnt_counter_resume(PCNT_COUNT_UNIT);                                   // Reinicia a contagem no contador PCNT
}

//----------------------------------------------------------------------------------
void tempo_controle(void *p)                                              // Fim de tempo de leitura de pulsos
{
  gpio_set_level(OUTPUT_CONTROL_GPIO, 0);                                 // Controle do PCNT - para o contador
  pcnt_get_counter_value(PCNT_COUNT_UNIT, &pulses);                       // Obtem o valor contado no PCNT
  flag = true;                                                            // Informa que ocorreu interrupção de controle
}

//---------------------------------------------------------------------------------
void inicializa_frequencimetro()
{
  inicializa_oscilador ();                                                // Inicia a geração de pulsos no oscilador
  inicializa_contador();                                                  // Inicializa o contador de pulsos PCNT

  gpio_pad_select_gpio(OUTPUT_CONTROL_GPIO);                              // Define o port decontrole
  gpio_set_direction(OUTPUT_CONTROL_GPIO, GPIO_MODE_OUTPUT);              // Define o port de controle como saida

  create_args.callback = tempo_controle;                                  // Instancia o tempo de controle
  esp_timer_create(&create_args, &timer_handle);                          // Cria parametros do timer

  gpio_set_direction(IN_BOARD_LED, GPIO_MODE_OUTPUT);                     // Port LED como saida

  gpio_matrix_in(PCNT_INPUT_SIG_IO, SIG_IN_FUNC226_IDX, false);           // Direciona a entrada de pulsos
  gpio_matrix_out(IN_BOARD_LED, SIG_IN_FUNC226_IDX, false, false);        // Para o LED do ESP32
}

//----------------------------------------------------------------------------------------
char *ultos_recursive(unsigned long val, char *s, unsigned radix, int pos) // Formata um número longo de 32 bits com pontos
{
  int c;
  if (val >= radix)
    s = ultos_recursive(val / radix, s, radix, pos + 1);
  c = val % radix;
  c += (c < 10 ? '0' : 'a' - 10);
  *s++ = c;
  if (pos % 3 == 0) *s++ = '.';
  return s;
}
//----------------------------------------------------------------------------------------
char *ltos(long val, char *s, int radix)    // Formata um número longo de 32 bits com pontos
{
  if (radix < 2 || radix > 36) {
    s[0] = 0;
  } else {
    char *p = s;
    if (radix == 10 && val < 0) {
      val = -val;
      *p++ = '-';
    }
    p = ultos_recursive(val, p, radix, 0) - 1;
    *p = 0;
  }
  return s;
}

//---------------------------------------------------------------------------------
void loop()
{
  if (flag == true)                                                       // Se a contagem tiver terminado
  {
    flag = false;                                                         // Impede nova impressao
    frequencia = (pulses + (multPulses * overflow)) / 2  ;                // Calcula a soma dos pulsos contados no PCNT
    printf("Frequencia : %s", (ltos(frequencia, buf, 10)));               // Print frequencia com pontos
    printf(" Hz \n");                                                     // Print unidade Hz

#if defined LCD_ON || defined LCD_I2C_ON                                  // Se estiver usando LCD ou LCD I2C  
    lcd.setCursor(2, 1);                                                  // Posiciona cursor na posicao 2 da linha 1
    lcd.print((ltos(frequencia, buf, 10)));                               // Print frequencia no LCD
    lcd.print(" Hz              ");                                       // Print unidade Hz no LCD
#endif

    multPulses = 0;                                                       // Zera contador de overflow
    // Espaco para qualquer função
    delay (100);                                                          // Delay 100 ms
    // Espaco para qualquer função

    pcnt_counter_clear(PCNT_COUNT_UNIT);                                  // Zera o contador PCNT
    esp_timer_start_once(timer_handle, janela);                           // Inicia contador de tempo de 1 segundo
    gpio_set_level(OUTPUT_CONTROL_GPIO, 1);                               // Porta de controle - habilita contagem dos pulsos
  }

  String inputString = "";                                                // Limpa string para entrada de dados
  oscilador = 0;                                                          // Zera o valor da frequencia
  while (Serial.available())                                              // Enquanto tiver dados na serial
  {
    char inChar = (char)Serial.read();                                    // Le um byte:
    inputString += inChar;                                                // Adicione na string:
    if (inChar == '\n')                                                   // Se pressionar ENTER:
    {
      oscilador = inputString.toInt();                                    // Transforma a string em inteiro
      inputString = "";                                                   // Limpa a string
    }
  }
  if (oscilador != 0)                                                     // Se foi digitado algum valor
  {
    inicializa_oscilador ();                                              // Reconfigura a frequencia do oscilador
  }
}
