Medir RPM ESP-32

Hola, estoy tratando de medir la RPM de un motor DC pequeño a través del MCU ESP32, sensor optico ITR8102 y encoder de 20 ranuras.

El programa que tengo funciona muy bien para el Arduino uno pero no para el esp32, les presento el codigo que funciona con Arduino:


/*Arduino Uno
Medición RPM con sensor ITR8102 y encoder 20 ranuras*/

volatile int contador = 0;
volatile int rpm = 0;  

void setup() {
  Serial.begin(57600);
  attachInterrupt(0,rutina,FALLING);
}

void loop() {
  delay(1000);
  rpm=(60*contador)/20;
  Serial.println(rpm);
  cont=0;
}

void rutina(){
  contador=contador+1;
}

Este código lo quise usar para el ESP32 y no funciono, el primer problema fue que no contaba bien las ranuras del encoder por que tenía mucho rebote (debounce). Después de haber investigado solucione que el sensor óptico contara de forma correcta las ranuras del encoder.

con el siguiente código el sensor cuenta las ranuras de forma correcta:

/*ESP 32
Contador sensor óptico ITR8102 y encoder 20 ranuras*/

volatile int contador = 0; //variable que se ejecutara al hacer la interrupción
int sensor = 23;  // pin que uso para hacer interrupción

volatile unsigned long tiempoDeInterrupcionAnterior = 0;
#define tiempoDeRebote 200

void IRAM_ATTR interrupcion(); // es para que la funcion se guarde en la memoria ram y no en la flash

void setup() {
  pinMode(23,INPUT);
  Serial.begin(115200);
  attachInterrupt(23,interrupcion,FALLING); // utilizo IO23 de interrupción
}

void loop(){
  /*Al introducir esta formula que funciono con el Arduino uno,
  en el ESP 32 me entrega datos diferentes.
  
  delay(1000); 
  rpm=(60*contador)/20;
  Serial.println(rpm);
  cont=0;
  
  explicación de la formula:
  Con un retardo de 1 segundo automáticamente la variable contador
  va a guardar el numero de interrupciones en ese segundo y 
  se multiplica por 60 entre el numero de ranuras que es 20 en mi caso.
*/
}

void IRAM_ATTR interrupcion(){
  if(millis() - tiempoDeInterrupcionAnterior > tiempoDeRebote){

   contador = contador+1;
   //Serial.println(contador);
    
    tiempoDeInterrupcionAnterior = millis();
  }                                                            
}

imagen

Agradecería cualquier comentario sobre este fallo, por que en Arduino Uno funciona bien y en el ESP-32 obtengo datos distintos, Saludos.

Hola @X_rramirez5411 .
La fuente de alimentación del fototransistor es de + 5 V,
para no dañar el puerto ESP32, use + 3.3V.

Tal vez a + 5V la salida del fototransistor no llegue a 0V

RV mineirin

Hola ruilviana gracias por comentar, alimente el sensor óptico ITR8102 CON 3.3V del ESP-32 "para evitar dañarse" y cuenta bien las ranuras del encoder.

Sin embargo las RPM sigo obteniendo los mismos resultados. Muestro a continuación una tabla de comparación del Arduino y ESP32.

Se logra apreciar que la lectura que hago del Arduino Uno coincide con el tacometro digital. Aun no se cual sea la razón por que no es el mismo resultado si de un lado el código funciona muy bien y para el otro no, a lo mejor el ESP-32 aplica otra formula, pero voy a seguir investigando para saber por que las lecturas del ESP-32 siempre me dan esos números, saludos y otra vez gracias por el consejo.

Hola @X_rramirez5411 .
Fui aquí para probar su boceto para ESP32 y di un error de compilación.
Queda por definir las variables "rpm" y "cont".
Busqué en el boceto para arduino y encontré la variable "rpm" pero no encontré la variable "cont".
RV mineirin

lo siento, ese fue un error mio que se me fue de uno de los borradores que hice, este es el codigo bueno para el ESP-32:


volatile int contador = 0; //variable que se ejecutara al hacer la interrupción
volatile int rpm = 0;
int sensor = 23;  // pin que uso para hacer interrupción

