2014年07月12日

Raspberry Piでロータリーエンコーダーの動作確認

Raspberry Piで秋月電子で販売されているロータリーエンコーダー(EC12PLRGBSDVBF-D-25K-24-24C-61)の動作確認を行いました。

ロータリーエンコーダーの接続

ロータリーエンコーダーとは以下の通り接続を行いました。GPIOはプログラムでプルアップしています。

[RE]  - [RaspberryPi]
Cpin  -  14pin(GND)
Apin  -  11pin(GPIO17)
Bpin  -  12pin(GPIO18)

プログラミング

プログラムではwiringPiのwiringPiISR関数を使用しました。この関数はGPIOのレベルが変化した際に指定した関数を呼び出す事ができ、ロータリーエンコーダーの回転に合わせたイベント処理が可能となります。
また、使用したロータリーエンコーダーはクリック機能が付いています。GPIOをプルアップすることにより、何もしていない時はA端子・B端子共にLレベルとなります。回転させるとA端子とB端子がそれぞれHレベルになり、次のクリック位置になると両方ともにLレベルに戻ります。右回転と左回転を識別するには、A端子とB端子のイベントが起こる順番を識別することで可能となります。

#include <wiringPi.h>
#include <stdio.h>
#include <stdlib.h>

int a;
int b;
int p_a;
int p_b;
int num;

void click_a(void){
        int _a;
        _a = digitalRead(0);//GPIOの値を取得。
        if(_a != a){ //同じ値が連続した場合にスキップする。
                if(a == 1){ //一つ前のA端子の値を格。
                        p_a = 1;
                }else{
                        p_a = 0;
                }
                a = _a;
                //a端子とb端子の直前の値が1であり、今の値が0である場合にTRUE。
                if(a == 0 && b == 0 && p_a == 1 && p_b == 1){
                        printf("right %d\n", ++num);
                }
        }
}
void click_b(void){
        int _b;
        _b = digitalRead(1);
        if(_b != b){
                if(b == 1){
                        p_b = 1;
                }else{
                        p_b = 0;
                }
                b = _b;
                if(a == 0 && b == 0 && p_a == 1 && p_b == 1){
                        printf("left  %d\n", --num);
                }
        }
}

int main(void){
        int setup = 0;
        a=1;
        b=1;
        p_a=0;
        p_b=0;
        num=0;
        setup = wiringPiSetup();
        pullUpDnControl(0, PUD_UP);
        pullUpDnControl(1, PUD_UP);
        wiringPiISR(0, INT_EDGE_BOTH, click_a);
        wiringPiISR(1, INT_EDGE_BOTH, click_b);
        while(setup != -1){
                sleep(1);
        }
}
2014年07月06日

Raspberry Piで大気圧センサ(AE-SCP1000-D01)の動作確認

Raspberry Piと秋月電子で販売されている大気圧センサーのSCP1000を使用して大気圧計を作成しました。

SPIの有効化

使用したLinuxはdebianで、以下のバージョンです。

pi@raspberrypi ~/work/lcd $ uname -a
Linux raspberrypi 3.12.22+ #691 PREEMPT Wed Jun 18 18:29:58 BST 2014 armv6l GNU/Linux

SPIを有効化するにはraspi-configコマンドを使用します。

pi@raspberrypi ~/work/lcd $ sudo raspi-config

[8 Advanced Options -> A5 SPI -> Yes]

SCP1000の接続

SCP1000はSPIインターフェースを備えています。以下の通り配線を行いました。どちらも物理ピン番号を記載しています。

[scp1000]       -     [RaspberryPi]
 3pin(SCK)      -      23pin(SCKL)
 4pin(GND)      -      25pin(GND)
 5pin(MOSI) - 0.01uF - 19pin(MOSI)
 6pin(MISO)     -      21pin(MISO)
 7pin(CSB)      -      24pin(CE0)
 8pin(VCC)      -      17pin(3.3v)

本来、上記接続で問題なく接続できるはずですが、Raspberry Piからどんなコマンドを送っても、読み出すと0x00又は0xFFの値しか返ってこない状態となりました。そこで、SCP1000側のMOSIの近くに0.01uFを挟むと4回に1回程度ですが信号が届くようになりました。原因は不明ですが、ブレッドボードで接続しているため、ノイズ等の影響を受けて不安定になっている可能性があります。

SC2004CSWB-XA-LB-Gの接続

表示デバイスは、秋月電子で販売されているSC2004CSWB-XA-LB-Gを使用しました。このデバイスは、4行x20桁が表示でき、バックライトに白色LEDを備えています。接続は以下の通り行っています。

