Contador múltiple

Hola a todos, hace poco empecé a desarrollar un proyecto el cual consiste en 4 sensores ópticos
Conectados a una placa Mega, cada sensor tiene una señal lógica, las cuales están conectadas a la placa, dentro de la programación los pines referentes a los sensores están configurados como entradas y aplicando lógica directa (1= alto y 0 bajo), cabe anotar que cada pin tiene asociada una IRQ, con el fin de que el programa pueda dedicarse a otra cosa mientras que no susedan cambios en los sensores ( la mayoría del tiempo el MCU está dedicado a la parte gráfica de una pantalla 128x64).

Luego de hacer varias pruebas resultó el primer problema:

  • Cuando dos o mas sensores ópticos cambian de estado al mismo tiempo (algo difícil que se presente pero se da) solo se ejecuta la IRQ de mayor prioridad, excluyendo las demás IRQ, lo cual arroja como resultado perdida de datos .

Alguien me podría ayudar a solucionar el problema de tener múltiples interrupciones habilitadas al mismo tiempo y de este modo. No perder información ?

Sé que una interrupción no puede interrumpir a otra (por defecto y excepto Reset), pero no sabía que las IRQ no se encolaban según prioridad.

De momento quisiera saber si se pueden optimizar las rutinas de ISR, porque tal parece que lo único que podría hacer que se pierdan pulsos, es la duración de una ISR sumado a la que se dispara cada desbordamiento del timer0 (el que hace posible a millis).

Movido a hardware

Haciendo un par de consultas con unos compañeros, me di cuenta de que este problema únicamente se puede solucionar teniendo un Multithreading, en otras palabras, dos arduinos conectados a un mismo bus, lo que se me ocurre es comunicación i2c entre ellos, obviamente maestro esclavo, pero tanbien he pensado en buscar algún elemento que almacene información de un conteo, existe algo parecido?, luego conectarlo al arduino maestro y configirarlo para que cada cierto tiempo lea la información contenida en el dispositivo esclavo.

sebas3124:
me di cuenta de que este problema únicamente se puede solucionar teniendo un Multithreading

Eso es lo ideal, pero con que las ISR fueran super-cortas bastaba para emular semejante funcionalidad; sin embargo, irónicamente hasta en la ejecución multuhilo hay procesos que podrían dar resultados indeterminados por el acceso concurrente (simultáneo desde varias partes). Estos procesos se le denominan atómicos (que no se pueden subdividir).

sebas3124:
en otras palabras, dos arduinos conectados a un mismo bus, lo que se me ocurre es comunicación i2c entre ellos, obviamente maestro esclavo, pero tanbien he pensado en buscar algún elemento que almacene información de un conteo, existe algo parecido?, luego conectarlo al arduino maestro y configirarlo para que cada cierto tiempo lea la información contenida en el dispositivo esclavo.

Los periféricos de los microcontroladores AVR carecen de DMA, lo que quiere decir que hasta la intercomunicación requiere de interrupciones; no hay escapatoria.

I2C no es lo más idóneo porque además de ser semi-dúplex (bidireccional pero no simultáneamente), el maestro solo puede consultar a los esclavos uno a la vez.
USART (puerto serial) es el más adecuado; ya que el maestro no necesita solicitar, sino que se limita a recibir de quien corresponda y punto. Como un Arduino Mega solo dispone de 3 puertos (4 si no necesitas la comunicación por USB), puede que quieras utilizar una alternativa como la interrupción por cambio de estado en un pin (PCINT).

Aunque al final caemos en lo mismo: alguien envía un pulso a manera de IRQ; por lo tanto, hallo más perjudicial que beneficioso separar la tarea entre varios Arduinos. Es más inmediato que la señal llegue al maestro, a que tenga que pasar por un intermediario.
ES POR ESTA RAZÓN QUE INISISTO EN OBSERVAR TUS RUTINAS DE INTERRUPCIÓN.