volatile unsigned long tiempoDeInterrupcionAnterior = 0;
#define tiempoDeRebote 200

void IRAM_ATTR interrupcion(); // es para que la funcion se guarde en la memoria ram y no en la flash

void setup() {
  pinMode(sensor,INPUT);
  Serial.begin(115200);
  attachInterrupt(sensor,interrupcion,FALLING); // utilizo IO23 de interrupción
}

void loop(){
  delay(1000); 
  rpm=(60*contador)/20;
  Serial.println(rpm);
  contador=0;
}

void IRAM_ATTR interrupcion(){
  if(millis() - tiempoDeInterrupcionAnterior > tiempoDeRebote){

   contador = contador+1;
   //Serial.println(contador);
    
    tiempoDeInterrupcionAnterior = millis();
  }                                                            
}

Es con el que se supone que debería funcionar como el Arduino Uno.

Hola @X_rramirez5411 .
mira si este esquema funciona como necesitas

La rutina debugTest no es necesaria.
Produce una cadena de pulsos en lo pin 2 que se pueden inyectar en lo pin 23 para simular.
Solo haz un puente de 2 a 23.
Si no desea usarlo, puede eliminarlo, pero comente la línea que lo llama en setup ().

RV mineirin

volatile int contador = 0;
volatile int rpm = 0;
void IRAM_ATTR rutina() ;
void debugTest();
//-------------------------------------------------------------
void debugTest() {
  ledcSetup(7, 39, 20);       // Chn 7  39 HZ resolution 20
  ledcAttachPin(2, 7);         // Pin2 is output
  ledcWrite(7, 524288);        // Chn 7 Duty 50% = 524288
}
//-------------------------------------------------------------
void IRAM_ATTR rutina() {
  contador = contador + 1;
}
//-------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  debugTest();
  attachInterrupt(23, rutina, FALLING);
}
//-------------------------------------------------------------
void loop() {
  delay(1000);
  rpm = (60 * contador) / 20;
  Serial.println(rpm);
  contador = 0;
}


Moderador:
Los temas que no son AVR (Arduino) van en Microcontroladores.
Movido a dicha sección.

Gracias ruilviana, ese codigo me dio al fin valores diferentes a los del otro codigo, pero no coinciden con los del tacómetro digital.

Voy a investigar más sobre resoluciones del ADC, por ahí debe de ser el problema. Gracias otra vez ruilviana.

Lo que encuentro yo es que el optoacoplador requiere mas corriente de la que esta recibiendo a 3.3V.
El arduino puede al tener salida de 5V lo logra pero el ESP32 a 3.3V no alcanza.

Veamos dice que con una If de 20mA tienes una caida en el diodo de 1.2V (ver hoja de datos)
Para 5V la cuenta con una R de 220ohms da

I = (5 -1.2)/220 = 17.2mA
pero para 3.3V la R debe ajustarse

R = (3.3 -1.2)/20mA = 105 ohms
Debes usar una R de 105 o 120 ohms incluso una de 100 ohms para que funcione correctamente en el ESP32
Prueba y nos cuentas.

Hola Surbyte gracias por contestar, acabo de hacer la prueba y estos fueron los resultados que obtuve:

Con la resistencia de 100 Ohm y alimentado con los 3.3V del Esp32 esta mas cerca del valor que esta proporcionando el tacometro digital:

imagen

Como el Arduino maneja 8 bits de resolución y el ESP32 tiene muchas resoluciones voy a investigar sobre este tema.

Yo movería

contador = 0;

a la primer línea de loop() para que el delay() ocurra inmediatamente luego de ponerlo a 0.

El tiempo de anti rebote en la interrupción del código de los posts #1 y #5 me parece muy largo, creo que 200 mseg es exagerado y se dejan de contar pulsos entre tanto.

Tomando el ejemplo de 139 RPM
139 / 60 seg da casi 2.32 RPS.
2.32 × 20 ranuras generan unos 46 pulsos.
1/46 da unos 21.7 mseg entre pulsos.

Corrijanme si me equivoco.