[LCD]         -     [RaspberryPi]
 1pin(VSS)    -      3pin(GND)
 2pin(VDD)    -      1pin(3.3v)
 3pin(Vo)  - 2kΩ -  14pin(GND)
 4pin(RS)     -      16pin(GPIO23)
 5pin(R/W)    -      5pin(GND P5-Header)
 6pin(E)      -      18pin(GPIO24)
 11pin(DB4)   -      11pin(GPIO17)
 12pin(DB5)   -      12pin(GPIO18)
 13pin(DB6)   -      13pin(GPIO21)
 14pin(DB7)   -      15pin(GPIO22)
 
[LCD LED]  - [Raspberry Pi]
 A         -  2pin(5v)
 K - 180Ω -  20pin(GND)

なお、コントラスト調整用のボリュームは測定すると1.98kΩでした。
また、LEDの駆動電源は5Vを用いています。Raspberry Piの3.3vピンは電流の制限値があることから、USBコネクタと直接つながっている5Vを使用し、制限抵抗は180Ωを用いました。

プログラミング

動作が安定しなかったため初期化に失敗した場合は、再試行を繰り返す動作にしています。一度でも正しく値を送信できると、極めて安定して気圧データを取得することができました。MOSIにコンデンサを挟むことで安定性が多少増しましたが、以下のコードの初期化の処理が誤っている可能性もあります。参考程度に留めておいて下さい。

#include<wiringPiSPI.h>
#include<wiringPi.h>
#include<stdio.h>
#include<time.h>
#include<lcd.h>

#define SPI_CHANNEL 0
#define SPI_CLK 500000

int main(void){
	double temp = 0;
	double pressure = 0;
	unsigned char buf[3];
	char str[24];
	time_t time_now;
	struct tm *local_time;
	int i,fd,flag1=0, flag2=0;

        //wiringPiライブラリの初期化
        if (wiringPiSetup () == -1){
                return (1) ;
	}

        /* LCDデバイスの初期化 */
        if((fd = lcdInit(4,20,4, 4,5, 0,1,2,3,0,0,0,0)) < 0){
                printf("[Error] open lcd device\n");
                return(-1);
        }

	//SPIデバイスの初期化
	if((wiringPiSPISetup(SPI_CHANNEL, SPI_CLK))<0){
		printf("error SPI\n");
	}

	//初期処理。失敗した場合は繰り返す。
	while(1){
		flag1 = 0;
		flag2 = 0;

		//時間の表示
		time_now = time(NULL);
		local_time = localtime(&time_now);
		strftime(str, sizeof(str), "%Y/%m/%d %a %H:%M:%S", local_time);
		printf("%s\n", str);

		//ソフトリセットコード
		buf[0] = 0x1a;
		buf[1] = 0x01;
		wiringPiSPIDataRW(SPI_CHANNEL, buf, 2);
		printf("delay...\n");
		delay(60);

		//STATUS確認
		buf[0] = 0x1c;
		buf[1] = 0xff;
		wiringPiSPIDataRW(SPI_CHANNEL, buf, 2);
		printf("STATUS:%02x\n", buf[1]);
		i= buf[1]&0x01;
		if(i == 0){
			flag1 = 1;
			printf("STATUS OK!\n");
		}

		//DATARD8確認
		buf[0] = 0x7c;
		buf[1] = 0xff;
		wiringPiSPIDataRW(SPI_CHANNEL, buf, 2);
		printf("DATARD8:%02x\n", buf[1]);
		i= buf[1]&0x01;
		if(i == 1){
			flag2 = 1;
			printf("DATARD8 OK!\n");
		}

		if(flag1==1 && flag2==1){
			printf("START...\n");
			break;
		}
	}


	//オペレーションレジスタに高精度測定モードを設定
	buf[0] = 0x0e;
	buf[1] = 0x0a;
	wiringPiSPIDataRW(SPI_CHANNEL, buf, 2);

	//メインループ
	while(1){
		//温度の測定
		buf[0] = 0x84;
		buf[1] = 0x00;
		buf[2] = 0x00;
		wiringPiSPIDataRW(SPI_CHANNEL, buf, 3);
		temp = (double)((buf[1] << (8+2)) + (buf[2] << 2)) / 20 / 4;

		//気圧の測定
		buf[0] = 0x7c;
		buf[1] = 0x00;
		wiringPiSPIDataRW(SPI_CHANNEL, buf, 2);
		pressure = buf[1] << 16;
		buf[0] = 0x80;
		buf[1] = 0x00;
		buf[2] = 0x01;
		wiringPiSPIDataRW(SPI_CHANNEL, buf, 3);
		pressure += (buf[1] << 8) + buf[2];
		pressure = pressure / 4 / 100;

		//時間の取得
		time_now = time(NULL);
		local_time = localtime(&time_now);

		//LCD表示
       		lcdClear(fd);
	        lcdPosition(fd,0,0);
		strftime(str, sizeof(str), "%Y/%m/%d %a", local_time);
		lcdPrintf(fd, "DATE=%s", str);
	        lcdPosition(fd,0,1);
		strftime(str, sizeof(str), "%H:%M:%S", local_time);
		lcdPrintf(fd, "TIME=%s", str);
	        lcdPosition(fd,0,2);
       		lcdPrintf(fd, "TEMP=%2.2f[C]", temp);
	        lcdPosition(fd,0,3);
       		lcdPrintf(fd, "PRESS=%4.4f[hPa]", pressure);


		delay(1000);
	}
}

