2011/12/20

小型液晶ディスプレイ e-DispをArduinoで使う

MTM07に行った際、桜パーツという北海道のお店が出店していて、
シリアル通信で制御できる液晶ディスプレイが売っていました。

e-Disp3
値段は4,500円とホイホイ買える値段ではないのですが、1000円ぐらいで売っているLCDディスプレイよりもかなり多くの情報量を出せそうなこと、カラー表示が可能なこと、SDカードもおまけで付いてくる、ということなので買ってしまいました。

ソフトウェアマニュアルなどを読んだところ、
  • 日本語が表示できる(文字コードはUTF-8,SJIS,EUCJPに対応)
  • フォントサイズを3段階で切り替え可能(1画面内で異なるフォントサイズは不可)
  • 文字色/背景色は黒、白、青、赤、黄色、緑、水色、紫から選択可能
  • マイクロSDカードにあらかじめ保存しておいた画像を、ファイル名を指定して表示することができる(320x240サイズにリサイズしておいた方がよい)
  • 背景バッファは4面分ある。それぞれに画像を読み込むことも可能
  • 長方形、楕円、ドット、1ピクセル太さの線を描画できる
ということがわかりました。
文字の色などを制御するには”エスケープシーケンス”と呼ばれる特殊コードをそれぞれ入力する必要がありますが、エスケープシーケンスを毎回出力するのは面倒なので、Arduino用のライブラリを作ってみました。

ソフトウェアマニュアルに記載されているエスケープシーケンスの8割ぐらいはカバーしているので、ある程度の役には立つと思います。

ライブラリは別館に保存してますので、必要な方はご自由にどうぞ。

ではまた。

2011/12/15

ArduinoでGPSモジュールのNMEAメッセージを解析する

ママチャリグランプリ用に購入したGPSモジュール(GT-723F)ですが、生のデータは数字の羅列だけなので、人間が読むには厳しい状態となっています。また、いろいろな種類の行が出てくるので、必要なところだけフィルタリングする必要があります。
そこで、ArduinoでGPSモジュールの出力を解析してみました。

NMEAデータにはいろいろな行がありますが、時間と緯度・経度の情報を含んでいるGPGGA行のみ取り出して処理することにしました。
カンマ区切りなのでプログラム自体はそんなに難しくはなかったのですが、肝心の緯度・経度が現在地と大幅に違うところを指していました。

調べてみたところ、google mapに表示するためには座標を少し変換してあげなければならないようです。
下記ページを元に座標変換を行うと、見事に現在地がgoogle mapに表示されました。

GT-730F/LとNMEAデータから得られる経度緯度

次はXBee Proの通信距離の実験です。
土曜日晴れたら見通しのよい公園などで実験してみたいと思います。




/*
 GT-723から取り出したNMEAデータを解析するプログラム
 緯度・経度を取り出します。
 arduino用。
 2011-12 lhaplus 8888
 This code is in the public domain.
 */

//シリアル接続開始してバッファ初期化
void setup()
{
  Serial.begin(9600);
  ClearBuffer;
}

//バッファなど
const int arrayLength=128;  //max buffer size.
char gpsRecvArray[arrayLength];

//GPGGAフィールド定義
const byte C_UTC=1;  //UTC時間フィールド
const byte C_LATITUDE=2;//緯度
const byte C_LATITUDE_HEMISPHERE=3;//北緯・南緯フィールド
const byte C_LONGITUDE=4;
const byte C_LONGITUDE_HEMISPHERE=5;
const byte C_QUALITY=6;  //品質
const byte C_NUMOFSAT=7;//衛星の数
const byte C_PRECISION=8;//精度?
const byte C_ANTENNA_HEIGHT=9;
const byte C_GEOIDAL_HEIGHT=11;
const byte C_NORTH='N';
const byte C_SOUTH='S';
const byte C_EAST='E';
const byte C_WEST='W';
int indexLoc=0;
 
