High accuracy frequency Meter from 1 Hz to 40 MHz! Resolution of one hertz. Up to eight Digits!
There is also included an Oscillator to test Frequency Meter. Connect the GPIO 33 to GPIO 34 to test it.
The tutorial is in portuguese language. I will translate to english as soon as possible.
There is also a version in the IDF. Wait for it.
Enjoy it!
Obs- the dot in portuguese has a comma function in english. I will translate this soon.
ESP32 Frequency Meter (tutorial in portuguese - you may use Google Tanslator):
https://blog.eletrogate.com/esp32-frequ ... -precisao/
Code: Select all
// 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
#include "soc/pcnt_struct.h"
#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
}
}