Piggyback on the millis() interrupt?

EDIT: jremington provided the answer. Attaching an interrupt to timer0 (which is tied to the millis function) is very simple. See: Timer Interrupts | Multi-tasking the Arduino - Part 2 | Adafruit Learning System where you also learn that the interrupt is slightly longer than 1ms.
EDIT2: stuart0 added: you can make it exactly 1 ms by issuing "OCR0A -=6;" at the start of each interrupt, but you interfere with analogWrite (see below).
EDIT3: cattledog made it understandable: The timer0 is an 8-bit timer incrementing from 0-FF(255) and then restarting from zero. The interrupt "TIMER0_COMPA_vect" occurs when the "OCR0A" value is equal to to the timer ("AF" according to the example above). However, PWM output on pin 5/6 (at least on Uno) are also using timer0. I.e., using analogWrite affect the value on "OCR0A" and will shift when the interrupt will occur. Thus, using analogWrite will move the interrupt within the window 0-FF. Normally you would not care (if you are not anal about getting one interrupt per ms) since you'd get one interrupt approximately every ms. Obviously, if you move OCR0A back and forth between 00 and 80 every millisecond you could end up never triggering the interrupt "TIMER0_COMPA_vect" but you'd feel really unlucky if you manage to do that.

I like the simplicity of not having to know everything. But I would also like to have an interrupt running every ms or two ... which does not seem to be too easy.

So far:
1: Run some PWM output and connect it to an interrupt pin would give me an interrupt ~ 1-2 ms. However, I'm already using my two interrupt pins on the Uno so that idea is not very usable.
2: Reprogram some timers. Very complicated when you don't want to know everything. Especially if you don't want unintended interactions with the millis() function or analogWrite or serial.

Back to the subject line. So why isn't there a simple way to piggyback on the millis() interrupt? I assume that there is such an interrupt running (since I'm lazy I have not bothered to find the relevant code sent to the shield).

So the suggestion is to provide: attachInterrupt(INTERNAL1MSINTERRUPTCONSTANT, ISR, mode); function to piggyback on the timer0 interrupt.

And the question is: How do I do that today?

Happy new years - Magnus

If you only want something to happen once per millisec you may not need an interrupt. Debugging programs that use interrupts can be complex.

Have a look at how millis() is used to manage timing in several things at a time. You can use the same technique with micros() if you need short intervals.

...R

Thank you for not being too lazy to reply!

Robin2:: I'm fine ... I look at millis() and run my function whenever it updates. It will work but it's a bit suboptimal and you constantly have to think when coding instead of just using an interrupt.

Delta_G Ok. I can setup a timer and I've spent a couple of hours reading things of google. However, the problem is that various post seem to suggest that PWM output is running through the timers (as well as millis() and serial). Thus, I could use random google recipes to get interrupts of a timer. However, I could not be certain that other things work as expected (without investing a lot of time). I think the main point is convenience, Arduino is convenient. Adding a way to piggyback on the (assumed timer0 interrupt) would be 1) convenient, 2) simple to implement 3) extremely useful for all users.

So the question stands: How to piggyback on timer0 without affecting millis() etc.

masse12345:
I like the simplicity of not having to know everything. But I would also like to have an interrupt running every ms or two ... which does not seem to be too easy.

But it is easy and there is no need to muck up the millis() timer ISR.
There are a couple of libraries that will do it all for you.
Just look for them.
TimerOne, MsTimer2, FlexiTimer2, TimerThree to name a few.

Google, look in the IDE library manager, or look here:

https://www.pjrc.com/teensy/td_libs.html

--- bill

If you don't really need an ISR there are other libraries that manage calling functions at pre-determined times like the metro or SimpleTimer libraries.

So basically libraries exists to make task scheduling easy from either ISRs or foreground loops.
There is no need to ever do the blink-without-delay technique that I see constantly recommended.

--- bill

Delta_G:
Actually the libraries you mention are doing just that, blink-without-delay behind the scenes. It can make it a lot easier to write code to use blink without delay if you use one of those libraries instead, but if you use the library as a way to avoid ever learning or understanding how blink without delay works then you're eventually headed for trouble you also won't be able to explain.

