Duda de como medir tiempo transcurrido entre 2 interrupciones(Tablero digital moto)

Hola! estoy probando de armar un tablero totalmente digital para mi moto, consegui por internet un codigo que me funciono perfecto para medir las rpm del motor y mediante un servo indicarlas en un tacometro impreso en 3D.
Ahora mi problema es al querer medir la velocidad, para esto habia pensado en un sensor de efecto hall(el mismo que use para medir las rpm del motor, el sensor esta colocado en los imanes del volante) y un iman pegado a la llanta de la rueda delantera, sabiendo el tiempo entre los pulsos puedo sacar la velocidad angular y posteriormente la velocidad lineal sabiendo el radio de la rueda. El problema es que la informacion que encontre, siempre lo hacen cambiando los valores de los registros, y por lo que lei, si cambio los registros modifico los tiempo de delay y demas, y mi codigo que actualmente funciona perfecto para las rpm me dejaria de funcionar.
Hay alguna forma de medir el tiempo entre 2 interrupciones sin modificar los registros?
Agradezco cualquier ayuda! les dejo el fragmento de codigo que deberia de encargarse de devolver el valor del tiempo entre interrupciones

void VelocidadRueda()    // Funcion que se ejecuta durante cada interrupion
{
  if (j == 0) {
    j++;
    inicio = millis();
  }
  if (j == 1) {
    j = 0;
    fin = millis();
    t = fin - inicio;
  }
}

Codigo completo



const byte PulsesPerRevolution = 2;  // Set how many pulses there are on each revolution. Default: 2.


// If the period between pulses is too high, or even if the pulses stopped, then we would get stuck showing the
// last value instead of a 0. Because of this we are going to set a limit for the maximum period allowed.
// If the period is above this value, the RPM will show as 0.
// The higher the set value, the longer lag/delay will have to sense that pulses stopped, but it will allow readings
// at very low RPM.
// Setting a low value is going to allow the detection of stop situations faster, but it will prevent having low RPM readings.
// The unit is in microseconds.
const unsigned long ZeroTimeout = 100000;  // For high response time, a good value would be 100000.
// For reading very low RPM, a good value would be 300000.


// Calibration for smoothing RPM:
const byte numReadings = 1;  // Number of samples for smoothing. The higher, the more smoothing, but it's going to
// react slower to changes. 1 = no smoothing. Default: 2.





/////////////
// Variables:
/////////////

volatile unsigned long LastTimeWeMeasured;  // Stores the last time we measured a pulse so we can calculate the period.
volatile unsigned long PeriodBetweenPulses = ZeroTimeout + 1000; // Stores the period between pulses in microseconds.
// It has a big number so it doesn't start with 0 which would be interpreted as a high frequency.
volatile unsigned long PeriodAverage = ZeroTimeout + 1000; // Stores the period between pulses in microseconds in total, if we are taking multiple pulses.
// It has a big number so it doesn't start with 0 which would be interpreted as a high frequency.
unsigned long FrequencyRaw;  // Calculated frequency, based on the period. This has a lot of extra decimals without the decimal point.
unsigned long FrequencyReal;  // Frequency without decimals.
unsigned long RPM;  // Raw RPM without any processing.
unsigned int PulseCounter = 1;  // Counts the amount of pulse readings we took so we can average multiple pulses before calculating the period.
unsigned long RPM1;
unsigned long PeriodSum; // Stores the summation of all the periods to do the average.

unsigned long LastTimeCycleMeasure = LastTimeWeMeasured;  // Stores the last time we measure a pulse in that cycle.
// We need a variable with a value that is not going to be affected by the interrupt
// because we are going to do math and functions that are going to mess up if the values
// changes in the middle of the cycle.
unsigned long CurrentMicros = micros();  // Stores the micros in that cycle.
// We need a variable with a value that is not going to be affected by the interrupt
// because we are going to do math and functions that are going to mess up if the values
// changes in the middle of the cycle.

