Tacómetro y rayos: adiós a interrupciones!

Hola chicos. Quiero compartir con vosotros un hecho curioso que me ocurre. Os pongo en contexto.

Hace ya un par de años que fabriqué un tacómetro para revoluciones lentas, además creo que discutimos un pequeño problema que había con el código en el post: Tacómetro y efecto no deseado en el código.

El tacómetro consiste en un display de 7 segmentos, cuatro botones y dos salidas a relé, de máxima y mínima velocidad. Dichas velocidades son programables y está pensado para una velocidad máxima de 500RPM. El sensor que utilizo es un sensor inductivo LJ12A3-4-Z/BX.

Se compone de dos placas: el panel con el display/botones y la placa con el atMega, fuente y conexiones.

He aquí la placa del panel y su esquema:

Y este el esquema del circuito principal:

El esquema de conexión es el siguiente:

El circuito funciona estupendamente y sin problemas, salvo algo que ocurre en algún punto entre Mayo y Julio. En esa época llega el monzón y hay tormentas a tutiplén y de golpe noto algo raro: cuando la máquina está parada, 0RPM, de vez en cuando, sin venir a cuento, sin haber nada raro, se produce un disparo por máxima velocidad!!

Me ha ocurrido ya dos años seguidos y lo he reparado obviamente. Cada vez que se avería compruebo que todo este funcionando correctamente: tensión de fuente, pantalla, botones, que el atMega responde y se deja programar.

Cuando reviso la entrada de pulsos proveniente del sensor reviso las tres cosas que hay: conector, resistencia y optoacoplador. De hecho no hay nada mas:

La alimentación de 12V del sensor es la misma que el Arduino reducida a 5V. Y es una conexión semi-aislada, es decir, comparte el GND aunque pasa por un optoacoplador, así solo hay que usar la resistencia PULL-UP.

Como se ve, es muy simple. Y después de analizar el problema siempre llego a la misma conclusión: el atMega falla, pero solo en el pin D2 (de interrupción) que se vuelve sensible de tal manera que cualquier alteración en el ambiente hace que provoque una interrupción.

Supongo que será que la descarga del rayo entra por GND y llega al pin 2. Curioso que solo en ese pin, ya que el resto de pines los he probado y siguen funcionando.

Programo otro atMega, lo pongo y a funcionar. Y el año que viene por las mismas fechas volver a repetir la operación.

No se me ocurre nada que proteja el pin: ¿varistor, zéner, TVS? Se supone que el atMega ya tiene diodos "clamp" que absorven picos, pero se ve que no son suficientes. ¿Alguna sugerencia? o estáis tan perplejos como yo.

Por si alguien quiere echar un vistazo al código lo dejo aquí, pero no es problema de software:

/* 
 TACOMETRO  

 Autor: VictorJAM.
 Fecha: 22/03/2021.
 */


#include <SimpleButton.h>
#include <EEPROM.h>

#define RELEMINIMA 7
#define RELEMAXIMA 8

//--- VARIABLES CONTROL DISPLAY.

// Contiene los segmentos que se han encender para cada digito.
const uint8_t numeros[10]  = { 0x3f, 0x06, 0x5b, 0x4f, 0x66, 
                               0x6d, 0x7d, 0x07, 0x7f, 0x67 };
// Pines donde están conectados los segmentos.
const uint8_t segmentos[7] = { A1, A3, 10, 11, 12, A2, 9 };

// Pines de control de cada digito.
const uint8_t digitos[4]   = { 13, A0, 5, 6 };
uint8_t       digitoActual;
uint32_t      td;

//--- Botones.
SimpleButton btnSetMax(A5);
SimpleButton btnSetMin(A4);
SimpleButton btnUp(4);
SimpleButton btnDown(3);

//--- VARIABLES PARA LA MEDIDA DE PULSOS Y RPM
volatile uint32_t tanterior;
volatile uint32_t tactual;
volatile uint32_t periodo;
float    f;
uint32_t rpm;

