Would this work - 6 Interrupts!!!!

Can I just say Hi and introduce myself to this forum first.

I’ve been toying with electronics for a few years (as and when interesting projects came up) and have always thought it’d be easier if I could build the control logic in software… and then I came across a link to Arduino and now I’m learning to code.

My hardware is on a slow boat from China, really, so I don’t have the ability to test this out yet in real life.

This year I built a Pinewood Derby 6 lane finishline Judge, purely using electronic logic gates to display he finish order on 6 7seg LEDs.
It worked really well thou it bought home to me the need to capture the finish times and not just the order.

So here I am playing with Arduino coding to see if I can do just that.

I’ve written some code which I can interface with my current electronics and I’m playing around with MAX7221 coding drive 6 displays for each lane (times and finish order).

My challenge here is how to capture the finish time of the cars going over the IR LED sensors I’ll be using, so I’ve been reading a lot on this forum about enabling additional interrupts (as going this way I’ll need 6 of them if I’ve got this right).

The main loop() is overly simplistic here, the focus is on coding for the interrupts. I’m also reading up about pointers to replace the arrays used here

If you get a chance I’d appreciate your feedback on this first attempt.

I look forward to learning from you and hopefully one day being able to help others… a lot to learn first thou

Many thanks

James

#include <avr/interrupt.h>                                    
#include <avr/io.h>                                    
                                                                        
void setup() {                                    
  pinMode(A0, INPUT);  // Set analogue pins 0- 5 as inputs                                    
  pinMode(A1, INPUT);                                    
  pinMode(A2, INPUT);                                    
  pinMode(A3, INPUT);                                    
  pinMode(A4, INPUT);                                    
  pinMode(A5, INPUT);                                    
  Serial.begin(9600);                                    
                                    
 PCICR |= (1 << PCIE1);  // Set PCICR mask on PortC to enable interrupts                                    
                                    
 PCMSK1 |= (1 << PCINT8);  // Set Mask on Pins AO - A5 to enable interrupts                                    
 PCMSK1 |= (1 << PCINT9);                                    
 PCMSK1 |= (1 << PCINT10);                                    
 PCMSK1 |= (1 << PCINT11);                                    
 PCMSK1 |= (1 << PCINT12);                                    
 PCMSK1 |= (1 << PCINT13);                                    
                                    
}  // End Setup()                                    
                                    
volatile unsigned long endtime[6] =  {(0, 0, 0, 0, 0, 0)};                                    
volatile bool timeflag[6]         =  {(false, false, false, false, false, false)};                                    
float finishtime[6]               =  {(0, 0, 0, 0, 0, 0)};                                    
unsigned long starttime = 0;                                    
int timestaken = 0;                              

                                    
                                    