参考文献

合同会社パレットソフト:気圧センサ SCP1000 (試食)

2013年11月17日

FreeBSD上のbyoubuでの使用メモリの表示

byobuは、標準でロードアベレージやメモリ使用量などを表示することができますが、あくまでLinuxを対象に作成されているためFreeBSDではまったく情報を表示することができなくなります。そのため、自作のスクリプトを作成し表示を行うようにしました。

メモリ表示スクリプト

試しに、メモリの表示を行うスクリプト作成しました。FreeBSDはメモリの情報表示に関して、Linuxのように/procを読み込んで表示することはできません。sysctlを叩いて情報を取得します。

% more /usr/local/bin/mem
#!/bin/sh
a=`echo "4098*$(sysctl -n vm.stats.vm.v_free_count)/1024/1024" | bc`
b=`echo "$(sysctl -n sysctl hw.physmem)/1024/1024" | bc`
echo "${a}MB/${b}MB"

byobu設定ファイル

~/.byobu/profileの設定を変更します。
backtickが、どうやらバックグラウンドで表示を行うスクリプトと表示番号を定義している模様です。今回は、138と139という番号でスクリプトを作成しました。
hardstatus stringが、表示位置やレイアウトを設定している模様です。一番最後に138と139を追加しました。

backtick 138    5       5               mem
backtick 139    5       5               sysctl -n vm.loadavg

hardstatus string '%99`%{-}%{=r}%12` %100`%112`%=%117`%133`%130`%135`%102`%101`%129`%131`%127`%114`%115`%108`%134`%128`%125`%126`%113`%119`%116`%106`%104`%103`%105`%107`%136`%123`%137`%132`%120`%121` %138` %139`'

参考

/usr/local/lib/byobu/profile以下に各情報を取得するためのシェルスクリプトが格納されています。手っ取り早く表示させるならば、ここのスクリプトを書き換えてしまうのが、もっとも手軽です。

2013年10月13日

Raspberry PiでI2C温度計作成メモ

Raspberry Piと秋月電子で販売されている、ADT7410を使用して温度計を作成しました。前回作成した、NTP時計にセンサーを追加して動作するようにしています。

ADT7410の接続

ADT7410は、I2Cインターフェースを備えています。接続は大変簡単で、3.3VとGNDをRaspberry Piの1Pinと6Pinに差し、センサーのSCAとSDAをRaspberry Piの3Pin(SDA)と5Pin(SCA)に接続するだけで完了です。

Raspberry Piの準備

I2Cのモジュールを有効化します。まず、modprobeのブラックリストから、I2Cモジュールを除きます。

pi@raspberrypi ~ $ more /etc/modprobe.d/raspi-blacklist.conf
# blacklist spi and i2c by default (many users don't need them)

blacklist spi-bcm2708
#blacklist i2c-bcm2708

次に、moduleの有効化を行います。

pi@raspberrypi ~ $ more /etc/modules
# /etc/modules: kernel modules to load at boot time.
#
# This file contains the names of kernel modules that should be loaded
# at boot time, one per line. Lines beginning with "#" are ignored.
# Parameters can be specified after the module name.

snd-bcm2835
i2c-bcm2708
i2c-dev

最後にツールをインストールします。

pi@raspberrypi ~ $ sudo apt-get install i2c-tools

この状態で再起動すると、起動後にモジュールが有効化されます。

I2Cデバイスの確認

接続したI2Cデバイスが正しく認識されているかを確認します。ADT7410のデバイス番号は0x48なので、この値が表示されれば正しく接続されていることが分かります。

