Left over variables on reset or strange interrupt behaviour

I am building a cycle computer and have run into an odd problem.

There is a reed switch on the bike that makes contact when a magnet on the spokes passes it. The reed switch fires an interrupt that notes down the time and increments a counter. Outside the interrupt the RPM is counted by dividing the number of revolutions (ticks) that have occurred by the time (millis) taken. This means the main program can go off and do some time consuming task and get an average of the RPM when it gets back.

The problem is the first time the sketch runs after power up or reset the value in the counter is often (but not always) 1 when it was declared as 0. It would appear the interrupt is firing when it is attached or a few ms after. I don't understand why? I enable the internal pullup resistor and set the interrupt for falling before attaching it.

If I pull the code out into it's own sketch it works perfectly, first time through the rpms are zero as they should be. If it is included in the rest of my cycle computer sketch the RPMs are not zero first time through.

Is it normal for interrupts to fire when first attached? Do interrupts not play nicely with pullup resistors? Have I done something else stupid in my code?

These compile and can be used for testing. Connect pin 3 to ground to simulate a wheel spining

This one works.

#include <Wire.h>

//Various I/O pins. Listed here to keep track of them. Uncomment as requred.
    #define pinTacho 2                //2  interrupt 0, tacho RPM sensor
    #define pinWheel 3                //3  interrupt 1, wheel RPM sensor

//Variables and stuff
int speedNow = 0;                     //the bike's speed
int tachoRPM = 0;                     //the tacho (engine RPM)
unsigned long wheelRPM = 0;           //the tacho (engine RPM)
int wheelMinRPM = 1;                  //constant to add to the RPM so we never show the ratio as 1:0 //read this from the SD card
int wheelCirc = 2000;                 //circumference of the wheel where the speed is measured, in millimeters. 


volatile unsigned long wheelMillisCurrent = 1;          //time of this wheel tick.  May need to use micros() if millis isn't fast enough
unsigned long wheelMillisPrevious = 0;         //time of last wheel tick.  May need to use micros() if millis isn't fast enough
volatile unsigned long wheelTickCurrent = 0;            //this wheel tick number
unsigned long wheelTickPrevious = 0;           //last wheel tick number
unsigned long wheelTickCount = 0;             //increments each time the wheel rotates

unsigned long wheelRPMTemp = 0;
unsigned long wheelRPMTemp1 = 0;
unsigned long wheelRPMTemp2 = 0;
unsigned long wheelRPMTemp3 = 0;
unsigned long wheelRPMTemp4 = 0;
unsigned long speedTemp1 = 0;
unsigned long speedTemp2 = 0;
unsigned long speedTemp3 = 0;
unsigned long wheelTickDifference = 0;
unsigned long wheelMillisDifference = 0;

/***** Setup ************************************************************
  Setup the Arduino
************************************************************************/
void setup() {
 // Debug
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
 //Debug end
  
 //setup various I/O pins
  pinMode(pinWheel, INPUT_PULLUP);                // Wheel RPM, int0. Enable internal pullup resistor

 //setup interrupts
  delay(1000);
  attachInterrupt(1, wheelUpdate, FALLING);        //interrupt 1 uses pin 3. Wheel RPM sensor must be on this input for speed measurement
}

/***** loop() ************************************************************
  Main pogram loop
************************************************************************/
void loop() {
//Calculate the wheel RPM - Method 1, split it into bits so we don't accidentally overflow .
  
    //Wheel RPM. Revs = ticks/millisec * 1000 = ticks /sec * 60 sec = ticks/min = rev/min
  wheelTickDifference = wheelTickCurrent - wheelTickPrevious;
  Serial.print(" wheelTickDifference ");
  Serial.print(wheelTickDifference);
  /**** HELP, Lines above: First time through this gives wheelTickDifference == 0 as it should  ***/
 
  wheelMillisDifference = wheelMillisCurrent - wheelMillisPrevious;
  Serial.print(" wheelMillisDifference ");
  Serial.print(wheelMillisDifference);
  wheelRPMTemp = ((wheelTickDifference) * 1000 * 60) / (wheelMillisDifference+1);   //add 1 so we don't divide by zero
  Serial.print(" wheelRPMTemp ");
  Serial.print(wheelRPMTemp);

//Calculate the wheel RPM - Method 2, All in one line, we could accidentally overflow .
  wheelRPMTemp1 = ((wheelTickCurrent - wheelTickPrevious) *1000 *60) / (wheelMillisCurrent - wheelMillisPrevious+1);
  Serial.print(" wheelRPMTemp1 ");
  Serial.println(wheelRPMTemp1);
  /**** HELP, Lines above: First time through this gives wheelTickDifference == 0 as it should  ***/

  //Turn the current variables into previous
  wheelTickPrevious = wheelTickCurrent;
  wheelMillisPrevious = wheelMillisCurrent;

  //may need to add a delay in here so we don't try to update the screen too fast. 
  delay(1000);
}    


 
/**** Interrupts ********************************************************
* We may be busy doing something else when the tick occurs and miss several ticks. 
* Use TickCount to tell how many ticks have occured since we last took the difference in millis
************************************************************************/