// We get the RPM by measuring the time between 2 or more pulses so the following will set how many pulses to
// take before calculating the RPM. 1 would be the minimum giving a result every pulse, which would feel very responsive
// even at very low speeds but also is going to be less accurate at higher speeds.
// With a value around 10 you will get a very accurate result at high speeds, but readings at lower speeds are going to be
// farther from eachother making it less "real time" at those speeds.
// There's a function that will set the value depending on the speed so this is done automatically.
unsigned int AmountOfReadings = 1;

unsigned int ZeroDebouncingExtra;  // Stores the extra value added to the ZeroTimeout to debounce it.
// The ZeroTimeout needs debouncing so when the value is close to the threshold it
// doesn't jump from 0 to the value. This extra value changes the threshold a little
// when we show a 0.

// Variables for smoothing tachometer:
unsigned long readings[numReadings];  // The input.
unsigned long readIndex;  // The index of the current reading.
unsigned long total;  // The running total.
unsigned long average;  // The RPM value after applying the smoothing.

volatile int j = 0;
volatile int t = 0;
volatile int fin = 0;
volatile int inicio = 0;

int velRueda = 0;
float r = 0.35 ;

#include <Servo.h>
Servo myservo;
Servo myservo1;
void setup()  // Start of setup:
{

  Serial.begin(9600);  // Begin serial communication.
  attachInterrupt(digitalPinToInterrupt(2), Pulse_Event, RISING);  // Enable interruption pin 2 when going from LOW to HIGH.
  attachInterrupt(digitalPinToInterrupt(3), VelocidadRueda, RISING); // Interrupcion 0 (pin2)
  delay(1000);  // We sometimes take several readings of the period to average. Since we don't have any readings
  // stored we need a high enough value in micros() so if divided is not going to give negative values.
  // The delay allows the micros() to be high enough for the first few cycles.

  myservo.attach(9);
  myservo.write(0);

  myservo1.attach(10);
  myservo1.write(0);
}  // End of setup.





void loop()  // Start of loop:
{

  // The following is going to store the two values that might change in the middle of the cycle.
  // We are going to do math and functions with those values and they can create glitches if they change in the
  // middle of the cycle.
  LastTimeCycleMeasure = LastTimeWeMeasured;  // Store the LastTimeWeMeasured in a variable.
  CurrentMicros = micros();  // Store the micros() in a variable.





  // CurrentMicros should always be higher than LastTimeWeMeasured, but in rare occasions that's not true.
  // I'm not sure why this happens, but my solution is to compare both and if CurrentMicros is lower than
  // LastTimeCycleMeasure I set it as the CurrentMicros.
  // The need of fixing this is that we later use this information to see if pulses stopped.
  if (CurrentMicros < LastTimeCycleMeasure)
  {
    LastTimeCycleMeasure = CurrentMicros;
  }





  // Calculate the frequency:
  FrequencyRaw = 10000000000 / PeriodAverage;  // Calculate the frequency using the period between pulses.





  // Detect if pulses stopped or frequency is too low, so we can show 0 Frequency:
  if (PeriodBetweenPulses > ZeroTimeout - ZeroDebouncingExtra || CurrentMicros - LastTimeCycleMeasure > ZeroTimeout - ZeroDebouncingExtra)
  { // If the pulses are too far apart that we reached the timeout for zero:
    FrequencyRaw = 0;  // Set frequency as 0.
    ZeroDebouncingExtra = 2000;  // Change the threshold a little so it doesn't bounce.
  }
  else
  {
    ZeroDebouncingExtra = 0;  // Reset the threshold to the normal value so it doesn't bounce.
  }





  FrequencyReal = FrequencyRaw / 10000;  // Get frequency without decimals.
  // This is not used to calculate RPM but we remove the decimals just in case
  // you want to print it.





  // Calculate the RPM:
  RPM = FrequencyRaw / PulsesPerRevolution * 60;  // Frequency divided by amount of pulses per revolution multiply by
  // 60 seconds to get minutes.
  RPM = RPM / 10000;  // Remove the decimals.





  // Smoothing RPM:
  total = total - readings[readIndex];  // Advance to the next position in the array.
  readings[readIndex] = RPM;  // Takes the value that we are going to smooth.
  total = total + readings[readIndex];  // Add the reading to the total.
  readIndex = readIndex + 1;  // Advance to the next position in the array.

  if (readIndex >= numReadings)  // If we're at the end of the array:
  {
    readIndex = 0;  // Reset array index.
  }

  // Calculate the average:
  average = total / numReadings;  // The average value it's the smoothed result.





  // Print information on the serial monitor:
  // Comment this section if you have a display and you don't need to monitor the values on the serial monitor.
  // This is because disabling this section would make the loop run faster.


  velRueda = (2 * 3.1416 * (1 / t) * r );
  int velRueda1 = map(velRueda, 0, 120, 180, 0);
  myservo.write(velRueda1);

  RPM1 = map(RPM, 0, 12000, 180, 0);
  myservo.write(RPM1);
}  // End of loop.





