Replacement for Arduinos millis() that is reliable also with disabled interrupts

I have read several times that the millis() function does not return the correct time, if the interrupts where disabled while Arduino had to detect an overflow of timer0. Now I have a program that uses a lot of functions which have to disable the interrupts. So my program runs 1:30 while it thinks it was running only for 1:00.

Is there another timer solution that I can use to avoid this problem?

Now I have a program that uses a lot of functions which have to disable the interrupts.

Why? It really seems unlikely that you need to abuse the interrupt process that way.

You posted in the programming section, where you are supposed to post code. WHERE IS YOUR CODE?

cymoon: Now I have a program that uses a lot of functions which have to disable the interrupts.

Why?

Read this before posting a programming question

How to use this forum

Well. I use the DmxSimple Library. It disables the interrupts when it writes a new command. Since I call the routine very often. The timer drifts away.

If you don't post code I'll move this to the project guidance section.

Sorry for not posting an example.
I looked at the code again and was able to narrow it down to the reads of the GPRS module.

      unsigned long t = 0;   
      unsigned long start = millis();
      
      while ( (millis()-start) < 30000 ){
        //read a chunk from the gprs module
        for (int i=0;i<8;i++)
          client.read();
  
        //do this loop every 10ms
        while( (millis()-start) < t*10 ){};
        t++;
      }

The outer while should abort after 30 seconds. Since the GPRS reads disable the interrupts while reading, it takes 65 seconds.
How could I measure the time in a better way?

        for (int i=0;i<8;i++)
          client.read();

Read, and throw away, 8 bytes, even if there are none to read. Useful how?

        while( (millis()-start) < t*10 ){};

Waste some time. Why?

Since the GPRS reads disable the interrupts while reading

Proof? The read() methods generally access data in a buffer. No interrupts are harmed in doing that.

PaulS:

        for (int i=0;i<8;i++)

client.read();



Read, and throw away, 8 bytes, even if there are none to read. Useful how?

Well, because I wanted to keep the code sniplet minimal.
I directly send the data to an dmx controller using DmxSimple.

PaulS:

        while( (millis()-start) < t*10 ){};

Waste some time. Why?

Because the timing is important. Every 10ms I have to send a packet to the dmx controller. The data that I load via GPRS is a visualisation of music. So punctual every 10ms I want to do something with it.

PaulS:

Since the GPRS reads disable the interrupts while reading

Proof? The read() methods generally access data in a buffer. No interrupts are harmed in doing that.

Well, the proof is a stopwatch that I held in my hand wile I was looking at the debug output on the Serial Monitor.

I can post my original code. But to test this you need a DMX Device, a DMX Shield, a GSM Shield and a SIM card. Since almost nobody got these ingredients, I stripped the code so far that it does nothing except reproducing the error. Which is, that my outer while loop stops like it should exactly when according to millis() 30,000ms passed. Well and in the real world at the same time 65 seconds passed. This is obviously an error.

Do you have any constructive idea how I could work on this?

Do you have any constructive idea how I could work on this?

I have an idea, but you obviously don't want to hear it, because it's already been made several times and you haven't listened.

You’ve got evidence that the millis() value is slipping with respect to real time. It sounds as if the slip is substantial. Having interrupts disabled for more than a fraction of a millisecond at a time is a likely explanation for that slip. The question is - what code is causing interrupts to be disabled for so long?

PeterH: You've got evidence that the millis() value is slipping with respect to real time. It sounds as if the slip is substantial. Having interrupts disabled for more than a fraction of a millisecond at a time is a likely explanation for that slip. The question is - what code is causing interrupts to be disabled for so long?

It is only the read method of the gprs client.

You find the example code under Examples->GSM->GsmWebClient. Just load a bigger file (>50kb) and check the millis before and after the call. Then stop the real time and you will notice that GSMClient::read() disables the interrupts for a fraction of time. Unfortunately so long that after 50k reads, it goes half a minute off.

What can I do? I can not cache that amount of data. I also can not change the gprs read function? Do you have a tip?

PaulS:

Do you have any constructive idea how I could work on this?

I have an idea, but you obviously don't want to hear it, because it's already been made several times and you haven't listened.

Sorry, for hitting the wrong tone. Well, but still I see no solution. I can not write my own gprs read function. I can not cache the data.

you will notice that GSMClient::read() disables the interrupts for a fraction of time.

I’d like to look at the source code to confirm that. I can’t, though, because I have no idea what GSM library you are using, or which GSM shield/device you are using. Your continued refusal to post ALL of your code and details about the hardware has gone beyond annoying.

One way to punt would be to get an RTC module to keep track of the time.

cymoon:
It is only the read method of the gprs client.

You find the example code under Examples->GSM->GsmWebClient. Just load a bigger file (>50kb) and check the millis before and after the call. Then stop the real time and you will notice that GSMClient::read() disables the interrupts for a fraction of time. Unfortunately so long that after 50k reads, it goes half a minute off.

Unfortunately I don’t have the hardware necessary to test that for myself.

The GSM library is moderately complicated and I haven’t looked into what’s going on behind the scenes, but the only place where I can see interrupts being disabled is within GSM3SoftSerial::finalWrite(), which disables interrupts for the time needed to write a single byte each time it writes to the GSM module. The port is only running at 9600 bps so that’s going to take an uncomfortably long time.

I don’t know why the GSM library provides its own implementation of SoftwareSerial - it is clearly similar to the standard SoftwareSerial library but not identical - but SoftwareSerial disables interrupts in the same way. The stated reason for disabling interrupts is to enable a ‘clean’ transmit i.e. to avoid disruptions to the signal timing, but I do wonder, given the low baud rate being used here, whether it might work acceptably well without interrupts disabled. You could always give it a try - just edit libraries\GSM\GSM3SoftSerial.cpp at line 228 to comment out the call to cli(). Remember to keep a ‘clean’ copy of the file so you can revert the change.

I don’t recall any comments about SoftwareSerial causing the value of millis() to slip, but I don’t see why that wouldn’t suffer from the same problem at low baud rates.

Testing confirms that SoftwareSerial exhibits the same problem, which confirms your diagnosis. If it isn't viable to inhibit the interrupt-disabling inside the GSM SoftwareSerial, your other option would be to replace the GSM library's SoftwareSerial implementation with one based on the AltSoftSerial library. However, note that this is hard-coded to use specific pins and timers so you may need to do some fettling to get it working in your project.

The default millis() handling uses a timer 0 prescaler of 64 and picks up the overflow interrupt every 256 ticks, which all works out to an interrupt every 1.024ms on a 16-mhz AVR. If interrupts are disabled for more than 1.024ms, you will miss some ticks. Maybe a solution would be to crank the prescaler up to 256 making the interrupt interval a bit over 4ms. The timing would be rougher, but you'd have to have interrupts disabled for 4 times as long to miss one.

I've noticed millis() timing slipping a bit with software serial in the picture too, but it's never been a big deal for me.