//--- VARIABLES DE CONTROL.
int      maxrpm;
int      minrpm;
uint32_t t1;
uint32_t t2;
uint32_t t3;
uint32_t t4;
uint32_t t5;
uint32_t t6;

//--- RUTINA DE SERVICIO A LA INTERRUPCIÓN.
void isr() {
  tactual = micros();
  periodo = tactual-tanterior;
  tanterior = tactual;
}

// Ajusta un valor entre un valor minimo y un maximo. Si el valor 'value' es 
// mayor que el de 'imax', 'value' será 'imin'. De la misma forma, si 'value'
// es menor de 'imin' su valor pasará a ser 'imax'.
void trimvalue(int &value, int imin, int imax) {
  if ( value > imax ) 
    value = imin;
  else 
  if ( value < imin ) 
    value = imax;
}

// Rutina de retraso al encendido. Su salida será uno cuando la condición sea
// cierta y haya transcurrido el tiempo indicado en duración; será cero en caso
// contrario.
unsigned int onDelayTimer(bool condicion, unsigned long &timer, 
                          unsigned long duracion) {
  if (condicion == false) { timer=0; return 0; }                   
  else                     
    if (timer == 0) { timer = millis(); return 0; }                 
    else              
      if (millis() - timer >= duracion ) return 1;  
      else return 0;
}

// Dado un entero devuelve el valor decimal (o digito) especificado en la
// posición p.
int digitopos(int v, int p) {
  int d, r;
  d = v;
  int i=0; 
  while (i<=p) {
    r = d % 10;
    d = d / 10;
    i++;
  }
  return r;
}

// Muestra/enciende los leds correspondientes a ese digito en el display.
void printd(uint8_t v) {
  if ( v>=0 && v<=9 ) {
    for (int i=0; i<7; i++) {
      digitalWrite(segmentos[i], bitRead(numeros[v], i));
    }
  }
}

// Muestra un digito de 4 cifras en un display de 4 dígitos. Usa la función
// delayMicroseconds. Útil para pruebas.
void actualizaDisplay1() {
  for (int d=0; d<4; d++) {
    printd(digitopos(valorDisplay, 3-d));
    digitalWrite(digitos[d], HIGH);
    delayMicroseconds(800); 
    digitalWrite(digitos[d], LOW);
    delayMicroseconds(200);
  }
}

// Muestra un digito de 4 cifras en un display de 4 digitos. Usa la función
// micros() con lo que no interrumpe el programa.
void actualizaDisplay2() {
  if ( micros()-td > 2000 ) {
    digitalWrite(digitos[digitoActual], LOW);
    digitoActual++; if ( digitoActual==4 ) digitoActual = 0;
    printd(digitopos(valorDisplay, 3-digitoActual));
    digitalWrite(digitos[digitoActual], HIGH);
    td = micros();
  }
}

//--- CONFIGURACIÓN DE PROGRAMA.
void setup() {
  // Establece los segmentos como salida.
  for (int i=0; i<7; i++) {
    pinMode(segmentos[i], OUTPUT);
  } 
  
  // Establece los pines de control de digitos como salida.
  for (int i=0; i<4; i++) {
    pinMode(digitos[i], OUTPUT);
  }

  // Leemos las posiciones 0 y 2 que contienen un int con el valor por defecto
  // de las variables minrpm y maxrpm.
  EEPROM.get(0, minrpm);
  EEPROM.get(2, maxrpm);
  
  // Ponemos los pones de relé como salida.
  pinMode(RELEMINIMA, OUTPUT); // RELE DE MINIMA
  pinMode(RELEMAXIMA, OUTPUT); // RELE DE MAXIMA
  digitalWrite(RELEMINIMA, 0);
  digitalWrite(RELEMAXIMA, 0);

  // Diodos de led indicadores de mínima y máxima.
  pinMode(0, OUTPUT);
  pinMode(1, OUTPUT);

  // Pin de interrupcion, no usamos una resistencia de PULL-UP en el opto ya
  // que podemos usar la interna.
  pinMode(2, INPUT_PULLUP);
  
  // IMPORTANTE!! La interrupción debe darse en el flanco de bajada.
  attachInterrupt(digitalPinToInterrupt(2), isr, FALLING);

}


