1

In short: I have an Input Capture code and a Power-Saving code combined into one program. Both are adapted from Nick Gammon's examples that work fine independently. When I combined them into one code it only reads the data every other time a single pulse is sent to be captured every 20 seconds. The board sleeps during one idle cycle between the 20 second readings, then wakes up but misses the first reading, and then it stays awake during the next idle cycle and reads the pulse correctly at the 40 second mark for the second reading. And repeats. The references of these two Nick Gammon codes are listed in the program.

Details: Adafruit BlueFruit Feather 32u4 w/nrf51 in the Arduino 1.8.10 IDE. Using Nick Gammon's example for Input Capture an external incoming pulse of around 800 us (micro sec) long pulse is being measured by (ICP3) Input Capture #3 (rewritten from ICP1) on Pin D13 (PC7) (removed the LED). Works good.

Then I had a separate program that puts the board into deep-sleep and using INT0 (D3) to wake it up with an external interrupt signal. Since I was not able to figure out how to use the Input Capture as an ISR Interrupt to wake up the board, I ran the same external pulse signal also to the D3 (INT0) pin. So when the pulse hits both D3 (INT0) and D13 (ICP3) at the same time, and D3 wakes up the board via INT0 and then the ICP3 can capture the pulse and measure it. Then right after that, or several seconds later, can go back to sleep until the next pulse arrives on D13 (ICP3). I spaced the external 800us single pulses 20 seconds apart to make it easier to diagnose. If I remove the line that places the board into sleep "sleep_cpu ();" I get a nice single-shot signal pulse measurement every 20 seconds. Since I am not using the USB-detach and reconnect feature, after the board goes to sleep it disconnects, but I am monitoring the power usage via a Fluke mA meter and I can see when it sleep and when it wakes up, and also monitoring the external pulse going to D13 (ICP3) and D3 (INT0) on the Oscope.

Notes: The Input Capture is listed under the Interrupts, but at a much lower priority then INTo or INT1. I do not need an active USB during the readings or sleep, because using BLE to send the data. The code I posted has the BLE portion removed the make it simpler to troubleshoot. The 800us external single-shot pulse is rock solid I have it showing on the Oscope, at the proper magnitude (0 to 3.3V) and intervals. I am trying to have the CPU sleep between readings. Can be right after the reading or a few seconds later. Interrupts have to be alive and ready to capture in case the 20 seconds becomes 1 or 2 seconds intervals (in real life).

// Time the interval between two consecutive pulses.Frequency timer using input //capture unit #3 on 32u4
// Author: Nick Gammon
// Date: 31 August 2013  http://www.gammon.com.au/timers 
//  References: here:  http://www.gammon.com.au/forum/?id=11504&reply=12#reply12    //and  here:  https://www.gammon.com.au/power Sketch J
// Info here: http://gammon.com.au/interrupts
//Freq (Hz)     Per (µs)
// 8,000,000*   0.125us  or 125ns per clock tick at 8mghz


#include "LowPower.h" 
//part of work around for BOSD missing functionality in 32u4.  
//Not needed for 328p MCUs for sleep.

#include <avr/sleep.h>
#include <avr/power.h>
#include <avr/interrupt.h>
#include "avr/io.h"
#include <math.h> 
#include <Arduino.h>


// Input: Pin D13 (PC7, ICP3) adafruit 32u  which is Timer3 Capture Input pin ,   //removed D13 LED by cutting trace or by removing resistor before LED13

const int wakeUpPin = 3;
volatile boolean first;
volatile boolean triggered;
volatile unsigned long overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;
volatile unsigned long elapsedTime;

//////START: ISR TIMER/COUNTER PULSE ROUTINE ON D13 INPUT-CAPTURE #3 on 32u4 //// 
//https://www.nongnu.org/avr-libc/user-manual/group__avr__interrupts.html   //http://medesign.seas.upenn.edu/index.php/Guides/MaEvArM-timer3

// timer overflows (every 65536 counts)
ISR (TIMER3_OVF_vect) //1 3 

{
        overflowCount++;
}  // end of TIMER3_OVF_vect

