{"id":694,"date":"2012-09-06T01:00:00","date_gmt":"2012-09-05T23:00:00","guid":{"rendered":"https:\/\/www.fussylogic.co.uk\/blog\/?p=694"},"modified":"2012-10-08T17:52:56","modified_gmt":"2012-10-08T16:52:56","slug":"cpu-interrupted-timers","status":"publish","type":"post","link":"https:\/\/www.fussylogic.co.uk\/blog\/?p=694","title":{"rendered":"CPU, Interrupted &#8212; Timers"},"content":{"rendered":"<p>Yesterday I mentioned that interrupts on an embedded microcontroller can cause you trouble. That\u00e2\u20ac\u2122s not strictly true \u00e2\u20ac\u201c it\u00e2\u20ac\u2122s not the interrupt that causes trouble it\u00e2\u20ac\u2122s the way you handle them. An awful lot of programmers don\u00e2\u20ac\u2122t handle them correctly and they end up in a sticky mess. Before I can talk about interrupts though, we need a practical instance of them in use. Let\u00e2\u20ac\u2122s begin then by looking at timers and their interrupts.<\/p>\n<p>Pretty much all embedded microcontrollers include at least one timer peripheral. Let\u00e2\u20ac\u2122s look at how we should safely handle such an interrupt.<\/p>\n<p>I\u00e2\u20ac\u2122ll use code for an AVR <code>atmega8<\/code> here, but the principles are identical whatever your CPU. AVR timer0 is 8-bit and only counts upwards; it can generate an interrupt when it overflows, allowing us to detect the overflow.<\/p>\n<pre class=\"sourceCode C\"><code class=\"sourceCode c\"><span class=\"dt\">uint8_t<\/span> Timer_1ms = <span class=\"dv\">0<\/span>;\n\n<span class=\"dt\">static<\/span> <span class=\"dt\">void<\/span> initTIMER0( <span class=\"dt\">void<\/span> )\n{\n    <span class=\"co\">\/\/ TCCR0      -     -     -       -     -    CS02  CS01 CS00<\/span>\n    <span class=\"co\">\/\/ TCNT0    ------------------ TCNT0[7:0] --------------------<\/span>\n    <span class=\"co\">\/\/ TIMSK    OCIE2 TOIE2 TICIE1 OCIE1A OCIE1B TOIE1  -   TOIE0<\/span>\n    <span class=\"co\">\/\/ TIFR     OCF2  TOV2   ICF1  OCF1A  OCF1B  TOV1   -   TOV0<\/span>\n\n    <span class=\"co\">\/\/ CLK\/64 prescaler (see datasheet for table)<\/span>\n    TCCR0 = _BV(CS01) | _BV(CS00)\n    <span class=\"co\">\/\/ CS0[2:0] set the prescaler, you&#39;ll pick this according to your<\/span>\n    <span class=\"co\">\/\/ project requirements, but we&#39;ll assume a 16 MHz crystal.  Divided<\/span>\n    <span class=\"co\">\/\/ by 64 gives us a 250kHz increment rate.  If we then counted<\/span>\n    <span class=\"co\">\/\/ every 250 ticks, we would have a millisecond timer.<\/span>\n    TCNT0 = (<span class=\"bn\">0x100<\/span> - <span class=\"dv\">250<\/span>);\n\n    <span class=\"co\">\/\/ Now imagine the timer ticking starting from this value...<\/span>\n    <span class=\"co\">\/\/ 6   = 0 ticks   (0 us)<\/span>\n    <span class=\"co\">\/\/ 7   = 1 tick    (4 us)<\/span>\n    <span class=\"co\">\/\/ 8   = 2 ticks   (8 us)<\/span>\n    <span class=\"co\">\/\/        ...<\/span>\n    <span class=\"co\">\/\/ 254 = 248 ticks (992 us)<\/span>\n    <span class=\"co\">\/\/ 255 = 249 ticks (996 us)<\/span>\n    <span class=\"co\">\/\/ 0   = 250 ticks (1000 us) OVERFLOW<\/span>\n\n    <span class=\"co\">\/\/ Enable timer0 overflow interrupt<\/span>\n    TIMSK = _BV(TOIE0);\n}\n\nISR(TIMER0_OVF_vect)\n{\n    <span class=\"co\">\/\/ 1 ms has passed<\/span>\n    Timer_1ms++;\n    <span class=\"co\">\/\/ Reload timer<\/span>\n    TCNT0 = (<span class=\"bn\">0x100<\/span> - <span class=\"dv\">250<\/span>);\n}<\/code><\/pre>\n<p>This isn\u00e2\u20ac\u2122t very good. Note these faults:<\/p>\n<ul>\n<li>The timer overflow interrupt is not the highest priority, and we have no way of guaranteeing that significant time hasn\u00e2\u20ac\u2122t passed between the interrupt occurring and the ISR being called.<\/li>\n<li>There is no way of knowing what the C compiler has added as preamble to the ISR. There can be other code before we even get to \u00e2\u20ac\u0153<code>Timer_1ms++<\/code>\u00e2\u20ac\u009d; i.e.\u00c2\u00a0more time passing.<\/li>\n<li>We\u00e2\u20ac\u2122ve ourselves made matters worse by reloading the timer after incrementing our millisecond timer.<\/li>\n<\/ul>\n<p>In short: what if TCNT0 is not zero when we reset it? In that case we will get the following ticks:<\/p>\n<pre><code>TCNT0 == 254   =&gt;   248 ticks since ISR (992 us)\nTCNT0 == 255   =&gt;   249 ticks since ISR (996 us)\nTCNT0 == 0     =&gt;   250 ticks since ISR (1000 us) OVERFLOW\nTCNT0 == 1     =&gt;   0   ticks since ISR (0 us)\nTCNT0 == 2     =&gt;   1   tick since ISR (4 us)\nTCNT0 == 3     =&gt;   2   ticks since ISR (8 us)\n   ISR finally runs and resets TCNT0\nTCNT0 == 6     =&gt;   2   ticks since ISR (12 us)\n   ...\nTCNT0 == 254   =&gt;   250 ticks since ISR (1000 us)\nTCNT0 == 255   =&gt;   251 ticks since ISR (1004 us)\nTCNT0 == 0     =&gt;   252 ticks since ISR (1008 us) OVERFLOW<\/code><\/pre>\n<p>Oops. Our 1ms timer is actually being incremented every 1008 microseconds. It might not sound like much but over time that will cause all our clocks to drift.<\/p>\n<p>All of these can be eased by updating the ISR.<\/p>\n<pre class=\"sourceCode C\"><code class=\"sourceCode c\">ISR(TIMER0_OVF_vect)\n{\n    <span class=\"co\">\/\/ 1 ms has passed<\/span>\n    Timer_1ms++;\n    <span class=\"co\">\/\/ Reload timer<\/span>\n    TCNT0 -= <span class=\"dv\">250<\/span>;\n}<\/code><\/pre>\n<p>Here we\u00e2\u20ac\u2122ve subtracted rather than reset <code>TCNT0<\/code>. That means any ticks that happened before we were able to reload our timer aren\u00e2\u20ac\u2122t lost. We\u00e2\u20ac\u2122re still not right though. Let\u00e2\u20ac\u2122s look at the assembly generated for the single C line that updates the timer register.<\/p>\n<pre><code>; TCNT0 -= 250;\nin      r24, 0x32\nsubi    r24, 0xFA\nout     0x32, r24<\/code><\/pre>\n<p>As is typical when we look at memory modifications in assembly, it is done as a read-modify-write cycle. Do you see the issue? There is a second process accessing the <code>TCNT0<\/code> register (@0x32) \u00e2\u20ac\u201c the timer hardware itself. Two processes with access to a single memory location screams \u00e2\u20ac\u0153race condition\u00e2\u20ac\u009d at us. What if, on entry, a tick is imminent?<\/p>\n<pre><code>; TCNT0 = 250 on entry\nin      r24, 0x32\n; R24 now 250; TCNT0 ticks and becomes 251\nsubi    r24, 0xFA\n; TCNT0, now zero, minus 250 should be 1, but\n; TCNT0 is loaded with R24-250, 0\nout     0x32, r24<\/code><\/pre>\n<p>If a tick occurs during the ISR, after TCNT0 has been read then we miss a tick. Again over time our clock will drift from reality.<\/p>\n<p>There is no fix for this when using a timer in this way, we have no way of preventing the race condition. The solution is to get help from the timer peripheral itself. Timer0 in the AVR is unsuitable, what we need is an automatic reload feature. On the AVR, the 16-bit timer, Timer1 supplies that via it\u00e2\u20ac\u2122s so-called <em>Clear Timer on Compare Match<\/em> mode (CTC).<\/p>\n<p>Let\u00e2\u20ac\u2122s start again then.<\/p>\n<pre class=\"sourceCode C\"><code class=\"sourceCode c\"><span class=\"ot\">#define F_CPU 16000000<\/span>\n\n<span class=\"dt\">uint8_t<\/span> Timer_1ms = <span class=\"dv\">0<\/span>;\n\n<span class=\"dt\">static<\/span> <span class=\"dt\">void<\/span> initTIMER1( <span class=\"dt\">void<\/span> )\n{\n    <span class=\"co\">\/\/ CTC mode (WGM1[3:0] = 0x04)<\/span>\n    TCCR1A = <span class=\"dv\">0<\/span>;\n    TCCR1B = _BV(WGM12);\n    <span class=\"co\">\/\/ CLK\/8 prescaler (see datasheet for table)<\/span>\n    TCCR1B |= _BV(CS11);\n\n    <span class=\"co\">\/\/ Automatic comparison register (16Mhz\/8) is a 2 MHz tick, i.e. two<\/span>\n    <span class=\"co\">\/\/ million ticks per second, or two thousand ticks per millisecond.<\/span>\n    <span class=\"co\">\/\/ Note that we get one more tick than the value we put in this<\/span>\n    <span class=\"co\">\/\/ register<\/span>\n    OCR1A = (F_CPU\/<span class=\"dv\">8<\/span>)\/<span class=\"dv\">1000<\/span> - <span class=\"dv\">1<\/span>;\n\n    <span class=\"co\">\/\/ Enable timer1 match-A interrupt<\/span>\n    TIMSK = _BV(OCIE1A);\n}\n\n<span class=\"co\">\/\/ Note the change of vector here from &quot;overflow&quot; to &quot;match&quot;<\/span>\nISR(TIMER1_COMPA_vect)\n{\n    <span class=\"co\">\/\/ 1 ms has passed<\/span>\n    Timer_1ms++;\n}<\/code><\/pre>\n<p>Now we have no need to reset the timer register and the automatic reset means we have no race condition to deal with since only one process (the hardware peripheral) is accessing the counter register.<\/p>\n<p>While we have created a good one millisecond timer, it\u00e2\u20ac\u2122s quite impractical. Unfortunately this article has gone on much longer than I\u00e2\u20ac\u2122d thought it would already, so I\u00e2\u20ac\u2122ll delay the improvements to another time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Yesterday I mentioned that interrupts on an embedded microcontroller can cause you trouble. That\u00e2\u20ac\u2122s not strictly true \u00e2\u20ac\u201c it\u00e2\u20ac\u2122s not the interrupt that causes trouble it\u00e2\u20ac\u2122s the way you handle them. An awful lot of programmers don\u00e2\u20ac\u2122t handle them correctly and they end up in a sticky mess. Before I can talk about interrupts though,\u2026 <span class=\"read-more\"><a href=\"https:\/\/www.fussylogic.co.uk\/blog\/?p=694\">Read More &raquo;<\/a><\/span><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[1],"tags":[39,37,41,38,6,40],"_links":{"self":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/694"}],"collection":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=694"}],"version-history":[{"count":10,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/694\/revisions"}],"predecessor-version":[{"id":811,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=\/wp\/v2\/posts\/694\/revisions\/811"}],"wp:attachment":[{"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=694"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=694"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.fussylogic.co.uk\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=694"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}