ISR(PCINT1_vect) {  // Interrupt Function - Triggered on pin change from HIGH to LOW on PortC pins A0 - A5                                    
                                    
if ( !(PINC & B00000001) && (!timeflag[0]) ) { // evaluates to TRUE when PortC bit #0 (Pin A0) is LOW and timeflag is False                                    
      endtime[0] = micros();                              
      timeflag[0] = true;                              
      return;                                
}  // end If                                    
if (!(PINC & B00000010) && (!timeflag[1]) ) { // evaluates to TRUE when PortC bit #1 (Pin A1) is LOW and timeflag is False                                    
      endtime[1] = micros();                              
      timeflag[1] = true;                              
      return;                              
}  // end If                                    
if (!(PINC & B00000100) && (!timeflag[2]) ) { // evaluates to TRUE when PortC bit #2 (Pin A2) is LOW and timeflag is False                                    
      endtime[2] = micros();                              
      timeflag[2] = true;                              
      return;                              
}  // end If                                    
if (!(PINC & B00001000) && (!timeflag[3]) ) { // evaluates to TRUE when PortC bit #3 (Pin A3) is LOW and timeflag is False                                    
      endtime[3] = micros();                              
      timeflag[3] = true;                              
      return;                              
}  // end If                                    
if (!(PINC & B00010000) && (!timeflag[4]) ) { // evaluates to TRUE when PortC bit #4 (Pin A4) is LOW and timeflag is False                                    
      endtime[4] = micros();                              
      timeflag[4] = true;                              
      return;                              
}  // end If                                    
if (!(PINC & B00100000)  && (!timeflag[5]) ) { // evaluates to TRUE when PortC bit #5 (Pin A5) is LOW and timeflag is False                                    
      endtime[0] = micros();                              
      timeflag[0] = true;                              
      return;                              
}  // end If                                    
                                    
}  // end Interrupt function                                    
                                    
                                    
void loop()                                    
{                                    

starttime = micros();                                    
                                    
while ( timestaken < 6 ) {  //  Create a loop until all 6 durations have been calculated                                    
      for (int i=0; i <= 6; i++) {                              
            if ( timeflag[i] )  {  //  If timeflag is TRUE, ie time available for comparison                        
                  finishtime[i] = ( endtime[i] - starttime ) / 1000000;                  
                  timestaken++;                  
                                    
//  I'll be inserting a whole lot of code here to report the numbers (and futher controls on the while loop) to display these                                    
//  results on a series of 5 digit 7 segment diplays controlled by MAX7221 IC's in series of 6 - lots of work to do here as well!!!                                    
//  As well as building additional exit routs from this loop                                    
                                    
            } // End If                        
      }  // End For                              
}  // end While                                    
                                    
//  Reset the variables and start again      
//  I'll have another switch here to control when the reset happens

endtime[6] =    (0, 0, 0, 0, 0, 0);                                    
finishtime[6] =  (0, 0, 0, 0, 0, 0);                                    
timeflag[6] =   (false, false, false, false, false, false);                                    
starttime = 0;                                    
timestaken = 0;                                                                        
} // End loop()

Why analogue pins? Don't you have enough contrast to use digital pins?

Korman

I choose the analogue pins because I wanted to keep all 6 interrupts on a single port, it wasn’t for any other reason than that.

From what I’ve read these pins should work just as well and I don’t need any analogue pins for the rest of the application.

I’m going to use the digital pins to drive the MAX7221’s (3 outputs needed) and some external switches (Start Gate and manual reset switch - 2 inputs needed) plus some LED’s which will flash on and off as the program and race evolves (2 outputs needed) though I could do that with some spare outputs from the MAX7221 chips as well.

James

When working with interrupts, it is very important to get the code correct the first time. It can be difficult to impossible to debug interrupt code.

Read through this... http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1261124850

Coding Badly, thanks for the reply, are you refering to the save-disable-restore method you outlined to stop reentrant ISR’s?

