Arduino ESP32 y su sistema de interrupciones.

Scooter

Cascarrabias crónico
¿Alguien ha usado las interrupciones por pin externo en un ESP32 desde la plataforma Arduino?
O soy tonto perdido (lo mas seguro) o está mal implementada el API, es un problema de hardware o lo que sea.
Siguiendo el manual "oficial", GPIO — Arduino-ESP32 2.0.14 documentation sencillamente "no va"
Casuística:
  • ONHIGH u ONLOW hace lo que debe, un tren de interrupciones mientras está alto o en bajo
  • RISING, FALLING o CHANGEn es indiferente, hace un tren de pulsos mientras la señal sube y mientras la señal baja
Con arduinos "normales" va perfecto. El esquema es el del detector por paso por cero (el pc 814) que ya expuse en el foro:

1559233991756-png.179317

El tema es que lo he solucionado haciendo el boticario con filtros, ignorando interrupciones durante un tiempo y peleando de mil maneras pero una ISR que era "cuenta++: " pasa a ser un galimatías.

También he intentado fitrar en hardware pero mas bien se empeora que se mejora.

A poner un trigger-shmith no he probado, pero ocuparía mas que el ESP porque estoy usando un xiao de seeedstudio que es diminuto

¿Alguien tiene experiencia en el tema?
Parece que el detector de flanco es hipersensible y cualquier fluctuación lo dispara dando un tren de pulsos en la subida y otro en la bajada, probablemente porque tenga ruido la señal.

Con los 50Hz de la red lo tengo fácil porque sé la frecuencia etc y puedo eliminar lo que sobra por software pero luego tengo que leer un resolver con un circuito parecido y la frecuencia cambia mucho, estoy temblando para cuando llegue.
 
Última edición:
Empezaria verificando como es la señal de salida del PC814 y que relacion guarda con la tension de linea. En un mundo ideal debiera ser un pulso rectangular centrado sobre el paso por cero de la linea, obviamente repetido a un ritmo de 50Hz. Sin embargo atencion que no es de ancho nulo (no es un impulso!). Tiene un ancho determinado por el voltaje de encendido de cada led. Calculando en que momento la tension de linea pasa por el valor de encendido del led es posible determinar cuando el transistor de salida del PC814 se apaga. Ojo que este valor ( que obviamente se traduce en un valor de pico sobre la linea de alimentacion) es tambien la inmunidad al ruido del circuito.
Por ejemplo, el PC814 necesita +/- 1,2 V (circula una corriente de +/- 20 mA en este caso) para que los leds enciendan y provoquen el encendido del transistor de salida (lo cual "tira" la salida a 0V). En un mundo ideal una senoide de valor inferior 2,4 V pico a pico no tendria que encender el transistor y la salida (pata 4) debiera mantenerse en valor logico alto (+Vcc en este caso segun parece).
Y aca viene la segunda comprobacion, la pata 4 deberia tener una resistencia "Pull Up" activada de forma interna en el micro o para mayor seguridad colocada externamente. Una resistencia de 10K entre la pata 4 y el +Vcc deberia eliminar cualquier indeterminacion en el pulso de salida.
Usualmente este circuito (que es un clasico de los detectores de cruce por cero) no necesita nada mas para funcionar. Sin embargo puede agregarse un capacitor en paralelo con las patas 1 y 2 y otro entre la pata 4 y masa (siempre y cuando se encuentre presente el "Pull Up" de 10K). Ambos capacitores en conjunto con las resistencias R3 y el Pull Up deben formar una constante de tiempo no mayor a la decima parte de la frecuencia de repeticion de la red electrica. Dicho de otra forma estas construyendo un pasabajos que corta en 500 Hz para un suministro de 50 Hz. Eso deberia suprimir cualquier porqueria que venga por la linea de alimentacion.
 