//--- PROGRAMA PRINCIPAL.
void loop() {
  // Actualizo el estado de los botones.
  btnSetMin.update();
  btnSetMax.update();
  btnUp.update();
  btnDown.update();

  // Rutina para que en el display se visualice cero, cuando hay pocas 
  // revoluciones (máquina parándose o arrancándose).
  if ( periodo == 0 ) 
    f=0;
  else {
    // Se guarda el tiempo actual y el valor de micros en sendas variables 
    // auxiliares. Esto reduce el error que se produce cuando intentamos leer
    // las variables y se produce la interrupción, resultando que tactual 
    // tiene valor 0 y provocando que la frecuencia sea 0.
    uint32_t a = tactual;
    uint32_t b = micros(); 
    if ( b-a > 1000000UL) {
      f=0;
    }
    else {
      f = 1000000.0 / periodo;  
    }
  }
  // La frecuencia se expresa en Hertzios, para convertirla a r.p.m. debemos
  // multiplicar por 60. Despues redondeamos el número obtenido.
  f = f*60.0;
  rpm = round(f);
  
  // Para activar los relés tenemos que tener en cuenta que la rutina no es
  // perfecta y da errores dando como resultado que la frecuencia es cero, aun
  // cuando es fija. Para ello se usa la función 'onDelayTimer' tanto en el
  // el encendido como en el activado.

  // Relé/Led de mínima.
  digitalWrite(RELEMINIMA, onDelayTimer(rpm<minrpm, t1, 500));
  digitalWrite(1, !onDelayTimer(rpm<minrpm, t3, 20));

  // Relé/Led de máxima.
  if ( onDelayTimer(rpm>=maxrpm, t2, 500) )
    digitalWrite(RELEMAXIMA, HIGH);
  else
  if ( onDelayTimer(rpm<maxrpm, t5, 100 ) )
    digitalWrite(RELEMAXIMA, LOW);

  if ( onDelayTimer(rpm>=maxrpm, t4, 20) )
    digitalWrite(0, LOW);
  else
  if ( onDelayTimer(rpm<maxrpm, t6, 20) )
    digitalWrite(0, HIGH);
     
  // Pequeño menú para interactuar con el display y poder elegir el número de
  // rpm de mínima y de máxima.

  if ( btnSetMin==Pressed ) {
    valorDisplay = minrpm; 
    if ( btnUp==Press ) { minrpm++; trimvalue(minrpm, 0, 500); }
    if ( btnDown==Press ) { minrpm--; trimvalue(minrpm, 0, 500); }
  }
  else
  if ( btnSetMax==Pressed ) {
    valorDisplay = maxrpm;
    if ( btnUp==Press ) { maxrpm++; trimvalue(maxrpm, 0, 500); }
    if ( btnDown==Press ) { maxrpm--; trimvalue(maxrpm, 0, 500); }
  }
  else
    valorDisplay = rpm;

  if ( btnSetMin==Release ) { EEPROM.put(0, minrpm); }
  if ( btnSetMax==Release ) { EEPROM.put(2, maxrpm); }
  
  // Muestra el digito.
  actualizaDisplay2();
}

Como estas Víctor, siempre con cosas fáciles de solucionar no?
Yo creo que debes idear un logger que detecte estos problemas, claro si pudieras hacerlo es porque tienes dominado qué ocurre y no es el caso, te aclaro tampoco yo podría.
Te ha tenido a mal traer todo el tiempo (años). Y claramente le das pelea pero es esquiva la solución definitiva aunque lo tienes mas que acotado.
Se me ocurre para empezar y sugerirte algo una chapuza (el término que amo de los españoles).
Pondría una rutina que descarte pulsos rápidos y abruptos. Los mismos se deben dar de forma progresiva y algo como lo que comentas no es posible. Solo lo sería si tuvieras un mal contacto y el sensor de golpe acusa de 0 300 RPM por ejemplo, pero aun así eso ocurre en un dt muy pequeño y por esa razón lo descartaría.
si en Tn-1 tuviste 0 RPM y en Tn tienes 300 RPM debes confirmarlo en Tn+1

