座席の離れた同僚にモールス信号でメッセージの送受信ができるデバイスを作りました

小原正大
121

この記事は RECRUIT MARKETING PARTNERS Advent Calendar 2015 の投稿記事です。

こんにちは。2015年度新卒フロントエンドエンジニアの小原正大です。PICというマイクロコントローラ(プログラムを組み込める集積回路)を使った組み込みデバイス開発をしてみました。電子工作もC言語も初めてで、回路やソースコードについて可読性や妥当性に不安がありますが『動かす』をゴールに実装してみました。

開発モチベーション

チャットツールを使って呼びかけても気付かないエンジニアを便利に呼び出すために、物理的なメンションを送る専用デバイスを作ろうとしたのが開発のきっかけです。自然な体験のためにWebで慣れているもの1)初めは信号を受信するとピカピカ光りながらバイブしてブザーを鳴らすデバイスを作りましたが迷惑だったので、エンジニアが普段慣れているものを開発して違和感をなくすように調整しました。を現実に帰化する逆スキュアモーフィズムの観点から現実世界にSlackのようなもの(メッセージが送れて通知があると光るデバイス)を作って物理メンションを送ることを目指しました。

機能の概要

機能の概要図

送信機と受信機の二つを作って一方向のコミュニケーションができます。送信機からボタンをぽちぽちしてモールス信号を送信するとそれを受信した受信機が信号を解析してその内容を液晶画面に表示するといった動作をします。

機能としては赤外線に7bitの情報を乗せた固有波形を発生させて送信モジュールの識別をできるようにしたり、モールス信号が送受信できるように判定を調整したり、I2C通信でPICから液晶を動かしたりなどの機能を実装しました。下記『ソフト面の開発』に詳細説明があります。

デモ動画

実際に動かしてみた動画です。

以下の文章は実装と感想についてです。長い説明になりますが、もしこの『実装記事という名の努力』を最後まで読んでいただける優しくて器の大きい方がいれば嬉しいです。ここで離脱される方も今までありがとうございました。

ハード面の開発について

必要機材

以下の機材を利用して開発を行いました。

PIC書き込み用デバイス・環境

デバイス名 購入数
マイクロチップ PICkit3 1個
ブレッドボード BB-801 1個
ブレッドボード・ジャンパーワイヤ EIC-J-L 1個
電池ボックス 単3×2本用 BH-321-1AS 2個
単三電池 4個

デバイスに組み込んだ部品

デバイス名 料金(税込) 購入数
PICマイコンPIC16F1827-I/P ¥170 2個
赤外線リモコン受信モジュール ¥50 1個
赤外線LED ¥20 1個
超高輝度5mm赤色LED ¥25 2個
カーボン抵抗(炭素皮膜抵抗) 1/4W 22Ω (100本入) ¥1 1個
デジタルトランジスタ ¥5 2個
ROHS カーボン抵抗(炭素皮膜抵抗) 1/4W 1kΩ ¥1 3個
タクトスイッチ(黒) (10個入) ¥12 2個
ROHS 電源用電解コンデンサ100μF35V105℃(ルビコンZLH) ¥20 2個
LCDキャラクタディスプレイモジュール(16×2行バックライト無) ¥500 1個

回路の組み込みについて

実際のブレッドボード上の回路写真(左:受信機、右:送信機)

発送信信機回路写真

回路図(左:受信機、右:送信機)

発信機回路図

それぞれの部品に対する簡単な説明

PIC
マイクロチップ・テクノロジー社が製造しているマイクロコントローラ(制御用IC)製品群の総称。アセンブリ言語やC言語を使ってそれぞれのピンに対して実行させたい処理を組み込むことができる。今回はこの部品C言語で処理を書き込んでデバイスを作成した。プロセッサとして利用。
赤外線リモコン受信モジュール
電位をかけると赤外線受信の有無で出力電位を制御する。赤外線を受信すると電位がGNDになる。赤外線の検知に利用。
LED/赤外線LED
正しい方向に電位をかけると電磁波を発生する。逆方向に電位をかけると閾値を越えるまでは断線する。LEDは入出力のインテラクションとして、赤外線LEDは赤外線発生源として利用した。
抵抗
かけられた電位に反比例して電流を流す。抵抗が大きいほど流れる電流は小さくなる。機器への過負荷を防ぐために利用。
デジタルトランジスタ
スイッチ動作をする半導体素子。三本足がありそのうちの一本のに外部からの入力電位がかかると残り二本を通線する。赤外線LEDを電源に並列につなぎ電力を最大化した状態で出力をスイッチングするために利用。
タクトスイッチ
スイッチ。ボタンを物理的に押すと通電する。モールス送信と状態リセットのための物理ボタンとして利用。
コンデンサー
一時的に電気を充電・放電することができ、電位差の急変を抑制する。急電圧降下などによりPICを壊さないために利用。
LCDキャラクタディスプレイモジュール(16×2行バックライト無)
I2C通信によって液晶表示を制御することができる。モールス信号の受信情報などを表示するために利用。

