2013年05月12日

Raspberry Pi+GPSで現在地図+ストリートビューを表示する車載機器を作ってみる

GWで実家に帰省してて時間もあるというわけで、Raspberry Piで、非Xで、Raspberry Piならではのアプリケーションを実現したい、と思っていたら、Raspbian "wheezy"の/opt/vc/以下にRaspberry Piのハードを活用できる各種サンプルコードが入っていました。主に、OpenMAXを叩いてどうこう、みたいなのが多いようです。

このコードをベースに、VideoCoreのハードを叩いて何か作ろう、と思いました。

さて、そのネタとして、たまたま僕が普段乗っている軽トラには非常に地図データが古いカロッツェリアのカーナビを搭載してまして、最近はスマホでナビしている関係で全然活用してませんでした。
このナビにはコンポジットビデオ入力がついているので、そこにRaspberry Piを接続し、たまたま手元にあったGPSモジュール(ストロベリー・リナックスで販売しているもの)を使用して、現在地のGoogle MapとStreet View表示するシステム MapTracer を作ってみることにしました。

/opt/vc以下にハードのデコーダーを使用してデコードのみ行なっているとおもわれるサンプルコード(hello_jpeg)があったので、これを参考にハードでコードしてフレームバッファ展開しようと思いましたが、結局ソフトデコードで全然問題なさそうでしたので、結局/opt/vc以下のコードベースのハードデコードは使ってません。


軽トラに取り付けた様子

IMG_20130504_183859resized.jpg
雨降ってます。笑
IMG_20130504_182919resized.jpg

IMG_20130504_182913resized.jpg

なんかー、若干(?)設置イメージがあやしいです。笑

MapTracerの構成
構成は以下のようにしました。
maptracer_image.png

maptracer_stack.png
GPSからのデータはRS-232Cで受信します。RS-232C-USB変換ケーブルを使用しました。Raspberry Piのピンヘッダ上のUART(/dev/ttyAMA0)でも通信出来るでしょう。
このGPSのデータ形式は多くのGPSモジュールで採用されているNMEA0183のタイプですので、オープンソースのNMEA Libraryを使用させていただき、パースすることにします。

http://nmea.sourceforge.net/

パースした結果の位置情報から、地図のデータを取得するのに、Google APIの「Static Maps API」と「Street View Image API」を使用します。Google APIは以下のサイトに入って有効にしておきます。
https://code.google.com/apis/console/

enable_api_map_and_stview.png
APIを使用したアクセスにはAPIキーが必要なのですが、それは上記Google APIのサイト内「API Access」にて取得できます。

APIのやりとりにhttpのリクエスト・レスポンスを使用します。その動作の実現にlibcurlを使用します。

GUIの描画及びキーイベント等のハンドリングにはSDL(Simple DirectMedia Layer)を使用します。
SDLの構造はこんな感じです(WikipediaのSDLライブラリの項目より)
sdl_architecture.png
SDLを使用して作成したコードは複数のプラットフォーム間で一定の互換性を持って使用できるようです。今回Linuxの非Xで構築しようとしていましたが、同じバイナリをXから起動するとマルチウィンドウでそのままうまく動作してくれました。

今回は静止画データを扱うので、SDLとその補助ライブラリであります「SDL_image」を使用しました。

実行環境の整備と、ソースダウンロード+ビルド方法

今回使用するRaspberry PiのディストリビューションはRaspbian "wheezy"の2013/2/9のもの(2013-02-09-wheezy-raspbian.zip)です。
これをRaspberry Piで起動し、SDL、libcurl等のセットアップを行っていきます。
$ sudo apt-get update
$ sudo apt-get install curl libcurl4-openssl-dev libsdl-dev
ついでに、今回使用するイーモバイルの3Gドングル D31HW用にwvdialとusb-modeswitchもインストールします。
$ sudo apt-get install usb-modeswitch
ここでudevのキックするか再起動する
$ sudo apt-get install wvdial
★ここでGPSモジュールがボードに繋がっていると、wvdialが実施する初期設定から戻ってこなくなので注意。
また、SDL_Imageの共有ライブラリのシンボリックリンクを貼っておきます(何故か張ってなかったので)。
$ cd /usr/lib/arm-linux-gnueabihf/
$ sudo ln -s libSDL_image-1.2.so.0 libSDL_image.so