Otra alternativa es el filtro mediana móvil de Luis Llamas
Los espurios son ignorados, creo que podría servirte. Prueba a ver!!

Ja ja ja!!

Lo que sorprende es que el cacharro está al lado de otras fuentes de ruido que si están activas todo el año, excepto en los meses de verano que es cuando ocurre el evento.

Ya intente filtrar por software los posibles espurios y daba igual. En lo alto la mesa del laboratorio, sin nada que pueda asustar al pin, se te ocurre señalar con el dedo y se activa la interrupción miles de veces!!.

En definitiva hay algún "evento" que se carga el pin y desde ese momento cuando le apetece se vuelve loco. Y lo díficil es ver que evento lo provoca, sé que debe ser un rayo por que ocurre en época de tormentas, pero lo mismo aguanta 10 tormentas que una.

En septiembre suele haber también tormentas pero ahí ya no ocurre, también funciona la maquinaría a intermitencias. Así que se lo mismo es un conjunto de tierra seca, con descargas electrostaticas gordas, calor, viento, aire... Me encanta la electrónica, :D:D:D:D.

Buenas, mi experiencia con ruido atmosférico, siempre da como culpable una mala tierra, y entiéndase como tierra la jabalina enterrada en ella ¿Cómo diseñaste la fuente? ¿Los cero volts están aterrizados?

Saludos @PeterKanTropus !

Antaño si había problemas de tierras y por eso ninguno de mis "inventos" tiene el GND conectado a tierra: muchos problemas con ModBus, ruidos en fuentes, entradas, etc. Pero hace dos años hice una revisión de las tierras y, pregúntele usted al electricista, de las tres "picas" que hay, solo dos estaban conectadas, la tercera brillaba por su ausencia y la tierra era penosa. Fue unirlas y la tierra paso a dar calidad excelente según el megger (pasó la inspeccion "oca" sin problemas).

La fuente es una fuente de 12V 100Wnormal, no se pasa de buena. Como mucho decir que para evitar problemas con los cortes de luz hay una batería tampón y un par de condensadores de 2200uF extras para eliminar mas ripple. En el tacométro la fuente de 5V si esta bien filtrada y protegida.

Si el optoaoplador cascara cuando se produce el problema entonces pensaría que el problema viene de la fuente de 12V, pero el optoacoplador no da problemas, cambiando el atMega se soluciona el problema. Eso sí, por el mismo precio cambio también el opto.

Hi,
El ground del sensor esta conectado al ground del arduino. Si esta deberia de desconectarlo ya que al hacerlo va a intruducir ruido en caso de estatica o de un rayo.. El proposito del optocoupler es para eliminar ruidos y aislacion de systemas. Que tipo de power supplies usas. Es un switcher o un power con transformador. Para mis projectos yo siempre uso una fuente que usa transfomadores ya que esta te aisla el alto voltaje.

Hola @tauro0221 !

He aquí las fuentes que uso:

Efectivamente, son fuentes conmutadas (switching). Estoy de acuerdo contigo de que lo mejor es un transformador, pero cada vez se usan menos, están caros y algunas cuesta hasta encontrar un transformador decente. Así que no queda mas remedio que usar fuentes conmutadas.

La que alimenta el tacómetro es la fuente que se ve arriba a la izquierda, 12V 100W, lo que se ve al lado es donde he colocado el circuito para la batería tampón: una resistencia y unos diodos mas algún condensador. Esta fuente se dedica a alimentar "automatas" que no han dado problemas de momento. Las otras tres fuentes se usan en las entradas de estos automatas y alimentar relés de salida.

