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