void loop() 
{
  char recvChar=0;//受信バッファ
  while(Serial.available())
  {
    recvChar=Serial.read();
    //check first $ mark.
   if((36==recvChar))
    {
      //start gps log line.
      //clear all buffer.
      ClearBuffer();
      indexLoc=0;
    }

    if(10==recvChar)
    {
      Serial.println("Receive LF");
      //check gps log buffer.
      CheckGPS();
      break;
    }
    else
    {
      //copy to buffer
      gpsRecvArray[indexLoc]=recvChar;
      indexLoc++;
      //限界超えたらバッファクリア
      if(indexLoc>=arrayLength)ClearBuffer();

    }
  }  

  delay(10);

}

//受信バッファのクリア
void ClearBuffer()
{
  memset(gpsRecvArray,0,sizeof(gpsRecvArray));
  Serial.println("Buffer cleared.");
}


//check GPS char.
void CheckGPS()
{
  /*  
   GT-723FのNMEA出力の中で、緯度・経度に関係しているのはGPGGAの行。
   フォーマット: $GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>,*<13><CR><LF> 
    意味:
       1  104549.04  UTC time in hhmmss.ss format, 000000.00 ~ 235959.99 
       2  2447.2038  Latitude(緯度)  ddmm.mmmm format 読み取り用の0が入れられる場合もあり
       3  N  Latitude hemisphere indicator, 窶朦窶・= North, 窶朶窶・= South 
       4  12100.4990  Longitude(経度) dddmm.mmmm format 読み取り用の0が入れられる場合もあり
       5  E  Longitude hemisphere indicator, 'E' = East, 'W' = West 
       6  1  Position fix quality indicator 
           0: position fix unavailable 
           1: valid position fix, SPS mode  
           2: valid position fix, differential GPS mode 
       7  06  Number of satellites in use, 00 ~ 12 
       8  01.7  Horizontal dilution of precision, 00.0 ~ 99.9 
       9  00078.8  Antenna height above/below mean sea level, -9999.9 ~ 17999.9     
       10  0016.3  Geoidal height, -999.9 ~ 9999.9 
       11  Age of DGPS data since last valid RTCM transmission in xxx format (seconds) NULL when DGPS not used 
       12  Differential reference station ID, 0000 ~ 1023 NULL when DGPS not used 
       13  5C  Checksum
       //時刻、緯度、経度を取ってくるには、1,2,4のフィールドを回収すればOK
   */

  //まずはGPGGAの行かをチェック。
  if(('G'==gpsRecvArray[4])&&('A'==gpsRecvArray[5]))
  {

      Serial.println("GPGGA DATA.Start process");

    //受信バッファを1個づつ探していく
    int charIndex=0;
    int colNumber=0;//列番号。取説にあわせて1から始める
    int colCharIdx=0;//対象列の文字配列のインデックス

    int i=0;
    //フィールドごとに区切っていく
    //とりあえず受信バッファ 20文字。
    char buff[20];
    memset(buff,0,sizeof(buff));


    double degreeLAT;
    double degreeLONG;
    char c=0;    
    for(charIndex=0;charIndex<arrayLength;charIndex++)
    {
      c=gpsRecvArray[charIndex];
      //NULL文字にあたったら終了
      if(0==c) break;

      if(44==c)//区切り文字の カンマ
      {
        //列切り替え
        //切り替える前に、今の行バッファを出力しておく

        switch(colNumber)
        {
        case C_UTC://UTC時間フィールド
          Serial.print("UTC:");
          Serial.println(buff);
          break;  
        case C_LATITUDE://緯度
          Serial.print("LAT:");
          degreeLAT=DMmtoDegree(buff,sizeof(buff));
          Serial.println(degreeLAT,8);
          break;
        case C_LATITUDE_HEMISPHERE://北緯・南緯フィールド
          Serial.print("N/S:");
          break;
        case  C_LONGITUDE:
          Serial.print("LONG:");
          degreeLONG=DMmtoDegree(buff,sizeof(buff));
          Serial.println(degreeLONG,8);
          break;
        case C_NUMOFSAT://衛星の数
          Serial.print("Num of SAT:");
          Serial.println(buff);
          break;


          default:break;
        }
        colNumber++;
        colCharIdx=0;
        memset(buff,0,sizeof(buff));
        //次の行へ切り替える。文字インデックスも初期化
      }
      else
      {
        buff[colCharIdx]=c;
        colCharIdx++;
      }
    }
  }
  else
  {
         Serial.println("NOT GPGGA DATA");
         Serial.println(gpsRecvArray);
         
  }
}


