前回、公開したタイマー割り込みライブラリはArduinoのタイマー割り込み周期をそのまま使うものだった。
今回は、高精度かつ高分解能で割り込みを使えるものを考えてみた。前回同様、他のタイマーは使わずにArduinoの標準タイマー(TIMER0)だけを使って実現しているためTIMER0を使ったPWM出力とは排他的にしか利用できない。
このライブラリを利用するとanalogWrite()/tone()でTIMER0のPWMピンを指定してもPWM出力はできなくなるが割り込み間隔には影響するので注意してほしい。
【スペック】
・分解能は、16MHz=4us/8MHz=8us
・割り込み間隔の範囲は、16MHz=32us-4.77hour/8MHz=64us-9.54hour
・ユーザー割り込みハンドラの戻り値で次の割り込みまでの時間指定が可能。ゼロを返すと割り込み停止。
・ユーザー割り込みハンドラー実行時のライブラリ・オーバーヘッドは16MHzクロックで約11us(実測値)。
TIMER0のような8bitカウンターで256以上をカウントするのに256カウント毎に割り込み発生させると割り込み処理のオーバーヘッドのために256の倍数直後の数カウント以内のタイミングで割り込みを発生させることができない。そのため128カウント毎に割り込みを発生させ半端なカウントは最初の割り込みで調整するように工夫している。
なお、タイマー割り込みが長期間禁止される状況があるとタイマーカウンターのオーバーランが発生し割り込み間隔が狂うので注意してほしい。ユーザー割り込みハンドラーは次の時間以内で処理する必要がある。
割り込み間隔が512us未満:(割り込み間隔 – ユーザー割り込みハンドラー実行時のライブラリ・オーバーヘッド)
割り込み間隔が512us以上:(512us – ユーザー割り込みハンドラー実行時のライブラリ・オーバーヘッド)
例えば、16MHzクロックで割り込み間隔を32 us毎にした場合、余裕は(32us – 11us) = 21us しかない。digitalWrite()/digitalRead()が2回程度しか実行できないくらいの時間なので他に何か処理する余裕はほとんどないと言える。
【サンプル・スケッチ】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
// // Sample Sketch for Arduino Pro-Micro Board // #include "TimerZeroHR.h" #define TIMER_INTERVAL0 1000000 #define TIMER_INTERVAL1 100000 uint32_t timer0(uint32_t tick) { PIND |= _BV(PD5); // Blink TXLED return tick; } uint32_t timer1(uint32_t tick) { PINB |= _BV(PB0); // Blink RXLED return tick; } void setup() { T0HR_Start(0, T0HR_US(TIMER_INTERVAL0), timer0); T0HR_Start(1, T0HR_US(TIMER_INTERVAL1), timer1); } void loop() { } |
【ライブラリ】
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
/* TimerZeroHR.h - Timer0 High Resolution Interrupt Library for Arduino AVR-Board Time Resolution: F_CPU = 16MHz ... 4us F_CPU = 8MHz ... 8us Copyright (c) 2020 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #ifndef __TIMERZEROHR_H #define __TIMERZEROHR_H #if F_CPU == 16000000 #define T0HR_US(t) ((t) >> 2) #elif F_CPU == 8000000 #define T0HR_US(t) ((t) >> 3) #else #define T0HR_US(t) ((t) / (1000000.0 / (F_CPU / 64))) #endif #ifdef __cplusplus extern "C" { #endif void T0HR_Start(uint8_t ch, uint32_t tick, uint32_t (*handler)(uint32_t)); #ifdef __cplusplus } #endif #endif |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
/* TimerZeroHR.c - Timer0 High Resolution Interrupt Library for Arduino AVR-Board Time Resolution: F_CPU = 16MHz ... 4us F_CPU = 8MHz ... 8us Copyright (c) 2020 Sasapea's Lab. All right reserved. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include <stdint.h> #include <avr/io.h> #include <avr/interrupt.h> #include "TimerZeroHR.h" #ifdef TIFR #define TIFR0 TIFR #endif #ifdef TIMSK #define TIMSK0 TIMSK #endif static uint32_t (*_handler[2])(uint32_t); static uint32_t _tick[2]; static uint32_t _tovc[2]; #define SETUP(ch, OCR) \ do \ { \ _tovc[ch] = (_tick[ch] >> 8); \ _tovc[ch] = (_tovc[ch] << 1) | ((uint8_t)_tick[ch] & 0x80 ? 1 : 0); \ if (_tovc[ch]) \ OCR += (uint8_t)_tick[ch] | 0x80; \ else \ { \ OCR += (uint8_t)_tick[ch]; \ *((uint8_t *)&_tovc[ch]) = 1; \ } \ } \ while (0) ISR(TIMER0_COMPA_vect) { if (--_tovc[0]) OCR0A += 0x80; else { _tick[0] = _handler[0](_tick[0]); if (_tick[0]) SETUP(0, OCR0A); else TIMSK0 &= ~_BV(OCIE0A); } } ISR(TIMER0_COMPB_vect) { if (--_tovc[1]) OCR0B += 0x80; else { _tick[1] = _handler[1](_tick[1]); if (_tick[1]) SETUP(1, OCR0B); else TIMSK0 &= ~_BV(OCIE0B); } } void T0HR_Start(uint8_t ch, uint32_t tick, uint32_t (*handler)(uint32_t)) { ch &= 1; if (handler) { TCCR0A = 0; // standard mode if (ch) { OCR0B = TCNT0; TIFR0 |= _BV(OCF0B); _handler[1] = handler; _tick[1] = tick; SETUP(1, OCR0B); TIMSK0 |= _BV(OCIE0B); } else { OCR0A = TCNT0; TIFR0 |= _BV(OCF0A); _handler[0] = handler; _tick[0] = tick; SETUP(0, OCR0A); TIMSK0 |= _BV(OCIE0A); } } } |