LPC810メモ:モールス信号出力クラス

圧電スピーカでモールス信号を出力できたら便利だと思い作ってみた。
例えばprintfデバッグ的な出力をしたり(デバッガの使い方とかは知らないので)。


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

特長は以下。

  • SCTを使わずMRTとGPIOで実現。1つしかないSCTはちゃんと別の用途に利用できる。
  • 以下をカスタマイズ可能。template引数なのでコンパイル時に解決できる。
    • GPIOの番号(0 .. 5)
    • MRTの番号(0 .. 3)
    • 鳴らす音の周波数
    • 速さ
    • バッファのサイズ
  • 文字列だけでなく、整数を10進または16進で出力する関数も用意

そして必要なROM容量も小さいとか言いたかったが、残念ながら500byteくらいある(整数出力を使うとさらに300byteくらいアップ)。

メンバ関数は以下。具体的な使い方はサンプル参照。

static void init() MRTの割り込みを開始させる。最初に必ず呼ぶ。
static void handleEvent() 割り込みハンドラ。void MRT_IRQHandler(void) から必ず呼ぶ。
static void out(const char *) 文字列を出力
static void outDec(const uint32_t number, const uint8_t minDigit = 1) 整数を10進数と解釈して出力
static void outHex(const uint32_t number, const uint8_t minDigit = 1) 整数を16進数と解釈して出力
static void reset() 現在鳴っている文字以降の文字をキャンセル
static void wait() バッファにある文字をすべて鳴らし終わるまで待つ

なお、モールス信号を1文字1byteで表したく、それなりに工夫はした。具体的には以下。

  • 1bitで1つの音。1が長音、0が短音。
  • ただし、上位bitから最初に0が出てくるbitまでは無視
  • 具体例
    • A(.-) 0b11111001 → 111110 までは無視。残りの01が.-を表す。
    • C(-.-.) 0b11101010 → 1110 までは無視。残りの1010が-.-.を表す。
    • !(-.-.--) 0b10101011 → 10 までは無視。残りの101011が-.-.--を表す。

これで、音が6個の文字でも1byteで表現できる。なお全部ゼロなら空白。

サンプルコード

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

#include "LPC8xx.h"

#include "MorseBase.h"

using Morse = MorseBase<0,3,800,80,32>;
// テンプレート引数の意味
// GPIO 0番(8番pin)を利用
// MRT 3番を利用
// 音は800Hz
// 短音1つは80ミリ秒
// 文字列のバッファは32文字

extern "C" {
    void MRT_IRQHandler(void) {
    	Morse::handleEvent(); // MRT割り込み用のハンドラからこれを必ず呼ぶ
    }
}

int main(void) {
	Morse::init(); // 初期化
	Morse::out("abcdefg "); // 文字列出力
	Morse::outDec(12345, 6); // 数値を10進数と解釈して出力、6桁("012345"と出力される)
	Morse::outHex(0x9abcd, 6); // 数値を16進数と解釈して出力、6桁("09abcd"と出力される)
	Morse::wait(); // これまで投入した文字が鳴り終わるまで待つ
	Morse::out("Hello, World!! 1 "); // ダミー
	Morse::out("Hello, World!! 2 "); // ダミー
	Morse::reset(); // 投入済みの文字を無効化
	Morse::out("Hello, World!! 3 "); // この部分は出力される
	return 0;
}

MorseBase.h(モールス信号クラス、今後使いまわす予定)

#ifndef MORSEBASE_H_
#define MORSEBASE_H_

#include<LPC8xx.h>