/***** Wheel ***********************************************************
  update the wheel RPM reading
  The wheel sensor fired, grab the time and increment the wheel ticks.
***********************************************************************/
void wheelUpdate(){
  wheelMillisCurrent = millis();        //wheel rotated, note the time
  wheelTickCurrent++;                   //increments each time the wheel rotates
}

The attached one gives funny rpm readings on first run. (It's too big to post the code, I think).

ScooterPuterMini.ino (39.2 KB)

You need to debounce the reed switch just lick any other switch!

Mark

holmes4:
just lick any other switch!

But make sure it's not a mains switch

chopsuwe:
The problem is the first time the sketch runs after power up or reset the value in the counter is often (but not always) 1 when it was declared as 0. It would appear the interrupt is firing when it is attached or a few ms after. I don't understand why? I enable the internal pullup resistor and set the interrupt for falling before attaching it.

If I was writing code to take counts from an ISR I would not care what the absolute number is - I would just work with the difference between the count now and the previous value of the count. Would that approach solve the problem ?

And I would probably use an unsigned long for the count variable so it could go all the way to 232-1

...R
PS, for debouncing you just need to ensure you leave a little time between successive reads of the switch.

Robin2:
If I was writing code to take counts from an ISR I would not care what the absolute number is - I would just work with the difference between the count now and the previous value of the count. Would that approach solve the problem ?

And I would probably use an unsigned long for the count variable so it could go all the way to 232-1

...R
PS, for debouncing you just need to ensure you leave a little time between successive reads of the switch.

It's not a problem with switch bounce. The problem only happens on the first run trough the program after a reset. At that point the wheel isn't turning, the switch is open and the internal pullup resistor is doing it's thing. The interrupt shouldn't fire at all. The problem doesn't come back after the first loop through the program.

I'm not sure I follow, isn't that what I'm already doing? Divide the difference in revolutions by the difference in time since I last checked. I need to know how many times the wheel has gone round and the time difference to be able to calculate the speed later on.

I thought interrupts would be the way to go since I will also be monitoring up to 12,000RPM from the engine, calculating speed, gear ratio, various other statistics and displaying it on a 16x2 LCD. Is there a better way?

After much experimenting there appears to be some odd interaction with the LCD. If I comment out this line the problem goes away:

LiquidCrystal lcd(pinLCDRs, pinLCDEnable, pinLCD4, pinLCD5, pinLCD6, pinLCD7);

Of course then the LCD doesn't work. So why does setting the LCD pins cause an interrupt to trigger later when it is attached?

chopsuwe:
The problem only happens on the first run trough the program after a reset. At that point the wheel isn't turning, the switch is open and the internal pullup resistor is doing it's thing. The interrupt shouldn't fire at all. The problem doesn't come back after the first loop through the program.

I was trying to say that I don't understand why it matters if there is one (or even several) spurious counts before the real work begins. So what if the initial value is 237 ? Just go on from there.

I am lazy and your code is much too long for the amount of time I am prepared to give it. Which lines of the program deal with the counting and speed calcs ?

...R

Did you ever do this "todo"?

//Todo: convert to volatile 
unsigned long tachoMillisCurrent = 0;

You posted two sketches. I'm basing this analysis on the file ScooterPuterMini.ino, attached to the original post.

Here's my theory: From the time the processor comes out of reset until it turns on the pullup resistors for external interrupt pins 2 and 3, those pins are floating. During that time, their input state could be either high or low, depending on electrical noise. If they see a transition, they'll latch it in bits INTF0 and INTF1 of the external interrupt flag register EIFR. When attachInterrupt() executes, the external interrupts are enabled, and the processor responds to the pending interrupts by executing the ISRs.

Once the pullup resistors are enabled, the pins have a well-defined logic state, and a degree of noise immunity, so they don't get additional spurious transitions.

A way to eliminate this behavior would be to clear the interrupt flags for the external interrupts by writing 1's to the flag register, after pullups are enabled, but before attachInterrupt() executes. Try this -

  pinMode(pinTacho, INPUT_PULLUP);                // Tacho RPM, int0. Enable internal pullup resistor
  pinMode(pinWheel, INPUT_PULLUP);                // Wheel RPM, int0. Enable internal pullup resistor
  EIFR = (1 << INTF1) | (1 << INTF0);  // <- ADD THIS

 //setup interrupts
  delay(1000);
  attachInterrupt(0, tachoUpdate, FALLING);        //interrupt 0 uses pin 2. Engine RPM sensor must be on this input
  attachInterrupt(1, wheelUpdate, FALLING);        //interrupt 1 uses pin 3. Wheel RPM sensor must be on this input for speed measurement

This code is tested-ish: it ran on an Arduino Uno with no other hardware attached, and it gave the expected results: without the write to EIFR, the counters were 2 and 1; with that statement, they were 1 and 0. When you try it, don't forget that variable tachoTickCurrent is initialized to a value of 1.

tmd3:
When attachInterrupt() executes, the external interrupts are enabled, and the processor responds to the pending interrupts by executing the ISRs.

You're a genius! I didn't know interrupts latch. It seem odd that old interrupts aren't cleared during the attach command How did you find this out?

aarg:
Did you ever do this "todo"?

//Todo: convert to volatile 

unsigned long tachoMillisCurrent = 0;

Yes, it made no difference to anything. Maybe I go lucky this time.

Robin2:
I was trying to say that I don't understand why it matters if there is one (or even several) spurious counts before the real work begins. So what if the initial value is 237 ? Just go on from there.

I am lazy and your code is much too long for the amount of time I am prepared to give it. Which lines of the program deal with the counting and speed calcs ?

...R

They are in the attached sketch :wink: That section of the code is below. I was going to use a capacitor to do hardware debounce. I'm still curious if you have a better idea because I have a feeling my method isn't the best.

chopsuwe:
They are in the attached sketch :wink: That section of the code is below.

It looks like you forgot the attachment and the "below"

(I do it all the time).

...R

Internal names (which you can use to set up ISR callbacks) are in brackets.

OT
Nick, can you give me a hint WHERE are the callbacks (names) defined for Due?
Thanks

And forgive me for hijacking the thread.

Here's my theory: From the time the processor comes out of reset until it turns on the pullup resistors for external interrupt pins 2 and 3, those pins are floating. During that time, their input state could be either high or low, depending on electrical noise. If they see a transition, they'll latch it in bits INTF0 and INTF1 of the external interrupt flag register EIFR. When attachInterrupt() executes, the external interrupts are enabled, and the processor responds to the pending interrupts by executing the ISRs.

Seems possible.
I'll have to check the Due manual , but I think pullups are enabled by default.
But I am not sure.

WInterrupts?

Vaclav:
When attachInterrupt() executes, the external interrupts are enabled, and the processor responds to the pending interrupts by executing the ISRs.

But does it matter ?

...R

Robin2:
It looks like you forgot the attachment and the "below"

(I do it all the time).

...R

Yep, distracted by family

void updateSpeed(){
	//Each time the wheel revolves the reed switch closes and the ISR fires. Inside the ISR wheelTickCurrent is incremented and wheelMillisCurrent = millis()
	
		//Calculate the wheel RPM. Revolutions divide by time = revolutions per millisecond. Multiply by 60000 to get revolutions per minute. 
  wheelRPMTemp = ((wheelTickCurrent - wheelTickPrevious) *1000 *60) / (wheelMillisCurrent - wheelMillisPrevious);

    //Calculate the engine RPM. Use the same method as wheelRPM
  tachoRPM = ((tachoTickCurrent - tachoTickPrevious) * 1000 * 60) / (tachoMillisCurrent - tachoMillisPrevious);

    //Turn the current variables into previous to get ready for the next 
  wheelTickPrevious = wheelTickCurrent;
  wheelMillisPrevious = wheelMillisCurrent;
  tachoTickPrevious = tachoTickCurrent;
  tachoMillisPrevious = tachoMillisCurrent;

		//rolling average 
  wheelRPM -= wheelRPM / 3;
  wheelRPM += wheelRPMTemp / 3;
  
    //Calculate the road speed. since we know the wheel RPM and it's curcumference 
  speedTemp1 = (wheelRPM * wheelCirc);      //mm/min
  speedTemp2 = (speedTemp1 * 60);           //mm/hr
  speedTemp3 = (speedTemp2/ 1000/1000);     //m/hr
  speedNow = speedTemp3;

    //calculate the gear ratio. +1 to eliminate divide by zero error. 
  gearRatio = tachoRPM / (wheelRPM + 1);
  gearRatioH = gearRatio / 10;                                         //split the gear ratio into whole and decimal
  gearRatioL = gearRatio % 10;

Vaclav:
Nick, can you give me a hint WHERE are the callbacks (names) defined for Due?
Thanks

I admit I know virtually nothing about the Due, however a search revealed that this file might help:

/home/nick/.arduino15/packages/arduino/hardware/sam/1.6.4/system/CMSIS/Device/ATMEL/sam3xa/include

In there was stuff like interrupt numbers:

/**< Interrupt Number Definition */
typedef enum IRQn
{
/******  Cortex-M3 Processor Exceptions Numbers ******************************/
  NonMaskableInt_IRQn   = -14, /**<  2 Non Maskable Interrupt                */
  MemoryManagement_IRQn = -12, /**<  4 Cortex-M3 Memory Management Interrupt */
  BusFault_IRQn         = -11, /**<  5 Cortex-M3 Bus Fault Interrupt         */
  UsageFault_IRQn       = -10, /**<  6 Cortex-M3 Usage Fault Interrupt       */
  SVCall_IRQn           = -5,  /**< 11 Cortex-M3 SV Call Interrupt           */
  DebugMonitor_IRQn     = -4,  /**< 12 Cortex-M3 Debug Monitor Interrupt     */
  PendSV_IRQn           = -2,  /**< 14 Cortex-M3 Pend SV Interrupt           */
  SysTick_IRQn          = -1,  /**< 15 Cortex-M3 System Tick Interrupt       */
/******  SAM3X8E specific Interrupt Numbers *********************************/

  SUPC_IRQn            =  0, /**<  0 SAM3X8E Supply Controller (SUPC) */
  RSTC_IRQn            =  1, /**<  1 SAM3X8E Reset Controller (RSTC) */
  RTC_IRQn             =  2, /**<  2 SAM3X8E Real Time Clock (RTC) */
  RTT_IRQn             =  3, /**<  3 SAM3X8E Real Time Timer (RTT) */
  WDT_IRQn             =  4, /**<  4 SAM3X8E Watchdog Timer (WDT) */
  PMC_IRQn             =  5, /**<  5 SAM3X8E Power Management Controller (PMC) */
  EFC0_IRQn            =  6, /**<  6 SAM3X8E Enhanced Flash Controller 0 (EFC0) */
  EFC1_IRQn            =  7, /**<  7 SAM3X8E Enhanced Flash Controller 1 (EFC1) */
  UART_IRQn            =  8, /**<  8 SAM3X8E Universal Asynchronous Receiver Transceiver (UART) */
  SMC_IRQn             =  9, /**<  9 SAM3X8E Static Memory Controller (SMC) */
  PIOA_IRQn            = 11, /**< 11 SAM3X8E Parallel I/O Controller A, (PIOA) */
  PIOB_IRQn            = 12, /**< 12 SAM3X8E Parallel I/O Controller B (PIOB) */
  PIOC_IRQn            = 13, /**< 13 SAM3X8E Parallel I/O Controller C (PIOC) */
  PIOD_IRQn            = 14, /**< 14 SAM3X8E Parallel I/O Controller D (PIOD) */
  USART0_IRQn          = 17, /**< 17 SAM3X8E USART 0 (USART0) */
  USART1_IRQn          = 18, /**< 18 SAM3X8E USART 1 (USART1) */
  USART2_IRQn          = 19, /**< 19 SAM3X8E USART 2 (USART2) */
  USART3_IRQn          = 20, /**< 20 SAM3X8E USART 3 (USART3) */
  HSMCI_IRQn           = 21, /**< 21 SAM3X8E Multimedia Card Interface (HSMCI) */
  TWI0_IRQn            = 22, /**< 22 SAM3X8E Two-Wire Interface 0 (TWI0) */
  TWI1_IRQn            = 23, /**< 23 SAM3X8E Two-Wire Interface 1 (TWI1) */
  SPI0_IRQn            = 24, /**< 24 SAM3X8E Serial Peripheral Interface (SPI0) */
  SSC_IRQn             = 26, /**< 26 SAM3X8E Synchronous Serial Controller (SSC) */
  TC0_IRQn             = 27, /**< 27 SAM3X8E Timer Counter 0 (TC0) */
  TC1_IRQn             = 28, /**< 28 SAM3X8E Timer Counter 1 (TC1) */
  TC2_IRQn             = 29, /**< 29 SAM3X8E Timer Counter 2 (TC2) */
  TC3_IRQn             = 30, /**< 30 SAM3X8E Timer Counter 3 (TC3) */
  TC4_IRQn             = 31, /**< 31 SAM3X8E Timer Counter 4 (TC4) */
  TC5_IRQn             = 32, /**< 32 SAM3X8E Timer Counter 5 (TC5) */
  TC6_IRQn             = 33, /**< 33 SAM3X8E Timer Counter 6 (TC6) */
  TC7_IRQn             = 34, /**< 34 SAM3X8E Timer Counter 7 (TC7) */
  TC8_IRQn             = 35, /**< 35 SAM3X8E Timer Counter 8 (TC8) */
  PWM_IRQn             = 36, /**< 36 SAM3X8E Pulse Width Modulation Controller (PWM) */
  ADC_IRQn             = 37, /**< 37 SAM3X8E ADC Controller (ADC) */
  DACC_IRQn            = 38, /**< 38 SAM3X8E DAC Controller (DACC) */
  DMAC_IRQn            = 39, /**< 39 SAM3X8E DMA Controller (DMAC) */
  UOTGHS_IRQn          = 40, /**< 40 SAM3X8E USB OTG High Speed (UOTGHS) */
  TRNG_IRQn            = 41, /**< 41 SAM3X8E True Random Number Generator (TRNG) */
  EMAC_IRQn            = 42, /**< 42 SAM3X8E Ethernet MAC (EMAC) */
  CAN0_IRQn            = 43, /**< 43 SAM3X8E CAN Controller 0 (CAN0) */
  CAN1_IRQn            = 44, /**< 44 SAM3X8E CAN Controller 1 (CAN1) */

  PERIPH_COUNT_IRQn    = 45  /**< Number of peripheral IDs */
} IRQn_Type;

And ISR prototypes:

/* Cortex-M3 core handlers */
void Reset_Handler      ( void );
void NMI_Handler        ( void );
void HardFault_Handler  ( void );
void MemManage_Handler  ( void );
void BusFault_Handler   ( void );
void UsageFault_Handler ( void );
void SVC_Handler        ( void );
void DebugMon_Handler   ( void );
void PendSV_Handler     ( void );
void SysTick_Handler    ( void );

/* Peripherals handlers */
void ADC_Handler        ( void );
void CAN0_Handler       ( void );
void CAN1_Handler       ( void );
void DACC_Handler       ( void );
void DMAC_Handler       ( void );
void EFC0_Handler       ( void );
void EFC1_Handler       ( void );
void EMAC_Handler       ( void );
void HSMCI_Handler      ( void );
void PIOA_Handler       ( void );
void PIOB_Handler       ( void );
void PIOC_Handler       ( void );
void PIOD_Handler       ( void );
void PMC_Handler        ( void );
void PWM_Handler        ( void );
void RSTC_Handler       ( void );
void RTC_Handler        ( void );
void RTT_Handler        ( void );
void SMC_Handler        ( void );
void SPI0_Handler       ( void );
void SSC_Handler        ( void );
void SUPC_Handler       ( void );
void TC0_Handler        ( void );
void TC1_Handler        ( void );
void TC2_Handler        ( void );
void TC3_Handler        ( void );
void TC4_Handler        ( void );
void TC5_Handler        ( void );
void TC6_Handler        ( void );
void TC7_Handler        ( void );
void TC8_Handler        ( void );
void TRNG_Handler       ( void );
void TWI0_Handler       ( void );
void TWI1_Handler       ( void );
void UART_Handler       ( void );
void UOTGHS_Handler     ( void );
void USART0_Handler     ( void );
void USART1_Handler     ( void );
void USART2_Handler     ( void );
void USART3_Handler     ( void );
void WDT_Handler        ( void );

The SAM chips are somewhat more complex, I wouldn't be fiddling with these without some examples under my belt.

Thanks, that is exactly what I was looking for.
I feel silly - I could just let windows search for known "TC3_Handler".
Thanks again.