ソフト面の開発について

開発環境

必要な開発環境は以下です。

主要機能についての実装について説明

デバイス開発を行う際に実装したそれぞれの機能についての説明を行います。

固有波形を用いた赤外線通信による発信者認証の実装

概要

信号についての画像を使った説明

赤外線通信に発信者判別情報を乗せて認証した通信のみを受信するようにしました。なお通信形式はNEC(家電製品協会)フォーマットを参考にして実装しました。

赤外線リモコン受信モジュールが38kHzを中心周波数として赤外線を感知するので、送信モジュールで赤外線信号を発生させる場合38kHzの信号を発生させつつ、約600マイクロ秒単位でその信号のオンオフを切り替えることによって波形に信号を乗せます。信号の具体的な仕様は初めの1bitを通信の開始サインとして次の7bitに送信者識別情報を乗せています。
この赤外線を赤外線リモコン受信モジュールで受信して600マイクロ秒単位で信号の解析を行うことで送信者を判定しました。

送信側の処理

1bitの通信開始信号の後に7bitに送信者識別情報『1110011』(shotaという文字列を二進数化した数字)を送信しています。outh()は約38kHz(13μs*2周期)の赤外線を出力、outl()は出力を停止のための関数でそれぞれ598μsの周期で信号を発生します。この二つを制御して通信信号送信しています。


#define led RB5 //赤外線led 0オフ, 1オン #define DATA_PERIOD 598 // 受信データの読み取り間隔 598μs default void main(void){ // 開始信号を送る startbit(); // データを送信 send_shota(); // 40ms休息 _delay_ms(40); } void startbit(){ outh(); outl(); } //赤外線LED消灯 void outl(){ led=0; __delay_us(DATA_PERIOD); } //赤外線LED点滅 38kHz 13μs*23 = 598μs void outh(){ for (int i = 0; i < 23; i++) { led=1; __delay_us(13); led=0; __delay_us(13); } } void send_shota(){ // 1110011 outh();outh();outh();outl();outl();outh();outh(); }
受信側の処理

赤外線を受信して送信者識別情報『1110011』を識別します。detect_signal()では受信信号から配列signal_dataを作り、shota_dataと比較することで送信者識別情報を判定しています。受信信号処理は通信開始信号を受信した後に598μsごとに赤外線を受信を判定してその結果に応じてsignal_dataの配列を準備するといった感じです。


#define detector RB5 // 赤外線感知モジュールからの信号 0受信, 1なし #define DATA_NUM 7 // 受信データ量 7bit #define DATA_PERIOD 598 // 受信データの読み取り間隔 598μs default void signal_data_init(); // 受信データを格納する配列を初期化する int shota_data[]={ 1, 1, 1, 0, 0, 1, 1}; //shota文字列の2進数 int signal_data[DATA_NUM]; // 赤外線で受信した波形情報を2進数で格納する配列 void main(void){ // 信号受信フラグを宣言 int detect_signal_result = 0; // 信号を検知 detect_signal(&detect_signal_result); // 信号を検知したかどうかで場合分け if ( detect_signal_result == 1 ){ }else{ } } void signal_data_init() { for (int i = 0; i < data_num; i++ ) { signal_data[i] = 0; } } void detect_signal(int *detect_signal_result) { // データの初期化 signal_data_init(); if ( detector == 0 ){ // 最初の信号分の遅延 __delay_us(DATA_PERIOD*2); // 受信データを読み取り for (int i = 0; i < DATA_NUM; i++ ) { if ( detector == 0 ){ signal_data[i] = 1; __delay_us(DATA_PERIOD); } else { signal_data[i] = 0; __delay_us(DATA_PERIOD); } } // 受信データがshotaかどうか配列の中身を比較 if ( memcmp(signal_data, shota_data, sizeof(int) * DATA_NUM) == 0 ) { *detect_signal_result = 1; }else{ *detect_signal_result = 0; } } else { *detect_signal_result = 0; } }

