皆さんは「アマチュア無線」を知っていますか?
携帯電話やPHSなどのコミュニケーションツールが一般的になるまでは、遠く離れた場所同士を無線でつないで通信するアマチュア無線は人気の高い趣味でした。アマチュア無線は単に通信するだけでなく、使用する機器の自作などを行なう電子回路の設計スキルが高い人達の集まりでもあります。
アマチュアと言っても活動できるのは国家試験をパスした無線技士です。特に上級のアマチュア無線技師の必須スキルとして「モールス符号を使った通信(モールス信号)」があります。
「トン/ツー」と表現されるモールス信号は皆さんも一度は聞いたことがあるのではないでしょうか? 近年では一般の人達の間でモールス信号が話題になることはありませんが、覚えてみると秘密のメッセージを送受信するなど面白い使い方ができるかも知れません。
今回はArduinoと電子ブザーを使って、このモールス符号を模倣してみます。
今回の電子工作はハードウェアの構成よりスケッチの方が難しいかも知れません。マイコンボードにはハードウェアだけでなくソフトウェアの構成も重要です。
目次
1. モールス信号は単純な音の組み合わせ?
モールス信号とは、点と線で文字や記号を表す信号法です。19世紀に誕生し、遠く離れた場所同士でメッセージを交換できるように考え出されました。
苛酷な状況でも使用に耐えるようにモールス信号で使用される符号は長短のパルスの組み合わせで表現されます。この長短のパルスを日本語では「ツー(長点)」と「トン(短点)」と表現します。
この単純なルールにより、簡単な送信機で遠く離れた場所へ送れるようになり、受信側も簡単な仕組みで音に変換し安定して情報の伝達ができるようになりました。
日本ではテレビ局や携帯電話、気象レーダーなどの電波を使ったサービスを運用する無線技士と呼ばれる専門職や、身近なところではアマチュア無線の上級者には必須の知識です。
本記事ではパソコンから送られるアルファベットのメッセージをArduinoで読み取り、対応するモールス符号をArduinoボードにつなげた圧電ブザーで再生するデバイスを作ってみます。
パソコン上ではArduino IDEのシリアルモニターを使ってメッセージをArduinoに送ります。
今回の電子工作では圧電ブザーを使用しますが音の代わりにLEDを点灯しても良いでしょう。
2. 準備
以下のものを準備してください。
Arduino Uno
Arduino IDE
ブレッドボード
圧電ブザー
上記に加えてUSBケーブルと、ブレッドボード用のジャンパワイヤが必要です。
3. 配線
いつもはスケッチの作成から始めますが、今回のスケッチはボリュームがあるので先に配線を済ませます。
以下の通り配線してください。
ArduinoのGNDピンをブレッドボードの電源ライン(-)に接続します。
圧電ブザーは筐体から赤・黒のリード線が出ています。赤のリード線はプラス側、黒のリード線はマイナス側です。
圧電ブザーの赤のリード線がArduinoの8番に、黒がブレッドボードの電源ライン(-)つながるように配線してください。
4. スケッチ
今回のスケッチはボリュームがあります。
ただし、ほとんどは送られたメッセージのアルファベット一文字一文字を対応するモールス符号に変換する処理です。
以下がスケッチの全体です。そのあとでスケッチをいくつかのパートに分けて説明します。
int buzzer = 8; // Assign buzzer to pin 8 int note = 1000; // Set the pitch for the buzzer tone int timeUnit = 100; // This variable will be used to measure dots, dashes, breaks, and pauses char input; // Variable to save the input to void setup () { Serial.begin(9600);//for the connect with the boared } void loop () { if (Serial.available()) { input = Serial.read();//read the input if (input == 'a' || input == 'A') {lA();}//if the input is a or A go to function lA if (input == 'b' || input == 'B') {lB();}//same but with b letter if (input == 'c' || input == 'C') {lC();} if (input == 'd' || input == 'D') {lD();} if (input == 'e' || input == 'E') {lE();} if (input == 'f' || input == 'F') {lF();} if (input == 'g' || input == 'G') {lG();} if (input == 'h' || input == 'H') {lH();} if (input == 'i' || input == 'I') {lI();} if (input == 'j' || input == 'J') {lJ();} if (input == 'k' || input == 'K') {lK();} if (input == 'l' || input == 'L') {lL();} if (input == 'm' || input == 'M') {lM();} if (input == 'n' || input == 'N') {lN();} if (input == 'o' || input == 'O') {lO();} if (input == 'p' || input == 'P') {lP();} if (input == 'q' || input == 'Q') {lQ();} if (input == 'r' || input == 'R') {lR();} if (input == 's' || input == 'S') {lS();} if (input == 't' || input == 'T') {lT();} if (input == 'u' || input == 'U') {lU();} if (input == 'v' || input == 'V') {lV();} if (input == 'w' || input == 'W') {lW();} if (input == 'x' || input == 'X') {lX();} if (input == 'y' || input == 'Y') {lY();} if (input == 'z' || input == 'Z') {lZ();} if (input == ' ') {wordPause();} Serial.println (input); } } //Letter functions void lA () {dot();dash();letterPause();}//letter A in morse code! void lB () {dash();dot();dot();dot();letterPause();}//same for B void lC () {dash();dot();dash();dot();letterPause();} void lD () {dash();dot();dot();letterPause();} void lE () {dot();letterPause();} void lF () {dot();dot();dash();dot();letterPause();} void lG () {dash();dash();dot();letterPause();} void lH () {dot();dot();dot();dot();letterPause();} void lI () {dot();dot();letterPause();} void lJ () {dot();dash();dash();dash();letterPause();} void lK () {dash();dot();dash();letterPause();} void lL () {dot();dash();dot();dot();letterPause();} void lM () {dash();dash();letterPause();} void lN () {dash();dot();letterPause();} void lO () {dash();dash();dash();letterPause();} void lP () {dot();dash();dash();dot();letterPause();} void lQ () {dash();dash();dot();dash();letterPause();} void lR () {dot();dash();dot();letterPause();} void lS () {dot();dot();dot();letterPause();} void lT () {dash();letterPause();} void lU () {dot();dot();dash();letterPause();} void lV () {dot();dot();dot();dash();letterPause();} void lW () {dot();dash();dash();letterPause();} void lX () {dash();dot();dot();dash();letterPause();} void lY () {dash();dot();dash();dash();letterPause();} void lZ () {dash();dash();dot();dot();letterPause();} void dot() //Emit sound for 100 milliseconds { tone(buzzer, note, timeUnit); delay(timeUnit * 2); } void dash() //Emit sound for 300 milliseconds { tone(buzzer, note, timeUnit * 3); delay(timeUnit * 4); } void letterPause() //Delay between letters for 300 milliseconds { delay(timeUnit * 3); } void wordPause() { delay (timeUnit * 7); }
スケッチのポイントを説明します。
最初は変数の初期化とsetup()関数です。
int buzzer = 8; // Assign buzzer to pin 8 int note = 1000; // Set the pitch for the buzzer tone int timeUnit = 100; // This variable will be used to measure dots, dashes, breaks, and pauses char input; // Variable to save the input to void setup () { Serial.begin(9600);//for the connect with the boared }
1行目は圧電ブザーを接続するピン番号の定義です。
2行目の変数noteは圧電ブザーから発する音階を定義しています。
続く変数timeUnitは、モールス信号のトンとツーの長さの基準となる時間です。今回は100ミリ秒を設定します。トンはこの時間、ツーはこの時間の3倍の長さとします。また符号と符号の間、メッセージの一文字一文字の間、単語の切れ目の無音部分の長さもこの時間を基準にします。
続くsetup()関数ではパソコンから送られるメッセージを受信できるようにシリアルモニターを初期化します。
loop()関数です。
void loop () { if (Serial.available()) { input = Serial.read();//read the input if (input == 'a' || input == 'A') {lA();}//if the input is a or A go to function lA if (input == 'b' || input == 'B') {lB();}//same but with b letter if (input == 'c' || input == 'C') {lC();} if (input == 'd' || input == 'D') {lD();} if (input == 'e' || input == 'E') {lE();} if (input == 'f' || input == 'F') {lF();} if (input == 'g' || input == 'G') {lG();} if (input == 'h' || input == 'H') {lH();} if (input == 'i' || input == 'I') {lI();} if (input == 'j' || input == 'J') {lJ();} if (input == 'k' || input == 'K') {lK();} if (input == 'l' || input == 'L') {lL();} if (input == 'm' || input == 'M') {lM();} if (input == 'n' || input == 'N') {lN();} if (input == 'o' || input == 'O') {lO();} if (input == 'p' || input == 'P') {lP();} if (input == 'q' || input == 'Q') {lQ();} if (input == 'r' || input == 'R') {lR();} if (input == 's' || input == 'S') {lS();} if (input == 't' || input == 'T') {lT();} if (input == 'u' || input == 'U') {lU();} if (input == 'v' || input == 'V') {lV();} if (input == 'w' || input == 'W') {lW();} if (input == 'x' || input == 'X') {lX();} if (input == 'y' || input == 'Y') {lY();} if (input == 'z' || input == 'Z') {lZ();} if (input == ' ') {wordPause();} Serial.println (input); } }
一番外側のif文はパソコンのシリアルモニターからデータが送られた(処理すべきデータがある)ことを検出します。
その後Serial.read()関数で受信した文字列の最初の一文字を取り出し、変数inputに格納します。
次に並ぶif文で文字(アルファベット)に対応した処理を行う(音を出す)関数を呼び出します。ここではアルファベットの大文字と小文字のどちらも同じ関数を呼び出します。
loop()関数が呼び出されるごとに上記を一文字ずつ処理します。
以下はスケッチの主処理(loop()関数)の外に配置した関数です。
//Letter functions void lA () {dot();dash();letterPause();}//letter A in morse code! void lB () {dash();dot();dot();dot();letterPause();}//same for B void lC () {dash();dot();dash();dot();letterPause();} void lD () {dash();dot();dot();letterPause();} void lE () {dot();letterPause();} void lF () {dot();dot();dash();dot();letterPause();} void lG () {dash();dash();dot();letterPause();} void lH () {dot();dot();dot();dot();letterPause();} void lI () {dot();dot();letterPause();} void lJ () {dot();dash();dash();dash();letterPause();} void lK () {dash();dot();dash();letterPause();} void lL () {dot();dash();dot();dot();letterPause();} void lM () {dash();dash();letterPause();} void lN () {dash();dot();letterPause();} void lO () {dash();dash();dash();letterPause();} void lP () {dot();dash();dash();dot();letterPause();} void lQ () {dash();dash();dot();dash();letterPause();} void lR () {dot();dash();dot();letterPause();} void lS () {dot();dot();dot();letterPause();} void lT () {dash();letterPause();} void lU () {dot();dot();dash();letterPause();} void lV () {dot();dot();dot();dash();letterPause();} void lW () {dot();dash();dash();letterPause();} void lX () {dash();dot();dot();dash();letterPause();} void lY () {dash();dot();dash();dash();letterPause();} void lZ () {dash();dash();dot();dot();letterPause();}
これらの関数は送られたメッセージの一文字一文字に対応するようにアルファベットの数だけ存在します。
この関数一つ一つの中は、モールス信号のアルファベットに対応するトン/ツーの並びを再現しています。
トンがdot()関数、ツーがdash()関数です。letterPause()関数は文字と文字の間を分かり易くするための無音部分を作ります。
これらの関数の中から直接、圧電ブザーに音を出すtone()関数を呼び出すこともできますが、ソースコードが見難くなります。そのため、tone()関数の呼び出しはdot()関数・dash()関数・letterPause()関数内で行います
関数の後ろにはそれぞれセミコロン(;)が必要です。通常は複数の行となるソースコードを改行なしで1行に並べています。
以下は上記の関数群から呼び出される、音を出すための関数です。
void dot() //Emit sound for 100 milliseconds { tone(buzzer, note, timeUnit); delay(timeUnit * 2); } void dash() //Emit sound for 300 milliseconds { tone(buzzer, note, timeUnit * 3); delay(timeUnit * 4); } void letterPause() //Delay between letters for 300 milliseconds { delay(timeUnit * 3); } void wordPause() { delay (timeUnit * 7); }
dot()関数は、圧電ブザーに音を出すための関数tone()を使用してモールス符号のトンを再現します。tone()関数に続くdelay()関数はトンとツーの間の無音部分です。
dash()関数はツーを再現します。
letterPause()関数は符号と符号の間、wordPause()関数はメッセージの中の「スペース」に対応します。いずれも無音部分を再現する関数なので内部はdelay()関数の呼び出しのみです。
スケッチの作成が終わったらArduinoへ転送します。
5. シリアルポートでメッセージを送信
準備ができたらシリアルポートを通してメッセージを送ります。
Arduino IDEからシリアルモニターを起動してください。
画面上にメッセージ(アルファベットの組み合わせ)を入力します。「エンター」キーを押すとメッセージはArduinoに送られ、対応するモールス符号が圧電ブザーから流れます。
トンとツーの長さだけでなく、トンとツーの間、文字と文字の間、スペースの無音部分などもチェックしてださい。
上手く動作しない時はスケッチの打ち間違いが考えられます。スケッチが大きいので、コピーミスや打ち間違いなどを確認しましょう。
6. まとめ
インターネットが発達した現在では、通信手段としてモールス信号の出番は無くなりました。しかし、このようなシンプルでコードも長く、実用的な機能を有するスケッチは学習用途として最適です。
本記事で解説している通り、単に「トン」「ツー」の音を出すだけでも相当大きいスケッチになります。
Arduinoのようなマイコンボードを使った電子機器を作る際は、電子回路の設計スキルも必要ですがソフトウェア(スケッチ)のテクニックも重要です。
今回のスケッチはif文を主に使用してコーディングしています。じっくりと眺めれば難しい点はないでしょう。
実はこのスケッチ、配列やswitch文を使えばスッキリと書くことができます。
冗長なソースコードは処理の効率が悪いだけでなく不具合を生む原因にもなります。
ぜひ皆さんも、単にコピー&ペーストでスケッチを作成するだけでなく、コードをアレンジして、見やすく理解しやすいスケッチを書くテクニックを身に着けてみましょう。