//GT-723はDMM形式というものになっている模様。
//GoogleMapに表示するにはDegree形式にしなければならないようです。
//参考:http://gps.meblog.biz/article/2761601.html
//Charの配列から、degree表示に変換する。
//2447.2038 とか
//12100.4990 の形式。
//Dの部分が3桁か2桁、
//Mの部分は2桁固定、
//mの部分は4桁固定。
double DMmtoDegree(const char* buff,const int bufflen)
{
  int d=0;
  int m=0;
  int s=0;

  double tmp=0;
  int i;
  int period;
  char atoibuff[4];
  double value=0;

  period=-1;
  for (i=0;i<bufflen;i++)
  {
    //まずはピリオドを探す。
    if('.'==buff[i])
      period=i;
  }

  if(period<0)
  {  
    return value;
  }
  else
  {
    //ピリオドの場所がわかったので、コピーしてく
    //dの部分。
    memset(atoibuff,0,sizeof(atoibuff));
    for(i=0;i<period-2;i++)
    {
      atoibuff[i]=buff[i];   
    }
    d=atoi(atoibuff);

    //mの部分。
    memset(atoibuff,0,sizeof(atoibuff));
    for(i=0;i<4;i++)
    {
      if(buff[period-2+i]=='.') break;
      atoibuff[i]=buff[period-2+i];
    }
    m=atoi(atoibuff);
    //Serial.println(*m);
    memset(atoibuff,0,sizeof(atoibuff));
    for(i=0;i<4;i++)
    {
      atoibuff[i]=buff[period+1+i];
    }
    s=atoi(atoibuff);
    value=(double)d;
    value=value+(double)m/60;
   //小数点部分。  
   tmp=(double)s/(double)60;
   tmp=tmp*0.0001;
   value=value+tmp;
  } 
  return value;
}

ではまた。

2011/12/10

ママチャリグランプリ用GPSシステムの構築

来年1月の上旬に、スーパーママチャリグランプリが富士スピードウェイで開催されます。
今年も友人たちと参加する予定です。

レース中は1〜2周おきにドライバーを交代するのですが、ドライバーがどこを走っているかわからないため、交代準備などの時間がなかなか読めません。ピットロードは混雑しているので、なるべくピットロードでの待ち時間を減らしたいところです。
去年はAndroid搭載の携帯を2個使って場所把握を行いそこそこ役に立ったのですが、電波状態が安定せず、現在地を受信できないこともありました。

そこで今年は携帯電話を使用するのではなく、XBee Proを使って通信を行うことにしました。
XBee Proの見通し通信距離は1500m程あるので、レーシングコース全体はカバーできる計算です。

今回のママチャリグランプリ用のGPSシステムのハードウェア構成は以下のとおりです。

送信側

  • Xbee Pro ZB (End device構成)
  • GPSモジュール(GT-723F)
  • DC-DCコンバータ CE-1004-TP (3.3V出力設定)
  • OpenLog+2GB MicroSDカード
  • Eneloop 6本直列


受信側

  • Arduino
  • XBee Pro ZB (Coodinator構成)
  • レースコース上にLEDを配置した基板
  • モバイルブースターなどの5V電源

システムの概要

  • GPSモジュールの出力をXBeeのVInとOpenLogのRxIに接続。
  • 自転車側XBeeからピット側XBeeに向けて、GPSモジュールの出力(NMEA形式)をそのまま送信。
  • ピット側のXBeeにつながっているArduinoがNMEAを解釈し、自転車の位置に一番近いLEDを光らせる。
  • 大体の場所がわかるので、ピットでのんびりできる


