7-segment speedometer and interrupts?

Hi

I am currently building a 2x7-segment bicycle rpm-/speedometer. I have got the code for driving the 7-segments up and running. Now I only need to attach the magnet sensor to the Arduino to update the display.

I was thinking of letting the code display the same speed until the sensor picks up one cycle and then calculate the new speed and let the display run that speed/rpm. The display uses the multiplexing method of updating the 7-segments.

So here´s my question. Should I let the code check for a change in the end/beginning of every loop or should I use interrupts ?

Personally, I would use this as an opportunity to learn more about interrupt handling and the pitfalls associated with them.

However, that is not to say that it is the best way.

After getting the interrupt way working, I'd see if simply reading the state of the switch on each pass through loop was sufficient. It may or may not be, depending on what else the sketch is doing, and how efficiently it is doing it.

You could get some idea about the need for interrupt based processing by toggling an LED on or off on each pass through loop. If the LED flashes slowly, interrupts will be required. If the LED flashes so fast that no flicker is observed, then interrupts are probably overkill.

You may find that the decision is made for you by the position of the maget/sensor from the hub.
The closer to the hub, the slower they're moving and generally, the longer the output pulse.
Out at the rim, the faster they're moving, and so the shorter the output pulse, which may be missed by polling too infrequently.

You may find that the decision is made for you by the position of the maget/sensor from the hub.

An excellent point. That's why prototyping and testing is so important.

Groove and Paul,

Ummmm, I don't think so. The time between pulses should be the same no matter where the sensor is located relative to the hub. The time interval in seconds will be 1 / wheel revolutions per second.

Now, if you're measuring linear velocity, by having the bicycle wheel turn a smaller wheel (as on a generator), then the position on the wheel does affect the speed you're measuring.

Regards,

-Mike

P.S. I doubt that interrupts are necessary. They will, however, make the program more complicated, harder to understand, and harder to debug.

The time between pulses should be the same no matter

No argument there.
I said the length of the pulses may be different.

If you're polling, a short pulse, like when the sensor is near the rim, may be missed.

P.S. I doubt that interrupts are necessary. They will, however, make the program more complicated, harder to understand, and harder to debug.

Properly written, an interrupt handler will do none of these. Think of the interrupt handler as being a person in charge of noting when the sensor came by. That's his or her only job. At any given time, you can see how many times the sensor has been detected, without having to worry about detecting it yourself.

The job of the interrupt handler is to note when a sensor came my. It simply increments a counter.

That's not hard to debug. The interrupt handler was either triggered or not. Not much can go wrong there. It either incremented the counter, or not. Not much can go wrong there. Pretty easy function to understand, too.

The rest of the code only needs to calculate the rpm by dividing the number of pulses (revolutions) by the time that was selected (one minute, one second, two weeks, whatever) and display the results.

You have to do that whether you are polling, or letting the interrupt handler "do the polling".

Hi all, some fun discussions here.

I think I will try the interrupt way first, basically because it´s a good excuse to learn about them. :sunglasses:

There are still a few things I don´t fully understand.
#1 Can I calculate time difference inside the interrupt and return it into the main loop ?
#2 When the interrupt has done it´s thing, will the main loop continue from where it was or does it begin from the start ?

Question #1: Possible, but it may be better just to increment a count, and let the main loop do the rate calculation

Question #2: it continues from the very next machine instruction, so no, it doesn't start at the top of the loop (unless the next instrction happens to be the jump back to the start!)

I have added the interrput part to my code but it wont add to the counter. Therefor it only shows a constant 00. Can anyone see watch wrong with it ?