Agreed, metro and SimpleTimer are doing foreground polling of the millis timer value.
But you could say not doing the code yourself is a crutch with respect to a lot of Arduino libraries and core code.
These two "timer" libraries are about ease of use and getting something up and going quickly, which is much more in line with the Arduino philosophy. Pushing people to dive down into the dirt of doing all the millis() calls and compares themselves - which is what all the blink-without-delay examples do - is definitely not as easy for many arduino users.
i.e. a lot of arduino is about giving non technical people or people with low technical programming skills the power to get things done.
Some of those people are not really interested in such low level details as it is not their field nor will it ever be.
They just want or need to get something done and using Arduino with a ready made library gives those people an easier alternative than having to learn and write the code themselves.

--- bill

Adafruit has a tutorial on piggybacking the millis() interrupt, using the Timer0 compare feature.

How close to the ms do you need it? Can it be a "sloppy" timer?

-jim lee

jremington provided the perfect answer. 976.5625 Hz is close enough and I'll probably not even run my routine every interrupt. I'll try it out and then report back later today.

masse12345:
Robin2:: I'm fine ... I look at millis() and run my function whenever it updates. It will work but it's a bit suboptimal and you constantly have to think when coding instead of just using an interrupt.

Thinking is why I like programming.

in what way is it sub-optimal?

...R

If you have a piece of critical code that has to run you don't want to stick that in the main loop where it depends on the whole code being correct. Put it in an interrupt and you are sure that it will execute even if you have screwed up the main loop. (unless you do something wrong in the interrupt code itself)

The method pointed out by jremington: Timer Interrupts | Multi-tasking the Arduino - Part 2 | Adafruit Learning System is very simple and does not screw with the other timers as some of the other suggestions in the thread.

Although the code worked I managed to screw up yet another pair of FETs ... blue smoke again :slight_smile:

masse12345:
If you have a piece of critical code that has to run you don't want to stick that in the main loop where it depends on the whole code being correct. Put it in an interrupt and you are sure that it will execute even if you have screwed up the main loop. (unless you do something wrong in the interrupt code itself)

For me, the essence of writing microprocessor code is to ensure that the whole thing is nicely tuned so that everything can operate at the time it needs to operate and nothing is " screwed up the main loop".

Using an interrupt as a band-aid for poor programming is like ordering a take-away before you sit down to dinner at your favourite restaurant in case the service is too slow.

...R

Robin2:
For me, the essence of writing microprocessor code is to ensure that the whole thing is nicely tuned so that everything can operate at the time it needs to operate and nothing is " screwed up the main loop".

Using an interrupt as a band-aid for poor programming is like ordering a take-away before you sit down to dinner at your favourite restaurant in case the service is too slow.

...R

Variable timing that can create lots of jitter in the calling of a critical function is not necessarily due to poor programming.
I think the point he was making is that you can't trust a main foreground loop to always give you guaranteed deterministic consistent timing.
It is possible in some situations, but in order to provide consistent timing or even a maximum guaranteed worst case latency requires lots of thinking and design of a main loop and often requires being in control of all the s/w used.
In the end, it is often not possible.
Once you get beyond doing simple things that are very quick operations like blinking LEDs or polling switches, things can get complicated quickly and the main idle loop code can start to create lots of jitter in the calling of a function, particularly if using more complex devices that involve using libraries like graphical LCDs, or network protocol stacks.
i.e. more complex i/o devices can have unpredictable or lengthy times for various i/o operations that are not acceptable and it is possible that no amount of re-writing or "good programming" in the main loop or device library could solve the issue.
This is particularly true in a bare metal environment resource limited environment like Arduino which tends to have most of its s/w run in the foreground using blocking APIs.

Often the only way to get the needed deterministic timing is to use an interrupt since it can pre-empt the foreground when needed vs have to trust that the foregound can ALWAYS meet the needed loop timing.

--- bill

masse12345:
jremington provided the perfect answer. 976.5625 Hz is close enough and I'll probably not even run my routine every interrupt. I'll try it out and then report back later today.

And you can make it exactly 1kHz if you wish. It's set for 4.0 microseconds per count, so the only reason to it's not exactly 1kHz is that there are 256 counts per cycle instead of 250.

To make it 1kHz exactly just start with any value that you like in OCR0A and subtract 6 from it each time the interrupt is executed.

For example "OCR0A -=6;" at the start of each interrupt would do it.

Also be aware that you will lose PWM (analogWrite) functionality on the pin (PD6 or PD5) corresponding to the compare unit that you're using.