Podrias simplemente hacer lo mismo que se hace cuando guardamos el estado anterior de un botón para esperar su flanco y entonces determinar que se ha presionado.
Acá en tu caso, podrias conectar las 4 barreras opticas a un PORT (el mismo), y supongamos que se produce una interrupción con otra muy cerca (he visto que hablamos de 1.5 a 5 useg que puede demorar determinada ISR), bien, si eso pasa, tu sistema responde al primer evento.
EL CPU va a bloquear interrupciones asi que se pierden todos los eventos involucrados durante la atención del que la disparó, pero al tener tu el estado anterior simplemente comparas y veras que no solo 1 sino dos bits o mas pueden estar cambiados asi que reaccionarás a eso.

Tu ISR entonces se dispara con el primero real pero atiende a todos los que se produzcan, incluso si los 4 actuaran dentro de esos 5 useg podrias detectarlos.

Que te parece la idea?
Incluso mejor idea sería cablear las 4 señales a una AND de 4 entradas 1 salida y ésta a una interrupción y hacer lo anterior.
Entonces las 4 barreras estas cableadas a 4 pines 3,4,5,6 y a su vez a una AND de 4 entradas con salida conectada al pin2.
Pin 2 es la Interrupción y 3 a 6 son los pines a leer del PORT.

Sabía que lo había leído hace mucho tiempo en épocas del 8080

Sabrán entender que 0,1,2,3 son las 4 barreras conectadas a 3..6

surbyte:
Que te parece la idea?
Incluso mejor idea sería cablear las 4 señales a una AND de 4 entradas 1 salida y ésta a una interrupción y hacer lo anterior.
Entonces las 4 barreras estas cableadas a 4 pines 3,4,5,6 y a su vez a una AND de 4 entradas con salida conectada al pin2.
Pin 2 es la Interrupción y 3 a 6 son los pines a leer del PORT.

Sabía que lo había leído hace mucho tiempo en épocas del 8080

Sabrán entender que 0,1,2,3 son las 4 barreras conectadas a 3…6

Algo así se puede hacer también a nivel de software. Hay que modificar un par de registros que no recuerdo los nombres: uno es para habilitar la interrupción PCINT (interrupción por cambio de estado de pin) correspondiente, y otro es para indicarle cuáles pines del grupo pueden disparar esta interrupción. De hecho es una parte de cómo funciona SoftwareSerial.

Poniendo de ejemplo que los cuatro sensores los conectáramos al grupo de pines de PINB (pin 8 al 13 en Arduinos basados en ATmega328P), la ISR sería algo como:

byte ep = PINB; // Capturamos el estado actual de los pines
for (byte i = 0; i < 4; i++) {
  if ((ep >> i) & 1) // Cuando el "estado activo" es alto. Pregunta si ese pin está activo para 
    contador[i]++;  // Sería más fácil agrupar los contadores en un array
}

Aclaro de una vez: la IRQ de PCINT se dispara con que alguno de los pines permitidos cambie de estado; esto quiere decir que si por alguna razón un sensor se capturó en el estado activo en dos interrupciones consecutivas, en ambas se incrementaría su respectivo contador aunque el flanco no haya bajado todavía. Esa es la única gran limitante de mi sugerencia.

Lucario448, surbyte, dejen que los invite a un café, son unos Tesos!! (palabra colombiana para admirar a alguien cuando esté sobre sale en alguna disciplina) , iba a desarrollar la comunicación i2c intercambiando de maestro esclavos, etc, para poder obtener la informacion, sin embargo en el desarrollo de esta solución tambien surgió la problemática:

que pasaría si durante la transmisión de datos sucede una interrupción?.

Algo me quedo claro y fue que el desarrollo del software sería complejo y también Presentaría problemas por las IRQ de cada micro.

Probé algo de lo que ambos mencionaron, me refiero al desarrollo comportamental por software de la compuerta AND de cuatro entradas y el PCINT, sin embargo no obtuve la respuesta deseada, una IRQ seguía bloqueado las demás IRQ.