Luego pongo el código y los oscilogramas.
La onda no es cuadrada, es un pico "campana de gauss" pero limpio.
El pin lleva activado el internal pullup, he probado a bajar esa resistencia con una resistencia externa y a poner un filtro RC e incluso empeora.
He consultado con un colega y le pasa lo mismo. Las interrupciones de los ESP van regumal. Lo había leído en otros foros también.
Algo debe de haber porque el código de la web de espressive es bastante tramposo y en muchos foros reportan ruido con ese montaje a pesar de los flags que se pasan de un lado a otro.

El problema de los filtros soft o hard es leve cuando se conoce la frecuencia.
El problemón es que pretendo usar el montaje para regular la velocidad de un motor de lavadora, que va de unas 5000 a 25000rpm y ahí los filtros se complican muy mucho
El motor lleva un resolver que da 8 pulsos por revolución.

Con Arduino normal ya está hecho un control de fase + realimentación por el resolver + PID y funciona aceptablemente bien, lo único que el sistema está bastante al límite ya y por eso la idea de usar una CPU más potente.
 
Última edición:
Yo iría descartando los posibles problemas mediante el uso de un debug usando el puerto serie o similar:

. ¿Funciona bien la irq por detección de flancos? Probaría usando un pulsador tradicional con un bruto R-C para evitar rebotes. Se debería detectar un único flanco, descartando problemas en el soft/detección de flancos.

- Si lo anterior va bien, agregaría el circuito detector por cruce en 0 y quitaria las rutinas del timer, de tal forma que puedas obtener en una variable, la cantidad de flancos detectados en un cierto tiempo.
Deberías detectar 50 por cada segundo.

Si lo anterior tiene problemas, habrá que analizar si la señal que genera el detector es correcta.
 
El problemón es que pretendo usar el montaje para regular la velocidad de un motor de lavadora, que va de unas 5000 a 25000rpm y ahí los filtros se complican muy mucho
El motor lleva un resolver que da 8 pulsos por revolución.
Y si en vez de usar interrupciones para el conteo de los flancos a 3.5kHz (o 7kHz si contás ambos flancos) mejor configurás un timer para conteo de pulsos externos???
De esa forma la cuenta se realiza por hardware y la única interrupción es la de muestreo para el controlador que vayas a ejecutar.
 
Y si en vez de usar interrupciones para el conteo de los flancos a 3.5kHz (o 7kHz si contás ambos flancos) mejor configurás un timer para conteo de pulsos externos???
De esa forma la cuenta se realiza por hardware y la única interrupción es la de muestreo para el controlador que vayas a ejecutar.
Eso está hecho así en el Arduino, T0 para el sistema, T1 cuenta los pulsos del resolver y T2 hace la temporización de el control de fase.
En el ESP32 no hay API implementada para contador así que o lo hago con los registros a pelo, cosa que no sé si sabré hacer o lo hago por interrupciones que van como van.


El código:
C:
/*
Esp32 detección de paso por cero
*/

int pCero = 0;    //Cuenta de pasos por cero


void IRAM_ATTR interr(){
  digitalWrite(D1,HIGH);
  pCero++;    //Cuenta pulsos
  digitalWrite(D1,LOW);
}

void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Detección de paso por cero");
pinMode(D0,INPUT_PULLUP);     //Pin de entrada de interrupciones
pinMode(D1,OUTPUT);           //Pin para mostrar cuando y como entran las interrupciones
attachInterrupt(D0,interr,RISING);    // Debería de hacer solo un pulso en el flanco de subida pero...

}

void loop() {
  // put your main code here, to run repeatedly:
Serial.print("Pasos por cero :   ");
Serial.println(pCero);    //Debería de contar 100 ya que son 100 semiperiodos
pCero = 0;
delay(1000);
}

Debería de contar 100 y cuenta cualquier tontería, del orden de 1000.
Si se cambia a RISING, FALLING o CHANGE da lo mismo y los oscilogramas se ven igual
Si lo cambias a ONHIGH u ONLOW da mucho mas la cuenta y el oscilograma se ve donde se tiene que ver, todo pulsos arriaba o abajo.11.png
13.png