I2C通信を用いたLCDキャラクタディスプレイモジュール(16×2行バックライト無)の操作の実装

秋月電子I2C接続小型LCDモジュールに表示を行うで公開されているライブラリのskI2CLCDlibとskI2Clibを液晶ディスプレイのI2C接続用に利用させて頂きました。ライブラリで定義された関数を使って実装できたので特に工夫することなく動作させることができました。自作すると結構しんどそうです。

I2C通信について

フィリップス社が提唱した周辺デバイスとのシリアル通信の方式で、これによりPIC間同士などで信号を通信することができます。機器にそれぞれマスタとスレーブの役割をもたせて、マスタがスレーブを完全に制御するような実装ができます。今回はPICがLCDキャラクタディスプレイモジュールを制御する形をとりました。

モールス信号の受信判定についての実装

概要

モールス信号の受信判定全体のフロー

モールス信号の押し時間判定のフロー

モールス信号の離し時間判定のフロー

受信状態を判定し続けることで赤外線送信装置の送信状態を監視待機できるので、これによって発信信号のモールス信号としての状態を判定して合計5bitのデータを受信します。

関数の説明ですが、detect_state_continue_pressedは赤外線送信ボタンの継続押し時間を判定する関数でモールス信号のツー・トンを判定しています。detect_state_continue_leftは赤外線送信ボタンの継続離し時間を判定する関数で次のモールス信号の開始か通信終了の判定を行います。以上二つの関数をdetect_stateから5bit分のモールス信号を受けるまで判定して受けたモールス信号の配列を作成しています。

処理の説明としては、detect_state_continue_pressedは598μs周期で0.5s(830回)受信判定を行い赤外線を検知する度に0.5s(830回)のカウントをリセットして受信を再開、合計100カウント以上(感覚的には0.5s以上送信機のボタンを押すと)受信状態を検知するとツー・トンで言う所のツーがそれ以下ならトンとして認識されます。detect_state_continue_leftdetect_state_continue_pressedと同様の処理で1sの間に一度でも赤外線を受信するかどうかで判定を行います。以上二つの関数をdetect_stateからdetect_state_resultで状態を管理しながら呼び出しを行います。

ソースコード
#define MORSE_DATA_NUM 5 // モールス信号の受信データ量 5bit
#define detector RB5    // 赤外線感知モジュールからの信号 0受信, 1なし
void detect_state(int *detect_state_result, int *detect_state_result_count); // 発信状態(ツートン)を検知して押している状態なら1, 離している状態なら0, 終了状態なら2をdetect_state_resultに代入する
void detect_state_continue_pressed(int *detect_state_result, int *detect_state_result_count); // 押し続けているかどうかを検知
void detect_state_continue_left(int *detect_state_result, int *detect_state_result_count); // 離し続けているかどうかを検知
int morse_data[MORSE_DATA_NUM]; // 受信したモールス信号のツートンを格納する配列 空0, トン1, ツー2

void main(void){
    // 先ほどの処理
    // 信号受信フラグを宣言
    int detect_signal_result = 0;
    // 信号を検知
    detect_signal(&detect_signal_result);
    // 信号を検知したかどうかで場合分け
    if ( detect_signal_result == 1 ){
        // ここからモールス信号の判定処理
        morse_data_init();
        int detect_state_result = 1;
        int detect_state_result_count = 0;
        // on, offなのでデータ量の倍のループ
        for (int i = 0; i < MORSE_DATA_NUM*2; i++) {
            if (detect_state_result_count < MORSE_DATA_NUM) {
                detect_state(&detect_state_result, &detect_state_result_count);
            }
        }

    }
}


void detect_state(int *detect_state_result, int *detect_state_result_count){
  if(*detect_state_result == 0){
    // 離している状態なので、終了状態になっているかどうかを判定
    detect_state_continue_left(&*detect_state_result, &*detect_state_result_count);
  }else if(*detect_state_result == 1){
    // 押している状態なので、ツーかトンか判定
    detect_state_continue_pressed(&*detect_state_result, &*detect_state_result_count);
  }else{
    // 終了状態なのでなにもしない
  }
}