ISR (TIMER3_CAPT_vect)   // 1 3  
  {
       // cancel sleep as a precaution  (added here from Power Save)
      sleep_disable();
    
   // grab counter value before it changes any more
  unsigned int timer3CounterValue;
  timer3CounterValue = ICR3;  
// see datasheet, page 117 (accessing 16-bit registers)   
  unsigned long overflowCopy = overflowCount;
  
  // if just missed an overflow
  if ((TIFR3 & bit (TOV3)) && timer3CounterValue < 0x7FFF)  // 1 3     //http://medesign.seas.upenn.edu/index.php/Guides/MaEvArM-timer3
    overflowCopy++;
  
  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
    {
    startTime = (overflowCopy << 16) + timer3CounterValue;
    first = false;
    return;  
    }
    
  finishTime = (overflowCopy << 16) + timer3CounterValue;
  triggered = true;
  TIMSK3 = 0;    // no more interrupts for now   // 1 3    //http://medesign.seas.upenn.edu/index.php/Guides/MaEvArM-timer3
  }  // end of TIMER3_CAPT_vect

/////////////////////////ENF OF: TIMER/COUNTER PULSE MEASURE ROUTINE//////////// 


////////PREPARE FOR INTERRUPTS ROUTINE FOR TIMER/COUNTER CAPTURE////////////////

void prepareForInterrupts ()
  {
 
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR3A = 0;   // 1 3
  TCCR3B = 0;    // 1 3
  
  TIFR3 = bit (ICF3) | bit (TOV3);  // clear flags so we don't get a bogus interrupt    // 1 3
  TCNT3 = 0;          // Counter to zero   // 1 3
  overflowCount = 0;  // Therefore no overflows yet
  
  // Timer 3 - counts clock pulses
  TIMSK3 = bit (TOIE3) | bit (ICIE3);   // interrupt on Timer 3 overflow and input capture
  // start Timer 1, no prescaler
  TCCR3B =  bit (CS30) | bit (ICES3);  

// plus Input Capture Edge Select (rising on D13)   
//TCCR1B &= ~(1<<ICES1);            
//ICP set to trigger on falling/rising edge
//   Bit 6 - ICES1: Input Capture Edge select    Select edge detection for //input capture function.          0 = Capture on falling edge  1 = Capture on //rising edge
  
  interrupts ();
  }  // end of prepare For Interrupts
  
///END OF:   PREPARE FOR INTERRUPTS ROUTINE FOR TIMER/COUNTER////////////
  
///POWER SAVING -SIGNAL INTERRUPT WAKEUP ISR ON D3 on 32u4 or D2 on 328p///

void wakeUp ()
 {
  // cancel sleep as a precaution
     sleep_disable();
  // precautionary while we do other stuff
   detachInterrupt(digitalPinToInterrupt(wakeUpPin));   
//D3 for 32u4 and D2 for 328p (INT0)
 }  // end of wakeUp
////POWER SAVING ROUTINE - WAKEUP ISR-END/////////////////

////////SET UP VOID ///////////////////////////////////////////

void setup(void)
{
 pinMode(wakeUpPin, INPUT); //this is pin D3 (INT0) on 32u4 and D2 (INT0) on 328p
digitalWrite (wakeUpPin, LOW);  
// enable pull-down on D3 (on 32u4 D3 is INT0) for Wake INTERRUPT   - PART OF //POWER SAVING ROUTINE WITH INT0 SIGNAL on D3 on 32u4 (or D2 on 328p)

 Serial.begin(115200);
  
  pinMode(13, INPUT);   
//This is the input/measure pin (LED taken off or LED13 resistor removed)
   analogWrite(13, LOW);  //must pull up or down to stop noise and floating

  // set up for interrupts
  prepareForInterrupts ();   

}   // <= this is for the SET UP () ///////

////////////////////////END OF SETUP SEGMENT/////////////////


//////START OF MAIN LOOP/////////////