pi@raspberrypi ~ $ sudo i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

プログラミング

ADT7410とRaspberry PiのI2Cは、相性があまり良くないようです。詳細なバグ情報までは調査できませんでしたが、WiringPiのライブラリやi2cset/i2cgetコマンドを使用して読み書きができませんでした。やむを得ないので、単純にC言語で直接呼び出し使用することとしました。

読み込みに関しては、4byte単位であれば温度情報(0x00, 0x01)、ステータス情報(0x02)及び設定情報(0x03)を読み出すことが可能でした。
書き込みに関しては、2byte単位で、書き込みたいレジスタ(1byte)と設定したい値(1byte)を同時に書き込むことで設定可能でした。今回は、温度センサーの分解能を13bit動作から16bit動作に変更するために、センサー接続後にレジスタを設定しています。
いずれも、試したら動いたといった挙動で正しい制御を行っているかは分かりません。

#include <wiringPi.h>
#include <lcd.h>
#include <string.h>
#include <time.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>

#define DATA_NUM 10

double get_temp(int rtc){
        char buf[4];
        double temp;

        if (read(rtc, buf, 4) != 4) {buf[0] = 0x00; buf[1] = 0x00;}
        //temp = ((double)((int)buf[0] << 5) + ((int)buf[1] >> 3)) / 16; //13bitモード
        temp = ((double)((int)buf[0] << 8) + (int)buf[1]) / 128; //16bitモード
        return temp;
}

int main(void){
        int fd, rtc, i, count;
        char str[32];
        char REG[2] = {0x03,0x80}; //0x03レジスタに0x80を書き込む。
        time_t time_now;
        time_t time_prev;
        struct tm *local;
        double temp;
        double temp_arr[DATA_NUM];

        if (wiringPiSetup () == -1)
                return (1) ;

        /* LCDデバイスの初期化 */
        if((fd = lcdInit(2,16,4, 10,11, 2,3,4,5,0,0,0,0)) < 0){
                printf("[Error] open lcd device\n");
                return(-1);
        }


        /* LCD表示テスト */
        lcdClear(fd);
        lcdPosition(fd,0,0);
        lcdPrintf(fd, "NTP LCD_CLOCK");
        sleep(2);
        lcdClear(fd);


        /*i2c温度センサーの初期化*/
        if ((rtc = open("/dev/i2c-1", O_RDWR)) < 0){
                printf("[Error] open i2c port\n");
                return(-1);
        }
        if(ioctl(rtc, I2C_SLAVE, 0x48) < 0){
                printf("[Error] access to 0x48\n");
                return(-1);
        }
        if(write(rtc, REG, 2) != 2){
                printf("[Error] set register 0x03\n");
                return(-1);
        }


        /* 温度センサーの平均算出用データの取得 */
        for(i=0; i<DATA_NUM;i++){
                temp_arr[i] = get_temp(rtc);
                lcdClear(fd);
                lcdPosition(fd,0,0);
                lcdPrintf(fd, "Init.           TEMP_COUNT=%d/%d", i+1, DATA_NUM);
                sleep(1);
        }

        /* メインループ(1秒間隔で情報を更新) */
        count = 0;
        while(1){
                time_now = time(NULL);
                /* 時刻が前回時刻から変化した場合、表示更新処理を実行 */
                if(time_now != time_prev){
                        /* 時計表示ロジック */
                        local = localtime(&time_now);
                        strftime(str, sizeof(str), "%Y/%m/%d %a  %H:%M:%S", local);

                        /* 温度表示ロジック */
                        count = (count + 1) % DATA_NUM;
                        temp_arr[count] = get_temp(rtc);
                        temp = 0;
                        for(i=0;i<DATA_NUM;i++) temp += temp_arr[i];
                        temp = temp / DATA_NUM;

                        /* LCDへ表示 */
                        lcdPosition(fd,0,0);
                        lcdPrintf(fd, "%s %2.2fC", str, temp);
                        time_prev = time_now;

                        usleep(800*1000);//800msec処理を停止。上記処理が200msで終わらなくなったら修正。
                }
                usleep(1*1000);//1msec処理を停止
        }
        return(0);
}

参考文献

wsnakのブログ:I2C温度センサADT7410を使う(1)

2013年07月09日

Raspberry PiでNTP時計作成メモ

Raspberry PiでNTP時計を作りました。時刻の表示には秋月電子のLCDキャラクターモジュールを使用しました。

Linuxの書き込み