Da la sensación que el ruidito que aparece lo cuenta como pulsos, en los arduinos normales no va así, debe de tener un trigger interno, o bien la CPU es tan lenta que se los pierde, se le cuelan entre ciclo y ciclo que también puede ser.
El caso es que filtrando no mejora.

A 50Hz y usando el timer para disparar el triac, precisamente eso salva bastante el tema, el primer pulso que entre, lanza el timer y ya no se hace caso hasta la interrupción del timer pero cuando la frecuencia sea variable y no se use timer...


Me parece que hay un fallo de diseño en el ESP.
Usando este código va bien, detecto el flanco "a mano":
C:
/*
Esp32 detección de paso por cero
*/


unsigned long hora=0;
int pCero = 0;    //Cuenta de pasos por cero
int alba = 1;   //flag para saber como vamos


void IRAM_ATTR interr(){
  attachInterrupt(D0,interr,DISABLED);
  if(alba==1){
    alba=0;
    delayMicroseconds(10);
    attachInterrupt(D0,interr,ONHIGH);
    alba=2;
    pCero++;
  }
  else if(alba==2){
    alba=0;
    delayMicroseconds(10);
    attachInterrupt(D0,interr,ONLOW);
    alba=1;

  }

  //pCero++;
}

void setup() {
  // put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Detección de paso por cero");
pinMode(D0,INPUT_PULLUP);
pinMode(D1,OUTPUT);
attachInterrupt(D0,interr,ONHIGH);    // En el ESP32 hagas lo que hagas solo va HIGH

}

void loop() {
  // put your main code here, to run repeatedly:
Serial.print("Pasos por cero :   ");
Serial.println(pCero);
pCero = 0;
delay(1000);
}
Pero no deja de ser un lío interesante todo eso en lugar de poner "contador++" que es lo que hace falta.
 
Última edición:
Tenés razón, es cruce por 0, deberías obtener 100 y no 50.

Acá hay un ejemplo del uso de las interrupciones y menciona tres formas de agregarlas, una que sería la correcta y dos que no:

Syntax​


attachInterrupt(digitalPinToInterrupt(pin), ISR, mode) (recommended)
attachInterrupt(interrupt, ISR, mode) (not recommended)
attachInterrupt(pin, ISR, mode) (Not recommended. Additionally, this syntax only works on Arduino SAMD Boards, Uno WiFi Rev2, Due, and 101.)

Y más adelante da los pasos que se deben realizar para agregar esa interrupción:

C:
const byte ledPin = 13;
const byte interruptPin = 2;
volatile byte state = LOW;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(interruptPin, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(interruptPin), blink, CHANGE);
}

void loop() {
  digitalWrite(ledPin, state);
}

void blink() {
  state = !state;
}

Tal vez ese "digitalPinToInterrupt(...)" hace la diferencia al convertir el pin en el número de la interrupción.

Editado:

Una alternativa que se me ocurre probar si lo de arriba no hace diferencia, es apagar la interrupción con un "detachInterrupt()" y al finalizar el ciclo normal del cruce por cero (la finalización del timer en su último estado), volver activar la interrupción. En "C" tradicional, sin librerías, sería apagar un flag, acá no se si se puede hacer eso.
 
Última edición:
Ya, digitalPintoInterrupt hace falta, o es una buena práctica porque en muchos Arduinos la interrupción 0 está en el pin 3, la 1 en el 7 y todo random. El XIAO "D0" es digital, analógico, interrupción y todo lo que se te ocurra, como debería de ser lógico.

Detach Interrupt ya lo he probado y entonces deja de ir
attachInterrupt( bla bla DISABLED) tampoco mejora gran cosa