// 押し続けているかどうかを検知(ツーかトンか判定)
void detect_state_continue_pressed(int *detect_state_result, int *detect_state_result_count){

  int detect_state_continue_pressed_count = 0;

  // 600μsに一度押しているかどうか判定して、押し続けている状態をカウント
  // 0.5s秒間押している状態が検知できなかった場合終了
  // 以下で押し続けている状態のカウントが100以上であるかどうかで条件分岐

  // 600μs*830 = 0.5s
  for (int i = 0; i < 830; i++) {
    // 600μs
    __delay_us(DATA_PERIOD);
    // 押している状態なら、押し判定をカウント
    if( detector == 0 ){
      detect_state_continue_pressed_count++;
      i = 0; // さらに0.5秒感知
    }
  }

  *detect_state_result = 0; // 離した状態に変更

  // 押し続けている状態のカウントが100以上かどうか判定
  if(detect_state_continue_pressed_count > 100){
    // 押し続けた際のデータを処理(ツーならば)
    morse_data[*detect_state_result_count]=2;
  }else{
    // すぐに離した際のデータを処理(トンならば)
    morse_data[*detect_state_result_count]=1;
  }
  *detect_state_result_count = *detect_state_result_count + 1; // カウントを1増加
}

// 離し続けているかどうかを検知
void detect_state_continue_left(int *detect_state_result, int *detect_state_result_count){

  int detect_state_continue_left_count = 0;

  // 600μsに一度離しているかどうか判定して、離し続けている状態をカウント
  // 入力信号が検知された場合または、離し続けている状態のカウントが3320以上(2秒以上離されている状態)で判定終了
  // 以下で離し続けている状態のカウントが3200以上であるかどうかで条件分岐

  // 600μs*3320 = 2s
  for (int i = 0; i < 3320; i++) {
    // 600μs
    __delay_us(DATA_PERIOD);
    // 離している状態なら、離し判定をカウント
    if( detector != 0 ){
      detect_state_continue_left_count++;
      // 離し続けている状態のカウント数で場合分け
      if ( detect_state_continue_left_count > 3320) {
        i = 3320; // 終了
        *detect_state_result = 2; // 終了状態に変更
      }else{
        i = 0;
      }
    // 押した状態を感知したらループ終了
    }else{
      i = 3320; // 終了
      *detect_state_result = 1; // 押した状態に変更
    }
  }
}

全体としての実装

送信側の実装

概要

機能を箇条書きすると以下の機能が実装されています。

  • 赤外線の周波数と信号周期を調整して7bit情報をのせる機能
  • 物理的なスイッチを押した際に赤外線送信を行う機能
  • 赤外線が不可視なので発光に連動してLEDを点灯させ動作確認する機能
ソースコード

/* * File: mian.c * Author: shota * * Created on December 4, 2015, 11:59 PM */ #include <stdio.h> #include <stdlib.h> #include <xc.h> // include standard header file // コンフィギュレーション1の設定 #pragma config FOSC = INTOSC // 内部クロックを使用する(INTOSC) #pragma config WDTE = OFF // ウオッチドッグタイマー無し(OFF) #pragma config PWRTE = ON // 電源ONから64ms後にプログラムを開始する(ON) #pragma config MCLRE = ON // 外部リセット信号は使用せずにデジタル入力(RA5)ピンとする(OFF) #pragma config CP = OFF // プログラムメモリーを保護しない(OFF) #pragma config CPD = OFF // データメモリーを保護しない(OFF) #pragma config BOREN = ON // 電源電圧降下常時監視機能ON(ON) #pragma config CLKOUTEN = OFF // CLKOUTピンをRA6ピンで使用する(OFF) #pragma config IESO = OFF // 外部・内部クロックの切替えでの起動はなし(OFF) #pragma config FCMEN = OFF // 外部クロック監視しない(FCMEN_OFF) // コンフィギュレーション2の設定 #pragma config WRT = OFF // Flashメモリーを保護しない(OFF) #pragma config PLLEN = OFF // 動作クロックを32MHzでは動作させない(OFF) #pragma config STVREN = ON // スタックがオーバフローやアンダーフローしたらリセットをする(ON) #pragma config BORV = HI // 電源電圧降下常時監視電圧(2.5V)設定(HI) #pragma config LVP = OFF // 低電圧プログラミング機能使用しない(OFF) #define _XTAL_FREQ 8000000 #define led RB5 //赤外線led #define testled RB1 //テスト用led #define sw RB0 //スイッチ static void pic_init(); //pic初期化 void outl(void); //赤外線Low出力 void outh(void); //赤外線Hi出力 void startbit(void); //スタートビット void stopbit(void); //ストップビット void send_shota(void); //shotaという信号を送る void main(void) { pic_init(); int b=1; //送信監視用 int c=3; //3回繰り返し信号を送信する while(1){ if (sw == 0) { testled=0; led=0; } else { if(c > b){ testled=1; // 押しっぱなしかどうかを判定 if(sw != 0){ b = 0; } // 開始信号を送る startbit(); // データを送信 send_shota(); // 40ms休息 __delay_ms(40); b++; } } } } static void pic_init() { TRISB = 0x01; ANSELB = 0x00; // set all RB to digital pin OSCCON = 0x70; //内部クロック 8 MHz 0111 1010b PORTB = 0x00; WPUB = 0x01; } void startbit(){ outh(); outl(); } //赤外線LED消灯 void outl(){ led=0; __delay_us(598); } //赤外線LED点滅 38kHz void outh(){ for (int i = 0; i < 23; i++) { led=1; __delay_us(13); led=0; __delay_us(13); } } void send_shota(){ // 1110011 outh();outh();outh();outl();outl();outh();outh(); }