stuart0:
And you can make it exactly 1kHz if you wish. It's set for 4.0 microseconds per count, so the only reason to it's not exactly 1kHz is that there are 256 counts per cycle instead of 250.

To make it 1kHz exactly just start with any value that you like in OCR0A and subtract 6 from it each time the interrupt is executed.

For example "OCR0A -=6;" at the start of each interrupt would do it.

Also be aware that you will lose PWM (analogWrite) functionality on the pin (PD6 or PD5) corresponding to the compare unit that you're using.

Are you saying that using the TIMER0_COMPA_vect interrupt interfere with analogWrite? Or that the modification of OCR0A throws the timing/interfere with the PWM output? If the former I'd like make it clear to everyone reading the forum.

I was just in the basement and the PWM output on pin 6 (uno) was at least driving a motor ... and I'm happy that there have been no more FETs dying yet :slight_smile:

bperrybap:
Variable timing that can create lots of jitter in the calling of a critical function is not necessarily due to poor programming.
I think the point he was making is that you can't trust a main foreground loop to always give you guaranteed deterministic consistent timing.

The impression I have is that he just wants to use an interrupt to avoid the need to think carefully about how the rest of his program should be written.

And I'm with @Delta_G's Reply #15

...R

When you were using the TIMER0_COMPA_vect interrupt as presented in the tutorial, you had compare value OCR0A set a a fixed value OCR0A = 0xAF which was triggered in the timer run up to 0xFF.

analogWrite on pin 6 also uses the OCR0A value for duty cycle, but it is not using the interrupt, but rather the hardware output. When analogWrite on pin 6 is active, the interrupt point for your compare interrupt will move around with the duty cycle.

For example, at 50% duty cycle OCR0A will be 0x80 a not 0xAF. The way you are using the interrupt it shouldn't matter, but you will not be able to use sturart0's suggested method of keeping the timing at exactlhy 1ms.

cattledog:
When you were using the TIMER0_COMPA_vect interrupt as presented in the tutorial, you had compare value OCR0A set a a fixed value OCR0A = 0xAF which was triggered in the timer run up to 0xFF.

analogWrite on pin 6 also uses the OCR0A value for duty cycle, but it is not using the interrupt, but rather the hardware output. When analogWrite on pin 6 is active, the interrupt point for your compare interrupt will move around with the duty cycle.

For example, at 50% duty cycle OCR0A will be 0x80 a not 0xAF. The way you are using the interrupt it shouldn't matter, but you will not be able to use sturart0's suggested method of keeping the timing at exactly 1ms.

Just to make sure I haven't misunderstood ... the OCR0A / OCR0B values are used to define the PWM on/off point in the cycle? I.e., they change with analogWrite to define the on/off point? In turn this would affect when in the cycle the TIMER0_COMPA_vect interrupt occurs? But the interrupt will still occur once every cycle (irrespectible of the value in OCR0A)?

If we instead went with the overflow interrupt the position in the cycle would stay constant? But will it interfere with the normal millis() interupt?

Sorry for the detailed questions ... my code seem to be working at the moment and since it's just for fun I'm happy ... but I'd like to update the first post for the next gal asking the same question.

the OCR0A / OCR0B values are used to define the PWM on/off point in the cycle? I.e., they change with analogWrite to define the on/off point? In turn this would affect when in the cycle the TIMER0_COMPA_vect interrupt occurs? But the interrupt will still occur once every cycle (irrespectivle of the value in OCR0A)?

It appears you understand correctly.

If we instead went with the overflow interrupt the position in the cycle would stay constant? But will it interfere with the normal millis() interupt?

Even if you wanted to break the millis() function, it's not that simple to just simply write a new Timer0 overflow ISR.

There is a file named wiring.c which is part of the Arduno core and is used in all sketches. Wiring.c sets up and enables the Timer0 overflow interrupt used in the millis() function. You can't write a new ISR without removing this one or the compiler will complain.You can find wiring.c on your computer at a path like this
C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino\wiring.c

You can indeed modify the existing Timer0 overflow interrupt to add whatever functionality you want to the standard isr, and it will not interfere with millis(). However, modifying wiring.c does not make the code very portable.

cattledog:
It appears you understand correctly.

Thank you! I have tried to explain what I learned in the first post. Please let me know if I misunderstood.