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」の変更はありません。