{"id":4816,"date":"2025-04-21T14:23:17","date_gmt":"2025-04-21T14:23:17","guid":{"rendered":"https:\/\/www.richardmudhar.com\/blog\/?p=4816"},"modified":"2025-05-07T09:14:21","modified_gmt":"2025-05-07T09:14:21","slug":"arduino-millis-interrupt-frequency-measurement-blues","status":"publish","type":"post","link":"https:\/\/www.richardmudhar.com\/blog\/2025\/04\/arduino-millis-interrupt-frequency-measurement-blues\/","title":{"rendered":"Arduino millis() interrupt frequency measurement blues"},"content":{"rendered":"\n<p>For the paramagnetism project, I need to measure the frequency of the oscillator accurately. I did that with a <a href=\"https:\/\/www.richardmudhar.com\/blog\/2015\/08\/measuring-paramagnetism-3-a-portable-instrument\/\">16F628 PIC last time<\/a>, but I am after an easier life, and the obvious choice here is Arduino. I get serial output, a way to use I2C OLED displays rather than bit-banging a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Hitachi_HD44780_LCD_controller\">Hitachi character display<\/a> 1980&#8217;s style, and all that extra wiring, what&#8217;s not to like?<\/p>\n\n\n\n<p>The lack of consistency, that&#8217;s what. I count the number of 16MHz Arduino UNO R3 clock cycles that have elapsed with every rising edge on D2 in an interrupt service routine &#8211; the code is at the end of the post. Once I have seen 16 rising edges pass, I copy that value over, set TCNT1 to zero and start over. The first result will be garbage, after that it should be OK. Every half a second I print a copy of the last result out. This measures the period, in 1\/16MHz segments. I collect 2300 results, of which the first 10 are like so<\/p>\n\n\n\n<p>freq,cnt<br>4793.92, 53398<br>4794.01, 53399<br>4795.35, 53400<br>4794.37, 53396<br>4794.01, 53400<br>4794.01, 53400<br>4794.01, 53400<br>4794.01, 53400<br>4793.92, 53401<br>4793.83, 53402<\/p>\n\n\n\n<p>and you can see right from the get-go that there is trouble in Paradise. These are two crystal oscillators. One is the 16MHz in the Arduino board, the other is a SEI 2.4576MHz oscillator block, which I divided by 512 using a CD4060 to get 4.8kHz<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"630\" src=\"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/DSCN5238.jpg?resize=840%2C630&#038;ssl=1\" alt=\"\" class=\"wp-image-4819\" srcset=\"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/DSCN5238.jpg?w=1024&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/DSCN5238.jpg?resize=550%2C413&amp;ssl=1 550w, https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/DSCN5238.jpg?resize=768%2C576&amp;ssl=1 768w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption class=\"wp-element-caption\">SEI oscillator block<\/figcaption><\/figure>\n\n\n\n<p>Okay, so the oscillator<sup data-fn=\"e1103851-5955-4951-bffe-5f92e9729413\" class=\"fn\"><a href=\"#e1103851-5955-4951-bffe-5f92e9729413\" id=\"e1103851-5955-4951-bffe-5f92e9729413-link\">1<\/a><\/sup> is 24 years old. Just to eliminate this going bad I ran a scope on the 1\/512 output, in persistence mode<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"840\" height=\"182\" src=\"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/persist2.png?resize=840%2C182&#038;ssl=1\" alt=\"\" class=\"wp-image-4821\" srcset=\"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/persist2.png?resize=1024%2C222&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/persist2.png?resize=550%2C119&amp;ssl=1 550w, https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/persist2.png?resize=768%2C167&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/persist2.png?resize=1200%2C261&amp;ssl=1 1200w, https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/persist2.png?w=1280&amp;ssl=1 1280w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px\" \/><figcaption class=\"wp-element-caption\">x divisions 50us<\/figcaption><\/figure>\n\n\n\n<p>It was OK, even on many more cycles in persistence mode.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Say the crystals are good for 50ppm, which is a bit rubbish really. And the conspire to drift opposite directions, I&#8217;m still expecting an error of 1 in ten thousand, son on a count of roughly 50k I will take an error of +\/- 5 or 6<\/p>\n\n\n\n<p>A histogram of the resulting counts using R is rubbish &#8211; 100 off to the low side and about 70 on the high side. <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"432\" src=\"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/counthist2.png?resize=700%2C432&#038;ssl=1\" alt=\"\" class=\"wp-image-4823\" srcset=\"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/counthist2.png?w=700&amp;ssl=1 700w, https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/counthist2.png?resize=550%2C339&amp;ssl=1 550w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px\" \/><figcaption class=\"wp-element-caption\">Histogram of counts. Yuck<\/figcaption><\/figure>\n\n\n\n<p>I discover Arduino uses interrupts to maintain the millis() function &#8211; not, apparently the micros(). I consider shutting that down, but it appears this is unwise, and it appears I would lose some of the <a href=\"https:\/\/forum.arduino.cc\/t\/how-to-stop-millis-forcibly\/670350\/17\">other handy functions<\/a>. <\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>No interrupts:<br>no serial send receive<br>no I2C-communication,<br>no SPI-communication<br>no timing<\/p>\n<\/blockquote>\n\n\n\n<p>Using <a href=\"https:\/\/docs.arduino.cc\/language-reference\/en\/functions\/interrupts\/noInterrupts\/\">nointerrupts();<\/a> will shoot myself in the foot as I want to use <strong>my <\/strong>interrupts. Since millis() runs every millisecond and my cycle time is 208us I am likely to take a hit every fifth time, and I average over 16 cycles for what I thought was greater resolution which ensures I take a hit. Enabling Serial probably means I set off some other ISR. I looked for a definitive guide to the Uno interrupts but all I got were many tutorials about how to use interrupts. I get the feeling I&#8217;m chasing down the wrong track here. <a href=\"https:\/\/www.perplexity.ai\/search\/how-do-i-turn-off-specific-int-r92AD4bbS9OXKp9ChhBHBw\">Perplexity suggested<\/a> I read the <a href=\"https:\/\/ww1.microchip.com\/downloads\/en\/DeviceDoc\/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf\">ATMega 328P datasheet<\/a> <sup data-fn=\"318c5100-2bd5-4716-966b-71ff62ecdd2d\" class=\"fn\"><a href=\"#318c5100-2bd5-4716-966b-71ff62ecdd2d\" id=\"318c5100-2bd5-4716-966b-71ff62ecdd2d-link\">2<\/a><\/sup>which is all very well and I discover I have three timers, and more sleuthing suggests that millis() indirectly comes off the <a href=\"https:\/\/forum.arduino.cc\/t\/millis-and-timer-0\/969036\">overflow of Timer0<\/a>.  So I added a line just after<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> void setup() { <\/code><\/pre>\n\n\n\n<p>to mask that overflow<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  TIMSK0=0x00; \/\/ can this. THIS WILL KILL MILLIS<\/code><\/pre>\n\n\n\n<p>and took out delay(500); because that depends on millis, so the board just madly throws out measurements. I repeat the R plot histogram<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"700\" height=\"432\" src=\"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/counthist-nomillis.png?resize=700%2C432&#038;ssl=1\" alt=\"\" class=\"wp-image-4828\" srcset=\"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/counthist-nomillis.png?w=700&amp;ssl=1 700w, https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/counthist-nomillis.png?resize=550%2C339&amp;ssl=1 550w\" sizes=\"auto, (max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px\" \/><figcaption class=\"wp-element-caption\">the spread in the value of count with millis() disabled<\/figcaption><\/figure>\n\n\n\n<p>It&#8217;s better\/less bad than the first histogram, confirming the hypothesis that the millis(); interrupt is screwing up the result. I have disabled  the timer0 overflow (TOIE0 bit in TIMSK0). The bad news is there is still something running infrequently that is giving me a big error. At a guess that&#8217;s Serial Receive, but I am going to quit while I am ahead and still have an unbricked board. After all the whole point of using Arduino is for the display libraries and if I am going to lose them, well, <em>why keep a dog if you have to bark yourself<\/em>?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Back to the 16f628A PIC &#8211; it&#8217;s primitive, but it will do my bidding and only that<\/h2>\n\n\n\n<p>Unlike the Arduino<sup data-fn=\"ad1092bc-be6f-4214-9c3a-4a2554df701c\" class=\"fn\"><a href=\"#ad1092bc-be6f-4214-9c3a-4a2554df701c\" id=\"ad1092bc-be6f-4214-9c3a-4a2554df701c-link\">3<\/a><\/sup> the 16F628 PIC isn&#8217;t clever enough to try and do more than one thing at a time, so I will go back to that and use the same sort of technique, hopefully for a <a href=\"https:\/\/www.richardmudhar.com\/blog\/2025\/05\/measuring-frequency-with-a-16f628a-pic\/\">more repeatable result<\/a>. It&#8217;s a little bit closer to the bare metal. I am getting too soft to code in MPASM any more, I will try in in MPLABX C \ud83d\ude09<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Arduino code<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>#include &lt;Arduino.h&gt;\n\/\/ pin used\n#define pulse_ip 2\n\/\/ how many cycles to count\n#define maxcyles 16\n#define ard_clockf 16000000\nvolatile unsigned int isr_cntr; \/\/ number of cycles, vol because changed in ISR\ndouble freq,period;\nvolatile unsigned long TCNT1_copy; \/\/ copy of TCNT!, volatile becaus changed in ISR\n\n\n\/\/ put function declarations here:\nvoid pinChange ();\n\nvoid setup() {\n  \/\/ put your setup code here, to run once:\n  pinMode(pulse_ip,INPUT);\n  Serial.begin(9600);\n  Serial.println(\"Booted\");\n  TCCR1A=0;\n  TCCR1B=0x01; \/\/ counter 1 running PSC 1\n  attachInterrupt (digitalPinToInterrupt (2), pinChange, RISING);  \/\/ attach interrupt handler for D2\n}\n\nvoid loop() {\n  double result=double(maxcyles*ard_clockf)\/TCNT1_copy;\n  Serial.print(result);\n  Serial.print(\", \");\n  Serial.println(TCNT1_copy );\n  delay(500);\n}\n\n\/\/ Interrupt Service Routine (ISR)\nvoid pinChange ()\n{\n  isr_cntr++;\n  if (isr_cntr &gt;= maxcyles) {\n    TCNT1_copy=TCNT1;\n    TCNT1=0;\n    isr_cntr=0;\n  }\n\n}  \/\/ end of pinChange<\/code><\/pre>\n\n\n\n<p>It is, of course, possible I have made a stupid mistake in the code \ud83d\ude09<\/p>\n\n\n<ol class=\"wp-block-footnotes\"><li id=\"e1103851-5955-4951-bffe-5f92e9729413\">If somebody is looking for the pinout of a SEI TTL crystal oscillator then it&#8217;s the same as most of these. Imagine it were a 14 pin DIL, then pin 1=NC (sometimes an enable but not here), pin 7 in GND which is connected to the case, 8 is O\/P and pin 14 is VCC, 5V in this case <a href=\"#e1103851-5955-4951-bffe-5f92e9729413-link\" aria-label=\"Jump to footnote reference 1\">\u21a9\ufe0e<\/a><\/li><li id=\"318c5100-2bd5-4716-966b-71ff62ecdd2d\">Just like people on Stackexchange would, along with Use the Source, Luke. Microchip move their datasheets about every so often so the link may go to a 404 <a href=\"#318c5100-2bd5-4716-966b-71ff62ecdd2d-link\" aria-label=\"Jump to footnote reference 2\">\u21a9\ufe0e<\/a><\/li><li id=\"ad1092bc-be6f-4214-9c3a-4a2554df701c\">If you programmed the ATMega328P directly without the Arduino bootloader then you would have full control over a more capable device than a 16F628A and could make it run only one interrupt at at time. I don&#8217;t know how to program that at the bare metal, but I do for the PICs. I ought to learn how to do that on the Atmel chips one day  <a href=\"#ad1092bc-be6f-4214-9c3a-4a2554df701c-link\" aria-label=\"Jump to footnote reference 3\">\u21a9\ufe0e<\/a><\/li><\/ol>","protected":false},"excerpt":{"rendered":"<p>A cautionary tale for those trying to measure frequency using Arduino<\/p>\n","protected":false},"author":1,"featured_media":4825,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":true,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"[{\"content\":\"If somebody is looking for the pinout of a SEI TTL crystal oscillator then it's the same as most of these. Imagine it were a 14 pin DIL, then pin 1=NC (sometimes an enable but not here), pin 7 in GND which is connected to the case, 8 is O\/P and pin 14 is VCC, 5V in this case\",\"id\":\"e1103851-5955-4951-bffe-5f92e9729413\"},{\"content\":\"Just like people on Stackexchange would, along with Use the Source, Luke. Microchip move their datasheets about every so often so the link may go to a 404\",\"id\":\"318c5100-2bd5-4716-966b-71ff62ecdd2d\"},{\"content\":\"If you programmed the ATMega328P directly without the Arduino bootloader then you would have full control over a more capable device than a 16F628A and could make it run only one interrupt at at time. I don't know how to program that at the bare metal, but I do for the PICs. I ought to learn how to do that on the Atmel chips one day \",\"id\":\"ad1092bc-be6f-4214-9c3a-4a2554df701c\"}]","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[57],"tags":[264,309,406],"class_list":["post-4816","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-electronics","tag-arduino","tag-paramagnetism","tag-uno"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.richardmudhar.com\/blog\/wp-content\/uploads\/2025\/04\/DSCN5242.jpg?fit=813%2C693&ssl=1","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/p5aOO7-1fG","jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/posts\/4816","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/comments?post=4816"}],"version-history":[{"count":15,"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/posts\/4816\/revisions"}],"predecessor-version":[{"id":4851,"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/posts\/4816\/revisions\/4851"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/media\/4825"}],"wp:attachment":[{"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/media?parent=4816"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/categories?post=4816"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.richardmudhar.com\/blog\/wp-json\/wp\/v2\/tags?post=4816"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}