Timing a low high event

Evening,

I’m working towards an end goal of creating a solenoid v8 engine, run from an arduino. I have a sainsmart hall sensor board, which will be used to sense top dead centre. I need to record the time for one revolution, then I can take this time, divide by the number of cylinders and this will give me the time between firing each solenoid for one revolution.

So now there is a bit of backround to the start of this project, I need a little help. I’ve looked at a stopwatch example and reworked it to time one revolution (or a transition from high → low -->high -->low) (the sainsmart is a comparitor so will have 2 magnets in opposite poles). This is working as it should, however I’ve realised that this is going to cause me an issue as it is only going to collect the data every other revolution of the engine because it is working as a stop watch, so on the first revolution it will see the low high low which will start and stop the timer, BUT it will then wait for a further set, i.e. it will only time every other revolution = no good! could someone have a look at this code please and see if you could advise me where to go from here? I guess it needs to start timing when it sees one transition from high to low, then time ever set of low high low, and at the end of each low high low save the time taken to “oneCycle”.

#define ledPin  13                  // LED connected to digital pin 13
#define buttonPin 4                 // button on pin 4

int value = LOW;                    // previous value of the LED
int buttonState;                    // variable to store button state
int lastButtonState;                // variable to store last button state
int running;                        // condition for running - timer is timing
long interval = 100;                // blink interval - change to suit
long previousMillis = 0;            // variable to store last time LED was updated
long startTime ;                    // start time for stop watch
long oneCycle ;                     // elapsed time for stop watch
int fractional;                     // variable used to store fractional part of time
long cylinderInterval;               // time between cylinders in one rotation
long totalCylinders = 8;             // v8 = 8 cylinders

void setup()
{
   Serial.begin(9600);

   pinMode(ledPin, OUTPUT);         // sets the digital pin as output

   pinMode(buttonPin, INPUT);       // not really necessary, pins default to INPUT anyway
   digitalWrite(buttonPin, HIGH);   // turn on pullup resistors. Wire button so that press shorts pin to ground.

}

void loop()
{
    // check for button press
   buttonState = digitalRead(buttonPin);                   // read the button state and store

   if (buttonState == LOW && lastButtonState == HIGH  &&  running == false){     // check for a high to low transition
      // if true then found a new button press while clock is not running - start the clock

      startTime = millis();                                   // store the start time
      running = true;                                     // turn on running while timing
      delay(5);                                               // short delay to debounce switch
      lastButtonState = buttonState;      // store buttonState in lastButtonState, to compare next time
      Serial.println("Timer Started");

   }

   else if (buttonState == LOW && lastButtonState == HIGH && running == true){     // check for a high to low transition
      // if true then found a new button press while clock is running - stop the clock and report

        oneCycle =   millis() - startTime;              // store elapsed time for one cycle
        running = false;                                  // turn off running, all done timing
        lastButtonState = buttonState;                     // store buttonState in lastButtonState, to compare next time

       // routine to report elapsed time
        Serial.println("Timer Stopped"); 
        Serial.print( (int)(oneCycle / 1000L));         // divide by 1000 to convert to seconds - then cast to an int to print

        Serial.print(".");                             // print decimal point

        // use modulo operator to get fractional part of time 
       fractional = (int)(oneCycle % 1000L);

       // pad in leading zeros - wouldn't it be nice if 
       // Arduino language had a flag for this? :)
       if (fractional == 0)
          Serial.print("000");      // add three zero's
       else if (fractional < 10)    // if fractional < 10 the 0 is ignored giving a wrong time, so add the zeros
          Serial.print("00");       // add two zeros
       else if (fractional < 100)
          Serial.print("0");        // add one zero

       Serial.println(fractional);  // print fractional part of time 
      
      cylinderInterval = (oneCycle/totalCylinders);   //total time for one revolution divided by number of cyinders
       Serial.print("Time Between Each Cylinder: ");
       Serial.println(cylinderInterval);
   }

   else{
      lastButtonState = buttonState;                         // store buttonState in lastButtonState, to compare next time
   }
      if (running == true){
         digitalWrite(ledPin, HIGH);
      }
      else{
         digitalWrite(ledPin, LOW);                         // turn off LED when not running
      }
}

Just because you use one particular transition for timing on one set, doesn't mean you can't also use it on the next set.

So you have a low1 - high2 - low3 - high4. You'll get time for the low1 - high2 - low3, but if you had a second set of the same types of code, you could also read the high2 - low3 - high4. Then you could even keep up with low3 - high4 - low1 from the next set.