void Pulse_Event()  // The interrupt runs this to calculate the period between pulses:
{

  PeriodBetweenPulses = micros() - LastTimeWeMeasured;  // Current "micros" minus the old "micros" when the last pulse happens.
  // This will result with the period (microseconds) between both pulses.
  // The way is made, the overflow of the "micros" is not going to cause any issue.

  LastTimeWeMeasured = micros();  // Stores the current micros so the next time we have a pulse we would have something to compare with.





  if (PulseCounter >= AmountOfReadings) // If counter for amount of readings reach the set limit:
  {
    PeriodAverage = PeriodSum / AmountOfReadings;  // Calculate the final period dividing the sum of all readings by the
    // amount of readings to get the average.
    PulseCounter = 1;  // Reset the counter to start over. The reset value is 1 because its the minimum setting allowed (1 reading).
    PeriodSum = PeriodBetweenPulses;  // Reset PeriodSum to start a new averaging operation.


    // Change the amount of readings depending on the period between pulses.
    // To be very responsive, ideally we should read every pulse. The problem is that at higher speeds the period gets
    // too low decreasing the accuracy. To get more accurate readings at higher speeds we should get multiple pulses and
    // average the period, but if we do that at lower speeds then we would have readings too far apart (laggy or sluggish).
    // To have both advantages at different speeds, we will change the amount of readings depending on the period between pulses.
    // Remap period to the amount of readings:
    int RemapedAmountOfReadings = map(PeriodBetweenPulses, 40000, 5000, 1, 10);  // Remap the period range to the reading range.
    // 1st value is what are we going to remap. In this case is the PeriodBetweenPulses.
    // 2nd value is the period value when we are going to have only 1 reading. The higher it is, the lower RPM has to be to reach 1 reading.
    // 3rd value is the period value when we are going to have 10 readings. The higher it is, the lower RPM has to be to reach 10 readings.
    // 4th and 5th values are the amount of readings range.
    RemapedAmountOfReadings = constrain(RemapedAmountOfReadings, 1, 10);  // Constrain the value so it doesn't go below or above the limits.
    AmountOfReadings = RemapedAmountOfReadings;  // Set amount of readings as the remaped value.
  }
  else
  {
    PulseCounter++;  // Increase the counter for amount of readings by 1.
    PeriodSum = PeriodSum + PeriodBetweenPulses;  // Add the periods so later we can average.
  }

}  // End of Pulse_Event.

void VelocidadRueda()    // Funcion que se ejecuta durante cada interrupion
{
  if (j == 0) {
    j++;
    inicio = millis();
  }
  if (j == 1) {
    j = 0;
    fin = millis();
    t = fin - inicio;
  }
}

No sé qué entiendes por "valores de registro", posiblemente lo que lees está en inglés y no estás entendiendo tampoco mucho del código o de matemáticas pues en este código la única variable a modificar sería el radio y quiero suponer que el valor que has puesto corresponde a tu vehículo.

Tu idea lo siento pero no la entiendo ni a nivel teórico ni a nivel de software con ese contador.
Si tus rpm estuviesen bien ¿Por qué no conviertes el resultado?

Edito el ejemplo para poner datos en base a un diámetro real:

Por ejemplo una moto con 635.8mm de diámetro a 600 rpm (de la rueda) viene a dar 71.9 Km/h Puedes encontrar la fórmula necesaria en cualquier calculadora de rpm a km/h.

Por supuesto recuerda que esto será una aproximación porque tu vehículo tendrá otros factores que afectarán a la velocidad.
saludos.

