Go Down

Topic: Getting RPM by measuring non-symetric signal. (Read 1 time) previous topic - next topic

ltetzner

I am working on a Motorcycle speed sensor.  The cycle provides a signal to the speedometer, from sensor on hub.  The signal comes from a reedswitch sensing magnets on a rotating disc.  The signal is two short duration negative pulses, followed by a long pause.  The duration of each of the items becomes shorter as the speed increases.  I am connecting to the Arduino with a pull up resistor, and the reedswitch is pulling to ground as magnet passes by.  The first short duration pulse is less than the second duration pulse and on the order of 1 to tens of milli-seconds.  For best accuracy I'd like to measure the pause between the pulses, and then I can convert that to MPH.  I am not sure if I should use attachinterrupt() or some other method to measure this.  I also am not sure how to ignore everything but the long time between pulses.  Any suggestions.  Attached is an image of the signal captured with an oscilloscope..

NOTE: This is part of a much larger project, so need it to be very efficient as far as time usage is concerned.

Any ideas would be appreciated.  I thought perhaps could attach two interrupts to a specific pin. e.g. attachinterrut(0,RisePulse,HIGH) and attachinterrupt(0,LowPulse,LOW).  Then with the RisePulse start a timer, and measure it with the LowPulse.  If the measurement is > last measurement I know I"m measuring the space between, and keep that value.  Otherwise I just discard the value.     ANY HELP/IDEAS?

Nick_Pyner

Quote
The signal comes from a reedswitch sensing magnets on a rotating disc.


Why don't you use a hall-effect sensor like everybody else does?

ltetzner

Since I already have an accurate source of data from the wheels rotation, I want to not add any additional hardware or modify the motorcycle any more than I have to. Thanks for asking!

ltetzner