OSはDebianベースのWheezyを使用しました。イメージファイルはRasberryPiサイトからダウンロードを行いました。
ディスクイメージの書き込みに使用したソフトはWin32DiskImagerです。

OSの初期設定

初回起動時に以下の設定を実施。

1.Expand Filesystemを実行しSDカードの容量最大まで/パーティションを拡張。
2.Change User Passwordを実行しrootのパスワードを変更。
8.Advanced Options -> A4 SSH -> Enableを設定し、SSHを有効化。

無線LANの設定

PlanexのGW-USValue-EZ 802.11n Wireless Adapterを使用しました。

まずUSBポートに無線LANアダプターを挿入し、USBを認識していることを確認します。

pi@raspberrypi ~ $ lsusb 
Bus 001 Device 002: ID 2019:ed17 PLANEX GW-USValue-EZ 802.11n Wireless Adapter [Realtek RTL8188CUS]
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

次に無線LANルーターに接続を行います。まずSSIDとパスワードを設定します。

root@raspberrypi:/home/pi# wpa_passphrase SSID PASSWORD > /etc/wpa_supplicant/wpa_supplicant.conf

使用してる無線LANルーターがステルスSSIDの設定になっているため、wpa_supplicant.confにscan_ssid=1を追記します。

network={
        ssid="SSID"
        psk=c2161655c6ba444d8df94cbbf4e9c5c4c61fc37702b9c66ed37aee1545a5a333
        scan_ssid=1
}

次にネットワーク設定を行います。/etc/network/interfacesに以下の内容に編集します。使用したRasberry Piには有線LANが付いていないため、ethの記述は削除しました。

auto lo
iface lo inet loopback

allow-hotplug wlan0
auto wlan0
iface wlan0 inet static
        address 192.168.1.128
        gateway 192.168.1.1
        netmask 255.255.255.0
        dns-nameservers 192.168.1.1
        wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

ここまでの状態でrebootすれば自動的にネットワークにつながるようになり、SSH接続が可能となります。

NTPDの設定

/etc/ntp.confにserver ntp.nict.jpを追記しNICTのNTPサーバを参照するようにします。

またローカルタイムを以下のコマンドで設定します。

pi@raspberrypi ~ $ sudo cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

GPIOの有効化(WiringPiインストール)

RaspberryPiのGPIOを簡単に制御するのに必要なライブラリをインストールします。今回導入したライブラリはWiringPi。公式サイトの導入方法に従ってインストールしました。

pi@raspberrypi ~/work $ git clone git://git.drogon.net/wiringPi
Cloning into 'wiringPi'...
remote: Counting objects: 507, done.
remote: Compressing objects: 100% (449/449), done.
remote: Total 507 (delta 353), reused 97 (delta 58)
Receiving objects: 100% (507/507), 214.88 KiB | 142 KiB/s, done.
Resolving deltas: 100% (353/353), done.
pi@raspberrypi ~/work $ cd wiringPi/
pi@raspberrypi ~/work/wiringPi $ ./build 

LCDデバイスへの表示プログラム

test.cという名称で以下のプログラムを作成しました。10ms毎に時間を取得して表示を書き換える動作になっています。LCDは4bitモードで接続しました。GPIOとwiringPi.hでのピンアサイン対応や、表示に関する制御等については公式サイトのLCD作例ページを参考にしました。

#include <wiringPi.h>
#include <lcd.h>
#include <string.h>
#include <time.h>

int main(void){
	int fd;
	char str[32];
	time_t time_now;
	struct tm *local;

	if (wiringPiSetup () == -1)
	    return (1) ;

	/* LCDデバイスの初期化 */
	fd = lcdInit(2,16,4, 10,11, 0,1,2,3,0,0,0,0);
	if(fd == -1){
		return(-1);
	}

	/* LCD表示テスト */
	lcdClear(fd);
	lcdPosition(fd,0,0);
	lcdPrintf(fd, "clock test");
	sleep(2);

 	/* 時計表示ロジック */
	lcdClear(fd);
	while(1){
		time_now = time(NULL);
		local = localtime(&time_now);
		strftime(str, sizeof(str), "%Y:%m:%d %a  %Z %p %H:%M:%S", local);
		lcdPosition(fd,0,0);
		lcdPrintf(fd, "%s", str);
		
		usleep(10*1000);//10msec処理を停止
	}

	return(0);
}

以下のコマンドでコンパイルし、出力されたバイナリファイルを実行します。

pi@raspberrypi ~/work/lcd $ gcc test.c -lwiringPi -lwiringPiDev
pi@raspberrypi ~/work/lcd $ sudo ./a.out