If I understand this I need to protect the non-atomic data (which is the endtime in this case as the flags are simple true/false statements (= 1 or 0, is this atomic?? Is it still atomic in an array??)

To do this I build your code into the main loop as follows:

if ( timeflag ) { // If timeflag is TRUE, ie time available for comparison

  • uint8_t SaveSREG = SREG; // save interrupt flag*
  • cli(); // disable interrupts*
    finishtime = ( endtime - starttime ) / 1000000;

* SREG = SaveSREG; // restore the interrupt flag*
… close statememnts etc
Is this right?
Thanks for the pointers
James

If I'm on the right track then this is the comment you are refering to..

A: It is not possible for an 8 bit processor to perform any operation atomically on a 32 bit value. Just reading the value requires four machine instructions; an interrupt can occur between any of them.

I think I'll break the reading of the "shared" variables out of the loops and read them all in one go, after disabling the ISRs, copy them into a seperate variable that the ISR's dont use then run the loop on these, ie revalidate the data once per loop

James

Coding Badly, thanks for the reply, are you refering to the save-disable-restore method you outlined to stop reentrant ISR’s?

Yes.

If I understand this I need to protect the non-atomic data (which is the endtime in this case

Yes.

as the flags are simple true/false statements

The array elements are a single byte so access is atomic.

Is it still atomic in an array??)

So long as you access individual array elements (which you are) then access is atomic.

To do this I build your code into the main loop as follows:

if ( timeflag ) { // If timeflag is TRUE, ie time available for comparison

  • uint8_t SaveSREG = SREG; // save interrupt flag*
  • cli(); // disable interrupts*
    finishtime = ( endtime - starttime ) / 1000000;

* SREG = SaveSREG; // restore the interrupt flag*
… close statememnts etc
Is this right?[/quote]
Close. You really should “temporary” variables so you can remove the math from inside the protected section.
> Thanks for the pointers
You are welcome.
> I think I’ll break the reading of the “shared” variables out of the loops and read them all in one go, after disabling the ISRs, copy them into a seperate variable that the ISR’s dont use then run the loop on these, ie revalidate the data once per loop

Exactly. If you don’t mind, please post your code so others can refer to it as an example.

OK, given the feedback this should work. I’ll post up physical test results once my hardware arrives.

Thanks for the help

James

#include <avr/interrupt.h>
#include <avr/io.h>

void setup() {
  pinMode(A0, INPUT);  // Set analogue pins 0- 5 as inputs
  pinMode(A1, INPUT);
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);
  pinMode(A4, INPUT);
  pinMode(A5, INPUT);
  Serial.begin(9600);

 PCICR |= (1 << PCIE1);  // Set PCICR mask on PortC to enable interrupts

 PCMSK1 |= (1 << PCINT8);  // Set Mask on Pins AO - A5 to enable interrupts
 PCMSK1 |= (1 << PCINT9);
 PCMSK1 |= (1 << PCINT10);
 PCMSK1 |= (1 << PCINT11);
 PCMSK1 |= (1 << PCINT12);
 PCMSK1 |= (1 << PCINT13);  

}  // End Setup()

volatile unsigned long isr_endtime[6] =  {(0, 0, 0, 0, 0, 0)};
volatile bool isr_timeflag[6]         =  {(false, false, false, false, false, false)};
volatile unsigned long endtime[6]     =  {(0, 0, 0, 0, 0, 0)};
volatile bool timeflag[6]             =  {(false, false, false, false, false, false)};

float finishtime[6]               =  {(0, 0, 0, 0, 0, 0)};
unsigned long starttime = 0;
int timestaken = 0;



ISR(PCINT1_vect) {  // Interrupt Function - Triggered on pin change from HIGH to LOW on PortC pins A0 - A5

if ( !(PINC & B00000001) && (!isr_timeflag[0]) ) { // evaluates to TRUE when PortC bit #0 (Pin A0) is LOW and timeflag is False
      isr_endtime[0] = micros();
      isr_timeflag[0] = true;
      return;  
}  // end If
if (!(PINC & B00000010) && (!isr_timeflag[1]) ) { // evaluates to TRUE when PortC bit #1 (Pin A1) is LOW and timeflag is False
      isr_endtime[1] = micros();
      isr_timeflag[1] = true;
      return;
}  // end If
if (!(PINC & B00000100) && (!isr_timeflag[2]) ) { // evaluates to TRUE when PortC bit #2 (Pin A2) is LOW and timeflag is False
      isr_endtime[2] = micros();
      isr_timeflag[2] = true;
      return;
}  // end If
if (!(PINC & B00001000) && (!isr_timeflag[3]) ) { // evaluates to TRUE when PortC bit #3 (Pin A3) is LOW and timeflag is False
      isr_endtime[3] = micros();
      isr_timeflag[3] = true;
      return;
}  // end If
if (!(PINC & B00010000) && (!isr_timeflag[4]) ) { // evaluates to TRUE when PortC bit #4 (Pin A4) is LOW and timeflag is False
      isr_endtime[4] = micros();
      isr_timeflag[4] = true;
      return;
}  // end If
if (!(PINC & B00100000)  && (!isr_timeflag[5]) ) { // evaluates to TRUE when PortC bit #5 (Pin A5) is LOW and timeflag is False
      isr_endtime[0] = micros();
      isr_timeflag[0] = true;
      return;
}  // end If

}  // end Interrupt function


void loop()
{

starttime = micros();



while ( timestaken < 6 ) {  //  Create a loop until all 6 durations have been calculated

        uint8_t SaveSREG = SREG;   // save interrupt flag
        cli();   // disable interrupts

        for (int i=0; i <= 6; i++) {  // copy to variables not accessed by the ISR's
                timeflag[i] = isr_timeflag[i];
                endtime[i]  = isr_endtime[i];
        }  // End For
        
        SREG = SaveSREG;   // restore the interrupt flag

      for (int i=0; i <= 6; i++) {
            if ( timeflag[i] )  {  //  If timeflag is TRUE, ie time available for comparison
                      finishtime[i] = ( endtime[i] - starttime ) / 1000000;
                      timestaken++;
            } // End If
      }  // End For
}  // end While


//  I'll be inserting a whole lot of code here to report the numbers (and futher controls on the while loop) to display these
//  results on a series of 5 digit 7 segment diplays controlled by MAX7221 IC's in series of 6 - lots of work to do here as well!!!
//  As well as building additional exit routs from this loop


//  Reset the variables and start again
//  I'll have another switch here to control when the reset happens

    uint8_t SaveSREG = SREG;   // save interrupt flag
    cli();   // disable interrupts
        isr_endtime[6] =    (0, 0, 0, 0, 0, 0);
        isr_timeflag[6] =   (false, false, false, false, false, false);
        endtime[6] =    (0, 0, 0, 0, 0, 0);
        timeflag[6] =   (false, false, false, false, false, false);
        
        finishtime[6] =  (0, 0, 0, 0, 0, 0);
        starttime = 0;
        timestaken = 0;
    
    SREG = SaveSREG;   // restore the interrupt flag


} // End loop()

James,

It looks to me that your ISR has a bias for the lower PCI pins because it exits on the first new pin found in a low state. Although it's unlikely, it's possible for a close tie to be awarded to the wrong car if the faster car was connected to a higher pin. I know we're talking uS, but you'd be surprised at how much latency there can be with interrupt servicing with all the pushing/popping.

I'd make two changes: First, read the time into a local variable at the start of the ISR so that ties get the same time. Second, check all pins before exiting the ISR.

Your comment at the top of the ISR implies that the Pin Change Interrupt happens on the low transitions. This interrupt happens when a pin toggles state. Your logic handles the check for pin low state, but note that the pin high interrupts (I'm assuming that this is possible with your sensors ...) will add additional latency, increasing the chance of an incorrect award as discussed earlier.

Of course, these are minor issues. But with interrupts, it's the minor issues that kill ya.

Cool project. Good luck.

MurMan, I must admit that I don't understand yet all the technical terms your using - need to do some more reading.

I was also considering the possibility of close ties and I'll certainly be considering your comments carefully.

I was also going to do some time testing on these interupts to see just how long they take and then relate this to distance travelled by the cars in the same time. Given the physical set up anything that has an accuracy to say a 1/3mm would be more than close enough.

Off to do some more researching to understand fully your comments.

Many thanks

James

I've made the following changes to the ISR routine as previously discussed below

The IR sensors I'm using are the Sharp IS471F which provide a nice logic High to LOW output when the beam is interrupted - ie car passes over IR LED.

The plan is to enable the interrupts only at the start of the race, clock the cars in each lane then disable the interrupts again. I'm hoping the use of the timeflag variables within each IF statement provide a latch that disables the ISR from writing a new time value until reset.

As the car pass the finishline I'll get a HIGH2LOW logic and then a LOW2HIGH when the car completly clears the finishline, of course I want to ignore the second transistion (and any more transistions until rest - it's not unknown for a car to bounce off the cushions at the end of the track and go back up the track under the finishline again.

With electronics I realised this with a couple of flipflops and feedback and reset logic gates.

Thanks for the continued support. I'm looking forward to unveiling this to our scout pack.

James

ISR(PCINT1_vect) {  // Interrupt Function - Triggered on pin change from HIGH to LOW on PortC pins A0 - A5

isr_time = micros();

if ( !(PINC & B00000001) && (!isr_timeflag[0]) ) { // evaluates to TRUE when PortC bit #0 (Pin A0) is LOW and timeflag is False
      isr_endtime[0] = isr_time;
      isr_timeflag[0] = true;
      return;  
}  // end If
if (!(PINC & B00000010) && (!isr_timeflag[1]) ) { // evaluates to TRUE when PortC bit #1 (Pin A1) is LOW and timeflag is False
      isr_endtime[1] = isr_time;
      isr_timeflag[1] = true;
      return;
}  // end If
if (!(PINC & B00000100) && (!isr_timeflag[2]) ) { // evaluates to TRUE when PortC bit #2 (Pin A2) is LOW and timeflag is False
      isr_endtime[2] = isr_time;
      isr_timeflag[2] = true;
      return;
}  // end If
if (!(PINC & B00001000) && (!isr_timeflag[3]) ) { // evaluates to TRUE when PortC bit #3 (Pin A3) is LOW and timeflag is False
      isr_endtime[3] = isr_time;
      isr_timeflag[3] = true;
      return;
}  // end If
if (!(PINC & B00010000) && (!isr_timeflag[4]) ) { // evaluates to TRUE when PortC bit #4 (Pin A4) is LOW and timeflag is False
      isr_endtime[4] = isr_time;
      isr_timeflag[4] = true;
      return;
}  // end If
if (!(PINC & B00100000)  && (!isr_timeflag[5]) ) { // evaluates to TRUE when PortC bit #5 (Pin A5) is LOW and timeflag is False
      isr_endtime[5] = isr_time;
      isr_timeflag[5] = true;
      return;
}  // end If

}  // end Interrupt function

I don't understand yet all the technical terms your using

Must have been the "pushing/popping" reference. What I was talking about is that the compiler inserts code into the beginning and ending of your ISR that save the status register and all of the other registers that the compiler uses for your ISR code. Registers are saved/restored by pushing them on the stack and latter popping them from the stack in reverse order. You can control this by adding the ISR_NAKED attribute to the ISR, but this puts the burden on you to manage registers. Best not to try this on your first project!

I read a post recently (that I can't find ...) where the poster found something over thirty uS of overhead due to this overhead.

Measuring interrupt latency is fun. ;D Best done with a logic analyzer if you have access to one. You'll need to add a digital out to the end of your ISR in order to detect that the ISR has run. Trigger on the sensor and measure the time to the digital out. Repeat until you find the max time. Note that the worst case latency occurs when a sensor triggers while you're already in the ISR due to a previous sensor interrupt. Measuring that is possible, but it gets more complicated to explain...

I'm guessing you may see worst case latency around 50-100 uSec. Should be easy to calc the distance moved once you get an estimate on the time. Unless your racers are really fast, you can safely ignore the relativistic effects ... ;)

As for your code, you still have the 'return' statements in each of sensor 'if' statements. If you remove them, all sensors will be evaluated on each pass of the ISR and you will be able to correctly detect a tie.

Murray

Murray,

Thanks again for the feedback, I'll remove the "return" statements.

Looking at some video of the last race day and doing some guesswork on the distances covered over time it looks like an accuracy of 1/3mm (more than close enough) would require the loop to complete in 75uSecs - right in the ballpark for the numbers you gave.

My kit has arrived in Zurich so hopefully by next weekend I'll have some time to test this code and see if I can replicate a tie.

I'll post back my findings.

Cheers

James