Aparte del código que he mostrado que lo "soluciona" he probado mil combinaciones, y ninguna me convence, funcionan pero a base de consumir recursos, tiempo etc Lo mas "limpio" es lo de arriba porque solo hay un delay de 10uS y va bastante bien.

Si todo funciona en los "no ESP" en varios ESP que tengo el ESP32S3, ESP32C3, ESP32 WROOM mas o menos funciona igual de mal. Las soluciones "oficiales" son poner un polling para reativar la ISR con un flag, poner un retardo de 250ms para una nueva interrupción y tonterías semejantes.
 
Lo que hiciste es una especie de anti-rebote por soft.

En base a los que decís, habría que ver si este comportamiento lo tiene con los sdk propios que te brinda Esp y no sea un problema por algún manejo errónea por parte de las librerías de Arduino.
 
Pueeeeeeessss

He probado con un trigger externo y no se nota la mejoría gran cosa, con el filtro por software va un poquito mejor entran menos 101 cuando todos deberían de ser 100. Sin el filtro por soft va mal, cuenta 400 o así

Pero probando probando la cosa toma mal cariz...
Si la rutina de interrupción es:
"cuenta++ "
Entonces cuenta como el doble bastante aproximadamente 200 o así, es decir que es CHANGE aunque pongas RISING o FALLING


Si la ISR es:
digitalWrite(D5,HIGH);
pCero++;
digitalWrite(D5,LOW);

Para ver los pulsos donde están si al inicio, si al final...
Entonces cuenta 1500, la interrupción está en D0, he probado a cambiar en varios pines y no se nota.
Llegados a este punto parece que la interrupción se dispara por cualquier cambio en un pin del port o algo así, y como yo cada vez que cambia muevo pines entonces se realimenta.
Y si no es eso, al menos es lo que parece. Puede que sencillamente la fluctuación de un pin afecte al otro, cosa que también tiene mucha guasa.
 
Llegados a este punto parece que la interrupción se dispara por cualquier cambio en un pin del port o algo así, y como yo cada vez que cambia muevo pines entonces se realimenta.
Has leído el datasheet del ESP32?? (Yo no)
Por que resulta que el Atmega328 del Arduino, si bien tiene interrupciones por pines, solo lanza la interrupción "por puerto". Esto es, no importa cual pin cambie --> se dispara una interrupcion "cambió un pin del puerto X" y luego hay que salir a buscar si es el que te interesa o nó.
Hace un tiempo encontré una API que solucionaba este inconveniente y te permitía adjuntar una función a cada pin, mas un pequeño código que discriminaba la ocurrencia de la interrupción en un pin específico y ejecutaba la función correspondiente.
Si el ESP32 tiene el mismo comportamiento es probable que ocurra lo que estás comentando...
 
A eso huele, la verdad.
En el API del ESP32 no dice nada. Habrá que leer el datasheet original.

En Arduino funciona bien la interrupción por cualquier pin, solo que solo se puede poner FALLING pero no se lía.

A mí me huele a mala implementación de la API.

Voy a cambiar la alimentación a ver si eso también influye.
 
Se me ocurre el siguiente paliativo para evitar falsos positivos, usando directamente polling en vez de una interrupción:

C:
#include <FlexiTimer2.h>

#define IDLE_STATE                            0
#define DPC_FIRST_CHECK_STATE        1
#define SET_HIGH_TRC_STATE            2
#define SET_LOW_TRC_STATE                3

#define DPC_PIN DO
#define TRC_PIN D1

int waitTime;
int stateVar = IDLE_STATE;