static constexpr uint8_t morsePattern[] {
    /* ' '(20) 空白    */ 0,          /* '!'(21) -.-.-- */ 0b10101011, /* '"'(22) .-..-. */ 0b10010010,
    /* '#'(23) 未定義  */ 0,          /* '$'(24) 未定義  */ 0,           /* '%'(25) 未定義 */ 0,
    /* '&'(26) 未定義  */ 0,          /* '''(27) .----. */ 0b10011110,  /* '('(28) -.--. */ 0b11010110,
    /* ')'(29) -.--.- */ 0b10101101, /* '*'(2a) 未定義  */ 0,           /* '+'(2b) .-.-. */ 0b11001010,
    /* ','(2c) --..-- */ 0b10110011, /* '-'(2d) -....- */ 0b10100001,  /* '.'(2e) .-.-.-*/ 0b10010101,
    /* '/'(2f) -..-.  */ 0b11010010, /* '0'(30) -----  */ 0b11011111,  /* '1'(31) .---- */ 0b11001111,
    /* '2'(32) ..---  */ 0b11000111, /* '3'(33) ...--  */ 0b11000011,  /* '4'(34) ....- */ 0b11000001,
    /* '5'(35) .....  */ 0b11000000, /* '6'(36) -....  */ 0b11010000,  /* '7'(37) --... */ 0b11011000,
    /* '8'(38) ---..  */ 0b11011100, /* '9'(39) ----.  */ 0b11011110,  /* ':'(3a) ---...*/ 0b10111000,
    /* ';'(3b) 未定義  */ 0,          /* '<'(3c) 未定義  */ 0,           /* '='(3d) -...- */ 0b11010001,
    /* '>'(3e) 未定義  */ 0,          /* '?'(3f) ..--.. */ 0b10001100,  /* '@'(40) .--.-.*/ 0b10011010,
    /* 'A'(41) .-     */ 0b11111001, /* 'B'(42) -...   */ 0b11101000,  /* 'C'(43) -.-.  */ 0b11101010,
    /* 'D'(44) -..    */ 0b11110100, /* 'E'(45) .      */ 0b11111100,  /* 'F'(46) ..-.  */ 0b11100010,
    /* 'G'(47) --.    */ 0b11110110, /* 'H'(48) ....   */ 0b11100000,  /* 'I'(49) ..    */ 0b11111000,
    /* 'J'(4a) .---   */ 0b11100111, /* 'K'(4b) -.-    */ 0b11110101,  /* 'L'(4c) .-..  */ 0b11100100,
    /* 'M'(4d) --     */ 0b11111011, /* 'N'(4e) -.     */ 0b11111010,  /* 'O'(4f) ---   */ 0b11110111,
    /* 'P'(50) .--.   */ 0b11100110, /* 'Q'(51) --.-   */ 0b11101101,  /* 'R'(52) .-.   */ 0b11110010,
    /* 'S'(53) ...    */ 0b11110000, /* 'T'(54) -      */ 0b11111101,  /* 'U'(55) ..-   */ 0b11110001,
    /* 'V'(56) ...-   */ 0b11100001, /* 'W'(57) .--    */ 0b11110011,  /* 'X'(58) -..-  */ 0b11101001,
    /* 'Y'(59) -.--   */ 0b11101011, /* 'Z'(5a) --..   */ 0b11101100,  /* '['(5b) -> '('*/ 0b11010110,
    /* '\'(5c) 未定義  */ 0,          /* ']'(5d) -> ')' */ 0b10101101,  /* '^'(5e) 未定義 */ 0,
    /* '_'(5f) 未定義  */ 0,          /* '`'(60) 未定義  */ 0,           /* 'a'(61) .-    */ 0b11111001,
    /* 'b'(62) -...   */ 0b11101000, /* 'c'(63) -.-.   */ 0b11101010,  /* 'd'(64) -..   */ 0b11110100,
    /* 'e'(65) .      */ 0b11111100, /* 'f'(66) ..-.   */ 0b11100010,  /* 'g'(67) --.   */ 0b11110110,
    /* 'h'(68) ....   */ 0b11100000, /* 'i'(69) ..     */ 0b11111000,  /* 'j'(6a) .---  */ 0b11100111,
    /* 'k'(6b) -.-    */ 0b11110101, /* 'l'(6c) .-..   */ 0b11100100,  /* 'm'(6d) --    */ 0b11111011,
    /* 'n'(6e) -.     */ 0b11111010, /* 'o'(6f) ---    */ 0b11110111,  /* 'p'(70) .--.  */ 0b11100110,
    /* 'q'(71) --.-   */ 0b11101101, /* 'r'(72) .-.    */ 0b11110010,  /* 's'(73) ...   */ 0b11110000,
    /* 't'(74) -      */ 0b11111101, /* 'u'(75) ..-    */ 0b11110001,  /* 'v'(76) ...-  */ 0b11100001,
    /* 'w'(77) .--    */ 0b11110011, /* 'x'(78) -..-   */ 0b11101001,  /* 'y'(79) -.--  */ 0b11101011,
    /* 'z'(7a) --..   */ 0b11101100, /* '{'(7b) -> '(' */ 0b11010110,  /* '|'(7c) 未定義 */ 0,
    /* '}'(7d) -> ')' */ 0b10101101, /* '~'(7e) 未定義  */ 0 };