QUE ES LO QUE SE LE TENGO PLANEADO HACER MAÑANA?

Bien, implementaré una compuerta OR con cuatro entradas en vez de la AND (harware), su salida irá conectada a la IRQ, con esto cualquier sensor puede invocar la interrupción, conjuntamente y siguiendo el esquema de surbyte, se añadirán las líneas de cada sensor al puerto PTB, posteriormente aplicarle el código desarrollado por lucario448 dentro de la IRQ para leer cada sensor y de este modo determinar a quien se le debe sumar, espero funcione, de otro modo investigaré más a fondo sobre la comunicación serial.

Les dos mis agradecimientos por su ayida.

sebas3124:
que pasaría si durante la transmisión de datos sucede una interrupción?.

Si el microcontrolador estuviera basado en ARM, de seguro que ese no sería un problema; pero como estamos con AVR, permíteme decirte que las interrupciones de las comunicaciones seriales suelen ser de las prioridades más bajas; por lo tanto, cualquier otra haría que simplemente se paralicen momentáneamente...

implementaré una compuerta OR con cuatro entradas en vez de la AND (harware), su salida irá conectada a la IRQ,

La AND funciona si niegas las entradas (esta claramente indicada como /IN0..3), la OR funciona directo sin modificar nada.

Ambas opciones funcionan debidamente configuradas pero me gusta mas tu opción OR de 4 entradas sin tener que modificar nada.
El esquema anterior viene de vieja data cuando se usaba mucha lógica previa.

He vuelto, pues he implementado la configuración anteriormente descrita pero aún no tengo el resultado deseado, solo lee el estado de uno de los sensores, quiero aclarar que estoy utilizando tres sensores idénticos y uno diferente a todos los demás, jajaja, pues si tengo conectado los 4 sensores, el Micro solo lee el sensor diferente(llamemos lo sensor 4), si cambio de posición el sensor 4 con otro sensor, el sensor 4 funciona bien en la posición nueva, Sin embargo el sensor cambiado qué paso a ocupar la posición que ocupaba anterior mente el sensor 4 donde se corroboró que se hacia el conteo, no funciona, detecta un estado lógico 0 aunque medi su voltaje con Un voltímetro y marcaba un nivel lógico alto permitido, para arduino, también corrobore el consumo de corriente para todos los sensores cuando estos cambian de estado y los 4 tiene medidas similares, por el momento voy a montar el resto de sensores iguales al sensor 4.

Otro problema que surgió con este montaje fue que el pin de salida de la compuerta or de 4 entradas permanece en alto siempre y cuando uno de los sensores esté en nivel lógico alto, la interrupción está programada para que se de cuando esa salida pase de un nivel lógico bajo a uno alto, sin embargo se producen señales con ruido entre los cambios de estado bajo-alto y alto -bajo, produciendo que la interrupción se ejecute varias veces erróneamente, tengo configurada una rutina algo semejante a un anti rebote pero no parece ser suficiente para poder eliminar este aspecto, he pensado en conmutar la salida o activador de. Dicha interrupción a nivel de hardware poniendo un MOSFET entre la salida de la compuerta or y tierra, este MOSFET será controlado por el arduino, ejemplo, si la interrupción es llamada, luego, en las primeras líneas de código de la función de interrupción, enviaría la instrucción por medio de un puerto para activar el MOSFET y de este modo poner la señal que dispara la IRQ a tierra, posteriormente ejecutaría el resto de código de la IRQ y una vez finalice la ejecución de la función desactivaría el MOSFET para que esté permita una nueva interrupción.

La verdad no he pensado aún en las complicaciones respecto a corriente y estado de los pines tanto como de la compuerta como los pines del arduino

Vaya que se ha complicado la cosa!!!

Yo me detendría a analizar cada parte de nuevo.

Las barreras son compradas o armadas por ti? En ese caso que elementos usas.
Porque muestran esa situación que llamas ruido en los cambios? Eso no debería pasar en ningún caso.
Hasta que no lo resuelvas o minimices estarás en problemas.
Además que detectas?