受信機側の実装

概要

機能を箇条書きすると以下の機能が実装されています。

  • 赤外線を受信する際に波形から発信元を特定、認証する機能
  • 認証後、発信源から送られてきたモールス信号を判定する機能
  • 認証後、受信状態を知らせるLEDを点灯する機能
  • 判定したモールス信号を事前に定義したチートシートと照合して意味をディスプレイに表示する機能
  • 起動、再起動、赤外線受信中、赤外線受信後、モールス信号意味判定後などのタイミングにディスプレイの表示を切り替える機能
  • 物理的なスイッチを押した時、状態をリセットしてシステムを再起動する機能

備考:秋月電子I2C接続小型LCDモジュールに表示を行うで公開されているライブラリのskI2CLCDlibとskI2Clibを液晶ディスプレイのI2C接続用に利用させて頂きました。

ソースコード

/* * File: main.c * Author: shota * * Created on December 5, 2015, 1:50 PM */ // 'C' source lin e config statements #include <xc.h> #include <string.h> #include "skI2Clib.h" #include "skI2CLCDlib.h" // コンフィギュレーション1の設定 #pragma config FOSC = INTOSC // 内部クロックを使用する(INTOSC) #pragma config WDTE = OFF // ウオッチドッグタイマー無し(OFF) #pragma config PWRTE = ON // 電源ONから64ms後にプログラムを開始する(ON) #pragma config MCLRE = ON // 外部リセット信号は使用せずにデジタル入力(RA5)ピンとする(OFF) #pragma config CP = OFF // プログラムメモリーを保護しない(OFF) #pragma config CPD = OFF // データメモリーを保護しない(OFF) #pragma config BOREN = ON // 電源電圧降下常時監視機能ON(ON) #pragma config CLKOUTEN = OFF // CLKOUTピンをRA6ピンで使用する(OFF) #pragma config IESO = OFF // 外部・内部クロックの切替えでの起動はなし(OFF) #pragma config FCMEN = OFF // 外部クロック監視しない(FCMEN_OFF) // コンフィギュレーション2の設定 #pragma config WRT = OFF // Flashメモリーを保護しない(OFF) #pragma config PLLEN = OFF // 動作クロックを32MHzでは動作させない(OFF) #pragma config STVREN = ON // スタックがオーバフローやアンダーフローしたらリセットをする(ON) #pragma config BORV = HI // 電源電圧降下常時監視電圧(2.5V)設定(HI) #pragma config LVP = OFF // 低電圧プログラミング機能使用しない(OFF) #define detector RB5 // 赤外線感知 #define ledout RA0 // テスト用led駆動 #define ledin RA1 // テスト用led駆動 #define sw RA3 // スイッチ #define _XTAL_FREQ 8000000 #define DATA_NUM 7 // 受信データ量 7bit #define DATA_PERIOD 598 // 受信データの読み取り間隔 598μs default #define MORSE_DATA_NUM 5 // モールス信号の受信データ量 5bit #define MORSE_WORD_MEANING_NUM 17 // モールス信号に対応する文章の登録文字限度数 14 #define MORSE_WORD_MEANING_REGISTRATION_NUM 10 // モールス信号に対応する文章の登録数 //#define MORSE_CHAR_MEANING_REGISTRATION_NUM 10 // モールス信号に対応する文字の登録数 26 #define MORSE_DISPLAY_CHARS_NUM 14 // 液晶に表示する文字数 static void pic_init(); // picの初期化情報を設定する void signal_data_init(); // 受信データを格納する配列を初期化する void morse_data_init(); // モールス信号受信データを格納する配列を初期化する void morse_display_chars_init(); // モールス信号を受けて表示する文字を格納する配列を初期化 void detect_signal(int *detect_signal_result); // 受信データを感知してshotaが送られているかどうか判定してtrueなら1, falseなら0をdetect_signal_resultに代入する void detect_state(int *detect_state_result, int *detect_state_result_count); // 発信状態(ツートン)を検知して押している状態なら1, 離している状態なら0, 終了状態なら2をdetect_state_resultに代入する void detect_state_continue_pressed(int *detect_state_result, int *detect_state_result_count); // 押し続けているかどうかを検知 void detect_state_continue_left(int *detect_state_result, int *detect_state_result_count); // 離し続けているかどうかを検知 void display_morse_input_num(); // 受信したモールス信号のツートンを表示 void display_morse_input(); // 受信したモールス信号を解析した情報を表示 void morse_correspond(); // モールス信号の対応 int morse_data[MORSE_DATA_NUM]; // 受信したモールス信号のツートンを格納する配列 空0, トン1, ツー2 int signal_data[DATA_NUM]; // 赤外線で受信した波形情報を2進数で格納する配列 int shota_data[]={ 1, 1, 1, 0, 0, 1, 1}; //shota文字列の2進数 /** //モールス信号の対応表 アルファベット1文字 typedef struct { int morse_data[MORSE_DATA_NUM]; char meaning; } morse_correspondence_char_t; morse_correspondence_char_t morse_correspondence_chars[MORSE_CHAR_MEANING_REGISTRATION_NUM]; */ char morse_display_chars[MORSE_DISPLAY_CHARS_NUM]; // モールス信号を受けて表示する文字を格納する配列 //モールス信号の対応表 単文章 typedef struct { int morse_data[MORSE_DATA_NUM]; char meaning[MORSE_WORD_MEANING_NUM]; } morse_correspondence_word_t; morse_correspondence_word_t morse_correspondence_words[MORSE_WORD_MEANING_REGISTRATION_NUM]; void interrupt InterFunction( void ) { InterI2C(); } void main(void) { pic_init(); InitI2C_Master(0); LCD_Init(LCD_NOT_ICON,32,LCD_VDD3V,8); LCD_Clear(); LCD_Puts("start") ; ledin = 0; ledout = 0; while(1) { // 信号受信フラグを宣言 int detect_signal_result = 0; // 信号を検知 detect_signal(&detect_signal_result); // 信号を検知したかどうかで場合分け if ( detect_signal_result == 1 ){ ledin = 1; ledout = 1; morse_data_init(); morse_display_chars_init(); int detect_state_result = 1; int detect_state_result_count = 0; LCD_Clear(); LCD_Puts("receiving..."); // on, offなのでデータ量の倍のループ for (int i = 0; i < MORSE_DATA_NUM*2; i++) { if (detect_state_result_count < MORSE_DATA_NUM) { detect_state(&detect_state_result, &detect_state_result_count); } } LCD_Clear(); LCD_Puts("end"); display_morse_input_num(); morse_correspond(); display_morse_input(); } else { if (sw == 1) { morse_display_chars_init(); LCD_Clear(); LCD_Puts("restart") ; ledin = 0; ledout = 0; } } } } static void pic_init() { TRISB = 0xF0; TRISA = 0x08; // RA3のみをインプット ANSELA= 0x00; // set all RA to digital pin ANSELB= 0x00; // set all RB to digital pin PORTB = 0x00; PORTA = 0x00; OSCCON = 0x70; //内部クロック 8 MHz 0111 1010b OPTION_REG = 0b00000000; TRISB = 0b00110010; WPUB = 0b00010010; WPUA = 0b00001000; // RA2にプルアップ抵抗 } void signal_data_init() { for (int i = 0; i < DATA_NUM; i++ ) { signal_data[i] = 0; } } void morse_data_init() { for (int i = 0; i < MORSE_DATA_NUM; i++ ) { morse_data[i] = 0; } } void morse_display_chars_init() { memset(morse_display_chars, '\0', strlen(morse_display_chars)); } void display_morse_input_num(){ LCD_Clear(); LCD_Puts("morse code is"); LCD_SetCursor(0, 1); for (int i = 0; i < MORSE_DATA_NUM; i++) { if (morse_data[i]==1) { LCD_Puts("1"); }else if(morse_data[i]==2){ LCD_Puts("2"); }else{ LCD_Puts("0"); } } __delay_ms(2000); } void display_morse_input(){ LCD_Clear(); //LCD_Puts("this means"); //LCD_SetCursor(0, 1); LCD_Puts(morse_display_chars); __delay_ms(3000); } void detect_signal(int *detect_signal_result) { // データの初期化 signal_data_init(); if ( detector == 0 ){ // 最初の信号分の遅延 __delay_us(DATA_PERIOD*3); // 受信データを読み取り for (int i = 0; i < DATA_NUM; i++ ) { if ( detector == 0 ){ signal_data[i] = 1; __delay_us(DATA_PERIOD); } else { signal_data[i] = 0; __delay_us(DATA_PERIOD); } } // 受信データがshotaかどうか配列の中身を比較 if ( memcmp(signal_data, shota_data, sizeof(int) * DATA_NUM) == 0 ) { *detect_signal_result = 1; }else{ *detect_signal_result = 0; } } else { *detect_signal_result = 0; } } void detect_state(int *detect_state_result, int *detect_state_result_count){ if(*detect_state_result == 0){ // 離している状態なので、終了状態になっているかどうかを判定 detect_state_continue_left(&*detect_state_result, &*detect_state_result_count); }else if(*detect_state_result == 1){ // 押している状態なので、ツーかトンか判定 detect_state_continue_pressed(&*detect_state_result, &*detect_state_result_count); }else{ // 終了状態なのでなにもしない } } // 押し続けているかどうかを検知(ツーかトンか判定) void detect_state_continue_pressed(int *detect_state_result, int *detect_state_result_count){ int detect_state_continue_pressed_count = 0; // 600μsに一度押しているかどうか判定して、押し続けている状態をカウント // 0.5s秒間押している状態が検知できなかった場合終了 // 以下で押し続けている状態のカウントが100以上であるかどうかで条件分岐 // 600μs*830 = 0.5s for (int i = 0; i < 830; i++) { // 600μs __delay_us(DATA_PERIOD); // 押している状態なら、押し判定をカウント if( detector == 0 ){ detect_state_continue_pressed_count++; i = 0; // さらに0.5秒感知 } } *detect_state_result = 0; // 離した状態に変更 // 押し続けている状態のカウントが100以上かどうか判定 if(detect_state_continue_pressed_count > 100){ // 押し続けた際のデータを処理(ツーならば) morse_data[*detect_state_result_count]=2; }else{ // すぐに離した際のデータを処理(トンならば) morse_data[*detect_state_result_count]=1; } *detect_state_result_count = *detect_state_result_count + 1; // カウントを1増加 } // 離し続けているかどうかを検知 void detect_state_continue_left(int *detect_state_result, int *detect_state_result_count){ int detect_state_continue_left_count = 0; // 600μsに一度離しているかどうか判定して、離し続けている状態をカウント // 入力信号が検知された場合または、離し続けている状態のカウントが3320以上(2秒以上離されている状態)で判定終了 // 以下で離し続けている状態のカウントが3200以上であるかどうかで条件分岐 // 600μs*3320 = 2s for (int i = 0; i < 3320; i++) { // 600μs __delay_us(DATA_PERIOD); // 離している状態なら、離し判定をカウント if( detector != 0 ){ detect_state_continue_left_count++; // 離し続けている状態のカウント数で場合分け if ( detect_state_continue_left_count > 3320) { i = 3320; // 終了 *detect_state_result = 2; // 終了状態に変更 }else{ i = 0; } // 押した状態を感知したらループ終了 }else{ i = 3320; // 終了 *detect_state_result = 1; // 押した状態に変更 } } } void morse_correspond(){ /** for (int i = 0; i < MORSE_CHAR_MEANING_REGISTRATION_NUM; i++) { // モールス信号からcharを推定 if ( memcmp(morse_data, morse_correspondence_chars[i].morse_data, sizeof(int) * MORSE_DATA_NUM) == 0 ) { } } **/ for (int i = 0; i < MORSE_WORD_MEANING_REGISTRATION_NUM; i++) { // モールス信号から文章を推定 if ( memcmp(morse_data, morse_correspondence_words[i].morse_data, sizeof(int) * MORSE_DATA_NUM) == 0 ) { for (int j = 0; j < MORSE_WORD_MEANING_NUM; j++) { // 表示用の配列に文字を代入 morse_display_chars[j] = morse_correspondence_words[i].meaning[j]; } } } } morse_correspondence_word_t morse_correspondence_words[MORSE_WORD_MEANING_REGISTRATION_NUM] = { {{1, 1, 1, 1, 1}, "plz help"}, {{1, 1, 1, 1, 2}, "look slack"}, {{1, 1, 1, 2, 1}, "go lunch"}, {{1, 1, 1, 2, 2}, "go dinner"}, {{1, 1, 2, 1, 1}, "go negishi"}, {{1, 1, 2, 1, 2}, "hurry up"}, {{1, 1, 2, 1, 2}, "this is demo"}, {{1, 1, 2, 2, 1}, "thx for watching"}, {{1, 1, 2, 2, 2}, "love you all"}, {{1, 2, 1, 1, 1}, "lgtm"} }; /** pic容量的に使えなかった morse_correspondence_char_t morse_correspondence_chars[MORSE_CHAR_MEANING_REGISTRATION_NUM] = { {{1, 2, 0, 0, 0}, 'a'}, {{2, 1, 1, 1, 0}, 'b'}, {{2, 1, 2, 1, 0}, 'c'}, {{2, 1, 1, 0, 0}, 'd'}, {{1, 0, 0, 0, 0}, 'e'}, {{1, 1, 2, 1, 0}, 'f'}, {{2, 2, 1, 0, 0}, 'g'}, {{1, 1, 1, 1, 0}, 'h'}, {{1, 1, 0, 0, 0}, 'i'}, {{1, 2, 2, 2, 0}, 'j'}, {{2, 1, 2, 0, 0}, 'k'}, {{1, 2, 1, 1, 0}, 'l'}, {{2, 2, 0, 0, 0}, 'm'}, {{2, 1, 0, 0, 0}, 'n'}, {{2, 2, 2, 0, 0}, 'o'}, {{1, 2, 2, 1, 0}, 'p'}, {{2, 2, 1, 2, 0}, 'q'}, {{1, 2, 1, 0, 0}, 'r'}, {{1, 1, 1, 0, 0}, 's'}, {{2, 0, 0, 0, 0}, 't'}, {{1, 1, 2, 0, 0}, 'u'}, {{1, 1, 1, 2, 0}, 'v'}, {{1, 2, 2, 0, 0}, 'w'}, {{2, 1, 1, 2, 0}, 'x'}, {{2, 1, 2, 2, 0}, 'y'}, {{2, 2, 1, 1, 0}, 'z'} }; **/