MapTracerのコード(+NMEA Library)はgithubに上げてあります。
https://github.com/yishii/MapTracer

ソースコードを取得される場合には、
$ git clone https://github.com/yishii/MapTracer.git
とします。(RPiでも、gitがセットアップしてあれば(sudo apt-get install git)そのまま取得出来ます)

ここで、handle_googleapi_key.hに取得したAPI KEYを設定して下さい。

#ifndef __HANDLE_GOOGLEAPI_KEY_H__
#define __HANDLE_GOOGLEAPI_KEY_H__

#define GOOGLE_API_KEY "SET_YOUR_OWN_API_KEY_TO_HERE!" // ★ここにAPIキーをセットする!

#endif

ビルドは、
$ cd ./src
$ make
とするだけです。ビルドに成功すると、「maptracer」というプロセスが生成されています。

起動と操作方法
いくつかのオプションで動作を指定出来ます。-p オプションは必須です。
maptracer [option]
-p [tty_device] : GPSを接続したポートを指定する。例)-p /dev/ttyUSB2
-c : コンポジット出力対応に画面解像度を落とす
-d : デモモード(GPSが何を言おうと大阪駅前を指す)

また、操作ですが、キーボードで行います。
[1] : Google Mapモードへ
[2] : Google Street Viewモードへ
[0] : アプリ終了
出来れば、画面の左右にストリートビューとマップを同時表示する画面作ったほうがよかったですね。そういえば。。

IMG_20130512_202227.jpg

GPSと3Gドングルのデバイス名の固定化
今回使用したイーモバイルの3Gドングル D31HWは/dev/ttyUSB*として認識します。
GPSモジュールはUSBシリアル変換ケーブル(PL2303を使用したもの)を使用したのですが、これも/dev/ttyUSB*として認識します。
認識した順序でデバイス名が割り振られるので、番号が変化するのは固定的にデバイス名を指定して起動したい場合には不都合です。
そこで、3Gドングルは/dev/ttyUSB_3G、GPSモジュール(PL2303のUSB)は/dev/ttyUSB_GPSとして認識するようにしてみます。
ここでudevルールファイルを設定します。以下のようなファイルを用意しました。
これを例えば「/etc/udev/rules.d/45-local.rules」というファイル名として保存します。
# udev rules
ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", NAME="ttyUSB_GPS"
SUBSYSTEM=="tty", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="143a", ATTRS{devnum}=="5", SYMLINK="ttyUSB_3G"
Raspberry Pi電源投入後自動で3G接続しアプリ起動するようにする
/etc/rc.localの最後の方に以下のように記載しました。maptracerのパス等はカスタマイズして下さい。
/usr/bin/wvdial &
sleep 20
/home/pi/MapTracer/src/maptracer -p /dev/ttyUSB_GPS


作ってみて
地図はきちんと追従してくれて楽しいです。特に、ストリートビューが楽しいかも、って思いました。ただ、自分が運転してると危なっかしくて画面見てられません。笑

助手席で見たいです。(出来れば)
また、GPSモジュールですが、今回使用したものは1Hz出力のものです。10Hz出力のものだとかなり細かく動いてもっと楽しいかもしれません。

ギモン
Raspberry PiのHDMI出力での使用時には問題ないのですが、コンポジット出力時はSDL_SetVideoMode関数の第二引数(画面の高さ)を400程度にしておかないとAPI内で落ちてしまうようでした。ひとまずコンポジット出力の時は400にするようにしています。別にコンポジット出力の解像度にバインドされている と思ってなかったのですが、そういうもんなのでしょーか?