No puedes pensar en un sistema de detección y discriminación de sensores con respuestas próximas si tus sensores no trabajan apropiadamente.
Asi que concentrate en que respondan como esperas y luego continuas con el tema de detectar situaciones.

bueno esto es lo que tengo y hasta el momento me ha funcionado para dos sensores, sin embargo presento problemas con las esperar lo cual lo cambie por un ciclo for, aclaro que utilice la funsion millis pero no funciono dentro de un ciclo do while.

y el esquema:

void interrup_sensor (void) 
{       
    bool indicador_a = false;
    bool indicador_b = false;
    
    for (long z = 0; z<= 250; z++){}
    
    do
      {
        buttonState_3 = digitalRead(buttonPin_3);
        buttonState_4 = digitalRead(buttonPin_4);
          
 //------------- /////////////////////////-------------
           
           // sensor  numero 1
              if (buttonState_3 == HIGH)
                 {
                  if (indicador_a == false)
                      {
                        contador_a ++;
                        indicador_a = true;
                      }
                 }
                 
           // sensor  numero 2
              if (buttonState_4 == HIGH)
                 {
                  if (indicador_b == false)
                      {
                        contador_b ++;
                        indicador_b = true;
                      }
                 }                 
//------------------------------------------------------------------                 
        buttonState_2 = digitalRead(buttonPin_2);   
      } while (buttonState_2 != LOW);  
}

sebas3124, me parece que no tienes muy claro cual es la filosofía básica de una interrupción. Una interrupción es algo que puede suceder en cualquier momento, sin aviso previo, que interrumpe la "tarea principal" para atender algo que no puede esperar y que no va a llevar mucho tiempo el hacerse. Una "verdadera interrupción" ha de ser lo más rápida posible para que dure lo menos posible y el programa pueda seguir su curso normal sin apenas "enterarse".

No está prohibido tener bucles en una interrupción, pero lo que sí que "no está bien visto" es que te metas en un bucle que dure "algo" de tiempo. Sí, "algo" de tiempo, porque "algo" de tiempo ya es mucho tiempo para una interrupción. Una interrupción ha de ser lo más breve posible. Porque mientras se está atendiendo la interrupción, las demás interrupciones se inhabilitan y el programa principal queda detenido, "congelado". Sólo unas pocas interrupciónes con mayor prioridad podrían ejecutarse (pero digamos que estas no cuentan). Incluso aveces el programa principal, cuando va a realizar una "tarea muy delicada", deshabilita las interrupciones para que nadie le interrumpa.

Dicho esto: si void interrup_sensor (void) es tu interrupción, el do {...} while (buttonState_2 != LOW); te diría que es un despropósito, aunque me garantices que buttonPin_2 va a estar a nivel alto menos de un milisegundo. Porque ya un milisegundo es demasiado tiempo para una interrupción. Una interrupción de 100 microsegundos ya entraría en el rango de "una mala interrupción".

En una interrupción procuras no hacer dos cosas si puedes atenderla haciendo sólo una. Haces algo rápido y el resto de lo que se tenga que hacer lo haces "tranquilamente", "cuando puedas", fuera de la interrupción y dentro del programa principal.

Deberías de replantearte la solución del problema.

Esto es lo que creo, y no descarto el estar equivocado.

Hola IgnoranteAbsoluto, comprendo todo lo que dices y estoy totalmente de acuerdo con tu opinión, a mi tampoco me llama mucho la atención y de hacer esperas dentro de una interrupción, sin embargo este prototipo está diseñado para una máquina que debe contar diferentes elementos, por eso surgió la necesidad de tener cuatro contadores, dichos elementos no superan una transición menor 1.5 segundos en la banda transportadora, como se mencionó anteriormente no puedo tener interrupciones para cada sensor ya que llegado el caso podría tener los 4 sensores activados al tiempo, lo cual produciría una perdida en el conteo ya que el micro solo atenderá una interrupción. Diseñe la espera para que el pin 2 este en bajo y a si asegurarme de que un elemento dentro de todo el hardware este funcionando correctamente, si este pin se queda atascado en alto el resto del programa debe notificar que hay un problema con una de las palas mecánicas.