Quiero decir que si tomas como base esa rutina de interrupción, desde mi punto de vista no te va a funcionar (aunque no se cuál código finalmente estás depurando).

Saludos

Hola gatul, los códigos del post #1 y #5, les coloque interrupción debido a que lo primero que necesitaba es comprobar si estaba contando bien las ranuras del encoder, al principio se saltaba la cuenta porque había mucho rebote y lo solucione con 200 milisegundos de espera.

Despues en el post #7 ruilviana me proporciono otro codigo sin interrupciones que me daba otros datos que a los del post #5 (en mi codigo solo obtenía 3 números y en el del post#7 proporcionaba datos más reales que son los del ejemplo 139 e hice un cambio de resistencia de 100 en lugar de 220 Ohms), sin embargo, no se acercaba a los datos del tacómetro digital.

En el Arduino funcionaba muy bien el codigo, yo creo que debe ser un problema de ajustar resolución del ESP32, desconozco de ese tema, tengo que investigarlo, saludos gatul y gracias por contestar.

No se porque repites a cada momento la palabra resolución!!
Estas contando pulsos. No usas un ADC donde si tiene sentido lo que dices, los 10 bits del Arduino contra los 12 del ESP32.

Los códigos a mi en lo personal no me gustan.

Una rutina como esta

void loop(){
  delay(1000); 
  rpm=(60*contador)/20;
  Serial.println(rpm);
  contador=0;
}

Tiene un gran defecto y es que los pulsos deben contarse en una ventana de tiempo. En tu caso el delay de 1000 mseg.
Pones el delay pero el sistema sigue contando.
Tienes que contar en medio de la ventana de tiempo, luego detener las interrupciones, poner a 0, presentar y volver a activar las interrupciones para el proximo ciclo de lectura.

Creo que en algo le erraste...

Aunque no me gusta ese delay() reconozco que es conveniente como ventana asi que lo haría de esta forma

void loop(){

  contador=0;
  delay(1000);
  noInterrupts();
  rpm=contador*3; // 60/20=3;
  interrupts();
  Serial.println(rpm);
}

Porque si no habilita las interrupciones antes del println() va a haber problemas con Serial.

De todos modos, en el rango de revoluciones que está midiendo no me parece que sea absolutamente necesario deshabilitar las interrupciones.
A lo sumo podrá tener un error de un pulso por medición, por lo que le marcaría unas 3 RPM de más, muy de vez en cuando.

gatul y surbyte gracias por tomarse el tiempo de contestar, envió los resultados de las lineas de código que enviaste.

Estas lineas las integre en el código que había puesto originalmente:

volatile int contador = 0; 
volatile int rpm = 0;
int sensor = 23;

volatile unsigned long tiempoDeInterrupcionAnterior = 0;
#define tiempoDeRebote 150

void IRAM_ATTR interrupcion(); // es para que la funcion se guarde en la memoria ram y no en la flash

void setup() {
  pinMode(sensor,INPUT);
  Serial.begin(115200);
  attachInterrupt(sensor,interrupcion,FALLING);
  //attachInterrupt(digitalPinToInterrupt(23),interrupcion,RISING);
}

  void loop(){
  contador=0;
  delay(1000);
  noInterrupts();
  rpm=contador*3; // 60/20=3;
  interrupts();
  Serial.println(rpm);
}

void  interrupcion(){
  if(millis() - tiempoDeInterrupcionAnterior > tiempoDeRebote){

   contador=contador+1;
   ///Serial.println(contador);
    
    tiempoDeInterrupcionAnterior = millis();
  }                                                            
}

No entiendo por que sigo obteniendo estos resultados:

imagen

Solo me da 2 numeros como resultados, no se a que se deba este problema

Tambien esas lineas de código las integre en el que me proporciono ruilviana.

volatile int contador = 0;
volatile int rpm = 0;
void IRAM_ATTR rutina() ;
void debugTest();