El uso del optoacoplador en este caso es para semi-aislar tensión de 12V a 5V. Lo hice por simplicidad y comodidad, o sea, por perro, por no sacar un cable de otra fuente auxiliar y llevarlo allí. Posiblemente si rediseño el circuito y alimento el sensor magnético con otra fuente reduzca el problema del todo. Me lo anoto para una revisión!!

Hi,
Para los rayos lo mas indicado es usar metal oxide varistors a la entrada que alimentan los power supplies. Esto previene que el ruido se propague en el systema. Tu mencionas que siempre se dana el pin dos pero no creo que esto pueda danar el pin ya que el opto esta usando el mismo voltaje que alimenta al micro. Que pasa al opto cuando esto sucede . Tienes que remplazarlo?

El opto funciona correctamente.

Todas las fuentes tienen protección con varistor y fusibles. Todos los "automatas", tacometro includo, disponen de TVS en la entrada de alimentación.

A mi me llama la atención que solo se "pinche" el pin 2, no le encuentro explicación.

[Off-topic] ¿Con qué programa hacés los esquemas?

Es casi un ELEKTOR de antaño.

Tal cuál! Y me encanta! :heart_eyes:

:laughing:

Eso es lo curioso, pero es lo que ocurre. Los demás pines digitales funcionan, incluso si le colocas otro arduino en el puerto serie te permite cargar los programas y la comunicación funciona perfectamente, más teniendo en cuenta la proximidad de esos pines al 2.

El esquema y diseño lo hago con Proteus, pero queda un poco feo y para mi "blog" personal hago un esquema usando Paint Shop Pro :smiley: :smiley: :smiley: :smiley:

Amo el PSP7, viejito pero cumple.

psp7

:sweat_smile: :rofl: :joy: :rofl: :sweat_smile:

¿Comprobante efectivamente que el cero Volt, de las fuentes, no estén unidas a su chasis? Yo he tenido ese comportamiento en algunas fuentes industriales. Supongo por un deficiente diseño del PCB.

Hi,
Cuando cae un rayo es posible que el pin D2 se vaya negativo. Este voltaje negativo va a danar el pin. Para protejer el pin de voltajes negativos debes de instalar un switching diode con el catodo conectado al pin D2 y el anodo a ground. El diodo te va a elimina el voltaje negativo a ground protegiendo el pin.

@PeterKantTropus :

El chasis está conectado a la conexión a tierra. El GND de la salida de 12V es independiente del chasis.

Tal y como lo veo hay dos opciones:

OPCION 1) Sin modificar la PCB podría instalar un diodo TVS de 8V o similar en el pin 2 para que absorba los transitorios que se pudieran producir en el pin. Lo malo es encontrar un TVS de esas caracteristicas NO SMD. Lo bueno que si lo encuentro hay un hueco para el diodo.

OPCION 2) Habría que modificar la parte del conector de la PCB, y conectar la alimentación del sensor usando una fuente externa. Se supone que ya el pin 2 estaría totalmente aislado de cualquier interferencia que pudiera haber. Opcionalmente colocar un TVS en dicha alimentación, en este caso de 18V, en cuyo cayo si tengo existencias por ser los que coloco antes del regulador de 5V.

Creo que la primera opción es la que comentaba @Tauro0221.

Y porque no usar las dos, al mejor estilo aeronáutico? El problema que tienes es muy esquivo, demasiado azaroso, difícil de capturar, no escatimaría medidas anti rayos.

Hi,
Para mi los tvsson 2 zener diodos "back to back" este va tener un delay cuando disparan. El switching diodes es mas rapido cuando el volatje es negativo.Para mi creo que el problema es el voltaje negativo.Si fuera positivo entonces el dana seria mas serio ya que lo unico que falla es el pin D2.El diodo se puedes soldar directaene a los pines del opto.. El catodo va al colector del transistor y el anodo al emiter del transistor . Uno bueno que yo uso es el 1N4148.