問題の概要
Arduinoで受信機の信号を受信して処理したい場合、pulseIn()関数を用いることが多いが、以下の理由で困ったことが起きる。
- 受信機メーカによっては同時にパルスが送られるため、同時に計測できない(例:タミヤ)。
- 各チャネルが五月雨式に送られるため、パルス計測開始の遅れで計測値がばらつく(参考)。
- 受信チャンネル数を増やすと、メイン処理を回す時間がなくなる。
本来ではマイコンに内蔵されたタイマのパルス幅計測機能を使いたいところだが、delayやtoneなんかの脳死で使えるタイマ機能と被りが気になるところ。そこで次に述べる解決手段を使用した。
解決方法
- attachInterrupt()により、両エッジの外部端子入力割り込みを設定する
- 割り込み関数の中で、立ち上がり、立ち下がりエッジを判定。micros()で取得した時間を保存する。
- 立ち下がりエッジで立ち下がりエッジ~立ち下がりエッジの時間間隔を計測する。
コード例
#define bit_read(portdat, portconf) ((portdat & portconf) ? 1 : 0)
#define bit_write(portdat, portconf, hilo) (portdat = (hilo ? (portdat | portconf) : (portdat & ~portconf)))
// STR_RCV = PA0
#define PINDIR_STR (VPORTA.DIR)
#define PINRAM_STR (VPORTA.IN)
#define PIN_STRRSV (0x01)
#define INTR_STRRSV (2)
// ESC_RCV = PF5
#define PINDIR_ESC (VPORTF.DIR)
#define PINRAM_ESC (VPORTF.IN)
#define PIN_ESCRSV (0x20)
#define INTR_ESCRSV (3)
volatile unsigned long time;
volatile unsigned long strFallingUs;
volatile unsigned long strRisingUs;
volatile unsigned long strPlsWdhUs;
volatile unsigned long escFallingUs;
volatile unsigned long escRisingUs;
volatile unsigned long escPlsWdhUs;
// セットアップ
void setup()
{
// 入出力設定
bit_write(PINDIR_STR, PIN_STRRSV, 0);
bit_write(PINDIR_ESC, PIN_ESCRSV, 0);
// 外部割り込み設定。INTRNo(0~45)を直接指示してもうまく行かない。digitalPinToInterruptを使用するのが推奨されるらしい。
attachInterrupt(digitalPinToInterrupt(INTR_STRRSV), fnc_intr_irqStr, CHANGE);
attachInterrupt(digitalPinToInterrupt(INTR_ESCRSV), fnc_intr_irqEsc, CHANGE);
// デバッグ用コンソール
Serial.begin(115200);
Serial.print("Start");
Serial.println();
Serial.print("time,Sterring, ESC");
}
// メインループ
void loop()
{
Serial.print(time);
Serial.print(",");
Serial.print(strPlsWdhUs);
Serial.print(",");
Serial.print(escPlsWdhUs);
Serial.println();
time++;
delay(100);
}
void fnc_intr_irqStr()
{
noInterrupts();
if (bit_read(PINRAM_STR,PIN_STRRSV) )
{
// RISING
strRisingUs = micros();
}
else
{
// Falling
strFallingUs = micros();
strPlsWdhUs = strFallingUs - strRisingUs;
}
interrupts();
}
void fnc_intr_irqEsc()
{
noInterrupts();
if (bit_read(PINRAM_ESC,PIN_ESCRSV) )
{
// RISING
escRisingUs = micros();
}
else
{
escFallingUs = micros();
// オーバーフローしても正しく計算可能
escPlsWdhUs = escFallingUs - escRisingUs;
}
interrupts();
}
結果
青がハンドル。オレンジがスロットル。少々ばらつきがあるのが気になるところだが、フィルタ処理を行えば良さそう。なお、受信機のスイッチを切るとパルスが来なくなるため、値が更新されなくなる。
ポイント
- 時間のかかるdigitalRead()を使わず、レジスタを直接参照する。
- ビット操作を楽にするためのマクロを使う(作る)。
- micros()のオーバーフロー対策として、unsignedでそのまま引き算する。