[電子工作]LPC810メモ:超音波距離センサHC-SR04をSCTで利用するクラス

SCTの例その3(超音波距離センサ)で実験した距離測定を改良してクラス化。
どんなセンサであるかは上記ページで書いたので省略。

前提とする環境はこちら→LPC810メモ 共通の準備
基本的なサンプルについてはこちら→LPC810みっかぼ自作サンプル集

工夫した点

  • 非同期で測定するが、割り込み不使用。一度測定開始すると、SCTの機能だけで定期的に(0.1秒毎などで)測定し、必要な時だけ測定結果を取り出す。
  • 割り算不使用。タイマの進み方を71clockで1カウントと設定していて、これは12MHzだと音速で約2mm(往復距離なら1mm)に相当するため、カウント数がmm数となる。


以下はSCTの状態遷移表的なもの。

イベント 発生条件 state0 state1
event0 カウンタリセット 出力rise 出力rise
event1 10μs経過 出力fall 出力fall
event2 入力rise カウンタをcapture2に記録 -
event3 入力rise - カウンタをcapture3に記録
event4 入力fall カウンタをcapture4に記録、state1に遷移 -
event5 入力fall - カウンタをcapture4に記録、state0に変更

上記の趣旨。

  • event0,1(10μsのhigh)でセンサ起動。
  • state0の時は、event2でriseをcapture2に記録、event4でfallをcapture4に記録する。その後state1に遷移するため、state1である間はcapture4 - capture2で距離が算出可能。
  • state1の時は、event3でcaprure3,event5でcapture4になる以外は同様。
  • state0,1でcapture4を共用しているのは、match/captureがあわせて5つしか使えないため。event0,1でmatchを2つ使ってしまったので、captureは2,3,4の3つしか使えない。たぶん矛盾はおきてないつもり。しかしeventは6個定義できるのに、なぜmatech/captureが5個しか定義できないのか・・・

テンプレート引数

  • template
    • TrigPort HCSR04のTriggerにつなぐGPIOの番号
    • EchoPort HCSR04のEchoにつなぐGPIOの番号
    • Cycle 1秒間に計測する回数

メンバ関数

  • static void init()
    • 初期化。最初に必ず呼ぶ。
  • static void start()
    • 測定開始。以降、測定しつづける。
  • static void stop()
    • 測定停止。次にstart()を呼ぶまで止まる。
  • static int16_t getDistance()
    • 距離をmm単位で返す。負値になったり、5000mmを超えた場合には、失敗とみなして-1を返す。

サンプルコード

sample.cpp(クラスを使う側)

#include "LPC8xx.h"
#include "HCSR04.h"

using HCSR04 = HCSR04Base<4,3>;
// TriggerにGPIO4、2番pin
// EchoにGPIO3、3番pin

int main(void) {
	HCSR04::init(); // 初期化
	HCSR04::start();

    // LED光らせる準備。。
    // GPIOに対してclockを有効化して、
    // PIO0_0(8番pin)をoutputに設定
    // UM10601 4.6.13 System clock control register
    // UM10601 27.1 Packages より、PIO0_0は8番
    // UM10601 7.6.3 GPIO port direction registers
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);
    LPC_GPIO_PORT->DIR0 |= (1<<0);

    while(1)  {
        int16_t distance = HCSR04::getDistance();   // 距離測定
        if(distance < 20) { distance = 20; }        // 測定エラーと20mm以下は20mm扱い
        else if(distance > 200) { distance = 200; } // 200mm以上は便宜上200mm扱い

        // 距離が長いほどwaitを長くしてLEDを点滅2回。
        for(int i = 0; i < 4; i++) {
            // PIO0_0の出力をtoggle
            // UM10601 7.6.9 GPIO port toggle registers
            LPC_GPIO_PORT->NOT0 |= (1<<0);
            for(volatile int i = 0; i < 1000 * (distance + 1); i++) {}
        }
    }
    return 0 ;
}

HCSR04.h(超音波距離センサHC-SR04をSCTで利用するクラス、今回の主題)

#ifndef HCSR04_H_
#define HCSR04_H_