GPSモジュールの予備実験を家の周りでしたところ、10~20mぐらいの誤差内で捕捉できることを確認できました。GPSモジュールはOK。
OpenLogの実験もやってみたところ、GPSモジュールの生データを10分間記録するとデータ量が大体300kBだったので、電池が切れなければ2GBのMicroSDに1000時間ぐらい記録できそうです。なのでOpenLogもOK。

残る予備実験はXBeeの長距離通信と駆動時間の実験です。

2011/12/07

XBee Product Manualのまとめ

先週末にあったMTM07でXBee Pro ZB(Programmableじゃないほう)と変換基盤を買ってきました。
Arduinoとつなげて遊ぶにも資料が無いと厳しいので、Digi.comから「Product Manual: XBee / XBee-PRO ZB RF Modules」をダウンロードして読んでみました。

XBee Product Manualのまとめ

ハードウェア
 Xbee/XBee-PRO(日本用) の比較
通信距離(屋内/壁有り) 40m/60m
通信距離(見通し)   120m/1500m
出力   2mW/10mW
通信速度  最大1Mbps/最大1Mbps
電源電圧 2.1~3.6V/3.0~3.4V
値段  1,700円/3,000円

モードについて
透過モード
デフォルトのモード。シリアルケーブルと等価のものとして動作します。DINに入ってきたデータを電波で送信します。また、受信下データはDOUTに出力されます。

APIモード
 
TCP/IPのように使えるモード。複数の送信先を切り替えたり、受信したデータの送信元を判断したいときに使用します。

デバイスタイプについて
Coordinator
ZigBeeネットワークの親玉的な役割をします。一つのネットワークに、必ずCoodinatorが一つ必要です。PCでいうとDHCPサーバ?
PAN ID(ネットワークの識別番号)を指定します。
RouterとEnd devicesをネットワークに参加することを許可することができます。
データのルーティングを補助することができます。
Sleepモードは使用できません

Router
まずはZigBeeネットワークに参加する必要があります。
ネットワーク参加後は、RouterやEnd devicesをネットワークに参加させることができます。
データのルーティングを補助することができます。
Sleepモードは使用できません。

End device
まずはZigBeeネットワークに参加する必要があります。
ZigBeeネットワークの一番外側にいるデバイスとなります。
親デバイス(Coordinator/Router)との間でのみ通信することができます。
データのルーティングは行えません。
低消費電力モードが使えます。

上記のデバイスタイプを切り替えるには、X-CTUというWindows用のソフトを使用し、対象のファームウェアを書き込む必要があります。なので遅くてもいいのでWindows機が必要となります。

その他
PAN ID:ネットワークの識別番号。64ビットまたは16ビットの識別番号が使えます。PANIDが同じものは同じネットワークに所属します。16ビットでも6万5千通りのPAN IDが使えますが、64ビットを使用したほうがPAN IDが重複しにくいため、64ビットを使うことが推奨されます。
64ビットデバイスアドレス:XBee1つずつに固有のアドレス。PCのMACアドレスに相当します。
16ビットデバイスアドレス:ZigBeeネットワーク内のアドレス。PCだとIPアドレスに相当します。