Pero si tienes una mejor solución para poder tener los 4 interruptores accionados al mismo tiempo y no perder el conteo, te agradecería cualquier aporte.

Para concluir quisiera mencionar que he montado dicho código para los 4 sensores y hasta el momento he tenido buenos resultados

No leiste la sugerencia que te di antes?

Podrias simplemente hacer lo mismo que se hace cuando guardamos el estado anterior de un botón para esperar su flanco y entonces determinar que se ha presionado.
Acá en tu caso, podrias conectar las 4 barreras opticas a un PORT (el mismo), y supongamos que se produce una interrupción con otra muy cerca (he visto que hablamos de 1.5 a 5 useg que puede demorar determinada ISR), bien, si eso pasa, tu sistema responde al primer evento.
EL CPU va a bloquear interrupciones asi que se pierden todos los eventos involucrados durante la atención del que la disparó, pero al tener tu el estado anterior simplemente comparas y veras que no solo 1 sino dos bits o mas pueden estar cambiados asi que reaccionarás a eso.

Si conectas las 4 barreras a un mismo PORT y simultáneamente cualquiera de las 4 usando una compuerta lógica dispara una interrupción en el Pin 2 o sea INT0, te aseguras que si hay eventos simultáneos o próximos podrás detectarlos sin problemas.
Cómo?
Pues vas leyendo el estado del PORT enmascarado a esos 4 bits.
Supongamos que tenemos 0000 como estado inicial, o sea nada pasa por las barreras.
Se produce un evento digamos 0010 (pasa un objeto).
Antes había sugerido que usaras pines 2345 de manera que el pin 4 fue el que se puso en 1.
Pero un par de useg se activa tmb pin 5.
Tu compuerta lógica activó la interrupción no importa por qué pin. Si el evento simultáneo ocurre en menos de 3 a 4 useg podrás leerlo.
En la ruitna de interrupcción tu lees el puerto y extraes los 4 bits.
Asi que no has perdido nada. El evento pasa y todo vuelve a su estado por defecto.
No pierdes ningún bit, y tus contadores se incrementan.

Se comprende?

EDITO: ahora que pienso un poco mas allá. La rutina ISR habrá que hacerla con cuidado extremo, midiendo los tiempos que involucren las instrucciones para garantizar no perder eventos.
De lo contrario assembler.

Nunca has dicho cuan próximo quieres discriminar objetos que pasen las barreras. O tal vez no sea discriminar si no que simplemente no quieres perder eventos.
Si es el caso para que te complicas, coloca un arduino por barrera asociado a un contador y listo.

miremos si comprendo:

  • manejar una unica interrupcion con el pin de salida de la compuerta logica
  • luego leer los estados de los sensores (4 bits), terminar interrupcion
  • luego, en otra parte del programa comparar el estado anterior con el estado actual, si se cumple con una
    pulsación completa ( flanco de subida y flanco de bajada) sumar al contador correspondiente.

Exacto. Como cuando lo haces con un pulsador y miras estado anterior y estado nuevo y dices... tengo un flanco 0->1 o 1->0

Aca lo haras como puerto enmascarado a tus 4 bits. Cuando cambie uno dispara la INT0, tu lees el puerto, descartas bits que no te importan y procesas.
Para hacerlo rápido, lees puerto comparas y activas contador correspondiente.

Esto es lo que tengo

int buttonState_2 = 0;
int buttonState_past_3 = 0;
int buttonState_past_4 = 0;
int buttonState_past_5 = 0;
int buttonState_past_6 = 0;

int buttonState_present_3 = 0;
int buttonState_present_4 = 0;
int buttonState_present_5 = 0;
int buttonState_present_6 = 0;