コードの引用
bool gui_open(bool cvbs,bool no_gui)
{
    if (SDL_Init(SDL_INIT_VIDEO) < 0){
        printf("SDL_Init failed (%s)\n",SDL_GetError());
        return false;
    }
    cvbs_resolution = cvbs;
    no_gui_mode = no_gui;

    SDL_VideoInfo* vinfo = SDL_GetVideoInfo();

    if(cvbs_resolution == false){
      screen = SDL_SetVideoMode(600,
                                600,
                                16,
                                SDL_HWSURFACE);
    } else {
      screen = SDL_SetVideoMode(600,
                                400, // ★ここ
                                16,
                                SDL_HWSURFACE);
    }
GROVE - GPS
GROVE - GPS
posted with amazlet at 13.05.12
スイッチサイエンス
Raspberry Piユーザーガイド
Eben Upton Gareth Halfacree
インプレスジャパン
売り上げランキング: 1,150
Raspberry Pi Type B 512MB
Raspberry Pi Type B 512MB
posted with amazlet at 13.05.12
RS Components Ltd
売り上げランキング: 1,093
posted by いしいっち at 12:03| Comment(7) | TrackBack(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2013年04月28日

Raspberry PiでI/O拡張&Arduinoシールドを使おう〜firmata+Ruby編

Raspberry Piでいろいろと制御を楽しむ時に、よりたくさんのI/Oを叩きたいとか、サーボを手軽に動かしたくなる場合があるかもしれません。また、Arduinoシールドを既に持っている場合、これを活用したいという場合もあるでしょう。
Raspberry PiはそれほどはI/Oが充実していませんしね〜。
以下は回路図のピンヘッダのところ。
RPi_PIN_sch.png
回路図にありますように、SPI、I2Cが出てるので、ポートエキスパンダを接続するという手は使えます。
ポートエキスパンダは、例えばこんなのとか。
http://akizukidenshi.com/catalog/g/gI-03708/

とはいえPWMやりたい、とかAD値を取りたいとか、なんてことを手軽な工作で実現するには手間がかかりそうです。


本記事では、上記を解決するアプローチの1つとして、firmataのスケッチを書き込んだArduinoマイコンボードをRaspberry PiのUSBポートに接続し、Raspberry Pi側からArduinoを手足のように使って制御を行なってみる内容になっています。
そんなわけで、ArduinoシールドもRaspberry Piから制御できるようになります。いろいろお持ちの方は資産活用できるかもしれませんね

firmataは、元々PC用のJavaベースの開発環境Processingで、PCに接続したArduinoマイコンボードを制御してフィジカルコンピューティングするものです(と理解してます)。

そこで、firmataのプロトコル http://firmata.org/wiki/Protocol をRaspberryPi側に喋らせる事で、Raspberry Piで手軽にArduinoマイコンボードを制御できるようにする準備をしていきたいと思います。
今回、Raspberry Pi側で使用する言語はRubyにしました。これでRaspberry Pi上のスクリプト言語で、お手軽にスケッチ相当のコードを書いて、フィジカルコンピューティング出来ますね!(たぶん)

IMG_20130428_030933resized.jpg
さて、今回はArduinoマイコンボードとして最もスタンダードなものの1つである、Arduino Unoを使用しました。
最近スイッチサイエンスさんではこのボード、永久保証されてるようで話題になりました。
【永久保証付き】Arduino Uno
スイッチサイエンス
売り上げランキング: 3,309

Arduinoマイコンボード側の準備

では、これに、Standard Firmataのスケッチを焼きこんでおきましょう。サンプルに用意されています。
ss_firmata.png

Raspberry Pi側の準備
Raspberry Pi側のディストリビューションはRaspbian Wheezyを使用します。その他のLinuxディストリビューションでも多分実施可能かとは思います。Arduno Unoを認識させる為には、USB CDC ACMデバイスを認識できるようにする必要がありますが、Raspbian Wheezyでは標準で認識するようになってます。

●Rubyのセットアップ
今回はRubyとRubyでシリアルポートを制御するruby-serialportを使用します。また、ruby-serialportをセットアップするのにRubygemを使用しますので、以下のようにセットアップするとOKです。

$ sudo apt-get install ruby
$ sudo apt-get install ruby-dev
$ sudo apt-get install rubygems
$ sudo gem install ruby-serialport

ruby-devは、gemでモジュールをインストールする際に必要になる関係でセットアップしています。

では、ライブラリを準備します。

patcoll氏のruby-firmataをベースに、現在のfirmataで使用できるように改造し、改変や機能追加したものを私のgithubで公開しています。

当初そのまま使えるかなと思ってたのですが、ruby-firmataの開発が2年ほど止まっているようで、その間のfirmataの拡張などもあったためでしょうか、そのままでは動作させられませんでした。
改変版は以下にあります。
https://github.com/yishii/ruby-firmata
これを取得します。
下記のようにgitで取得出来ますし、gitを使わない場合、「zip_icon_github.png」アイコンをクリックするとZip形式でダウンロード出来ます。
git clone https://github.com/yishii/ruby-firmata
それではサンプルコードを用意します。以下のコードは、上述のリポジトリ内にあります、test.rbとなってます。
runメソッドにクラスを渡すとsetupとloopを呼び出すArduino IDEライクなスタイルです。直接Arduinoクラスメソッド呼び出しでの制御もできますので、お好みで。

digital_readやanalog_readメソッドの値は、firmataではレポートが定期的(または変化があった時)にArduino側から送られてきますので、それをパースした結果を返します。本ライブラリでいえば、Arduino::parseを呼び出した内部でArduinoからのレポートを解釈し、内部で記録します。

コードを見ていただくとおわかりになるかと思いますが、内容としては、デジタルピン2番をリードしてデジタルピン13番のHigh/Lowを制御するのと、アナログピン0番をリードしてデジタルピン9番に接続したサーボモーターを制御させています。

require './lib/arduino'

class Test
  def initialize(arduino)
    @arduino = arduino
  end

  def setup
    @arduino.set_report_interval(100) # アナログ値のレポート間隔 = 100[ms] 短かくし過ぎた場合通信量が大きくなるので注意
    @arduino.pin_mode(Arduino::DIGITAL2 , Arduino::INPUT)
    @arduino.pin_mode(Arduino::DIGITAL3 , Arduino::INPUT)
    @arduino.pin_mode(Arduino::DIGITAL13, Arduino::OUTPUT)
    @arduino.pin_mode(Arduino::DIGITAL9 , Arduino::SERVO)
    @arduino.pin_mode(Arduino::ANALOG0  , Arduino::ANALOG)
    @arduino.report # デジタル・アナログのレポート開始
  end

  def loop
    @arduino.parse # レポートを受信して解釈する

    # デジタルピン2番のLOW/HIGHでデジタルピン13番を制御
    if (@arduino.digital_read(Arduino::DIGITAL2) == 0)
      @arduino.digital_write(Arduino::DIGITAL13,0)
    else
      @arduino.digital_write(Arduino::DIGITAL13,1)
    end

    # アナログピン0番の入力値を用いてデジタルピン9番のサーボを回転させる
    val = @arduino.analog_read(Arduino::ANALOG0)
    @arduino.servo_write(Arduino::DIGITAL9,val >> 2)
   end

end

arduino = Arduino.new("/dev/ttyACM0")
arduino.run(Test)



/dev/ttyACM0は、Arduino Unoのシリアルポートのデバイススペシャルファイルです。これを介してRaspberry PiからArduino Unoにアクセスします。

テストに使用したのは、ジョイスティックシールド+サーボモーター+LEDです。
IMG_20130428_030941resized.jpg
IMG_20130428_030852resized.jpg
使用してるところの動画撮りましたのでどーぞ!


firmataを使用すれば、手軽にRaspberry PiでI/O制御の拡張と、既存Arduinoシールド資産が活用できるので、よかったら是非どうぞー!

ちなみに、今回使用したライブラリはArduino::parseメソッドを呼び出して1バイトずつデータを解釈させるのですが、できれば受信は別スレッドで行わせたほうが扱いやすいように思います。その内また更新して本ブログに掲載させていただこうかなーと思います。
Raspberry Piユーザーガイド
Eben Upton Gareth Halfacree
インプレスジャパン
売り上げランキング: 1,952
Raspberry Pi Type B 512MB
Raspberry Pi Type B 512MB
posted with amazlet at 13.04.28
RS Components Ltd
売り上げランキング: 1,915
posted by いしいっち at 04:16| Comment(0) | TrackBack(0) | Raspberry Pi | このブログの読者になる | 更新情報をチェックする

2013年04月17日

Raspberry PiでSPIシリアルで有機ELにフレームバッファを表示する実験してみた

Raspberry Piでちょこちょこ遊んでおります石井です。

先日、本ブログにRaspberry PiのI2Cを使用する実験で簡易電圧電流計を作ってみたというのを書かせて頂きました。
http://projectc3.seesaa.net/article/350916175.html

こんどはSPIシリアルを試そうと思いまして、何か制御対象物ないかなーとマイ積み基板BOXを漁っていたところ、出てきたのがaitendoで1年前に買ってた数百円程度で買える有機ELモジュールです。このモジュールはバスインターフェース(6800タイプ or 8080タイプ)と、SPIに対応してるということなんですね。で、取り出しました。

今回使用したモジュールの、aitendoのサイトでの商品情報へのリンクはこちら。
http://www.aitendo.com/product/2099 ai_oled1.gif
http://www.aitendo.com/product/2484 ai_oled2.gif

上記リンクの内、上のはSPIシリアル専用の回路になっていて、下のはバスのピンも全部出されてます。とはいえ今回はSPIで使用するのでどちらでも適用可能です。僕は、下のタイプを手持ちしていたのでそれを使ってます。

接続はこんなイメージ。
oled_overview.png

試したところ、とりあえずSPIからの制御自体はできたので、表示データを用意すべく昔の資産を掘り起こして簡易的な描画ライブラリを考えましたが、LinuxのコンソールやXの描画に使用されているフレームバッファ(/dev/fb0)をそのまま展開してしまえば、と思い、今回はそうしてみました。


動作させた状態の動画を作りました。良かったらどーーぞー!

従って、以下の様にフレームバッファをSPI経由で有機LEDモジュールに周期的に領域コピーするような構成にします。

oled_block.png

結線は、以下のようにしてみました。有機ELモジュールのRS端子(D/C#端子)は、SPIで有機ELモジュールに対して通信している内容がコマンドなのか、データなのかを通知する為の信号です。
コマンドをやりとりする場合はLowにし、それ以外の時はHighに制御する為、Raspberry PiのGPIO_GEN0ピン(汎用入出力端子)を使用しています。

oled_connection.png

ディストリビューションはRaspbian "wheezy"を使用します。デフォルトではSPIシリアルはローダブルモジュールになっており、自動でロードされません。
以下のサイトに書いてあるような方法を参考にSPIのデバドラが叩けるようにしました。
http://www.pwv.co.jp/~take/TakeWiki/index.php?raspberrypi%2FSPI%E3%82%92%E8%A9%A6%E3%81%99

本記事のソースコードはgithubにて公開させていただいております。
https://github.com/yishii/RaspberryPi_SPI_OLED

Raspberry Pi環境でセルフビルドもできますし、PC上で、Ubuntu+Sourcery g++によるクロス環境でもビルド出来ました。src/Makefileに記載してますので、ご覧になってください。

有機ELモジュールはSPIモードで使用するため、変換基板ピンヘッダコネクタ部の3番、4番(BS1、BS2)は共にGNDに落としておきます。以下のように変換基板にすすメッキ線で配線しました。

oled_jumper.jpg

実際に結線したイメージはこちら。
P1130476resized.jpg

フレームバッファを展開するプログラムを作成し、実行すると、テキストもグラフィックも結構クッキリで、いい感じ。

oled_text.jpg
oled_bitmap.jpg
ss.jpg
それでは、コードを見ていきましょう。SPIの初期化です。有機ELコントローラの仕様書を参照すると、SPIのクロックは最高4[MHz]の規定ですが、高速に画面更新してみようということで、おためしで8[MHz]に設定しています。
一見問題なく使えているように見えますが、メーカーの仕様を超えた使い方であるので、商品等に組み込むソフトの制御などでは当然NGでしょうし、想定外の不具合の発生やデバイス寿命短縮などになるかもしれません。
void oled_main(void)
{
    int fd;
    int result;
    uint8_t mode = SPI_MODE_0;
    uint8_t bits = 8;
    //uint32_t speed = 4000000;
    uint32_t speed = 8000000; // on the spec,SCLK max freq=4[MHz]
    uint16_t delay;

    fd = open("/dev/spidev0.0",O_RDWR);
    if(fd == -1){
	printf("Device open error\n");
	return;
    }

    /*
      set SPI read/write mode
    */
    result = ioctl(fd,SPI_IOC_WR_MODE,&mode);
    if(result < 0){
	printf("error(L%d)\n",__LINE__);
	return;
    }
    result = ioctl(fd,SPI_IOC_RD_MODE,&mode);
    if(result < 0){
	printf("error(L%d)\n",__LINE__);
	return;
    }

    result = ioctl(fd,SPI_IOC_WR_BITS_PER_WORD,&bits);
    if(result < 0){
	printf("error(L%d)\n",__LINE__);
	return;
    }
    result = ioctl(fd,SPI_IOC_RD_BITS_PER_WORD,&bits);
    if(result < 0){
	printf("error(L%d)\n",__LINE__);
	return;
    }

    result = ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ,&speed);
    if(result < 0){
	printf("error(L%d)\n",__LINE__);
	return;
    }
    result = ioctl(fd,SPI_IOC_RD_MAX_SPEED_HZ,&speed);
    if(result < 0){
	printf("error(L%d)\n",__LINE__);
	return;
    }

フレームバッファは、以下のように/dev/fb0のデバイスをマップしてアクセスしています。

bool fb_init(void)
{
    int fb_fd;
    struct fb_var_screeninfo vinfo;

    fb_fd = open("/dev/fb0",O_RDWR);
    if(fb_fd < 0){
	printf("framebuffer open error\n");
	return(false);
    }

    if(ioctl(fb_fd,FBIOGET_VSCREENINFO,&vinfo)){
	printf("screen information retrieve error\n");
	return(false);
    }
    printf("%dx%d,%dbpp\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel);

    screen_size_x = vinfo.xres;
    screen_size_y = vinfo.yres;
    screen_bpp = vinfo.bits_per_pixel;
  
    if(vinfo.bits_per_pixel != 16){
	printf("This color mode is not supported.\nOnly 16bpp is supported currently\n");
	return(false);
    }

    fb_mem = (volatile unsigned char*)mmap(
	NULL,
	(vinfo.xres*vinfo.yres*vinfo.bits_per_pixel) >> 3,
	PROT_READ|PROT_WRITE,
	MAP_SHARED,
	fb_fd,
	0);

    if(fb_mem == NULL){
	printf("framebuffer mapping error\n");
	return(false);
    }

    return(true);
}

シリアル通信でイメージデータと制御コマンドとを切り分けるのにRSという信号を使用しますが、これはGPIO17番を使用します。実装のgpiolib.cご覧ください。

このように結構いい感じにカラーのイメージ展開が出来ました!

Raspberry Pi Model B (512MB)本体のみ
kalron
売り上げランキング: 16,048

Raspberry Piユーザーガイド
Eben Upton Gareth Halfacree
インプレスジャパン
売り上げランキング: 1,492
posted by いしいっち at 23:13| Comment(0) | TrackBack(0) | 電子工作 | このブログの読者になる | 更新情報をチェックする
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。