Arduino 周波数/周期カウンタ (3)
12 月 7 日の記事の「PeriodCounter」ライブラリを使って、200 Hz の信号1〜2周期の測定を多数回繰り返してみたところ、やはり数千分の1程度の頻度でエラーが生じました。
エラー対策のための修正を施したバージョンで同じ測定を試すと、ほほ丸一日、数百万回の繰り返し測定でもエラーはゼロになりました。
エラーの原因と対策の説明は後に回し、修正版の「PeriodCounter.cpp」を下に示します。
/***********************************************/ /* Period measurement library for Arduino 0017 */ /* 2009/12/18 Modified by pcm1723 */ /* 2009/12/6 Created by pcm1723 */ /***********************************************/ #include "PeriodCounter.h" namespace PeriodCounter { // public volatile uint8_t p_ready; volatile int16_t p_den; volatile uint32_t p_num; volatile int8_t p_pin = 8; // digital 8 pin = ICP1 volatile uint8_t p_mode = 0x00; // private volatile int16_t max_ucnt; volatile int16_t icr1x; // software extended ICR1 volatile int16_t capt_cnt; volatile uint32_t capt, capt0; volatile uint8_t tifr1s, tifr1s0; // saved TIFR1 #define OVF_MASK (0x8000) typedef volatile union tag_u32_u16_2_t { uint32_t u32; uint16_t u16[2]; } u32_u16_2_t; // // macro for 16 bit access #define U16_L(x) (((u32_u16_2_t *)&x)->u16[0]) #define U16_H(x) (((u32_u16_2_t *)&x)->u16[1]) #ifndef INT16_MAX #define INT16_MAX 0x7FFF #endif #define AIN1_PIN (7) void stop( void ) { noInterrupts(); TIMSK1 &= ~(_BV(ICIE1) | _BV(TOIE1)); // disable CAP/OVF int interrupts(); p_ready = 0; } // viod stop() void timer1_setup(void) { stop(); // disable Timer interrupts // Timer1 setup // FastPWM (WGM13:WGM12:WGM11:WGM10 = 1:1:1:1) // clk=CPUCLK (CS12:CS11:CS10 = 0:0:1) OCR1A = 0xFFFF; // MAX = 0xFFFF TCCR1A |= (_BV(WGM11) | _BV(WGM10)); // Fast PWM TCCR1B = (0xE0 & TCCR1B) | (_BV(WGM13) | _BV(WGM12) | _BV(CS10)); // clock = 16MHz // TCNT1 = 0x0000; // clear counter capt_cnt = 0; // clear capture count icr1x = 0; // clear ICR1 extension // AD MUX and comparator setup if ((0 <= p_pin) & (AIN1_PIN >= p_pin)) { // analog pin ADCSRA &= ~(_BV(ADSC) | _BV(ADIE)); // disable ADC interrupt if (AIN1_PIN == p_pin) { // AIN1 pin (digital 7 pin) ADCSRB &= ~(_BV(ACME)); // disable ACMP MUX DIDR1 |= _BV(AIN1D); // disable digital in for AIN1 } else { // ACMP MUX input ADCSRA &= ~(_BV(ADEN)); // disable ADC ADCSRB |= _BV(ACME); // enable ACMP MUX DIDR0 |= _BV(p_pin); // disable digital input ADMUX = (0xF0 & ADMUX) | p_pin; // set MUX } // if (AIN1_PIN == ... ACSR = (_BV(ACBG) | _BV(ACIC) | _BV(ACIS1)); // 1.1V BG ref, } // if noInterrupts(); TIFR1 |= (_BV(ICF1) | _BV(TOV1)) ; // clear CAPT/OVF flag TIMSK1 |= (_BV(ICIE1) | _BV(TOIE1)); // enable CAPT/OVF int interrupts(); } // void timer1_setup() void start(int16_t ms) { // convert millisec to OVF count max_ucnt = (ms / ((1000 * 0x10000UL) / F_CPU) ); timer1_setup(); } // void start() ISR(TIMER1_OVF_vect) { if (max_ucnt <= ++icr1x) { icr1x = 0; // reset upper count TIMSK1 &= ~(_BV(ICIE1) | _BV(TOIE1));// disable CAPT/OVF int // detect unprocessed overflow for the first edge if (_BV(TOV1) & tifr1s0) { // if (0 == (OVF_MASK & U16_L(capt0))) { U16_H(capt0)++; // adjust overflow } // if (0 == } // if (_BV(TOV1) ... // detect unprocessed overflow for the last edge if (_BV(TOV1) & tifr1s) { // if (0 == (OVF_MASK & U16_L(capt))) { U16_H(capt)++; // adjust overflow } // if (0 == } // if (_BV(TOV1) ... p_num = capt - capt0; p_den = (capt_cnt-1); p_ready = 1; } } // ISRITIMER1_OVF_vect) ISR(TIMER1_CAPT_vect) { tifr1s = TIFR1; U16_L(capt) = ICR1; U16_H(capt) = icr1x; if (0 == capt_cnt++) { // first time U16_L(capt0) = U16_L(capt); U16_H(capt0) = U16_H(capt); // remember capture value tifr1s0 = tifr1s; } // if (0 == ... if (INT16_MAX == capt_cnt) { // is max count? TIMSK1 &= ~(_BV(ICIE1)); // no more capture } // if (INT_MAX == } // ISR(TIMER1_CAPT_vect) } // namespace PeriodCounter
主な変更箇所は、TIFR1 レジスタの値をセーブしておくプライベート 8 ビット変数 2 個の追加と、オーバーフロー割り込みルーチンとインプット・キャプチャ割り込みルーチンの修正です。
「PeriodCounter.h」の変更はありません。