0

I'm currently working on an Arduino project where I need to keep track of entries for a certain amount of time, in this case, 30 days. Our company will attempt to sell this product to cities and governments. Therefore it really has to just work, which makes me want to refrain from dynamically allocating memory.

The entries are really small, they (currently) consist out of a single char, but they might get bigger before the end of this project. An entry will be generated every specific interval, for now I am assuming this to be once every 5 minutes.

My idea was to allocate one array of entries, with as size the maximum amount of entries. To read or write an entry, I was thinking of grabbing the time in milliseconds since startup and dividing that by the amount of milliseconds in between every entry. Some pseudo-code: (The values in the defines do not match the described reality, I use simple values to make the example as clear as possible)

#define TOTAL_ENTRIES_TO_KEEP 5000
#define MILLISECONDS_PER_ENTRY 50

Entry[] entries = new Entry[TOTAL_ENTRIES_TO_KEEP];

Entry getCurrentEntry(){
    return entries[(millis()/MILLISECONDS_PER_ENTRY)%TOTAL_ENTRIES_TO_KEEP];
}

I've implemented this, tested it, and it works fine. My problem is brought to me by Arduino.cc: (emphasis mine)

Returns the number of milliseconds since the Arduino board began running the current program. This number will overflow (go back to zero), after approximately 50 days.

This product has to keep running for way more than 50 days in a row, it should be able to stand for a couple years with only the bare minimum on maintenance. So I'll have to deal with this overflow. If the timer would overflow at, say, exactly TOTAL_ENTRIES_TO_KEEP * MILLISECONDS_PER_ENTRY this would be no problem, as then it would overflow from entry 4999 to 0, which is what I do with %TOTAL_ENTRIES_TO_KEEP anyways. Point is, it doesn't, and messing with the internal libraries to make it do so would not seem like the best idea to me.

I do not care about a slight deviation in the interval between entries, nor in the total amount of entries to keep. This system needs to show an average of entries over a monthly period, which is already ambiguous, so whether a month to the system lasts 30, 31, 32 or 30.94836 days does not matter to the end result.

How could I protect this system against the effects of this overflow? Thanks in advance for your input.

  • You are using an arduino for a government project? Really? – old_timer Jul 21 '16 at 1:33
  • Likewise you are using the Arduino sandbox software libraries? For a government project? Anyway. If you use the timer directly you can manage all of this yourself and know when the rollover happens and not lose any timer ticks. Can most likely do this with polling or interrupts. Otherwise you need to dig into the arduino sources for the library you are using and understand how they use the timer and if/how they manage the rollover, you might be able to attack it there with small or large tweaks as needed. – old_timer Jul 21 '16 at 1:37
  • 1
    Your mcu may also have other timers that you can use instead, maybe a larger one with a prescaler or maybe two timers that can be cascaded, etc. Get the docs for your chip and examine the libraries you are using to see if those timers are used or free for you to use. – old_timer Jul 21 '16 at 1:40
  • @dwelch see imgur.com/7LNN85K. That's a display that shows how much power some solar panels have produced. Those panels and the display were placed there by the local government. This project is kind of in a similar category, so yes, using arduino for government projects :) – Daniël van den Berg Jul 21 '16 at 4:27
  • Note: 50 days is 4,230,000,000 ms. It's likely tracked by an uint32. However, circumventing the overflow would be better. – Armaghast Jul 21 '16 at 13:30
1

If you insist on using millis() to index the array, then you either have to make the array size a factor of the max millis() value, or you have to reset the millis() value to zero when your array wraps. But these seem like bad ideas and I don't understand why you are using millis() to index the array.

Think of the array as a circular (ring) buffer. Create a variable that represents the current index value into the array. The current index gets incremented every time an entry is stored. When the array is full the index value wraps to zero. Use this index variable to index the array instead of millis(). You can still store entries at the same rate but now you've disassociated the millis() value from the array index.

  • I guess I can deviate from my idea of using millis() as an index, but the main reason why I wanted to do this was for stability reasons. Using millis() as index would make it so that the program would keep functioning just fine if it were halted for a while. I'll look into your suggestion though. – Daniël van den Berg Jul 20 '16 at 17:00
  • 1
    @DaniëlvandenBerg you can still deal with that (if there's any genuine reason to) by having your event handler notice when millis() increases by more than MILLISECONDS_PER_ENTRY at a go, and increasing the index more than once. As long as you check the clock at least once per 49 days, that won't become unstable. – hobbs Jul 20 '16 at 18:21
  • (your basic intuition is valid, and commonly used in game programming, control systems, etc. — instead of relying on code running at a fixed time step, you make it do the right thing based on how long it's been since it last ran. You just need to adapt it appropriately to the constraints you're working under.) – hobbs Jul 20 '16 at 18:24
  • @hobbs overnight I figured why I wanted to do it how I wanted it in the first place, I tries to keep it stateless as much as possible. – Daniël van den Berg Jul 21 '16 at 4:15
0

The key to this is to extend the range of the counter that you are using to track time.

Just keep a "number of overflows" 32bit counter that you increment each time the millis() value wraps, and use your counter and the millis() value to form a 64-bit counter -- that will give you a wrap time of 50 * 2^32 (or about 200 billion) days -- probably longer than you can expect your device to keep functioning.

You can detect the overflow by keeping track of the last value of millis() you got, and if the current value is smaller, then you increment your "number of overflows" counter.

You'll have to be sure to call this routine at least once every wrap period.

Something like:

uint64_t long_term_millis() 
{
static uint32_t last_millis = 0;
static uint32_t wrap_count = 0;
uint32_t current_millis = millis();

    if (current_millis < last_millis) wrap_count++;

    last_millis = current_millis;

    return (((uint64_t)wrap_count)<<32) + current_millis;
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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