Tracker: Interrupt-based data logging

The second major iteration of the GPS tracker was fantastic because it worked without GSM coverage, but ithad a serious flaw: It used the same program logic as the initial version, which meant if you were driving outside of a GSM coverage area for an hour, when you came back into the coverage area it would spend the next 10 minutes firing off that hours worth of events to the server, and not log anything during that time.

You can see what I mean in this trip report.

To combat the issue I needed a way where transmission of the data would be suspended and the current position and data appended to the SD card during data logging. Since the ATMega supports several timer interrupts, interrupts seemed like the way forward.

There is plenty of documentation on the Internet on how to use the ATMega/Arduino interrupts. I wanted to increase the data logging time to every 10 seconds, however the Output Compare Register isn’t large enough to store the value to fire the interrupt every 10 seconds.

What we do in this case is fire the interrupt every 1 second and increment a counter variable in the ISR. Once this counter reaches 10 we execute the logic we want to run every 10 seconds within the ISR.

Here’s how I setup the timer interrupt (using CTC – Clear Timer on Compare mode on timer 1, since timer 0 is used internally):

cli(); // Disable interrupts

// Running timer CTC match on Timer 1
TCCR1A = 0;
TCCR1B = 0;

// Compare match register to 1s count
OCR1A = 15624;
TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << CS10);
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << OCIE1A);

sei(); // re-enable interrupts

And here is the Interrupt Service Routine:

ISR(TIMER1_COMPA_vect)
{
++int_counter;

if (int_counter == 10)
{
data_collection();

int_counter =0;

}
}

Now this worked well, however in my testing collecting the data and especially writing to the SD card took waaaay too long to be running in an ISR, and actually ended up screwing with other things – such as the internal counter for millis() (Since all other interrupts are suspended by default whilst in an ISR on this platform).

So to get around that problem, I changed the code to set a flag to true on the 10 second mark instead of running the data_collection routine:

ISR(TIMER1_COMPA_vect)
{
++int_counter;

if (int_counter == 10)
{
data_collection_pending=true;
int_counter = 0;
}
}

Ensuring that variables such as int_counter and data_collection_pending are marked as volatile, otherwise the compiler will potentially optimise them to be loaded from registers, not RAM which under certain conditions (such as in an ISR running asynchronously) can be unreliable.

Then in my main loop I added a conditional to check the data collection flag, and if true, run the data collection. However doing it this way does have the drawback that I need to add an additional condition during my data transmission routine to check the flag (otherwise I am back to square one).

if (data_collection_pending)
{

pos = pnd.position();
pnd.close();
data_collection();
pnd = SD.open(“PENDING.TXT”,FILE_READ);
pnd.seek(pos);

}

If the flag is set, the above code saves the current position of the pending file, closes the file, runs the data collection the re-opens the file and continues sending.

So far in my testing it is working quite well, although in long data transmissions sometimes the data collection will cause a timeout/disconnect, however that’s acceptable as it will continue during the next send_data iteration.

In hindsight I probably could have just compared the millis() output, but interrupts are fun!