void timer(){
   
    switch(stateVar)
    {
        case DPC_FIRST_CHECK_STATE:
            if(digitalRead(D0) > 0)
            {
                // First check pass after 100uS later
                stateVar = SET_HIGH_TRC_STATE;
            }
            else
            {
                // Bounce or noise
                stateVar = IDLE_STATE;
                FlexiTimer2::stop();
            }
            break;

        case SET_HIGH_TRC_STATE:
            if(digitalRead(DPC_PIN) > 0)
            {
                // Second check pass after 200uS later
                stateVar = SET_LOW_TRC_STATE;
               
                digitalWrite(TRC, HIGH);
                FlexiTimer2::stop();
                FlexiTimer2::set(waitTime, 1.0/10000, timer); // Set waitTime*100uS "tick"
                FlexiTimer2::start();
            }
            else
            {
                // Bounce or noise
                stateVar = IDLE_STATE;
                FlexiTimer2::stop();
            }
            break;

        case SET_LOW_TRC_STATE:
            // Clear TRC PIN  after waitTime*100uS later
            stateVar = IDLE_STATE;
           
            digitalWrite(TRC, LOW);
            FlexiTimer2::stop();
            break;
     
        default:
            // Wrong state!
            stateVar = IDLE_STATE;
            FlexiTimer2::stop();
    }
}


void setup() {
    // put your setup code here, to run once:
    pinMode (DPC_PIN,INPUT_PULLUP);
    pinMode (TRC_PIN,OUTPUT);
    digitalWrite(TRC_PIN,LOW);
}

void loop() {
    // put your main code here, to run repeatedly:
    waitTime = map(analogRead(0),0,1023,0,100);

    if((stateVar == IDLE_STATE) && (digitalRead(DPC_PIN) > 0))
    {
        stateVar = DPC_FIRST_CHECK_STATE;
        FlexiTimer2::stop();
        FlexiTimer2::set(1, 1.0/10000, timer); // Set 100uS "tick"
        FlexiTimer2::start();
    }
}

Cuando soluciones el problema de la interrupción, la modificación a ese código, quedaría de esta forma:

C:
...
void IRAM_ATTR dpcIrq(){
    if(stateVar == IDLE_STATE)
    {
        stateVar = DPC_FIRST_CHECK_STATE;
        FlexiTimer2::stop();
        FlexiTimer2::set(1, 1.0/10000, timer); // Set 100uS "tick"
        FlexiTimer2::start();
    }
}
...
void setup() {
    // put your setup code here, to run once:
    pinMode (DPC_PIN,INPUT_PULLUP);
    pinMode (TRC_PIN,OUTPUT);
    digitalWrite(TRC_PIN,LOW);

    attachInterrupt(digitalPinToInterrupt(DPC_PIN), dpcIrq, ONHIGH);
}
...
void loop() {
    // put your main code here, to run repeatedly:
    waitTime = map(analogRead(0),0,1023,0,100);
}

Y timer te queda igual.
 
Has revisado este sitio:
???
Lo hacen muy fácil con un pulsador...lo que no quiere decir que esté OK...

Yo estuve revisando el manual técnico y el datasheet del ESP32...y no es para cristianos, al menos la parte de interrupciones: es un soberano despelote atómico!!!
 
Si que he probado el trigger y no va, mejora residualmente.
El código de drZ no va bien además de que no me sirve.

Este esp no tiene dos núcleos si no dedicaba uno a contar flancos...
 
El código de drZ no va bien además de que no me sirve.
Que es lo que le ocurre a ese código??
Seguramente tiene rebotes, pero mas adelante hay una rutina tonta para solucionarlo.
No digo que te sirva tal como está, pero para contar pulsos colocando un transistor en lugar del pulsador debería servir.
Otra cosa que se deduce del datasheet es que la absoluta mayoría de las interrupciones son activadas por nivel y no por flancos, aunque hay un par que sí que usan flancos, pero el despelote de la documentación es taaaaaan grande que es casi imposible deducir a que corresponde cada cosa en la descripción.
 
Ese código no funciona, está reportado en bastantes sitios acaban poniendo un delay de carril en algún sitio para que funcione, además de que no me sirve porque si tengo que poner cosas en el loop que desinterrupcionen la interrupción interrumpida, para eso detecto el flanco en el loop.
 