Rather than think of a "startTime" time just think of a "newPulseMillis". Record the newPulseMillis every time the sensor is triggered at TDC and record the fact that it triggered. Then the timing code will be something like this.

if (newPulse == true) {
  newPulse = false;
  pulseDuration = newPulseMillis - prevPulseMillis;
  prevPulseMillis = newPulseMillis;
}

...R

Thanks Robin, I'll have a play about with that later, makes sense :) The only thing I can see that may cause a problem (?) is that the timer will always be counting up and will get very big very quickly, will that be an issue to the arduino? I was thinking it would be better to reset milis() on each cycle to avoid this, but obviously I'm a total newbie with all of this, so I stand to be corrected :)

jamiem:
the timer will always be counting up and will get very big very quickly, will that be an issue to the arduino? I was thinking it would be better to reset milis() on each cycle to avoid this

Don’t think of resetting millis(). The millis() function returns an unsigned long value which can count to 232 before it rolls over to 0 - takes about 49 days, I think. However as long as you use unsigned long variables for related values and use subtraction to get the difference you will always get the correct value even when the rollover happens. One of the nice things with integer maths.

…R

Awesome, thanks Robin, off to try and make some progress now :slight_smile:

Evening Gents,

I finally had a chance to sit down and have another look at this. I've re-written it, and it appears to work as expected, so it prints the time for each "revolution" so thats every time it sees a new low input, it displays the time since the last low input. Cold you please have a check and see if it's all good, and if there is anything I could do better? Thanks :)

#define ledPin 13            //Led on pin 13
#define TDCsensorPin 4                //tdc sensor low/high pin 4


int lastTDCsensor;
unsigned long oneRev;
unsigned long previousMillis = 0;
unsigned long currentMillis;
boolean timing;
int TDCsensor;


void setup()
{
   Serial.begin(9600);

   pinMode(ledPin, OUTPUT);         // sets the digital pin as output

   pinMode(TDCsensorPin, INPUT);       // not really necessary, pins default to INPUT anyway
   digitalWrite(TDCsensorPin, HIGH);   // turn on pullup resistors. Wire button so that press shorts pin to ground.

}

void loop()
{
  TDCsensor = digitalRead(TDCsensorPin);              //read the input and assign to TDCsensor
  
  if (TDCsensor == LOW && lastTDCsensor == HIGH){     //check for a new low input
      currentMillis = millis();                       //currentMillis = time stamp
      oneRev = currentMillis - previousMillis;        //calculate time for one revolution
      previousMillis = currentMillis;                 //assign current time stamp to "previousMillis" to referece next loop
      timing = true;                                  //variable to read when sensor is low and timing active
      Serial.print("Time for one Revolution: ");      //serial monitor print...
      Serial.println(oneRev);                         
          }
   if (TDCsensor == LOW && timing == true){           //check if sensor low and timer running
      digitalWrite(ledPin, HIGH);                     //led on
   }
   else digitalWrite(ledPin, LOW);                    //led off
   
   lastTDCsensor = TDCsensor;                         //lastTDCsensor = current TDCsensor reading for next loop
if (TDCsensor == LOW && timing == true){           //check if sensor low and timer running
      digitalWrite(ledPin, HIGH);                     //led on
   }
   else digitalWrite(ledPin, LOW);

I'm not sure that this will do what you think. Also I find it much easier to understand when it is styled like this

if (TDCsensor == LOW && timing == true){           //check if sensor low and timer running
      digitalWrite(ledPin, HIGH);                     //led on
}
else {
       digitalWrite(ledPin, LOW); 
}

What I think will happen is that the led will light briefly when the sensor is LOW but most of the time it will be off. Is that what it is intended to do?

I don't see any code to change timing back to false so I can't see what value it adds.

...R

How do you know which of the two pulses per revolution is TDC?

I would use an interrupt.

    volatile unsigned long revMicroseconds = 0;
    volatile lastTDCTime = 0;
    static boolean evenPulse = true;

void setup() {
    attachInterrupt(0, isr, RISING);  // Call isr() each time Pin 2 (Interrupt 0) goes HIGH
}

void isr() {
    static unsigned long newPulseTime = micros();  // Current time in microseconds
    static unsigned long previousPulseTime, twoPulsesAgo;
  
    revMicroseconds = newPulseTime - twoPulsesAgo;

    // Record the TDC time every other pulse
    if (evenPulse) 
        lastTDCTime = newPulseTime;
    evenPulse = !evenPulse;

    twoPulsesAgo = previousPulseTime;
    previousPulseTime = newPulseTime;
}