開発全体を振り返って

よかったこと

  • 安めの液晶を買えば送受信機セットで800円くらいで自作できる(液晶がボトルネック…)。
  • モールス信号を送るのが楽しい。
  • 高校物理で勉強した回路理論的な知識が役に立つ日がきた。
  • 組み込みシステムに対して小さな知見が得られた。

課題

  • 赤外線に与える電圧をもっと強化しないと電波強度に不安がある(3mくらいの距離にしか送れない)。
  • 赤外線の送受信の判定周期におそらく誤差があり精度が悪い(発振器などを使って精度を上げる必要がある)。
  • メモリ確保のなどの工夫が足りない。全アルファベット等を登録して自由にメッセージを送れるようにしたかったが(おそらく)PICの容量的な問題で登録できず妥協してしまった。
  • 送信機と受信機を分けたこと。送受信による対話やペアリング機能も実装したかった。
  • C言語もっと綺麗に書きたかった。
  • モールス信号を覚えるまでチートシートがないと辛い。

まとめ

PICはインターネットで調べても古い情報しか出てこない上に型番で仕様が違ったり辛いのに対して、最近はRaspberry PiやArduinoなどで便利にデバイスを動かすことができるので明らかに敷居が高いと感じました。しかしそんな中でも一個300円と格段に低価格(1/10以下)なPICは気軽にデバイスが作れる夢がありこちらで実装してみました。やってみると以外と難しくなかったので、今後はWebという視点にとどまらず三次元的なユーザー体験を作っていくような挑戦をしてみたいと思いました。ここまで読んでいただいて本当にありがとうございました。

脚注

脚注
1 初めは信号を受信するとピカピカ光りながらバイブしてブザーを鳴らすデバイスを作りましたが迷惑だったので、エンジニアが普段慣れているものを開発して違和感をなくすように調整しました。