const int buttonPin_2 = 2;   
const int buttonPin_3 = 3;
const int buttonPin_4 = 4;
const int buttonPin_5 = 5;
const int buttonPin_6 = 6;

unsigned long contador_a =0;
unsigned long contador_b =0;
unsigned long contador_c =0;
unsigned long contador_d =0;

void setup() 
{
    pinMode(buttonPin_2, INPUT);
    pinMode(buttonPin_3, INPUT);
    pinMode(buttonPin_4, INPUT);
    pinMode(buttonPin_5, INPUT);
    pinMode(buttonPin_6, INPUT);

    attachInterrupt(digitalPinToInterrupt(buttonPin_2), interrup_sensor, CHANGE );
}

void loop() 
{
  if (buttonState_present_3 == HIGH &&  buttonState_past_3 == LOW)
     {
       contador_a ++;
       buttonState_past_3 =buttonState_present_3;
     }
  
 
  if (buttonState_present_4 == HIGH &&  buttonState_past_4 == LOW)
     {
       contador_b ++;
       buttonState_past_4 =buttonState_present_4;
    
  
  if (buttonState_present_5 == HIGH &&  buttonState_past_5 == LOW)
     {
       contador_c ++;
       buttonState_past_5 =buttonState_present_5;
     }
 
 if (buttonState_present_6 == HIGH &&  buttonState_past_6 == LOW)
     {
       contador_d ++;
       buttonState_past_6 =buttonState_present_6;
     }
  
}

void interrup_sensor (void) 
{       
   buttonState_present_3 = digitalRead(buttonPin_3);
   buttonState_present_4 = digitalRead(buttonPin_4);
   buttonState_present_5 = digitalRead(buttonPin_5);
   buttonState_present_6 = digitalRead(buttonPin_6);
           
}

Me adelanté unos cuantos pasos ya que aquí te dejaré una versión optimizada:

unsigned long contador[4];
volatile bool cambio = false;
volatile byte estado = 0;
byte estadoAnterior = 0;

void setup() 
{
 /*pinMode(buttonPin_2, INPUT);
    pinMode(buttonPin_3, INPUT);
    pinMode(buttonPin_4, INPUT);
    pinMode(buttonPin_5, INPUT);
    pinMode(buttonPin_6, INPUT);*/
 // Es reduntante declararlos como entrada ya que todos los pines por defecto tienen esa configuración.
 // Sin embargo es una buena práctica

    attachInterrupt(0, interrup_sensor, CHANGE);
}

void loop() 
{
 if (cambio) {
 byte activos = estado & ~estadoAnterior;
 // Esto equivale a ese montón de ifs: básicamente dice que el pin está "activo" si el estado actual es alto pero el anterior es bajo.
 for (byte i = 0; i < 4; i++)
 if ((activos >> i) & 1) // Cuando el "estado activo" es alto. Pregunta si ese pin está activo para incrementar su contador
 contador[i]++;  // Sería más fácil agrupar los contadores en un array
 // Indice 0 representa al pin 3, 1 al 4 y así sucesivamente
 
 cambio = false;
 estadoAnterior = estado;
 // ADVERTENCIA: el valor de estado podría cambiar incluso antes de habérselo asignado a estadoAnterior; lo que podría resultar en
 // no contar algún flanco de estado bajo al alto ("rising" en inglés).

 }
}

void interrup_sensor (void) 
{       
   estado = PIND >> 3; // Descartamos de una vez los tres bits que no nos interesan (estado de pin 0, 1 y 2)
   cambio = true;
           
}

Para entenderlo mejor, tendría que darte un pequeño curso de manejo de bits.

PD:

attachInterrupt(0, interrup_sensor, CHANGE);

Si el interrupción se ejecuta por un cambio de estado, ¿por qué no utilizar la interrupción de cambio de estado de pin (PCINT)?
Hasta me atrevería a decir que una interrupción externa (las que se ejecutan únicamente con el pin 2 y 3) necesita más instrucciones máquina para entrar y salir de su respectiva ISR.