// テンプレート引数
// Port -> GPIOの番号、Channel -> MRTの番号、Frequency -> 音の周波数、
// UnitTimeMs -> 短音1つの長さ、BufferSize -> 出力用バッファのバイト数
// 例) using Morse = MorseBase<0,3,800,80,32>;
template <uint8_t Port, uint8_t Channel, int Frequency, int UnitTimeMs, uint8_t BufferSize>
class MorseBase {
public:
    static void init();  // 最初に必ず呼ぶ
    static void handleEvent(); // void MRT_IRQHandler(void) から必ず呼ぶ
    static void reset(); // 現在出力している文字以降をキャンセル
    static void wait(); // バッファ中の文字を出力しきるまで待つ
    static void out(const char *); // バッファに文字列を出力

    // バッファに数値を出力(10進、16進)。 minDigitは最小桁数、足りなければ0で埋める。
    static void outDec(const uint32_t number, const uint8_t minDigit = 1) { outNumber(number, 10, minDigit); };
    static void outHex(const uint32_t number, const uint8_t minDigit = 1) { outNumber(number, 16, minDigit); };

private:
    static void outNumber(const uint32_t number, const uint8_t base, const uint8_t minDigit); // outDec,outHexの実装
    static constexpr uint32_t unitToneCount { Frequency * 2 * UnitTimeMs / 1000 }; // 単位時間あたりのカウントダウン数
    static volatile uint32_t counter; // 音(または消音)の残り長さのカウンタ(カウントダウン)
    static volatile bool currentToneStatus; // trueなら発音中、falseなら消音中

    static volatile uint8_t currentPattern; // 現在出力している文字のパターン
    static volatile uint8_t currentPatternPosition; // パターン状の位置

    static volatile uint8_t buffer[BufferSize]; // 出力待ちの文字列バッファ
    static volatile uint8_t currentPosition; // バッファ中の出力中の文字の位置
    static volatile uint8_t lastPosition; // バッファ中の文字が入っている最後の位置
    static uint8_t nextPosition(uint8_t position) { return (position + 1) % BufferSize; } // 次の位置を算出
    static inline void ioOff() { LPC_GPIO_PORT->CLR0 |= (1<<Port); } // GPIOをoff
    static inline void ioToggle() { LPC_GPIO_PORT->NOT0 |= (1<<Port); }; // GPIOをtoggle
};

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
volatile bool MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::currentToneStatus {false};

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
volatile uint32_t MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::counter {0};

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
volatile uint8_t MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::currentPattern {0};

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
volatile uint8_t MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::currentPatternPosition {0};

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
volatile uint8_t MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::buffer[BufferSize];

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
volatile uint8_t MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::currentPosition {0};

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
volatile uint8_t MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::lastPosition {0};

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
void MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::reset() {
    lastPosition = 0; currentPosition = 0; // バッファ中の位置をリセット(これまで入ってた文字が無効になる)
}

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
void MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::wait() {
    // 消音状態で、文字が入っていない状態になるまで待つ
    while(currentToneStatus != false || currentPatternPosition != 0 || currentPosition != lastPosition) {}
}

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
void MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::out(const char *str) {
    const char *p {str};
    // null終端がくるまで文字をバッファにコピー
    while(*p != '\0' && nextPosition(lastPosition) != currentPosition) {
        lastPosition = nextPosition(lastPosition);
        buffer[lastPosition] = *(p++);
    }
}

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
void MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::outNumber(const uint32_t number, const uint8_t base, const uint8_t minDigit) {
    // 数値をbase進数(10 or 16)の文字列に直してからバッファにコピー
    constexpr uint8_t maxDigit {12};
    static char subBuffer[maxDigit + 1] {};
    char *currentPosition { subBuffer + maxDigit };
    char *leftPosition { subBuffer + maxDigit - minDigit };
    if(leftPosition < subBuffer) { leftPosition = subBuffer; };
    uint32_t n {number};
    while(n != 0 || currentPosition > leftPosition) {
        *(--currentPosition) = "0123456789abcdef"[n % base];
        n /= base;
    }
    out(currentPosition);
}