delay de carril
:oops::oops::oops::oops: Que es un "delay de carril" ??? La solución a los rebotes solo usa el millis( ) para evitarlos, pero con un transistor no hay rebotes.

además de que no me sirve porque si tengo que poner cosas en el loop que desinterrupcionen la interrupción interrumpida
La ISR no puede interrumpirse a menos que se reactive el flag de habilitación dentro de ella. Por lo general las interrupciones nunca son reentrantes...

La verdad es que no sé si ese código funciona, ni quien reporta en otros sitios que no funciona, pero es un código muy bobo como para que dé fallas. Por otra parte, el ESP32 trabaja igual que el Atmega328 del Arduino: las interrupciones por pin se disparan indicando que un pin interrumpió según lo que comentan en StackExchange y luego hay que salir a buscar cual fué el que hizo lío.

Pero por otra parte, YO no encuentro ni en el datasheet ni en el manual de referencia técnica que el ESP32 soporte interrupciones por pin. Lo que sí soporta son interrupciones por "touch sensor" que trabajan de una forma medio rara usando una máquina de estados finitos que determina cuando el tren de pulsos (WARNING!) puede interpretarse como un click en el touch sensor. Hay que leer bien la info del fabricante (que tampoo es taaaan clara), pero me está pareciendo que para disparar una interrupción por pin lo que hace la API de Arduino es configurarlo como si fuera un sensor touch. El disparo de la interrupción depende de la máquina de estados asociada a ese pin, que está cargando y descargando la capacidad del touch-pad (que obviamente no existe) y eso genera pulsos a un ritmo de 8 Mhz.
Lo que YO haría sería buscar la API del Arduino IDE para el ESP32, abrir los archivos de código fuente y ver que es lo que está haciendo con las interrupciones por pin.... por que no puede ser algo tan raro...

Y esto está tomado del propio Espressif, y es del 2024:
Y habla de como programar las interrupciones de pin usando la API Arduino para ESP32.
Fijate y vas a ver que hace lo mismo del artículo que linkee antes.
 
@cosmefulanito04 : he probado por polling y va perfecto, ni rebotes ni filtros ni falsos nada. Cuenta 100, alguna vez 101 y alguna 99 pero una de cada bastantes. Va mejor que el mejor filtrado de las interrupciones.
@Dr. Zoidberg : Si, si la web oficial la he visto, osea que ellos saben que algo está mal y están echando tierra para disimular. Es absurdo ese código aparte de que va mal, está hecho con un pulsador y no va no hace lo que el fabricante dice que hace.
Un delay de caril, un delay enorme es 1ms, es absurdo.
El motor en vacío alcanza las 25000rpm por 8 pulsos por revolución son 200000 pulsos/min f= 3333,33Hz T=0,3ms osea que un delay de 1ms es demasiado porque perdería tres pulsos.

Una de las formas en las que "mejor" funcionó es leyendo micros() y quitando 250us, es decir, entra al ISR pero no hace nada durante 250us, puede valer pero no deja de ser un incordio que en lugar de entrar 100 veces a una ISR, resulta que está entrando 3000 que aunque no haga nada y no entre el ruido y tal estás gastando 30 veces mas tiempo de máquina del que hace falta sencillamente porque el hardware está mal diseñado.
Algo así, no es lo mismo que un delay porque no mata al sistema, pero le tiene un aire:
C:
void ISR(){
   if (micros()-ultima>250){
   cuenta ++;
   ultima=micros();
   }
}

micros desborda mas frecuentemente que millis, habrá que ver que se hace cuando desborda, el código se ve limpio pero no lo es tanto está entrando 3000 veces en lugar de 100.

Datasheet: Hay muchos ESP32, el que uso es este.



Referencia del fabricante, parece que de aquí es de donde se copia todo el mundo que "sabe", en ocasiones con distorsión:
 
Última edición:
Atrás
Arriba