ATコマンド 使いそうなやつ一覧
ATDH:Destination Address High. 宛先アドレス(64ビット)の上位32ビット。
ATDL:宛先アドレスの下位32ビット。ATDHとATDLの組み合わせには、2つの特殊なアドレスがあります。(0x0000->Coordinatior, 0xFFFF->BroadCast)
ATMY:16ビットネットワークアドレス。現在参加しているZigBeeネットワーク内でのアドレスです。特殊なアドレスは0xFFFEで、この場合はどのネットワークにも参加していないことになります。
ATMP:16ビット親ネットワークアドレス。親デバイスのZigBeeネットワークアドレスを指します。0xFFFEの場合は、親がないことを意味します。
ATNC:Number of Remaining Children。あと何台の子デバイスをネットワークに追加できるかを示します。0が返ってくると、これ以上追加することができません。読み取り専用。
ATSH:Serial Number High。 端末のシリアルナンバーの上位32ビット。読み取り専用
ATSL:Serial Number Low。端末のシリアルナンバー下位32ビット。読み取り専用。
ATNI:Node Identifier。ノードの名前。ASCIIコードで20文字まで設定可能。
ATCH:Operating Channel。通信で使用しているチャンネル番号を表します。読み取り専用。
ATID:Extended PAN ID。 64ビットのPAN ID。0を設定したら、CoordinatorがランダムなIDを使用します。
ATOP:Operating Extended PAN ID。現在使用中のPAN IDを表します。読み取り専用。IDが1以上であれば、IDと同じになります。
ATOI:Operating 16-bit PAN ID。16ビットの PAN IDを返します。
ATPL:Power Level。電波強度を変更することができます。ただし、日本国内で使えるバージョンでは変更できません。
ATDB:Received Signal Strength。受信した信号の強度を出力します(最後のHopのみ評価対象)
ATAP:API Enabe。 APIモードを有効にします。
ATAO:API Options。 APIモードのオプションを変更/確認します。
ATBD:Interface Data Rate。 シリアルポートの通信速度を設定します。デフォルトは9600bps。
ATNB:Serial Parity。 シリアル通信のパリティを変更/確認します。デフォルトはパリティなし。
ATSB:Stop Bits。 シリアル通信のストップビットを変更/確認します。デフォルトは1ストップビット
ATV+:Voltage Supply Monitoring。供給電圧を読み取ります。
ATTP:Module temperature。精度+-7℃で端末の温度を読み取ることができます。
ATVR:Firmware version。ファームウェアのバージョンを表示します。
ATHV:Hardware version。モジュールのバージョンを表示します。
ATCT:Command mode timeout。コマンドモードになっているときに、何も入力がなかったらIdlモードに戻る時間。100ms単位。デフォルトは100(=10秒)
ATCN:Exit Command mode。コマンドモードを終了します。

ATGT:Guard Times. ATコマンドモードに入るためのガードタイムを確認/変更します。
ATCC:Command Sequence Character。コマンドモードに入るための文字を指定します。デフォルトは'+'。
ATSM:Sleep Mode。スリープモードの設定を確認/変更できます。
ATAC:Apply Changes。ATコマンドモードで設定した変更内容をすぐに反映させます。
ATWR:Write。不揮発性メモリに設定を書き込みます。ATWRコマンドを送った後は、'OK'が返ってくるまでなにも文字を送らないほうがよいです。
ATRE:Restore Defaults。設定を工場出荷状態に戻します。
ATFR:Software Reset。モジュールをリセットします。'OK'が直ちに返り、2秒程度で端末がリセットされます。
ATNR:Network Reset。PANのネットワークパラメータをリセットします。 ATNR0でコマンドを送っている端末のみ、ATNR 1でネットワーク全体に対してリセットをPAN内にブロードキャストします。
ATSI:Sleep Immediately。このコマンドを発行すると、End deviceは直ちにスリープモードに入ります。


2011/12/01

コールバック関数のテスト

特定のタイミングで何らかの処理を行わせたいときに使用する、コールバック関数のテストコードを書いてみました。
コールバック関数の形式にしておけば、渡す関数を変えるだけで異なる処理ができるようになります。
pthreadの作成時や、signal関数を使うときも、このコールバック関数の方式がとられています。

//
//コールバック関数のテスト
//

#include <stdio.h>


void *callBackFunction(void *data)
{
        printf("コールバック関数が呼ばれました。\n何かの処理を行います。\n");
}

//コールバック関数を引数とする関数。
void somefunc(void* (*callbackFunc)(void*))
{
        printf("some process...\n");
        printf("some process2...\n");
    
        //コールバック関数を呼ぶ。
        callbackFunc(NULL);
    
        printf("finish somefunc\n");
}


int main(void)
{
        somefunc(callBackFunction);
        return -1;
}


実行例:

iMac:~ lhaplus8888$ ./a.out
some process...
some process2...
コールバック関数が呼ばれました。
何かの処理を行います。
finish somefunc
iMac:~ lhaplus8888 $