int pin1 = 3;
int pin2 = 4;                                    //                            --6--
int pin3 = 5;                                    //                         5 |     | 7
int pin4 = 6;                                    //                           |--4--|
int pin5 = 7;                                    //                         1 |     | 3
int pin6 = 8;                                    //                            --2--
int pin7 = 9;
int gnd1 = 11;                                 //                          gnd1 is display 1's gnd
int gnd2 = 10;                                   //                          gnd2 is display 2's gnd
int timer = 500;                               //   A timer, to run the for loop 500 times, which turns out as 1 second.
int sens1 = 0;                                  //   Analog measurement pin
float gildi = 0;
float gildi1 = 0;
int fyrritala = 0;                              //  The first 7-segment display
int seinnitala = 0;                             //  The second 7-segment display
int gamlitimi = 0;
//volatile int stada = HIGH;
volatile int talning = 0;
int gamlatalning = 0;

void setup(){
  pinMode(pin1, OUTPUT);
  pinMode(pin2, OUTPUT);
  pinMode(pin3, OUTPUT);
  pinMode(pin4, OUTPUT);           //The following sets up all of the pins for use.
  pinMode(pin5, OUTPUT);
  pinMode(pin6, OUTPUT);
  pinMode(pin7, OUTPUT);
  pinMode(gnd1, OUTPUT);
  pinMode(gnd2, OUTPUT);
  pinMode(sens1, INPUT);
  
  attachInterrupt(0, talning_nemi, RISING);    //  Assigning the interrput to when digital pin 2 rises 
                                               //  from LOW to HIGH it triggers the interrupt
  
  Serial.begin(9600);
}
void loop()
{
  if (talning != gamlatalning){                // Here should the measured time be calculated if old count isn´t he same as the new
  gildi1 = (millis() - gamlitimi)/1000;
  
  gildi = (1/(gildi1))/3600;                   //  convert the value from seconds/turn to turns/minute
  
  talning = gamlatalning;
  gamlitimi = millis();
  
  //Serial.println(talning);                      //  For debugging
  //Serial.println(gamlatalning);
  
  
  if ((0 <= gildi) &&  (gildi < 10)){
   fyrritala = 0;
   seinnitala = gildi; 
  }

  if ((10 <= gildi) &&  (gildi < 20)){
   fyrritala = 1;
   seinnitala = gildi - 10; 
  }

  if ((20 <= gildi) &&  (gildi < 30)){
   fyrritala = 2;
   seinnitala = gildi - 20; 
  }

  if ((30 <= gildi) &&  (gildi < 40)){
   fyrritala = 3;
   seinnitala = gildi - 30; 
  }  
  
  if ((40 <= gildi) &&  (gildi < 50)){
   fyrritala = 4;
   seinnitala = gildi - 40; 
  }
  
  if ((50 <= gildi) &&  (gildi < 60)){
   fyrritala = 5;
   seinnitala = gildi - 50; 
  }
  
  if ((60 <= gildi) &&  (gildi < 70)){
   fyrritala = 6;
   seinnitala = gildi - 60; 
  }
  
  if ((70 <= gildi) &&  (gildi < 80)){
   fyrritala = 7;
   seinnitala = gildi - 70; 
  }
  
  if ((80 <= gildi) &&  (gildi < 90)){
   fyrritala = 8;
   seinnitala = gildi - 80; 
  }
  
  if (90 <= gildi){
   fyrritala = 9;
   seinnitala = gildi - 90; 
  }
  //Serial.println(fyrritala);
  //Serial.println(seinnitala);
  
  //for (int i=0; i<timer; i++){
  } 
    digitalWrite(gnd2, B1);
    if (fyrritala == 0){
      tala0();
    }
    if (fyrritala == 1){
      tala1();
    }
    if (fyrritala == 2){
      tala2();
    }    
    if (fyrritala == 3){
      tala3();
    }    
    if (fyrritala == 4){
      tala4();
    }    
    if (fyrritala == 5){
      tala5();
    }    
    if (fyrritala == 6){
      tala6();
    }    
    if (fyrritala == 7){
      tala7();
    }    
    if (fyrritala == 8){
      tala8();
    }    
    if (fyrritala == 9){
      tala9();
    }
    
       delay(0.5);
       digitalWrite(gnd2, B0);
       digitalWrite(gnd1, B1);
      
      if (seinnitala == 0){
      tala0();
      }
      if (seinnitala == 1){
      tala1();
      }
      if (seinnitala == 2){
      tala2();
      }
      if (seinnitala == 3){
      tala3();
      }
      if (seinnitala == 4){
      tala4();
      }
      if (seinnitala == 5){
      tala5();
      }
      if (seinnitala == 6){
      tala6();
      }
      if (seinnitala == 7){
      tala7();
      }
      if (seinnitala == 8){
      tala8();
      }
      if (seinnitala == 9){
      tala9();
      }
   
   delay(0.5);
   digitalWrite(gnd1, B0);
     

  //}
     //fyrritala = 0;
     //seinnitala = 0;
  

  }
  
  void tala0(){                //Writes the number 0 to the display
   digitalWrite(pin1, B1);
   digitalWrite(pin2, B1);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B0);
   digitalWrite(pin5, B1);
   digitalWrite(pin6, B1);
   digitalWrite(pin7, B1);
  }
  void tala1(){
   digitalWrite(pin1, B0);
   digitalWrite(pin2, B0);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B0);
   digitalWrite(pin5, B0);
   digitalWrite(pin6, B0);
   digitalWrite(pin7, B1);
  }
   
   void tala2(){
   digitalWrite(pin1, B1);
   digitalWrite(pin2, B1);
   digitalWrite(pin3, B0);
   digitalWrite(pin4, B1);
   digitalWrite(pin5, B0);
   digitalWrite(pin6, B1);
   digitalWrite(pin7, B1);
  }
   
    void tala3(){
   digitalWrite(pin1, B0);
   digitalWrite(pin2, B1);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B1);
   digitalWrite(pin5, B0);
   digitalWrite(pin6, B1);
   digitalWrite(pin7, B1);
  }
   
    void tala4(){
   digitalWrite(pin1, B0);
   digitalWrite(pin2, B0);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B1);
   digitalWrite(pin5, B1);
   digitalWrite(pin6, B0);
   digitalWrite(pin7, B1);
  }
   
    void tala5(){
   digitalWrite(pin1, B0);
   digitalWrite(pin2, B1);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B1);
   digitalWrite(pin5, B1);
   digitalWrite(pin6, B1);
   digitalWrite(pin7, B0);
  }
  
    void tala6(){
   digitalWrite(pin1, B1);
   digitalWrite(pin2, B1);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B1);
   digitalWrite(pin5, B1);
   digitalWrite(pin6, B1);
   digitalWrite(pin7, B0);
  }
  
    void tala7(){
   digitalWrite(pin1, B0);
   digitalWrite(pin2, B0);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B0);
   digitalWrite(pin5, B0);
   digitalWrite(pin6, B1);
   digitalWrite(pin7, B1);
  }
  
    void tala8(){
   digitalWrite(pin1, B1);
   digitalWrite(pin2, B1);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B1);
   digitalWrite(pin5, B1);
   digitalWrite(pin6, B1);
   digitalWrite(pin7, B1);
  }
  
    void tala9(){
   digitalWrite(pin1, B0);
   digitalWrite(pin2, B0);
   digitalWrite(pin3, B1);
   digitalWrite(pin4, B1);
   digitalWrite(pin5, B1);
   digitalWrite(pin6, B1);
   digitalWrite(pin7, B1);
  }
  
  void talning_nemi(){
    talning++ ;
    Serial.println(talning);
  }

