// 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 // Biblioteca para I2C #include // 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 // 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 } }