// テンプレート引数
// TrigPort -> Triggerに使うGPIOの番号
// EchoPort -> Echoに使うGPIOの番号
// Cycle -> 1秒に何回計測するか。多分20くらいが限界、デフォルト10とする。
// 例) using HCSR04 = HCSR04Base<4,3>;
template<uint8_t TrigPort, uint8_t EchoPort, uint8_t Cycle = 10>
class HCSR04Base {
public:
	static void init();  // 最初に必ず呼ぶ
	static void start(); // 測定開始
	static void stop();  // 測定停止
	static int16_t getDistance(); // 距離をmm単位で返す、失敗は-1
};

template<uint8_t TrigPort, uint8_t EchoPort, uint8_t Cycle>
void HCSR04Base<TrigPort, EchoPort, Cycle>::init() {
    //  SWMのclockを有効化
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<7);

    //  EchoPortのpinにSCT入力の0番(CTIN_0) を設定
    //  UM10601 9.5.6 Pin assign register 5
    LPC_SWM->PINASSIGN5 = 0x00ffffffUL | (EchoPort << 24);

    //  2番pin(PIO_4)にSCT出力の0番(CTOUT_0) を設定
    //  UM10601 9.5.7 Pin assign register 6
    LPC_SWM->PINASSIGN6 = 0x00ffffffUL | (TrigPort << 24);

    // RESET 以外の無効化
    LPC_SWM->PINENABLE0 = 0xffffffbfUL;

    // UM10601 10.3 Basic configuration に従った準備
    // SCTのclockを有効化(UM10601 4.6.13 System clock control register)
    if(!(LPC_SYSCON->SYSAHBCLKCTRL & (0x1<<8))) {
    	LPC_SYSCON->SYSAHBCLKCTRL |= (1<<8);
    }

    // Clear the SCT peripheral reset using the PRESETCTRL register (Table 19).
    LPC_SYSCON->PRESETCTRL &= ~(0x1<<8);
    LPC_SYSCON->PRESETCTRL |= (0x1<<8);

    // 以下、SCTの動作の設定
    // UM10601 10.6.1 SCT configuration register
    // 16bit一本(L)のみを使う。
    LPC_SCT->CONFIG =
        (0 << 0 )  // UNIFY = 0 -> 16bitのタイマ2本(L,H)で利用する
        | (1 << 17)  // AUTOLIMIT_L = 1 -> matchの0番(後で定義)を、カウントアップの上限とする
	;

    // UM10601 10.6.2 SCT control register
    LPC_SCT->CTRL_L  |= (71 - 1 ) << 5; // PRE_L = 71 -> 71clock(音が2mm進む時間)で1カウント

    // match/captureレジスタの2,3,4番は、capture用に設定。
    // センサからの入力を受け取る。
    // UM10601 10.6.10 SCT match/capture registers mode register
    LPC_SCT->REGMODE_L = 1 << 2 | 1 << 3  | 1 << 4;

    //  カウンタにmatchする値を定義
    // UM10601 10.6.20 SCT match reload registers 0 to 4 (REGMODEn bit = 0)
    // 10.6.22 SCT event state mask registers 0 to 5
    // 10.6.23 SCT event control registers 0 to 5
    // UM10601 10.6.21 SCT capture control registers 0 to 4 (REGMODEn bit = 1)

    // event0番 MATCHRELでreloadされた際に発生
    // 出力を立ち上げる
    LPC_SCT->MATCHREL[0].L = 12000000 / 71 / Cycle; // 秒間10回
    LPC_SCT->EVENT[0].STATE = 0x03; // state0,1にかかわらず発生
    LPC_SCT->EVENT[0].CTRL =
        (0 << 0)    // MATCHSEL = 0 -> match0に対応
        |  (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (1 << 12);  // COMBMODE = 1 -> match だけに対応
    LPC_SCT->OUT[0].SET = (1 << 0);

    // event1番 カウント1で発生。reload後、約10μs、のつもり。
    // 出力を立ち下げる
    LPC_SCT->MATCHREL[1].L = 1; // 立ち上げ後約 10μs後に立下げる。カウンタ的に10μsぴったり(ここで出力をLOWにする) ★まずは+2で。
    LPC_SCT->EVENT[1].STATE = 0x03; // state0,1にかかわらず発生
    LPC_SCT->EVENT[1].CTRL =
        (1 << 0)    // MATCHSEL = 1 -> match2に対応
        |  (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (1 << 12);  // COMBMODE = 1 -> match だけに対応
    LPC_SCT->OUT[0].CLR = (1 << 1);

    // event2番  state0で、入力1の立ち上がりで発生
    // CAPCTRL[2]にその時点でのカウンタを記録
    LPC_SCT->EVENT[2].STATE = 0x01; // state0で発生
    LPC_SCT->EVENT[2].CTRL =
        (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (0 << 6)    // IOSEL 入力1に対応
        |  (1 << 10)   // IOCOND riseに対応
        |  (2 << 12);  // COMBMODE = 2 -> I/O condition だけに対応
    LPC_SCT->CAPCTRL[2].L = 1 << 2;

    // event3番 state1で、入力1の立ち上がりで発生
    // CAPCTRL[3]にその時点でのカウンタを記録
    LPC_SCT->EVENT[3].STATE = 0x02; // state1で発生
    LPC_SCT->EVENT[3].CTRL =
        (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (0 << 6)    // IOSEL 入力1に対応
        |  (1 << 10)   // IOCOND riseに対応
        |  (2 << 12);  // COMBMODE = 2 -> I/O condition だけに対応
    LPC_SCT->CAPCTRL[3].L = 1 << 3;

    // event4,5番
    // それぞれstate0,1で入力1の立下りで発生。
    // CAPCTRL[4]にその時点でのカウンタを記録
    // それぞれstate1,0に遷移する
    LPC_SCT->EVENT[4].STATE = 0x01; // state0で発生
    LPC_SCT->EVENT[4].CTRL =
        (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (0 << 6)    // IOSEL 入力1に対応h
        |  (2 << 10)   // IOCOND fallに対応
        |  (2 << 12)  // COMBMODE = 2 -> I/O condition だけに対応
        |  (1 << 14)   // STATELD = 1 -> 状態をSTATEVに設定
        |  (1 << 15);   // STATEV = 1
    LPC_SCT->EVENT[5].STATE = 0x02; // state1で発生
    LPC_SCT->EVENT[5].CTRL =
        (0 << 4)    // HEVENT = 0 -> タイマLに対応
        |  (0 << 6)    // IOSEL 入力1に対応h
        |  (2 << 10)   // IOCOND fallに対応
        |  (2 << 12)  // COMBMODE = 2 -> I/O condition だけに対応
        |  (1 << 14)   // STATELD = 1 -> 状態をSTATEVに設定
        |  (0 << 15);   // STATEV = 0
    LPC_SCT->CAPCTRL[4].L = 1 << 4 | 1 << 5;
}

// captureを読んで距離測定
template<uint8_t TrigPort, uint8_t EchoPort, uint8_t Cycle>
int16_t HCSR04Base<TrigPort, EchoPort, Cycle>::getDistance() {
	volatile uint16_t rise, fall;
	auto getRiseAndFall = [&] {
		rise = LPC_SCT->CAP[LPC_SCT->STATE_L == 0 ? 3 : 2].L; // 立ち下がりcapture
		fall = LPC_SCT->CAP[4].L; // 立ち上がりのcapture
 	};

	volatile uint8_t state = LPC_SCT->STATE_L;
	// rise,fallを持ってくる
	getRiseAndFall();
	// この一瞬でstateが変わってるかもしれないから、その場合には取り直し
	if(state != LPC_SCT->STATE_L) { getRiseAndFall(); };
	// fall,riseの差分をとって距離とする
	int16_t distance = fall - rise;
	if(distance < 0 || distance > 5000) { distance = -1; } // 最大4mまで測れるらしいので、とりあえず5m超えたらエラーとする
    return distance;
}

// haltを解除して計測開始
template<uint8_t TrigPort, uint8_t EchoPort, uint8_t Cycle>
void HCSR04Base<TrigPort, EchoPort, Cycle>::start() {
    /// UM10601 10.6.2 SCT control register
    LPC_SCT->CTRL_L &= ~(1 << 2);  // HALT_L = 0
}

// haltを設定してstop計測停止
template<uint8_t TrigPort, uint8_t EchoPort, uint8_t Cycle>
void HCSR04Base<TrigPort, EchoPort, Cycle>::stop() {
    /// UM10601 10.6.2 SCT control register
    LPC_SCT->CTRL_L |= 1 << 2;  // HALT_L = 1
}

#endif

接続


補足/雑感

  • こんな雑なメモで将来の自分が読んでわかるんだろうか。でも時間ないし仕方ない。

みっかぼの無料Androidアプリはこちら。