template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
void MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::init() {

    // GPIOに対してclockを有効化
    LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);

    // PIO0_0..5のいずれか(8,5,4,3,2,1pinのいずれか)をoutputに設定
    LPC_GPIO_PORT->DIR0 |= (1<<Port);

    // MRTに対してclockを有効化、
    // UM10601 11.3 Basic configuration
    // enable the clock to the register interface.
    // In the SYSAHBCLKCTRL register, set bit 10 (Table 30) to
    if(!(LPC_SYSCON->SYSAHBCLKCTRL & (0x1<<10))) {
		LPC_SYSCON->SYSAHBCLKCTRL |= (0x1<<10);

		// UM10601 11.3 Basic configuration
		// Clear the MRT reset using the PRESETCTRL register (Table 19).
		// これ2回やっちゃだめらしいので、初回だけにする。
		LPC_SYSCON->PRESETCTRL &= ~(0x1<<7);
		LPC_SYSCON->PRESETCTRL |= (0x1<<7);
    }

    // The global MRT interrupt is connected to interrupt #10 in the NVIC.
    // 以下にしたがって設定
    // UM10601 3.4.1 Interrupt Set Enable Register 0 register
    NVIC->ISER[0] = (0x1 << 10);

    // Repeat interrupt modeで割り込み設定
    // UM10601 11.6.3 Control register
    LPC_MRT->Channel[Channel].CTRL = 0x1;

    // 割り込み発生させる周期を設定
    // UM10601 11.6.1 Time interval register
    LPC_MRT->Channel[Channel].INTVAL = 12000000 / 2 / Frequency; // 音の周波数に合わせたカウントにする
    LPC_MRT->Channel[Channel].INTVAL |= 0x1UL<<31;

}

template<uint8_t Port, uint8_t Channel, int Frequency, int UnitTime, uint8_t BufferSize>
void MorseBase<Port,Channel,Frequency,UnitTime,BufferSize>::handleEvent() {
    if (LPC_MRT->Channel[Channel].STAT & 0x1) {
        // 同じbitに1を書き込むことで割り込みを処理したことを伝える
        LPC_MRT->Channel[Channel].STAT = 0x1;

        if(currentToneStatus) { // 発音中
            ioToggle(); // GPIO反転(スピーカーにつないでるので音になる)

            if(counter == 0) { // 発音終了
                currentToneStatus = false; // 消音状態に移行
                ioOff(); // GPIO off
                counter = unitToneCount; // 消音の長さは短音1つ分
            }

        } else if(counter == 0) { // 消音終了

            if(currentPatternPosition == 0) { // 文字のパターン終了、次の文字を読む
                currentPattern = 0;
                if(currentPosition != lastPosition) { // まだバッファに文字がある
                    currentPosition = nextPosition(currentPosition); // 次の文字を読む
                    currentPattern = morsePattern[buffer[currentPosition] - 0x20]; // 文字に相当するパターンを設定

                    // パターン中のスタート位置を設定(7bit目から下げていき、最初に0だったbit)
                    currentPatternPosition = 7;
                    while(currentPatternPosition > 0) {
                        if((currentPattern & (1 << currentPatternPosition)) == 0) { break; }
                        currentPatternPosition--;
                    }
                }

                if(currentPattern == 0) { // パターン0なら空白相当、短音6個分、消音時間追加
                    currentPatternPosition = 0;
                    counter = unitToneCount * 6;
                } else { // それ以外なら通常の文字、短音2個分、消音時間追加
                    counter = unitToneCount * 2;
                }

            } else { // 文字のパターンまだ残ってる
                currentToneStatus = true; // 発音状態に移行
                if(currentPattern & (1 << --currentPatternPosition)) { // 長音
                    counter = unitToneCount * 3;
                } else { // 短音
                    counter = unitToneCount;
                }
            }
        }
        counter--;
    }
}

#endif /* MORSEBASE_H_ */

接続

  • スピーカは本例ではGPIO_0を使うため、8番pinとGND。
    • GPIO_1なら5番pin、2なら4番、3なら3番、4なら2番、5なら1番につなぐ。

補足/雑感

  • 実は「モールス信号を1byteで表す」が思いつかなくて一番苦労していて、最初は「上位3bitで音の数を表し、下位5bitで5音までのモールスを表現」としていたが、それだと6音の文字('!'の-.-.--など)を表せなくてハマった。

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