#3
Mar 06, 2014, 04:56 pm Last Edit: Mar 07, 2014, 03:40 am by ltetzner Reason: 1
UPDATE:
I tried the following code, and got the following response.  Can any one tell me if my understanding of the attachInterrupt(0,xxx,RISING) call is what I need.  And it seems that in the function xxx, when i tried to disable interrupts to finish some calculations, the program stopped running. (note after the calculation I ussue the Interrupts() command to re-enable them.  What am I doing wrong here?

Help is appreciated..

Code follows...
Code: [Select]
/*
 RPM counter for Non-Symetrical Signal:
 Turns on an LED on for one second, then off for one second, to indicate code is running
 
 Want to Capture the pulses shown below, and measure the time/duration of the pulses.
 Due to the non symettrical nature of the pulses, a simple time measurement won't work.
 I'm thinkig if I triger on the Low to High pulses with an interrupt, I would then be
 getting two pulses/revolution.  A short pulse, followed by a long pulse.  I could then
 compare the prev pulse against current pulse, and if it is shorter throw the data out, but
 if it is longer save the data to be averaged.
 Then periodically stop the interrupt, allowning time to calculate an average for the
 captured pulses, and calculate an "RPM" value on that averaged value.  After the calculation
 allow the interrupts to continue, and start all over...
 
 Any ideas on this?  Does the code below look feasible.  I am new to Arduino, and esp the
 use of the interrupts.  Open to suggestions.
                                                          ltetzner   3/6/2014
 Signal as seen on an Oscilloscope                                                    
[tt] +5V_______   _____      _______________________   _____      _______________________
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
gnd        ----    -------                      ----    -------
             ^           ^                         ^         ^
Low-> Hi      |           |                         |         |  [/tt]
         
*/

// Pin 13 has an LED connected on most Arduino boards.
// give it a name:
int led = 13;
int led2 = 12;
 static long counter;    // Holds # of times interrupt was called

// the setup routine runs once when you press reset:  Target board is an Arduino UNO
[b]void setup()[/b] {          

// initialize the digital pin as an output.
 Serial.begin(9600);
 pinMode(led, OUTPUT);       //flash every second or so
 pinMode(led2,OUTPUT);      // Flash when input changes
 pinMode(0,INPUT);          //  Input is pin 2, which used interrupt 0.
 pinMode(0,HIGH);           // Enable the internal pull-up resistor
 Serial.print("Arduino Tachometer .. 3/6/2014\n\n");
 attachInterrupt(0,rpmfan,RISING);  //trigger low to high change
}




// the loop routine runs over and over again forever:
[b]void loop() [/b]{
   // Main loop just blinks led 13 to indicate code is running, real work done in interrupt.
Blink();

}



/*  Called by attachInterrupt(0,rpmfan,RISING);

*/
[b]void rpmfan() {[/b]
 long nww;            // Time of this interrupt call
 int k;
 static int j=0;        // Pointer into array holding all Pulse Widths
 static long oldnw;      // store last time interrupt was called
 static long PulseWidth[100];  // array to hold pulse widths
 counter++;    //global val counting # times interrupt called

 
 nww = millis();
 
 //each 100 time pulse detected, blink another LIED
 if ( counter == 50 || counter == 100) {    
   digitalWrite(led2,HIGH);
 } else {
   digitalWrite(led2,LOW);
 }
 if (counter >= 100) { counter=0;}    // Reset counter
 
 PulseWidth[j]= nww - oldnw;          // Save width of pulse was called
 j++;

if (j == 100) {
 //noInterrupts();      // disable all interrupts
 /// do other stuff
 Serial.print("Filled up 100 items in the array\n");
 
   // print out all the items in the array

/* for (k=1;k<=100;k++) {
   Serial.print(k);
   Serial.print("\t");
   if (k % 20== 0) {
     Serial.println(".");
   }
 }
 */
 j=0;
 Serial.println(".");

// interrupts();      // enable interrupts
}


 oldnw = nww;
}


[b]void Blink()[/b]
{

 // check to see if it's time to blink the LED; that is, if the
 // difference between the current time and last time you blinked
 // the LED is bigger than the interval at which you want to
 // blink the LED.
 unsigned long currentMillis = millis();
const int ledPin = 13;      // Blink LED pin
static int ledState=LOW;
static long previousMillis=0;
static int count=0;

 if(currentMillis - previousMillis > 1000) {
   // save the last time you blinked the LED
   previousMillis = currentMillis;  

   // if the LED is off turn it on and vice-versa:

     ledState = !ledState;

   // set the LED with the ledState of the variable:
   digitalWrite(ledPin, ledState);
   if (ledState) {
    Serial.print(currentMillis);
    Serial.print("\t ON   ");
    if (count==5){
      Serial.println();
      count=1;
    } else {
      count++;
    }
   }  //endif ledState
 }  //endif CurrentMillis
}   //end sub Blink


====================================================================
This is what I was seeing on the Serial Monitor

Arduino Tachometer .. 3/6/2014

1001    ON   3003    ON   5005    ON   7007    ON   9009    ON   11011    ON  
13014    ON   15016    ON   17018    ON   19020    ON   Filled up 100 items in the array
.
21022    ON  
23024    ON   Filled up 100 items in the array
.
25026    ON   27028    ON   29030    ON   Filled up 100 items in the array
.
31032    ON  
33034    ON   35036    ON   Filled up 100 items in the array
.
37038    ON   39040    ON   Filled up 100 items in the array
.
41042    ON  
43044    ON   45046    ON   Filled up 100 items in the array
.
47048    ON   49050    ON   51052    ON  
53055    ON   55057    ON   57059    ON   59061    ON   61063    ON  
63065    ON   65067    ON   67069    ON   69071    ON   71073    ON  
73075    ON   75077    ON   77079    ON   79081    ON   81083    ON  
83085    ON   85087    ON   87089    ON   89091    ON   91094    ON  


jremington

Interrupts should always be as short and fast as possible. Never print from an interrupt! For this application, I doubt you need interrupts. I would just sample the input in the main loop, put a time stamp on each input transition using millis() or microsec() and calculate rotational velocity from there. 
"It seems to run on some form of electricity"

Riva

#5
Mar 06, 2014, 05:16 pm Last Edit: Mar 06, 2014, 05:34 pm by Riva Reason: 1
First off you need to place code between code tags.
From what I think you describe and show would it be simpler to just measure the time between alternate rising or falling edges? A flipflop bit/byte that gets it's state flipped every call to the interrupt but the interrupt code only reads the time on one particular flipflop state.
Sudo interrupt code...
Code: [Select]
volatile byte flipflop = true;
volatile unsigned long lastTime;
volatile unsigned long timeDifference;

void int0_ISR(){
  flipflop = !flipflop; // Flip the state
  if (flipflop == true){
    timeDifference = millis() - lastTime;
    lastTime = millis();
  }


The time between a1-a2 and b1-b2 will be the same, no need to ignore short duration.
Code: [Select]
 Signal as seen on an Oscilloscope                                                    
+5V _______   _____      _______________________   _____      _______________________
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
gnd        ----    -------                      ----    -------
             ^           ^                         ^         ^
Low-> Hi    a1|         b1|                       a2|       b2|  

ltetzner

Riva:
Thank you for the feedback.  I'm not sure what you meant by "place code between code tags".  I did have some code I commented out with /* & */ so it wouldn't run when debugging.  Was that what you were reffering to?  Nevermind I just figured it out (was wondering how that was done when I initially posted this).  Got it now!

Questions:

  • Good idea on the flip flop idea, as deleting the need to filter out different times.  As for you second point would I use attachinterrupt(0,int_ISR,RISING) to measure time between the points mentioned?  instead of "timeDifference" I could put that into an long array? 

  • It seems to me that this would randomly lock onto either the long time (A1-b1) OR longer time (b1-a2).  Any ideas on how to only time the (b1 - a2) time.  I think that would give me better resolution, especially at high rotational speeds (since the pules get closer together at the high speeds). 

  • Also I am a little "fuzzy" on the need and use of the "volatile" item.  Could you explain it?



jremington:
You may be right on not needing interrupts for this.  I am just using it as an excuse to "play-with/learn" how to use interrupts.  Once I get this working, I may try a non-interrupt approach.  The input from a sensor just seemed like a natural use for an interrupt driven routine.  (but then again, I am new to this Ardunio stuff)!!!  I was able to get the above code to work and display the "Filled up 100 items..." string.  But when I tried to disable interrupts (in the interrupt routine) to give me time to print out the results, the code locks and no further output.  (never gets to the interrupts() command to re start interrupts.).  Anyway thank you for taking the time to respond.

ltetzner

#7
Mar 07, 2014, 03:59 am Last Edit: Mar 07, 2014, 04:05 am by ltetzner Reason: 1
Follow up:

I've made some progress, in that I've been able to read some pulses and made a first pass at correlating the pulse count to Miles-per-hour (I'd thought it would be linear, but it isn't).  I am now trying to break the relationship into a series of lines, to convert from rotations to MPH (each 10MPH has a different line... see attached graph..  

Code follows...

Code: [Select]
 /*    Tachometer/Speedometer measurement fron non-symmetrical signal
 
 
 */
 #include<Arduino.h>;
 
 //Global constants and variables...
 int LED1=13;
 int LED2=12;
 volatile int ptr=1;
 volatile byte flipflop = true;
 volatile unsigned long lastTime;
 volatile unsigned long TimeDifference;
 volatile unsigned long Measurements[20];
 volatile unsigned long RevolutionCounter=0;
 
 
 void setup()
 {
   Serial.begin(9600);
   pinMode(LED1,OUTPUT);
   pinMode(LED2,OUTPUT);
   pinMode(0,INPUT);
   pinMode(0,HIGH);
   Serial.println("Tachometer Program:  3/6/14 8:08pm\n");
   
   attachInterrupt(0,MeasRPM,RISING);
   digitalWrite(LED1,HIGH);
   
 }  //end setup
 
 
 
 /*
     Parameters for the line segments, where Y = mX + b is the equation for the line
       X    Y       m            b        Line
     124971.       10 -0.000183273 32.90399536 Y =-0.000183273260431303* X + 32.9039953570774
     70408.5    20 -0.000414139 49.15888431 and
     46262.0    30 -0.001362175 93.01694546 Y actually is MPH
     38920.8    40 -0.001132357 84.07225951
     30089.7    50 -0.004106664 173.5681432
     27654.6    60 -0.002184789 120.4194796
     23077.5    70 -0.003528167 151.4212631
     20243.2    80 -0.002419745 128.9833038
     16110.5    90 -0.00562799 180.6697308
     14333.7    100 0.012752004 -82.78297304
     15117.9    110 -0.00571693 196.4277353
     13368.7    120 -0.005201711 189.5399376
     11446.2    130  0.011357459 0
 */
 void loop()  {
 float Y;      // -3.4028235E+38 < float range < 3.4028235E+38\
 float m;
 float X;
 float b;
 float MPH;
 char buffer[16];    //future usage
 
   if (ptr == 10) {      // This is set in the interrupt routine
     noInterrupts();    //stop interrupt during this calculation
     
      digitalWrite(LED1,HIGH);    // Show that this is running
 
 
          Serial.print("Average= ");
          X = (Measurements[1]+Measurements[2]+Measurements[3]+Measurements[4]+Measurements[5]+Measurements[6]+Measurements[7]+Measurements[8]+Measurements[9])/9;
 
          /* NOTE regression analysis gave the equition 3x10^6 * X^-1.075  Using this did NOT
          give good results for the speedometer.  Will try using multiple line segments between
          inter MPH's instead
          0-10, 10-20, 20-30, etc.... - 140
             Y=mX + b    or MPH = m * Counts + b
          */
          if (X >= 124972 && X < 70409) {         // 10-20 MPH
            m = -0.000183273;
             b= 32.90399536;
          } else if (X >= 70409 && X < 46262) {  // 20-30 MPH
             m= -0.000414139;
             b=  49.15888;
          } else if (X >= 46262 && X < 38921) {  // 30-40 MPH
             m= -0.001362175;
             b=  93.01695;
         } else if (X >= 38921 && X < 30090) {  // 40-50 MPH
             m= -0.001132357;
             b=  84.07226;
          } else if (X >= 30090 && X < 27655) {  // 50-60 MPH
             m = -0.004106664;
             b=  173.5681;
          } else if (X >= 27655 && X < 23078) {  // 60-70 MPH
             m = -0.002184789;
             b=120.4195;
          } else if (X >= 23078 && X < 20243) {  // 70-80 MPH
             m = -0.003528167;
             b=151.4213;
          } else if (X >= 20243 && X < 16111) {  // 80-90 MPH
             m= -0.002419745;
             b=128.9833;
          } else if (X >= 16111 && X < 14334) {  // 90-100 MPH
             m= -0.00562799;
             b=180.6697;
          } else if (X >= 14334 && X < 15118) {  //100-110 MPH
             m= 0.012752004;
             b=-82.7829;
          } else if (X >= 15118 && X < 13369) {  //110-120 MPH
             m = -0.00571693;
             b=196.4277;
          } else if (X >= 13369 && X < 11446) {  //120-130 MPH
             m = -0.005201711;
             b=189.5399;
          } else if (X >= 20243 && X < 16111) {  // 130+
             m= = 0.011357459 ;
             b=0;
          } //if (X
         
          MPH = (m * X) + b;     //Y = mX + b.... line segment for each 10 MPH range.
       
          dtostrf(MPH,3,1,buffer);      //Re-format to show MPH with a single digit
         
          Serial.print(buffer);
          Serial.print(" MPH \t based on count of [");
          Serial.print(X);
          Serial.print("]\t\t RawValue= ");
          Serial.print(MPH);
          Serial.print(" \t# Rev's = [");
          Serial.print(RevolutionCounter);
          Serial.println("] Rev's");
            ptr=1;      // Reset pointer into results array, and refill
   }  
   else
   {
      digitalWrite(LED1,LOW);  
   } // endif ptr
   interrupts();
 
 } //end loop
 
 /*      Take 10 Delta-T (time) readings, and store them in array Measurements[]
       Store the RevolutionCounter  (will be used later to calculate ODOMETER readings.
 
 */
 void MeasRPM()  {
 unsigned long now;
   now=micros();
 
     // otherwise do nothing till ptr is set back to 0
    flipflop = !flipflop; // Flip the state
   
   if (flipflop == true){
     TimeDifference = now - lastTime;
     RevolutionCounter++;      //Eventually use to determine mileage/odometer
     
     if (ptr <= 10) {
       Measurements[ptr] = TimeDifference;
         ptr++;
     } //endif ptr
       lastTime = now;
   }//endif FlipFlop
 }  //end sub


Riva


  • It seems to me that this would randomly lock onto either the long time (A1-b1) OR longer time (b1-a2).  Any ideas on how to only time the (b1 - a2) time.  I think that would give me better resolution, especially at high rotational speeds (since the pules get closer together at the high speeds).  

In your original post you said the signals came from magnets placed on a disc so I assumed both pulses come from 2 magnets. If it is that simple then you need to include the whole time between alternate pulses and where the ISR locks on does not matter. If this is some electronic system that puts out a short duration double pulse and the land between the second rising edge and the next drop (d-e) is the speed then what I suggested is no good as it is and will need re-thinking.
  • Also I am a little "fuzzy" on the need and use of the "volatile" item.  Could you explain it?

Volatile should be used on any variables that are accessed by the main program and the interrupt code.

A quick look at your latest code above and a couple of problems I can see.
Your not bounds checking ptr in the ISR so it could start writing to memory that does not belong to it. An array also starts at zero not one.
You need to disable interrupts in your main code to safely access interrupt variables (as you do) but then you go on to do calculations and printing before enabling them again. A better approach would be disable ints/copy vars/enable ints/calc on copies/print so the interrupts are disabled for the least time possible.

Code: [Select]
+5V _______   _____      _______________________   _____      _______________________
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
          |  |    |     |                      |  |    |     |
gnd        ----    -------                      ----    -------
          ^  ^    ^     ^                      ^  ^    ^     ^
          a  b    c     d                      e  f    g     h


ltetzner

Hi Riva,  Excellent feedback  I will look into this some more.  I am not sure what causes non-symmetry of the signal.  I am connecting to a both sides of a 'reed switch', that is sensing something inside a Motorcycle Speedometer.  (originally this switch was intended for use in the cycle to turn off turn signals after a certain distance is moved.  The cable comes off a mechanical gear on the hub of the front wheel.)

I am working on trying to get a better feel for this signal, by looking at it with an oscilloscope.  One side of switch is at "ground",  the other is pulled up with resistor to 5V, and connected to the Arduino input.   The basic shape (small pulse - large pulse - long pause) stays the same, but as the speedometer increases the duration of each "event" gets shorter.  (I'll post two Oscilloscope screen shots later for anyone who is interested).

I've been playing with using external interrupts, and just a very fast interrupt to track when transitions occur.  By comparing the input signal (on O-scope) to an output pulse, to see if I'm triggering on every change etc.  e.g.
Code: [Select]
{   K = digitalRead(7);   digtalWrite(8,K);  }  With not much going on in other loops I am able see a duplicate signal on pin 8 (output) as is coming in on pin 7 (input) with a slight delay of course.  But when other things are occurring in code I'm not getting the same signal symmetry.

ASIDE:  Thanks for the info on "Volatile" and the idea about when to disable interrupts etc.  I will look into this next.  This is to be just another part of a bigger project that is using interrupt 2 as a clock to do other things.  I am now trying to add an Odometer/SpeedOmeter/Trip computer/On Time/Trip Time functions to the current project.   
This is a lot to handle for a "newbie" but very intersting, and a lot of fun!
Thank you for taking the time to provide feedback!

toddm

A reed switch will close when the magnetic field on one end of the switch is strong enough to cause attraction of the reed to the other part of the switch. If you pass a magnet over the center of the reed switch (perpendicular to the reed switch center line) so that the magnetic fields are equal on both ends, the switch will not close. It could be that the reed switch you are using is oriented such that the magnet passes down the length of it causing it to close, open, then close again as the magnetic field changes in the reed switch.  If you can it would be better to have the switch oriented so that the magnet passes over one end of it only perpendicular to the reed switch centerline.

michinyon

The duration of the pulses should not concern you too much,    what you need to measure is the elapsed time between pulses.   

Since you appear to be getting two pulses per wheel revolution,   you actually want to measure the time from the rising ( or falling ) edge of a pulse,   not to the next pulse,   but to the pulse after that.

If doesn't matter if you are detecting the first pulse of each pair,   or the second pulse of each pair,  you just want to measure the time between every second pulse.

michinyon

Quote
and made a first pass at correlating the pulse count to Miles-per-hour (I'd thought it would be linear, but it isn't).


Your idea that it would be linear,  is mis-conceived.

It IS linear,  but you have your units upside down.    The pulse counter correlates directly  to  hours per mile.

jremington

I second michinyon. If you measure the time between pulses (say between the leading edges), then miles-per-hour is directly proportional to 1/(time between pulses). The graph you show is the inverse of that relationship.
"It seems to run on some form of electricity"

Go Up