#define sensor 23
//-------------------------------------------------------------
void debugTest() {
  ledcSetup(7, 39, 20);       // Chn 7  39 HZ resolution 20
  ledcAttachPin(2, 7);         // Pin2 is output
  ledcWrite(7, 524288);        // Chn 7 Duty 50% = 524288
}
//-------------------------------------------------------------
void IRAM_ATTR rutina() {
  contador = contador + 1;
}
//-------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  debugTest();
  attachInterrupt(sensor, rutina, FALLING);
}
//-------------------------------------------------------------
void loop() {
  contador=0;
  delay(1000);
  noInterrupts();
  rpm=contador*3; // 60/20=3;
  interrupts();
  Serial.println(rpm);
}

Surbyte, la razón de por que comentaba mucho la palabra resolución era por este código, lo que tiene diferente al código que yo presente son dos cosas:

1- La parte void debugTest().
2- En la interrupción no existe tiempo de rebote.

Y sus resultados se me hacen mejores que al mio aunque un poco alejados a los del tacometro digital.

Resultados obtenidos:
imagen

Probá con tiempos de antirrebote entre 10 a 15 mseg. a ver como se comporta.

Si tenes pulsos cada 20 mseg (para redondear) y vos pones un antirrebote de 150 mseg, 6 pulsos no los vas a leer porque los filtras como si fuesen rebotes cuando no lo son.

Agrego:
¿Pero por qué el código trabaja bien con el arduino y no con el ESP?

Viendo las hojas de datos de los micros se entiende más fácilmente.

Para el ATMega un nivel alto tiene que ser superior a 0.6×Vcc, o sea que para una alimentación de 5V, el nivel se toma como alto a partir de 0.6×5V = 3V.
El nivel es bajo cuando está por debajo de 0.3×Vcc, o sea 0.3×5V = 1.5V.

Sin embargo para el ESP32 los niveles son:
Alto a partir de 0.75×Vcc entonces por encima de 0.75×3.3V = 2.475V se toma como alto.
El nivel bajo tiene que estar por debajo de 0.25×Vcc, o sea, 0.25×3.3V = 0.825V

Esa diferencia en los niveles de tensión para cada estado es lo que hace que una señal (en este caso, un posible rebote) que para el arduino es invisible porque no está por encima/debajo de los niveles alto/bajo, el ESP lo vea mucho más fácilmente como un pulso.

Por ejemplo, un rebote con una tensión de 2.8V el arduino no lo ve, por lo que no verá tampoco el flanco de caída de ese rebote. En cambio el ESP si lo detecta (porque pasa los 2.475V) y claramente detectará su flanco de bajada generando una interrupción.

Espero que se entienda mi explicación.

Saludos

Hola gatul, tome lectura de voltaje en la IO23 y es de 3.1 V, significa que si esta entrando el voltaje adecuado.

Realicé la prueba de disminuir el tiempo de rebote como me indicaste y el resultado fue satisfactorio (Coinciden los datos del ESP32 y tacómetro digital), este es el código en el cual hice la prueba:

volatile int contador = 0; 
volatile int rpm = 0;
int sensor = 23;

volatile unsigned long tiempoDeInterrupcionAnterior = 0;
#define tiempoDeRebote 14 

void IRAM_ATTR interrupcion(); // es para que la funcion se guarde en la memoria ram y no en la flash

void setup() {
  pinMode(sensor,INPUT);
  Serial.begin(115200);
  attachInterrupt(sensor,interrupcion,FALLING);
  //attachInterrupt(digitalPinToInterrupt(23),interrupcion,RISING);
}

  void loop(){
 //void loop hecho por gatul
  contador=0;
  delay(1000);
  noInterrupts();
  rpm=contador*3; // 60/20=3;
  interrupts();
  Serial.println(rpm);
}

void  interrupcion(){
  if(millis() - tiempoDeInterrupcionAnterior > tiempoDeRebote){

   contador=contador+1;
   ///Serial.println(contador);
    
    tiempoDeInterrupcionAnterior = millis();
  }                                                            
}

Resultados obtenidos:

imagen

imagen

En lo que he investigado del ESP a diferencia del Arduino es que existe mucho rebote al momento de tomar lecturas. No se si sea recomendable hacer un filtro para que tome lecturas más exacto porque a veces existe rebotes de lectura de ±3 rpm.