void loop(void)
{

   // wait till we have a reading
if (!triggered) //if "triggered" is NOT TRUE  (i.e. NOT TRIGGERED) then STOP HERE
 return;      
    
unsigned long  elapsedTime = (finishTime - startTime); 
//period count between start and stop times

Serial.print ("Start time: ");
Serial.print(startTime);
Serial.print (" counts.  ");
Serial.print ("Finish time: ");
Serial.print(finishTime );
Serial.print (" counts.  ");
Serial.print ("Elapsed time: ");
Serial.print (elapsedTime);
Serial.println (" counts.   ");

 // Delay before next measurement update
 delayMicroseconds(1000) ;

 prepareForInterrupts (); 
//  goes back up outside LOOP to RESET conditions for next INPUT CAPTURE ISR AND //OVERFLOW ISR TIMER/COUNTER reading, clear registers, etc


///START: POWER SAVING ROUTINE USING D3 (INT0) on 32u4  (D2 is INT0 on 328p) //INTERRUPT FROM PULSE OR USER SWITCH////////////

//delayMicroseconds(3000000) ; 
// DELAY SO SLEEP ONLY CAN HAPPEN EVERY 3 sec =>  3000000 us (micro sec)

 delay(500);// MUST have delay to allow power down process to proceed

      // disable ADC
      ADCSRA = 0;  
  
      set_sleep_mode (SLEEP_MODE_PWR_DOWN);  
      sleep_enable();

  // Do not interrupt before we go to sleep, or the ISR will detach interrupts and we won't wake.
      noInterrupts ();

  // will be called when INT0 (pin D2 for 328p) or D3 for 32u4 goes HIGH//
     attachInterrupt(digitalPinToInterrupt(wakeUpPin), wakeUp, RISING);   

// [External Interrupts] //3 for 32u4 and 2 for 328p (INT0)  Use RISING as //trigger for (INT0 on D2 for 328p and) INT0 on D3 on 32u4 from op AMP.  Same //signal as the D13 INPUT CAPTURE TIMER from op Amp OUT
     EIFR = bit (INTF0);  // clear flag for interrupt 0
     EIFR = bit (INTF1);  // clear flag for interrupt 1 
//  added just for good measure to cover both D2 and D3 INT pins
          
// turn off brown-out enable in software, BODS does not work with 32u4, only //328p.  Work around is to use "low-power.h" library, not implemented here
// BODS must be set to one and BODSE must be set to zero within four clock cycles
       // MCUCR = bit (BODS) | bit (BODSE);
       // The BODS bit is automatically cleared after three clock cycles
       // MCUCR = bit (BODS); 
        
//We are guaranteed that the sleep_cpu call will be done as the processor //executes the next instruction after interrupts are turned on.
        interrupts ();  // one cycle
     // sleep_cpu ();   // one cycle    //*****DISABLED BECAUSE KEEPS DOUBLING 
                                        //DATA SEND TIME, AND ONLY WAKES UP HALF 
                                        //THE TIME

///////END OF POWER SAVING ROUTINE///////////////////////////////////////////////

} 
///////////////////////END OF MAIN LOOP///////////////////

1 Answer 1

2

It seems to me your problem lies here:

set_sleep_mode (SLEEP_MODE_PWR_DOWN);

In this mode, all the synchronous peripherals are turned off, including your timer. However, you need this timer for generating the input capture interrupt. You also want it to be running in order to count the overflows, otherwise the values captured would be meaningless.

In order to keep the timer running, you have to use SLEEP_MODE_IDLE, which is the default sleep mode anyway. This mode puts the CPU to sleep while keeping the peripherals running. If you are afraid this may have too high a power consumption, you can selectively turn off all the peripherals you do not need. Note that you cannot selectively enable peripherals in other sleep modes.

2
  • Thank you for the suggestion & link. Saw an example for IDLE mode. Will check if shutting down unused peripherals lowers close to same as consumption as in SLEEP_MODE_POWERDOWN. My assumption was that using an INT0-type interrupt would wake up the CPU so the INPUT CAPTURE TIMER would be fully ready. Works half of the time, on every 2nd external pulse, so I thought that there is some sort of an interrupt-sequence/priority conflict in my code, or some of the flags (which are still murky for me) are not set properly as a result of the two combined codes. I suspect "if !triggered" and "return".
    – TommyS
    Commented Sep 13, 2020 at 1:07
  • Edgar Bonet. Implemented & worked w/IDLE! Much lower mA. However, there maybe a coding solution to the problem outlined in my post.Convinced that when I combined Mr. Nick Gammon's two examples "InputCaptureCounter/Timer" & "Deep Sleep" the issue is in Void Loop=> if (!triggered) return; This stops the program until a flag inside the ISR Capture is "true". When the attacheInterrupt on INT0/D3 fires same time as the ISR Capture reads on D13, the program may not make past WakeUp ISR, added by me. As a consequence the ISRs Capture & Overflow cannot read the pulse on D13 (PC7 on 32u4).
    – TommyS
    Commented Sep 13, 2020 at 20:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.