Cuando me refiero al tema de los registros me refiero a esto en especifico, busque algo de informacion pero lo unico que logre entender es que si modifico algo de eso, cambian los delay y me dejaria de funcionar el codigo actual.
Esto es un fragmento de un codigo que encontre por internet.

ISR(TIMER2_OVF_vect) {
    digitalWrite( Inyector, LOW);
//Parar el timer 2
    TCCR2B &= ((0<<CS22) | (0<<CS21) | (0<<CS20));
    //TIMSK2 |= (0<<TOIE2) ;  //InHabilita interrupción por desbordamiento del Timer2

Como mi conocimiento de programacion es practicamente nulo, solo un if, while, for y no mucho mas, lo mas probable es que entendi cualquier cosa respecto a los registros y timers.

Ahora el codigo que actualmente tengo que funciona perfecto para medir las rpm del motor es el codigo completo que subi en el post, ese lo saque de internet y me funciono a la primera.
Lo que quise agregar fue para medir la velocidad de la moto y seria el void VelocidadRueda.

void VelocidadRueda()    // Funcion que se ejecuta durante cada interrupion
{
  if (j == 0) {
    j++;
    inicio = millis();
  }
  if (j == 1) {
    j = 0;
    fin = millis();
    t = fin - inicio;
  }
}

Eso fue lo unico que le agregue. Necesitaria que me devolviera el tiempo en segundos entre pulsos y asi mediante esta formula velRueda = (2 * 3.1416 * (1 / t) * r ); donde t es el tiempo en seg y r es el radio de la rueda. Luego con un servo mediante la funcion map lo visualizo.
El problema es que ese void, que entiendo se activa al haber una interrupcion, no me funciona, queda constantemente en 0

Publicaste un código que no veo que relación tiene con el código completo.

ISR(TIMER2_OVF_vect) {
    digitalWrite( Inyector, LOW);
//Parar el timer 2
    TCCR2B &= ((0<<CS22) | (0<<CS21) | (0<<CS20));
    //TIMSK2 |= (0<<TOIE2) ;  //InHabilita interrupción por desbordamiento del Timer2

Estas usando micros o milllis() e interrupciones de flanco de subida en pin 2 y 3

El código que has mostrado es mas facil de modificar que aquel que usa TIMERs.

Asi que ordena tu pregunta porque me sumo a @cuantron en que es dificil seguirte.
Un poco de Física tal como lo has indicado.
Tu mides el período de los pulsos, dado por el flanco de crecimiento. Eso es t
2pir es la longitud de la rueda dividido entre t es lo que se ha desplazado.

Tiempo entre 2 interrupciones, claro que hay manera pero no has explicado porque tiens algo conectado al pin 2 y otra cosa al pin 3.
Uno es RPM y el otro velocidad.
RPM digamos que esta bien y es el pin 2
Velocidad no se porque no esta bien porque lo que leo luce bien.

Si, confundi mucho con el tema del registro y timers, obviemos esa parte.
Es como dijiste, tengo 2 interrupciones, pin 2 RPM motor que funciona perfecto, pin 3 velocidad moto que no funciona

Para simplificar un poco las cosas. Estoy probando con este codigo que es el fragmento que se encarga de medir el tiempo entre las interrupciones, dejando de lado el tema de las rpm el cual ya se que funciona.

volatile int j = 0;
volatile int t = 0;
volatile int fin = 0;
volatile int inicio = 0;
void setup() {
   attachInterrupt(digitalPinToInterrupt(3), VelocidadRueda, RISING);
   Serial.begin(9600);
}

void loop() {
 if(t > 0 ){
  Serial.println(t);
 }
}

void VelocidadRueda()    // Funcion que se ejecuta durante cada interrupion
{
  if (j == 0) {
    j++;
    inicio = millis();
  }
  if (j == 1) {
    j = 0;
    fin = millis();
    t = fin - inicio;
  }
}

Me devuelve siempre t = 0

Si te devuelve siempre 0 es porque tal vez el sensor HALL no esta funcionando como piensas.
Observa que pasa cuando esta sin el imán presente y cuando esta el iman.
Recuerda que quieres medir flanco ascendente.
Describe como esta conectado el sensor, de que sensor se trata, etc, etc.

Te devuelve siempre 0 porque millis() no se actualiza dentro de la ISR ya que las interrupciones están desactivadas, entonces tanto inicio como fin tienen el mismo valor y su diferencia es 0.

Te recomiendo que leas el sig. artículo
https://www.gammon.com.au/interrupts

Saludos

Tienes el código para leer rpm ya escrito en el primer post. Lo usas para leer las rpm del motor. Solo basta con aplicarlo tambien a la velocidad.

En ese código te hace una especie de filtro y aunque esta bien documentado, quizás el inglés sea el problema.

En realidad, lo que está haciendo es medir el tiempo entre pulsos. En el caso de la velocidad de la rueda, cuenta el tiempo que tarda en dar una vuelta completa. Para ello hace uso de la función micros() en la rutina.

Micros es como millis, pero en vez de contar milisegundos, cuenta microsegundos desde que se inicio el sistema.

Cuando la rueda pasa por el sensor, se produce la interrupcion, lees el valor actual de micros y le restas el valor anterior desde la ultima interrupcion y tienes cuanto tiempo ha tardado en dar una vuelta.


unsigned long actual;
unsigned long anterior;
unsigned long tiempo;

void rutinaVelocidad() {
  actual = micros();
	tiempo = actual - anterior;
	anterior = actual;
}

Ese tiempo será en microsegundos. Conoces el tiempo que tarda en dar una vuelta, conoces cuanto recorre la rueda en esa vuelta, tienes la velocidad.

Ese codigo funciona con "peros". Si la velocidad es baja o nula, la interrupcion tarda en ejecutarse mas tiempo o no se ejecuta nunca (cuando esta parado).

Por ello en el loop se crea una condición que compara el velor del ultimo pulso, que estará en actual con micros. Si este tiempo es mayor a uno que se haya fijado el tiempo se hace cero.


void loop() {
  
	// restamos a micros() el valor del ultimo pulso que estará en la variable
	// actual, si ese tiempo es mayor a un segundo (1000000UL) te dice que es
	// el tiempo es cero y por lo tanto la velocidad será cero.
  if ( micros()-actual > 1000000UL ) {
	  tiempo = 0;
	}
}

A groso modo es como funciona y como lo debes aplicar a la velocidad. Te recomiendo que leas el código de las rpm y lo analices bien, así podrás aplicar la misma técnica a la velocidad.

Es que no hay más... Son matemáticas. Eso de hallar con el intervalo de tiempo de las interrupciones... Y el resultado siempre va ser una aproximación.
Yo no entiendo nada de lo que plantean ni para qué volver a calcular algo que ya puedes obtener con las rpm.

Porque para calcular la velocidad en base a las RPM debes conocer en qué cambio se encuentra la caja para saber que relación aplicar.

Saludos

Exacto, precisamente por eso la velocidad que puede calcular es aproximada, pero no es eso lo que estoy indicando desde el principio. El autor quiere poner otro sistema para ¿Hallar la velocidad angular? en base al tiempo que transcurre una interrupción y otra.
Yo lo que estoy diciendo es que si tienes rpm si tienes la longitud de la rueda puedes obtener Km/h de forma aproximada sin añadir ni toquetear nada, sólo con una fórmula.

Puede ser pero ¿conoces muchos vehículos cuyo velocímetro trabaje con el sensor de RPM?
Yo no conozco ninguno.

De todas maneras, con tu enfoque, igual habría que hacer una intervención para poder sensar en que cambio está la caja.

Saludos

¿Conoces alguna moto que no tenga control de RPM (DEL MOTOR NO DE LA RUEDA) o velocímetro? Yo entiendo que el autor esta usando una moto para sus proyectos pero no al punto de desmontarla y llegar a conectar en su moto algo que le indique si va en 1º, 5º o la marcha que sea y porque sus rpm no van a ser las del motor, las que calcula son las de la rueda (Harto usado en cálculos aprox. de motos de ahí que hayan calculadoras de esto en internet)

Ten en cuenta que el autor está cogiendo un código copiado, que su sistema de control de ¿velocidad angular? radicaba en un contador y que dice:

... pero el código sólo dispone de una variable a modificar: El radio.

Si, su proyecto va ser por estimaciones y no va a destrozarla ni es una moto que esté creando él desde cero, lo suyo es sencillamente multiplicar diámetro de rueda(en cm) * rpm * 0.001885
Si él puede tener en cuenta de alguna forma las revoluciones de su motor y la marcha que tiene seleccionada sencillamente tiene en cuenta este dato a la hora de calcular.

Pero

no en la rueda, en la rueda pondría el de velocidad.

Saludos

¿Necesita o no necesita el tamaño de la rueda?
¿Se puede conseguir multiplicando sí o no?
¿Se saca la velocidad angular con un contador y el intervalo de tiempo entre interrupciones?
De verdad... Yo doy esto ya por más que explicado.

Hacer copy and paste de códigos en inglés y no entender da lugar a estas cuestiones. Es cómo ese tema del Bluetooth donde ustedes de pronto pasan a leer comunicación por cable (Serial) y dicen que está bien o dan códigos por bueno cuando una persona les pregunta y el pin analógico definido es 0 en vez de un A0.
Yo explico las cosas, los autores de las dudas que decidan :slight_smile:

Voy a aclarar algunas cosas que se me pasaron en el post principal.
El proyecto consta de 2 sensores de efecto hall, uno que se encarga de medir las RPM del motor(conectado al pin 2) y otro sensor en la rueda delantera de la moto(conectado el pin 3).
El codigo que copie desde internet funciona perfecto para medir las rpm del motor.
Mi problema estaba en el sensor que va en la rueda, que se encarga de medir la velocidad.
No seria una mala idea saber la relacion de la caja de cambios y a base de calculos usando las rpm del motor hallar la velocidad de la moto. Seria mas facil a nivel software porque no tendria 2 interrupciones, el problema, no puedo saber en que cambio esta.
Espero que halla quedado un poco mas claro!

Listo, fin de la discusión, yo no me creo dueño de la verdad así que hasta aquí llego.

Un detalle: el compilador es lo suficientemente inteligente para reconocer que

analogRead(0)

equivale a

analogRead(A0)

Saludos

Perfecto, ahora ya se van aclarando un poco las dudas!
Yo llegue a algo que funciona pero dudo que este en lo correcto.

volatile int j = 0;
float t = 0;
int fin = 0;
int inicio = 0;
int band = 0;
int r = 35;
float velRueda = 0;
int k = 2*3.1416*r*3.6;
void setup() {
   attachInterrupt(digitalPinToInterrupt(3), VelocidadRueda, RISING);
   Serial.begin(9600);
}

void loop() {

    if (j == 1 && band == 0) {
    inicio = millis();
    band = 1;
  }
  if (j == 2) {
    j = 0;
    fin = millis();
    band = 0;
    t = (fin - inicio);
  }
  
  if (t > 0) {
  velRueda = k/t;
  Serial.println(velRueda);
  t = 0;
 }
 
 //Serial.println(j);
}

void VelocidadRueda()    // Funcion que se ejecuta durante cada interrupion
{
j++;
}

Ahora viendo como lo escribiste, lo pude comprender y creo que lo que yo hice funciona pero lo hice de una manera mucho mas dificil

Me queda preguntarte porque usaste micros en vez de millis? y ese valor de tiempo no seria la diferencia de tiempo en microsegundos, no deberia convertila a segundos? asi puedo sacar los m/s y luego multiplicar por 3,6 y tener km/h?

El uso de micros es por precisión.

Contra mayor velocidad, mayor número de pulsos. Contra menor sea el diametro de la rueda, mayor número de pulsos.

Al tratarse de velocidad, de 0 a 100Km/h, puede que obtengas hasta 20-30 pulsos por segundos que no es una barbaridad y se podría usar millis. Pero con micros obtendrás una mayor precisión.

Incluso puedes aumentar la relosución del sensor aumentando el número de imanes pegados en la llanta, pero al aumentar el número de pulsos puede que en un solo milisegundo tengas varios pulsos, con lo cual millis ya no te sirve.