Not usually a good idea to do serial prints in an interrupt service routine.
If you want to debug an interrupt routine, try toggling a LED on/off.

Best to strip down you sketch to the bare minimum until you've got the interrupt doing something visible, then add in the features, probably starting with serial reports of the rates.

[edit]Off-topic - What does "talning" mean? Something to do with "count" (or tally)?[/edit]

you got it right. Talning means counting in Icelandic. It was purely out of debugging I added that in, because the counting wasn´t working.

p.s. I don´t remember what/if the Serial printed out but I´ll update tomorrow

talning needs to be protected by disabling interrupts when accessed outside the interrupt service routine.

I´ve made two changes to my code now.

#1 To simplify the code while debugging I let the display show the interrupt counter talning instead of calculating the rpm

#2 I think I have protected talning sufficiently

void loop()
{
         
  if (talning != gamlatalning){                // Here should the measured time be calculated if old count isn´t he same as the new
  cli();                                       // disable interrputs to protect talning while working with it

  gildi = talning;
  sei();

The problem now is that the interrupter picks up way to many Rises. Each time the pin is HIGH the interrupt adds some where between 1 and 7 to the counter. Don´t I need some kind of filter to let the signal stabilize before it adds to the counter, but because it is forbidden to use delay() inside of interrupt I don´t know how I should do that. Should I add a small capacitor to even the signal out ?

Any ideas ? :-?

#2 I think I have protected talning sufficiently

You haven't. It needs to be protected from ALL access...

void loop()
{
  if ([glow]talning[/glow] != gamlatalning){  // <<<<< [glow]Bad News[/glow]
  cli();                                       // disable interrputs to protect talning while working with it
  gildi = talning;
  sei();

Something like this is what you'll need...

  cli();                                       // disable interrputs to protect talning while working with it
  gildi = talning;
  sei();

  if (gildi != gamlatalning){

I can't help with the other problem.

Thanks for that. I have changed it again

...but the problem is still there with picking up to many interrupts. I tried to add a capacitor parallel between ground and the sensor pin, which is pulled down to ground, but then it just picked up even more interrupts ! :o

I think I will add a statement into the main loop that if a interrupt has added again to the counter in less than X milliseconds it doesn´t add to another counter which I will then use to calculate the time with

Sounds like really bad contact bounce.
Are you using a reed relay as your sensor?
If so, you may want to consider a Hall effect sensor instead.

Some more comments on the Sketch...

delay(0.5);

delay takes an integer parameter. "deley(0)" is what gets executed. The parameter is the delay in milliseconds.

int gamlitimi = 0;

The value returned from millis is an unsigned long. I suggest changing gamlitimi to match.

  gildi1 = (millis() - gamlitimi)/1000;
  gildi = (1/(gildi1))/3600;                   //  convert the value from seconds/turn to turns/minute

Even though gildi1 is declared float, the first equation is an integer expression. Change it to this...

  gildi1 = (millis() - gamlitimi)/1000[glow].0[/glow];

If it isn't too difficult, replace the sensor with a simple pushbutton and a pulldown resistor. You may need to create a simplified Sketch that just outputs the count. If the count accurately follows button clicks, then there was very likely a hardware problem.

Has anyone mentioned that you don't have to call millis() to get a time - setup the 16 bit timer1 to count at some appropriate rate and read it in the ISR. Needs a bit of direct hardware manipulation (and will affect PWM on a couple of pins), but then you can just keep the last two timestamps from the last two pulses to calculate speed.

The debouncing can then be done in the ISR by ignoring any interrupt that happens implausibly soon after the last timestamp was recorded.

Thanks for the input :slight_smile:

I am currently using a pushbuttonswitch and the whole circuit and display is on a breadboard, so changing it won´t be to much of a problem. Maybe I´ll try hooking the reed switch to the circuit :-/
Code:

delay(0.5);

delay takes an integer parameter. "deley(0)" is what gets executed. The parameter is the delay in milliseconds.

The display has worked quite well all this time. I suppose it just multiplexes double as fast as it should instead

Has anyone mentioned that you don't have to call millis() to get a time - setup the 16 bit timer1 to count at some appropriate rate and read it in the ISR. Needs a bit of direct hardware manipulation (and will affect PWM on a couple of pins), but then you can just keep the last two timestamps from the last two pulses to calculate speed.

Is that method somewhat quicker than calling up millis() ?

The debouncing can then be done in the ISR by ignoring any interrupt that happens implausibly soon after the last timestamp was recorded.

I was thinking of something like that, but how can it be done inside of the ISR, doesn´t it have to be in the main loop ? Aren´t there problems with calling up time and having delays in side the ISR ?