Can I forcibly set the system uptime (value of the millis() counter)?

I'm working with the Adafruit GPS library, and I'm pretty sure there's a bug in it that is causing it to lose fix after a LONG time. (I have built a clock that uses the GPS as a source of the time, because the clock displays astronomical time instead of wall time. IOW, I need position as well as UTC time.)

The point is, every 25 days, the fix drops and never comes back. 25 days is almost exactly 2^32/1000/2, or.... the number of milliseconds in a signed 32 bit int. I STRONGLY suspect that something is going wrong in the driver and I'd like to debug it and submit a fix.

The best way to do this would be, in setup(), to set the counter to 2^32 - 10k milliseconds. That way, the project would start up, immediately find a GPS fix (assuming I'd not powered the board down between resets) and start running.... then within 10 seconds, it would start exhibiting the bug, allowing me to track it down in an evening instead of.... well, ignoring it because it'll take me 25 days to reproduce it in the serial monitor the first time. Then another 25 days for the next debugging cycle.

I've seen a number of people asking exactly this question who are invariably told to make sure that their project can support millis() overflow, but that's a crap answer because... without being able to forcibly reset the counter, it is impossible to properly debug your system. Sure, you could write a replacement for millis() and go through all of your code, all all of your dependency libraries, altering them to work with your fixture.... or you could reset the freaking counter during startup and debug the entire thing in a few seconds of change instead of... who knows...

Thanks...

Short answer: no

Post your code, or at least a short sketch that demonstrates the problem. Perhaps we can think of a fix.

if you use millis() with unsigned long-variables and do the calculation
this way

if(millis() - previousMillis >= MyInterval

even the rollover from max-value to zero will work
You have mentioned 25 days. It is very likely that you use signed longs instead of
UNsigned longs which causes the bug
because after 25 days the most significant bit will change and in an signed long this means a negative value

To really find the bug
You should post code by using code-tags
There is an automatic function for doing this in the Arduino-IDE
just three steps

  1. press Ctrl-T for autoformatting your code
  2. do a rightclick with the mouse and choose "copy for forum"
  3. paste clipboard into write-window of a posting

best regards Stefan

Long answer: yes
For 386P:

extern unsigned long timer0_millis;

void setup() {
  // put your setup code here, to run once:
  Serial.begin( 115200 );
  uint8_t oldSREG = SREG;
  cli();
  timer0_millis = 1000000;
  SREG = oldSREG;
  Serial.print("Starting with millis=");

}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println( millis() );
  delay(1000);

}

that's not a short answer :slight_smile:

(but indeed you can mess up with timer0_millis even if there is no point to do so probably)

Ok, I corrected :grinning:

Of course, usually you should not do so. But for testing purposes it's ok.

:innocent: :smiley:

indeed - always fun to play with stuff when you know how things work behind the curtain

This is always the answer, but as you see, wrong.

TBF it's like asking "Can I drink a liter of vodka?", and "no" is a good answer, but obvsly incorrect.

I happen to have a draft post on this matter, not discussing the pros and cons of tampering with the millis() mechanism, not even on the other hot-wire issues around use of delay().

I have run into an odd problem with building a sketch, this seems as good a time and place as any, so here is my problem:

undefined reference mystery

I tried to add a function

//void setMillis(unsigned long you Cannot)
void setMillis(unsigned long youCannot)
{
	timer0_millis = youCannot;
}

to wiring.c. If it has errors the build fails which was my clue to having found the correct wiring.c, but when I declare

void setMillis(unsigned long);

in my sketch and try to use it, I get

undefined reference to `setMillis(unsigned long)'

Meanwhile a bit of searching turns up a simpler solution, just declare

extern volatile unsigned long timer0_millis;

and use that, works as far as I can see but I am curious why my function, which was clearly getting complied was nevertheless not resolved.

Just now on another idea I tried to add

unsigned char addMyUnsignedChar = 99;

to wiring.c and that "comes through" in my sketch fine with

extern unsigned char addMyUnsignedChar;

So what am I missing?

TIA

a7

can you try with adding

extern "C" void setMillis(unsigned long);

instead of just the void setMillis(unsigned long); you were referring to?

I think your issue is due to the way the linker deals with C files in C++ compilation process You need to inform the compiler of the "C" origin so that the name of the function is calculated in the right way.

if it works (I would be surprised though) by just adding extern volatile unsigned long timer0_millis; then it probably means the compiler made some optimizations and inlined the function directly into your code thus not having a link problem anymore. But that sounds weird.

so I believe

extern "C" void setMillis(unsigned long);
void setup() {
  Serial.begin(115200);
  setMillis(100000);
  Serial.println(millis());
}

void loop() {}

would compile and kinda work. (it would be better as offered above to disable interrupts as your unsigned long assignment could be interrupted and the resulting value would be a mess)

but I don't think this would compile

void setMillis(unsigned long);
extern volatile unsigned long timer0_millis;
void setup() {
  Serial.begin(115200);
  setMillis(100000);
  Serial.println(millis());
}

void loop() {}

can you clarify what you meant?

There is a difference between getting compiled and getting linked.
At compile time, it is not clear if a function is going to be used or not. If the linker discovers the function is unreachable, it is ignored and never makes it into the object code.

@J-M-L thank you, the full declaration the function works.

And yes, the other idea does not work. As you predicted.

Even I (!) can see why.

As for "[my] unsigned long assignment could be interrupted", I'm unclear: I thought the "volatile" qualifier was fixing me up.

To what, exactly, were you referring when you said "it would be better as offered above to disable interrupts" - I interpret to mean I should add disabling interrupts to the function I wrote?

Just a bit remaining confused. TIA

@6v6gt yes, understood. When I said resolved I was referring to the linking process, THX.

a7

Nope... it guarantees direct memory access but not atomic operation (You would get atomic operation if that was a byte). Because we are on a 8 bit architecture, an assignment over 4 bytes will require a number of memory access to move the 4 bytes to the destination. You could be interrupted in the middle of those moves by the timer handling millis() and Murphy guarantees it will :wink:

Yes, I meant something like MicroBahner proposed to guarantee that timer0_millis = youCannot is done atomically.

void setMillis(unsigned long youCannot)
{
  uint8_t oldSREG = SREG;
  cli();
  timer0_millis = youCannot;
  SREG = oldSREG;
}

Something like exactly, thank you!

I had seen the cli() in wiring.c, but tots overlooked the stash of SREG, wondered why I didn't find a complementary function to turn interrupts back on.

a7

yeah saving and restoring SREG is fun trickery :wink:

In that simple sketch it is not really necessary. But in complex sketches it ensures that the interrupts are not switched on by mistake, if they were already switched off before for some reason.

So, to summarise the last 12+ posts for @foodini's benefit... Short answer: no. Long answer: yes, but it's complicated, and the exact implementation depends what type of Arduino you are using, which you didn't mention. Suggestions above for atmega328p might not work for other variants of atmega328, let alone esp8266/32, samd21....

Point is, it will probably take a while to get it working, time you could have spent fixing your original problem! So post that code, and check you have followed @StefanL38 's advice about unsigned long Vs long.

As I understand him, it's not a problem of his own code, but he wants to examine a library to find a bug in it and to fix it. And to check if it's ok after the fix.

Surely. If someone does'nt tell about the used processor, I assume a 'standard' ( UNO/Nano ). That may be wrong, but than it's a problem of the poster. Anything other than a standard AVR should definitely be mentioned.

To me, a "short answer" should be a correct answer, albeit one that whoever offers it is unable, unwilling or too lazy to elucidate upon.

It should not mean "yes but in your best interests I am compelled to lie."

I would wager that you did know it is entirely possible to monkey with millis(), no matter how fraught. You might even have good ideas to share about just what disasters lurk for those foolish enough to do.

I would have answered with, well, the answer. Probably even if it wasn't very clear that the OP was doing research into the possibility that an error was being caused by something external to his code, as rare as that may be.

There is value to being able to do. That kind of investigation.

a7

C++ Functions can be overloaded. Variables can't.

Because in C++ you can have multiple functions with the same name but different arguments, the linker knows them by their 'mangled' names which include information about the argument list. Variable names and C function names can't be overloaded so they go through the linker 'un-mangled'. That is why C functions called from C++ have to be declared extern "C", so the compiler knows to use their C name, not a C++ 'mangled' name.

Yes, exactly what I was trying to say in simple terms with "You need to inform the compiler of the "C" origin so that the name of the function is calculated in the right way."