スマートホーム構築記

IoTの恩恵を受けたいけど家庭用はなかなか出てこない。なら作ろうというブログ

Raspberry Pi3 でのlirc信号生成スクリプトの作成

目的

前回信号解析を行ったリモコンの信号を解析結果に基づいて生成することを目指します。

方針

システムは以下の方針で設計します。

  1. 今後、他のリモコンも登録して使う可能性があるので汎用的なシステムとする
  2. 設定入力は人間が理解できるフォーマットにする
    1. 将来的にはGUIによって設定可能なものとする
  3. 出力はlircのフォーマット

システム構成

上記の方針と自分の勉強もかねて以下のシステム構成にしました。
f:id:kagemomiji:20161002123347p:plain

設定はjson形式で入力し、内部には機械の設定情報と使用する信号フォーマットのIDを格納します。
コンバートスクリプトはこのフォーマットIDから信号フォーマットライブラリを参照して、標準出力にlirc用の信号を出力します。
信号フォーマットライブラリを拡張することでコンバートスクリプトを大幅に変更することなく信号が出力できるようになる仕組みです。
json形式にしたのはwebアプリケーションとの親和性が高そうなことと、自分の勉強のためです。

詳細

次にそれぞれの詳細を説明していきます。

設定(json)

 {
        "format_id":"a01",
        "data":{
                "mode":"d",
                "status":"on",
                "temperature":28,
                "strength":"auto",
                "direction":"auto",
                "dry":"off",
                "power":"off",
                "silent":"off",
                "smell":"on"
        }
}

大きく分けて二つのオブジェクトからなります。
使用するフォーマットIDと設定値格納オブジェクトです。
フォーマットIDはこの後に'.json'をつけると信号フォーマットライブラリのフォーマットファイルになります。
設定値格納オブジェクトは運転モードや温度などの設定情報をさらにネストして格納しています。

信号フォーマットファイル(json)

今回解析したエアコンの信号をこのようなjson形式として書きました。

{
        "id":1,
        "type":"AEHA",
        "target":"aircon",
        "signal":{
                "T":436,
                "trailer_t":10000,
                "frame":[
                                {
                                        "bitmap":[
                                                        [0],
                                                        [0],
                                                        [0,4],
                                                        [0],
                                                        [0],
                                                        [4,0],
                                                        [1],
                                                        [0],
                                                        [4,0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [6,5,0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0]
                                        ],
                                        "value":[
                                                "0x2",
                                                "0x20",
                                                "0x0+0xe",
                                                "0x4",
                                                "0x0",
                                                "mode+status",
                                                "temperature",
                                                "0x80",
                                                "strength+direction",
                                                "0x0",
                                                "0x0",
                                                "0x6",
                                                "0x60",
                                                "dry+silent+power",
                                                "0x0",
                                                "0x80",
                                                "0x0",
                                                "smell",
                                                "sum"
                                        ]
                                },
                                {
                                        "bitmap":[
                                                        [0],
                                                        [0],
                                                        [0,4],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0],
                                                        [0]
                                        ],
                                        "value":[

                                               "0x2",
                                                "0x20",
                                                "0x0+0xe",
                                                "0x4",
                                                "0x0",
                                                "0x0",
                                                "0x0",
                                                "0x6"
                                        ]
                                }
                ],
                "rule":{
                        "mode":{
                                "a":"0x0",
                                "h":"0x4",
                                "c":"0x3",
                                "d":"0x2"
                        },
                        "status":{
                                "off":"0x0",
                                "on":"0x1",
                                "toff":"0x5",
                                "ton":"0x8"
                        },
                        "strength":{
                                "auto":"0xa",
                                "1":"0x3",
                                "2":"0x4",
                                "3":"0x5",
                                "4":"0x7"
                        },
                        "direction":{
                                "auto":"0xf",
                                "1":"0x1",
                                "2":"0x2",
                                "3":"0x3",
                                "4":"0x4",
                                "5":"0x5"
                        },
                        "smell":{
                                "on":"0x16",
                                "off":"0x6"
                        },
                        "dry":{
                                "on":"0x1",
                                "off":"0x0"
                        },
                        "silent":{
                                "on":"0x1",
                                "off":"0x0"
                        },
                        "power":{
                                "on":"0x1",
                                "off":"0x0"
                        }

                }
        }
}

大雑把には以下のような構成です。
f:id:kagemomiji:20161002131256p:plain:w500
header部ではこのファイル自体の情報を定義しています。
次に信号設定部で信号における必要情報を定義しておきます(単位時間Tなど)
次に出力されるフレームを1フレーム毎にビットシフト量の定義である"bitmap"と格納する値の定義である"value"をセットにして配列として格納します。
基本的な考え方として8bit(1バイト)を一単位として考えその中でどのようなデータがどのようなビットシフトがなされて格納されているのかといった情報をフレーム毎に持っているというものです。
バイトが固定値であればその値を、そうでなければ内容について"value"に定義します。上位4bitと下位4bitで意味が異なる場合は"+"で上位ビットから順番に連結して表現します。
また、設定ファイルから設定値を反映するためのルールを"rule"オブジェクトとして格納します。人間が理解できる値から信号化するための16進数変換するルールをここに定義します。

コンバートスクリプト

次にコンバートスクリプトになります。
機能としては

  1. 設定jsonの読み込み
  2. 信号フォーマットjsonの読み込み
  3. 信号を各バイトごとに10進数化して生成
  4. 信号を2進数化およびLSB,MSB対応
  5. 2進数化された信号をpulse,space記法に変換
  6. 結果を標準出力

の6個の機能からなります。

# coding:utf-8

#import mod
import json
import sys
import rev_bit

依存関係は以上のようになります。rev_bitは前回に作成したMSB/LSBの反転スクリプトです。

#check argvs
argvs = sys.argv
argc = len(argvs)
if(argc !=2):
        print 'Usage: #python %s filename' % argvs[0]
        quit()
#set local value
SIGNAL_LIB_DIR = "/home/pi/system/settings/format/"

初期化処理です。引数チェックと信号ライブラリのディレクトリを設定しています。
ディレクトリ指定は絶対パススクリプトを置いたところからの相対ディレクトリです。

##################################
#load setting
##################################
try:
        s = open(argvs[1],'r')
        set_data = json.load(s)
        data = set_data["data"]
except ValueError:
        print 'setting json format error'
        s.close()
        quit()
except IOError:
        print 'No Such File or Directory %s' %(argvs[1])
        quit()
except NameError:
        print 'Unknown format of setting %s' %(argvs[1])
        s.close()
        quit()
except :
        print 'Unexpected error:',sys.exc_info()[0]
        quit()

引数で指定された設定ファイルを読み込み処理です。例外処理を入れていますが基本的にいれなくてもよいと思います。今回少し私が勉強したことを試す意味で入れています。

##################################
#load format
##################################
format_file = set_data["format_id"] + ".json"
f = open(SIGNAL_LIB_DIR + format_file,'r')
format = json.load(f)

信号フォーマットファイルの読み込みです。設定ファイルに格納された["format_id"]+'.json'のファイルを初期化時に指定した"SIGNAL_LIB_DIR"内からロードするといった処理になります。

##################################
#convert setting to binary signal
##################################


#initialize
signal = format["signal"]
rule = signal["rule"]
format_type = format["type"]
bin_signal = ''

#convert setting to binary signal
for frame in signal["frame"]:
        bin_signal = bin_signal+'L'
        bitmap = frame["bitmap"]
        raw_signal = []
        for i in xrange(len(bitmap)):
                val = frame["value"][i].split("+")
                sig = 0
                for j in xrange(len(val)):
                        #append fixed_bit
                        if '0x' in val[j]:
                                sig = sig + int(val[j],16) << bitmap[i][j]
                        #append setting_bit
                        elif val[j] in rule.keys():
                                sig = sig + int(rule[val[j]][data[val[j]]],16)<<bitmap[i][j]
                        #append error_check_bit
                        elif val[j] == 'sum':
                                sig = reduce(lambda x,y:x+y,raw_signal) & 0x0ff
                        else:
                                sig = sig + data[val[j]] << bitmap[i][j]

                #append new signal
                raw_signal.append(sig)
                bin_signal = bin_signal + bin(rev_bit.reverse_bit8(sig)).replace('0b','').zfill(8)

        if format_type == 'AEHA':
                #append tracer tag
                bin_signal = bin_signal + 'T'

'bin_signal'にLeaderは'L',信号は'0'or'1',Trailerは'T'として格納していきます。

バイトごとのデータをsigに格納し、これを変換していくことで実現しています。
sigの計算は大きく3つあります.

  1. 固定値をビットシフトして追加
  2. 設定値を読みこんでビットシフトして追加
  3. エラー処理用の信号を処理して追加

1つ目は固定値の追加です。1バイトすべてが固定値の場合はvalue='0x16',bitmap=[0]という組み合わせになっていて0x16を左側0bitシフトして追加という処理になります。
Data0とParityコードは4bitずつの固定値となっているので以下のような組み合わせで記述されていますvalue='0x0+0xe',bitmap=[0,4]。これは固定値をそれぞれ順番に"+"で表現し、それらをいくつ左側にbitシフトするかといったようにbitmapを記述しています。
これは設定値を反映する場合にも使用されています。たとえば"mode+status"のように1バイトに運転モードと電源ステータスが共存する場合にも同じように"+"で結合して記述します。その後,ruleオブジェクト内に記載された設定値変換ルールによって置換してbitmap=[4,0]に従ってそれぞれビットシフトして加算されます。
最後にエラー処理用のバイトは今回のエアコンのリモコン信号がすべてのバイト信号の総和で定義されていたのでその処理を実装しています。今後別の信号を扱う際に拡張する予定です。
最後にこのsigをMSB/LSBのため並び順を逆順に変換して、8ケタの2進数文字列化を行って結合していきます。
リモコン信号は下の桁から順番に1桁目,2桁目,3桁目…,8桁目と流れていますが、プログラム上では下の桁が最後にくるように8桁目,7桁目,...2桁目,1桁目とならんでいるからです。

###################################
#convert binary signal to ir_signal
###################################

#initialize
T = signal["T"]
ir_signal = ''

#AEHA format setting
if format_type == 'AEHA':
        trailer_t = signal["trailer_t"]

        for i in xrange(len(bin_signal)):
                #append 0
                if bin_signal[i] == '0':
                        ir_signal = ir_signal + 'pulse ' + str(T) + '\nspace ' + str(T) + '\n'
                #append 1
                elif bin_signal[i] == '1':
                        ir_signal = ir_signal + 'pulse ' + str(T) + '\nspace ' + str(3*T)+ '\n'
                #append Leader
                elif bin_signal[i] == 'L':
                        ir_signal = ir_signal + 'pulse ' + str(8*T) + '\nspace ' + str(4*T)+ '\n'
                #append Trailer
                elif bin_signal[i] == 'T':
                        ir_signal = ir_signal + 'pulse ' + str(T) + '\nspace ' + str(trailer_t) + '\n'
print ir_signal

最後はAEHA,NEC,sonyフォーマット毎にそれらに従ったpulse,space信号に変換していきます。
置換で生成せずわざわざfor文で順番に処理するのは変換した文字列に0,1が生成された場合に誤って変換されるのを防ぐためです。
最後にprint文を用いて生成した文字列を標準出力に出力しています。

ソースコード

以下にjson2python.pyとテスト用のjsonをlibに格納していますのでどうぞご自由にご利用ください。
github.com

Rasberry Pi 3でリモコン信号を解析する (10/1リモコン信号解析結果更新)

目的

多くのサイトで送られた信号を記録してそのまま出力するといったものが見られますがこれだと多くのパターンを記録させなければならないので非効率です。
今回、エアコンに設定したい状態があった場合にその状態に変更するための信号を生成できるようにリモコンの信号生成ルールを明らかにします。

リモコンについて

今回使用したリモコンについて紹介します

概要

NationalエアコンのA75C3026
パナソニック ナショナル エアコンリモコン A75C3026
部屋に備え付けのエアコンのリモコンです。

ボタン

ボタン名 動作
運転ON/OFF 電源ON/OFF状態のトグル
温度UP 設定温度を1度あげる
温度DOWN 設定温度を1度下げる
運転切替 自動/暖房/冷房/除湿の順に切り替え
風量 自動/静か/弱~強4段階で順番に切り替え
風向 自動/下~上を4段階で順番に切り替え
におい除去 長押しでにおい除去モードに切り替え
切タイマ 電源ON時に0.5,1,2,3,4,5,6,7,8,9,10,11,12hの順でタイマの時間を変更
入タイマ 電源OFF時に0.5,1,2,3,4,5,6,7,8,9,10,11,12hの順でタイマの時間を変更
予約 切タイマ,入タイマで設定した時間を送信する
取消 予約の取消
パワフル パワフルモードに切り替え
メニュー エアコンへの信号は特になし

信号解析

次にこのリモコンの信号を解析します

信号フォーマット

リモコンの信号は以下のサイトで詳しく解説されています。
赤外線リモコンの通信フォーマット
大きく分けて以下の3つのフォーマットに分類されるそうです。

今回使用するNational(Panasonic)は2番目の家製協フォーマットを使用しています。
家製協フォーマットについて詳しく説明していきます。

赤外線の信号は一定時間T(数100μsec)を1単位としてON/OFFが何単位時間の長さかで情報を表現しています。
前回紹介したリモコンの信号を取得する"mode2"の出力におけるpulseがON spaceがOFFに対応しています。

家製協フォーマットではON→OFFの長さの組み合わせで以下のような意味を持ちます。

ON OFF 意味 詳細
8T 4T Leader Frameの始めを意味する
1T 1T 0(bit) bitにおける0を意味する(DataBit)
1T 3T 1(bit) bitにおける1を意味する(DataBit)
1T >=800ms Trailer Frameの終了を意味する

つまり1Tの長さのOnの後にOFFの信号が3T続くと"1"を意味するということです。

さらに、信号の構成について見ていきます。以下は信号を含んだFrameと呼ばれるブロックをしめしています。

Leader Customer Code1 Customer Code2 Parity Code Data0 Data1 ・・・ DataN Trailer
8bit 8bit 4bit 4bit 8bit ・・・ 8bit

さきほど出てきたLeaderというのはFrameの開始を示す信号です。これによって受信側はこれからデータが来ることがわかります。
この後にDataBitフォーマットで4bitもしくは8bitのデータが順番に二進数で表現されて続きます。
一般的に最初の二つの8bit信号はカスタマーコードと呼ばれ固有のコードが格納されています。その後4bitがparityコードと呼ばれるカスタマーコードのエラーチェック部になるようです。
次に4bitのデータ0のデータコードが格納された後、8bit単位でデータコードが格納されます。
そしてFrameの最後にはTrailerと呼ばれる信号が格納されて終了となります。

リモコンによってキーを押している間、ずっとframeが繰り返されたりリピート信号が送信されたりするそうなのでこのあたりは実際に信号を確認してみないとわかりません。

以上が信号フォーマットについてになります。

信号データの保存

次に受信した信号を4bitもしくは8bitのコードに変換して人間が読めるような形式に変換します。

まず信号をファイルに保存します。

$mode2 -d /dev/lirc0 | tee ファイル名

このコマンドを実行した後に、センサにリモコンを向けて信号を送信します。
いくつかの信号を送ったらctri+cで処理から抜けます。
すると指定したファイルに以下のように信号が記録されます。

space 4121433 
pulse 142 
space 2075 
pulse 145 
space 111986 
pulse 136 
space 12832 
pulse 142 
space 190883 
pulse 139 
space 1998 
pulse 287
space 272314
pulse 142
space 2031
pulse 235

mode2コマンドの出力がそのままファイルに保存されています。
これで信号出力を保存できました。

単位時間Tの取得

しかしこのままでは人間が理解できる形式ではないので分析できません。

分析するためにはまず単位時間Tを調べる必要があります。
家製協フォーマットはPuiseの長さが1Tと8Tしかありません。1TがFrameに大量に現れますが8TはFrameの最初1回しか現れません。
なので頻度分布を調べて最も頻度が高い値が単位時間Tになるはずです。

なのでまず保存したファイルの中のpulseで始まる行に書かれた値のヒストグラムをとってその最大となる範囲の値を平均化してTを求めます。

今回ヒストグラムは100μsec単位で0~10msecのレンジまでを解析するようにします。

hist = [0] * 100
sum = [0] * 100
for row in reader:
	#make histgram
	length = int(row[1])
	if row[0] == 'pulse' and length <= 10000:
		hist[length/100] = hist[length/100] + 1
sum[length/100] = sum[length/100] + length

hist配列に頻度、sum配列に平均値演算用の和を格納していきます。indexは2列目/100とすることで200から299まではindex=2というように簡単に振り分けができます。

index = hist.index(max(hist))
T = sum[index]/hist[index]

配列作成が終わったら最大頻度のindexを探してそのindexでの平均値を出力します。
この時得られたTは今回476となりました。

tmp_hist=[]
for x in range((index-1)*8,(index+1)*8+1):
	tmp_hist.append(hist[x])
tmp_index = tmp_hist.index(max(tmp_hist))
index = hist.index(tmp_hist[tmp_index])
T8 = sum[index]/hist[index]

次に8Tを探します。
8Tがあるであろうindex周辺でもっとも頻度が高いindexを探してそのindexの平均値を出力します。
この時得られた8Tは3531になりました。

Tをそのまま8倍にしても8Tと同じ時間になりません(476*8 = 3808 > 3531)
これは信号の立上がりと立下りに検出の遅れが発生しているためと推定されます。
なので今回検出されたTと8Tは真の単位時間{T_{true}}と検出遅れ{\alpha}を用いて表すと
{ 
 T = T_{true} + \alpha \\
 8T = 8T_{true} + \alpha
}
よって{T_{true}}はこの二つの差から求められます。

true_T = (T8-T)/7

これで単位時間Tが取得できます。
このコードでリモコンの出力する単位時間Tは436となりました。

以下に検出プログラムを公開しています。家製協フォーマットであれば以上のロジックで単位時間Tを検出できると思います。
https://github.com/kagemomiji/raspi3/blob/master/ir_detect_T.py
使用方法は以下になります。

 $python ir_detect_T.py ファイル名

信号のデコード

単位時間Tがわかりましたので次はpulseとspaceの信号を人間が読めるようにデコードします。

処理の流れとしては以下のようになります。

  1. mode2コマンドで取得したファイルを一行ずつ読み込む
  2. 各信号の長さを単位長さの何倍か計算する
  3. Leaderを検出する
  4. 1TのPulseを検出してその後のSpaceの長さで信号を判定する
  5. データ毎にビットを格納してビット反転を行う
  6. Frame配列にデータを格納する
  7. Frame配列に格納されたデータを16進数に変換して出力する。

順番に見ていきます

#detect pulse length
length = int(round(int(row[1])/T))

まず単位時間に対して何倍の長さかを検出します。
row[1]はmode2の出力の2列目(数値の部分)です。
最後のintが冗長な気もしますがこれでlengthに何倍という情報が格納されます。

if row[0] == 'pulse' and length == 8:
		leader_flag = 1	#8length pulse detect
	if row[0] == 'space' and length == 4 and leader_flag == 1:
		leader_flag = 2 #leader_detect
		data_counter = 0
		frame = []
		datum = 0
		pulse_flag = 0

次にleaderを検出します.pulseの長さが8Tの後にspaceの長さが4Tであればleaderと検出して以降の信号をFrameの信号という判定leader_flag = 2としています。
また、leaderの時に各変数を初期化しています。

if row[0] == 'pulse' and length == 1:
	data_counter = data_counter + 1
	pulse_flag = 1
if row[0] == 'space' and pulse_flag:
	#print "%d,%d" % (data_counter, length)
	#pulse_flag off
	pulse_flag = 0
	
	#detect bit on/off/trailer
	if length == 1:
		bit = 0
	elif length == 3:
		bit = 1

datum = datum << 1 | bit

1Tのpulseの後にspaceがあればそれを信号と判定してspaceの長さによってbitの値を判定します。
1Tなら0、3Tなら1です。
最後にdatumにbitシフトさせながら格納していきます。

			 
#detect 4bit datum(parity, data0)
if data_counter == 20 or data_counter == 24:
	frame.append(rev_bit.reverse_bit4(datum))	
	datum = 0
#detect 8bit datum(customer, data1~)	
elif data_counter % 8 == 0:
	frame.append(rev_bit.reverse_bit8(datum))
	datum=0

datumに格納されたデータをparityコードやdata0コードでは4bit毎にその他コードでは8bit毎にLSB/MSB反転を行います。
なぜならリモコン信号のデータ並び順はD0,D1,D2,D3....D7と下の桁から順番となっています。しかし数値処理する場合それは逆でD7,D6,...D1,D0というように並んでいなければなりません。
そのためLSB/MSBを反転する処理が必要です。
各コードごとに反転したコードをframe配列に順番に格納し行きます。

data_str = [hex(x) for x in frame]
writer.writerow(data_str)

最後にcsvに対してframeに格納されたデータを16進数表記に変換して信号を出力します。

以下に私の書いたサンプルコードがありますのでよければそちらをご確認ください。
https://github.com/kagemomiji/raspi3/blob/master/decode_ir.py

使い方は

$ python decode_ir.py 入力ファイル名

とすると "ファイル名_format.csv"として出力されます
このコードによってデコードされた我が家のエアコンのリモコン信号が以下になります。

0x2,0x20,0x0,0xe,0x4,0x0,0x31,0x3a,0x80,0xaf,0x0,0x0,0x6,0x60,0x0,0x0,0x80,0x0,0x16,0x9c
trailer
0x2,0x20,0x0,0xe,0x4,0x0,0x0,0x0,0x6
0x2,0x20,0x0,0xe,0x4,0x0,0x31,0x3c,0x80,0xaf,0x0,0x0,0x6,0x60,0x0,0x0,0x80,0x0,0x16,0x9e
trailer
0x2,0x20,0x0,0xe,0x4,0x0,0x0,0x0,0x6
0x2,0x20,0x0,0xe,0x4,0x0,0x31,0x3c,0x80,0xaf,0x0,0x0,0x6,0x60,0x0,0x0,0x80,0x0,0x16,0x9e
trailer
0x2,0x20,0x0,0xe,0x4,0x0,0x0,0x0,0x6

一つのボタンを押すと3行ずつがセットで出力されているようです。
状態信号+trailer+識別信号のような順番で出力されているようです。

信号対応付け

信号を分析できる形式で見ることができるようになりました。
ここですべてのボタンを押していきそれぞれの状態がどこにどのように割り当てられているのか分析していきました。
短い式別信号のような信号は基本的に固定されていたので長いほうの状態信号を調べました。
Dataコードの結果は以下のようになりました。

0 固定(4bit)
1 固定
2 固定
3 モード(上位4bit) 電源ステータス(下位4bit)
4 温度の2倍(8bit)
5 固定
6 風量(上位4bit) 風向(下位4bit)
7 固定
8 ONタイマの時間上位8bit
9 ONタイマの時間下位8bit/OFFタイマの時間上位8bit
10 OFFタイマの時間下位8bit
11 乾燥機能ON/OFF(上位7bit目)/静モード(上位6bit目)/パワフルモード/通常(下位4bit)
12 固定
13 固定
14 固定
15 におい防止 ON/OFF
16 カスタマーコードからデータコードを8bit単位にしたものの総和のうち下位8bit

最後のデータビットの解析に手間取りました。CRCチェックやXORなどを考えたのですが一番単純な総和でした。
データコードの12~14はもしかするとためしていないボタンがいくつかあるのでそれに割り振られているかもしれませんが
今回は解析をここで終了したいと思います。

10/1追記分
11バイト目と15バイト目について修正加筆しました。
メニューから設定される以下の設定信号について追加しました。
11バイト目は冷房、除湿機能時に有効になる乾燥機能
15バイト目はON/OFF機能だと考えていましたが、実際は冷房・除湿機能時に有効になるにおい防止
になります。
なかなかリバースエンジニアリングは奥が深いです。

まとめ

今回は家にあるエアコンのリモコン信号を取得して、家製協フォーマットの信号を解析しました。
そして信号の単位時間Tを求めて、人間が読める形に信号を変換して分析を行いました。
次はこの解析した信号をもとに信号を生成して出力することを目指します。

Rasberry Pi 3でリモコン信号(赤外線)を受信する

目的

今回はRasberry Pi3を用いてリモコン信号(赤外線)を取得することを目指します。

回路

まずリモコンの受信モジュールですが秋月電子さんで売られているこちらを使います。

消費電流が小さく3.3Vの電源に対応しています。

こちらを接続する回路は以下のようになります。
f:id:kagemomiji:20160820175914p:plain

GPIOとセンサの間にはGPIOの設定を誤っれ出力0Vとした時にショートしないように保護抵抗をいれています。

また、このセンサにはプルアップ抵抗が内蔵されているようなのでRaspberry PiのGPIOのプルアップを解除します。
今回私は詰めて実装したい人間なのでGPIO4を用いて実装します。
通常の設定だとGPIO16になります。適宜読み替えていただければと思います。

$raspi-gpio set 4 ip pn

その後このように配線します。
f:id:kagemomiji:20160820181958p:plain:w360
これで回路の準備ができました。

Rasberry Pi設定

Rasberry Piで信号を取得するためにlircを使用します.

$sudo apt-get install lirc

まずlircをインストールします。
次にlircのドライバを有効にしなければならないのですがRasberry Pi 3なのかRasbian jessieの設定のせいなのか最初は有効にすることができません。
Raspberry Pi • View topic - I2C, SPI, I2S, LIRC, PPS, stopped working? Read this.
このフォーラムに書いてある通り/boot/config.txtを編集しなければなりません。

#dtoverlay=lirc-rpi
 dtoverlay=lirc-rpi,gpio_in_pin=4

51行目のこの記述がコメントアウトされているのでこのコメントアウトを解除して入力するgpioの番号をセットします。これはピンの番号ではなくGPIO4といったほうの番号なので注意してください。何も設定しない(コメントアウトしただけ)であればGPIO16がInputとなっています。

Raspberry Pi 3 model B でエアコンをコントロールした話 - s4kr4.blog
次にこちらで紹介されているように/etc/lirc/hardware.confを編集します。

ここで一度Raspberry Piを再起動します。
これで受信の準備は完了しました。

mode2 -d /dev/lirc

このコマンドで受信する準備ができました。
センサに向けてエアコンのリモコンの信号を送って見ましょう。

space 4121433
pulse 142
space 2075
pulse 145
space 111986
pulse 136
space 12832
pulse 142
space 190883
pulse 139
space 1998
pulse 287
space 272314
pulse 142
space 2031
pulse 235

このような表示が出ていれば受信ができています。
最後にCtrl+cで処理から抜けます。

まとめ

今回は受信回路を設計してRaspberry pi3で受信するところまでできました。

次はエアコンの信号を解析して温度を設定できるように信号を生成するところまでしたいと思います。

Raspberry Pi 3 でK09421(BME280)のセンサ出力を定期的にcsvへ出力する

目的

 K09421からセンサ出力を取得できるようになりましたので今度は定期的にデータを取得してログファイルに残すことを目指します。

方針

f:id:kagemomiji:20160815221304p:plain
方針としてはcronでBME280から出力を読みとるプログラムを定期的に呼び出して"yyyyMMdd_bme280.csv"という形式で一日ごとにファイルを分割してcsvファイルに書き込むという流れです。
センサの値を読みとってcsvに出力するプログラムは前回に作ったものを改造して実装します。

hh:mm:ss,温度(℃),圧力(hPa),湿度(%)

保存するcsvのフォーマットはこのようにします。時間(hh):分(mm):秒(dd)としています。これは今後処理するときに容易に他のプログラムでも取り込めるようにiso拡張フォーマットとしています。
この方針に従って設定やコーディングを進めていきます。

コーディング

まず前回作成したセンサ値取得プログラムを先ほど決めた方針に従って書き換えていきます。
追加する機能は以下の2つです。

  • 現在の時刻と日付を取得する
  • 取得した時刻と日付からファイル名を生成し、時刻とセンサ値をcsvに書き込む

モジュールのインポート

ここで新しい機能を実現するために datetimeモジュールとcsvモジュールを追加します。

 import smbus
 #import time      #不使用
 import datetime  #追加  
 import csv    #追加

変数設定部

変数を設定する部分にデータを保存するディレクトリを設定します。

 i2c_address = 0x76
 DATA_DIR = '/home/pi/data/' #追加
 bus = smbus.SMBus(bus_number)

関数

次に変更を加える関数の一覧です。

  • readData
  • compensate_T
  • compensate_P
  • compensate_H

readDataではcsvへの書き出しを行うように変更します。
compensate_*ではreadDataにデータを受け渡すように変更を行います。

compensate_T

まず、compensate_Tでは以下のようにプログラムの最後に一文を追加します。

 print "temp : %-6.2f ℃ %" %(temperature)
 return temperature #追加
compensate_P
 print "pressure : %7.2 f hPa" %(pressure/100)
 return pressure/100.0 #追加
compensate_H
 print "hum : %6.2f %" %(var h)
 return var_h #追加
readData
 #get record time and date
 record_datetime = datetime.datetime.now() #現在の日時と時刻を取得
 record_file_name = record_datetime.strftime('%Y%m%d')+'_bme280.csv' #ファイル名を生成
 record_time = record_datetime.strftime('%X') #時刻文字列を生成

 data=[]

このように現在時刻を取得してファイル名と時刻の文字列を生成します。

 T=compensate_T(temp_raw)
 P=compensate_P(pres_raw)
 H=compensate_H(hum_raw)

先ほど返り値が出力されるように変更したのでその出力を変数に格納します。

 writer = csv.writer(open(DATA_DIR+record_file_name, 'ab'))
 writer.writerow([record_time,T,P,H])

csvをappendモードで開き、[record_time,T,P,H]の順番に配列を格納してcsvに書き込みます。

プログラム配布

以下に作成したプログラム bme280_csv.pyを公開しています。使用される方はご自由にご利用ください。
GitHub - kagemomiji/raspi3

cronの設定

次に作成したプログラムを定期的に実行するcronの設定をします。

 crontab -e

crontabを用いて設定します。

 */5 * * * * python {ファイルへの絶対パス}

上記のように5分おきに実行されるように設定しました。
10分おきでは */10 * * * * コマンド
1時間おきでは (実行したい分) * * * * コマンド
というように設定します。
設定を終わり編集を完了したらcronに設定が反映されその後実行されるようになります。

出力例

22:30:01,29.46672882,1001.354688,56.09067817
22:35:01,29.39716974,1001.405821,54.73285085
22:40:01,29.43100929,1001.44832,54.44254727
22:45:01,29.4075096,1001.442746,54.00647973
22:50:02,29.36019691,1001.457592,53.52315578
22:55:01,29.33419061,1001.406453,52.97012485
23:00:01,29.30661768,1001.435586,52.58357062
23:05:01,29.25241182,1001.510298,52.34396932
23:10:01,29.23110548,1001.519919,52.43595777
23:15:01,29.58548072,1001.406111,53.4440672
23:20:02,29.4075096,1001.373139,53.34538086
23:25:01,29.31006429,1001.384926,53.00620219
23:30:01,29.56448763,1001.39142,54.19737383
23:35:01,29.5441212,1001.356211,53.91712263
23:40:01,29.38996317,1001.359144,53.30443224
23:45:01,29.59174732,1001.302045,53.63377335
23:50:02,29.55947436,1001.348349,54.10231546
23:55:01,29.40030303,1001.362922,53.60009775

1時間のデータを確認してみましたがエアコンを付けているためほとんど変化していないことがわかります。

おわりに

これで定期的にログを取得するシステムが完成しました。
次はエアコンの制御をするためのリモコン部分の設計をしたいと思います。

K09421(BME280)のSwitch Sensorサンプルプログラムの詳細分析

目的

前回は定期的にログを取得するプログラムを作ると書いたのですが取得プログラムの仕様がわからないままでは勉強になりませんので
サンプルプログラムを詳細に見ていくことにしました。

サンプルプログラム

GitHub - SWITCHSCIENCE/BME280github.com
rev a43306eのものを参考とします。
以下に示した表はサンプルプログラムの関数一覧になります。

関数 機能
writeReg registryへ書き込む
get_calib_param registryから校正パラメータ読み込み
readData センサ出力の生値を取得する
compensate_P 圧力補正値を出力する
compensate_T 温度補正値を出力する
cmopensate_H 湿度補正値を出力する
set_up センサの設定を書き込む

これからそれぞれの関数について詳しく見ていきます。

writeReg

def writeReg(reg_address, data):
 bus.write_byte_data(i2c_address,reg_address,data)

i2c_addressは7行目にグローバルで定義されていて、通常であれば0x76となっています。
なのでreg_addressに変更するレジストリのアドレス、dataにそのアドレスに書き込む値(8bit)をいれることでレジストリの値が変更できるようになっています。
呼び出されているのはsetup()です。

get_calib_param

calib = []
for i in range (0x88,0x88+24):
	calib.append(bus.read_byte_data(i2c_address,i))
calib.append(bus.read_byte_data(i2c_address,0xA1))
for i in range (0xE1,0xE1+7):
        calib.append(bus.read_byte_data(i2c_address,i))

校正値が格納されているレジストリから値を読みだしてcalib配列に格納しています。
しかし格納されているのは0x88~0x9F,0xA1,0xE1~0xE7となっています。
取り扱い説明書に記載されているMemory Mapには0x88~0xA1,0xE1~0xF0にあると書かれています。
http://trac.switch-science.com/wiki/BME280
BME280 – Attachments – スイッチサイエンス
これではなぜこのアドレスが読まれているのかわかりません。そこでBME280のデータシートを確認してみます。
https://ae-bst.resource.bosch.com/media/_tech/media/datasheets/BST-BME280_DS001-11.pdf
すると22~23ページに校正値の保存されているアドレスが記述されています。
この表を簡易的にまとめると以下の表のようになります。

補正値種類 アドレス calib番号
温度 0x88~0x8D 0~5
圧力 0x8E~0x9F 6~23
湿度 0xA1,0xE1~0xE7 25~32

これでどうしてあのようなアドレス指定で校正値を取得しているのかがわかりました。

digT.append((calib[1] << 8) | calib[0])
digT.append((calib[3] << 8) | calib[2])
digT.append((calib[5] << 8) | calib[4])

digTという温度校正値を取得する配列にそれぞれ校正値を代入しています。

calib[1] << 8 | calib[0]

この処理でmsbとlsbを結合する処理をしています。
'<<8'で8bit左にシフト、'|'で論理和という処理になっています。
圧力補正値digPについても特に特殊な処理はなくdigP1~9までdigTと同様の処理で取得しています。
湿度については若干特殊なアドレス構成になっているのでシフトが4ビットとなっているものがありますがほぼ同様の処理をが行われています。

for i in range(1,2):
	if digT[i] & 0x8000:
		digT[i] = (-digT[i] ^ 0xFFFF) + 1

for i in range(1,8):
	if digP[i] & 0x8000:
		digP[i] = (-digP[i] ^ 0xFFFF) + 1

for i in range(0,6):
	if digH[i] & 0x8000:
		digH[i] = (-digH[i] ^ 0xFFFF) + 1 

次の処理はunsigned→signedの変換処理ですがこの処理は誤りがあります。
データシートP22からp23に書かれた表から型と校正変数についてのみ抜粋すると以下のようになります。

校正値
T1 unsigned
T2,3 signed
P1 unsigned
P2-9 signed
H1,H3 unsigned
H2,H4,H5 signed

これに従うと上記コードだけでは変換が不十分であることがわかります。以下のように書きなおす必要があります。

for i in range(1,3): #T2,T3
	if digT[i] & 0x8000:
		digT[i] = (-digT[i] ^ 0xFFFF) + 1

for i in range(1,9): #P2~P9
	if digP[i] & 0x8000:
		digP[i] = (-digP[i] ^ 0xFFFF) + 1

for i in [1,3,4]: #H2,H4,H5
	if digH[i] & 0x8000:
		digH[i] = (-digH[i] ^ 0xFFFF) + 1 

readData

	data = []
	for i in range (0xF7, 0xF7+8):
		data.append(bus.read_byte_data(i2c_address,i))

data配列に測定値が格納されている0xF7~0xFEのメモリの値を格納します。

	pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4)
	temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4)
	hum_raw  = (data[6] << 8)  |  data[7]

それぞれをMemoryMapに従ってビットシフトと論理和で結合してそれぞれの補正前の値を生成します。

	compensate_T(temp_raw)
	compensate_P(pres_raw)
	compensate_H(hum_raw)

最後に補正値を出力するプログラムを呼び出しています。

compensate_T

global t_fine

圧力や湿度の補正用の温度をグローバルで定義しています。

	v1 = (adc_T / 16384.0 - digT[0] / 1024.0) * digT[1]
	v2 = (adc_T / 131072.0 - digT[0] / 8192.0) * (adc_T / 131072.0 - digT[0] / 8192.0) * digT[2]
	t_fine = v1 + v2
	temperature = t_fine / 5120.0

ここが補正になっています。しかしこれではどういった補正なのかわからないので実際にこの式を何かしらの意味がある形に変形して数式に起こしていきます。
{
T  =\frac{T_{adc}-digT_{1} \times 2^{4}}{2^{20}}
 }
温度は32bitのsigned型に対して20bitのフォーマットで出力されているのでdig_T1(16bit)を4bit左にビットシフトして20bitフォーマットにして減算し、20bit右にbitシフトさせます。
{
T_{comp}=\frac{digT_{3}\times 2^{4}\times T^{2} + digT_{2}\times 2^{4} \times T}{2^8 \times 5}
}
このTに対して2次近似式で補正値を出力しています。
digT3の16bitを同様に4bit左にビットシフトして20bitにし2次の係数に、同様にdigT2の4bitシフトして20bitにして1次の係数として演算し、最後に8bit右にビットシフトしたものを5で除算して最終出力値としています。
{
T_{fine} = T_{comp} \times 5 \times 2^{10}
}
最後に圧力や湿度の補正用のt_fineはTcompを10bit左へビットシフト×5倍となっています。
とても難解でこの解釈は私の推測でしかないので気になる方は自分でもう少し掘り下げていただければと思います。

最後に以下のprint出力で温度を出力しています。

print "temp : %-6.2f ℃" % (temperature) 

compensate_P

global  t_fine
pressure = 0.0
v1 = (t_fine / 2.0) - 64000.0
v2 = (((v1 / 4.0) * (v1 / 4.0)) / 2048) * digP[5]
v2 = v2 + ((v1 * digP[4]) * 2.0)
v2 = (v2 / 4.0) + (digP[3] * 65536.0)
v1 = (((digP[2] * (((v1 / 4.0) * (v1 / 4.0)) / 8192)) / 8)  + ((digP[1] * v1) / 2.0)) / 262144
v1 = ((32768 + v1) * digP[0]) / 32768

こちらも同様に数式に起こしていきたいと思います。
先ほどの温度での{t_{fine}}{T_{comp}}の関係から

{
 v1 = (T_{comp}-25)\times 10 \times 2^{8}
}

最初に気温から25減算して 10を乗算し8bit左シフト下値がv1となります。ここから25℃を基準に補正をしていることがわかります。
ここで{T'=(v1 / 2^{16}) }とすると

{
 v2 = (\frac{digP_{5} \times T'^2}{2} + \frac{digP_{4} \times T'}{2}+digP_3) \times 2^{16}
}

と表わされます。2次近似式が生成されます。若干意味が通らない式なので何かしらの意図があってこのような値になっているのでしょう。

次に{T''=(v1/2^{19})}とすると

{
 v1 = digP_0 \times (\frac{(digP_2 \times T''^{2}+digP_1 \times T'')}{2^{15}}+1)
}

となります。こちらも温度の2次近似式ではありますが非常に小さい値になっていると考えられます。

if v1 == 0:
	return 0

v1で除算するため0の場合returnで抜けるようになっています。

pressure = ((1048576 - adc_P) - (v2 / 4096)) * 3125

これを数式に起こすと
{
 pressure = (2^{20}-P_{adc})-\frac{v2}{2^{12}})*10^5/2^5
}
となります。adc_Pも20bit仕様ですので2^20と減算をしてv2のオフセット項でオフセットさせた後、10万倍して5bit右シフトしています。v2に対して12bitも右シフトをしています。

if pressure < 0x80000000:
	pressure = (pressure * 2.0) / v1
else:
	pressure = (pressure / v1) * 2

この部分はオーバーフロー対策と精度を両立させるために計算順序を入れ替えているようです。
pythonでこの処理をする必要性がないので以下のように書き換えます。

 pressure = pressure * 2.0 /v1

最後に以下の処理を数式に起こします。

v1 = (digP[8] * (((pressure / 8.0) * (pressure / 8.0)) / 8192.0)) / 4096
v2 = ((pressure / 4.0) * digP[7]) / 8192.0
pressure = pressure + ((v1 + v2 + digP[6]) / 16.0)  

ここで{P' = pressure/2^{16}}とすると
{
 P_{comp} = pressure + \frac{(digP_8 \times P'^2 + digP_7 \times P')}{2^3}+\frac{digP_6}{2^4}
}
ここでは演算されたpressureに2次の近似補正式で補正を行っています。
分解すればするほど非常に難解な補正が入っていることがわかります。正直補正値を書き変えるのは難しいと感じます。

print "pressure : %7.2f hPa" % (pressure/100)

最後もprintで表示しています。 pressureはPa単位ですのでhPaにするために100で割っています。

compensate_H

global t_fine

補正用温度 t_fineを定義しています。

var_h = t_fine - 76800.0

t_fineとT_compの関係から数式に起こすと
{
 var_h = (T_{comp}-30)\times 10 \times 2^8
}
となります。ここから温度30度を基準にして補正していることが読み取れます。

if var_h != 0:
	var_h = (adc_H - (digH[3] * 64.0 + digH[4]/16384.0 * var_h)) * (digH[1] / 65536.0 * (1.0 + digH[5] / 67108864.0 * var_h * (1.0 + digH[2] / 67108864.0 * var_h)))
else:
	return 0
var_h = var_h * (1.0 - digH[0] * var_h / 524288.0)

この処理を数式に起こします。

{
 h_{temp} = (H_{adc}- (digH_3 \times 2^6 + \frac{digH_4 \times var_h}{2^{14}})) \times \frac{digH_1}{2^{16}} \times (1 + \frac{digH_5}{2^{26}}\times var_h + digH_2 \times digH_5 \times \frac{var_h}{2^{26}}^2 )
}

{
 humidity = h_{temp} * (1 - digH_0 \times \frac{h_{temp}}{2^{19}})
}
ここまで起こしてみましたが流石にこの補正のかけ方は何をしているのかさっぱりです。センサの設計思想がわからないとわかりません。

if var_h > 100.0:
	var_h = 100.0
elif var_h < 0.0:
	var_h = 0.0
print "hum : %6.2f %" % (var_h)

最後に%表示のために0~100までの値に制限して表示を行っています。

setup

def setup():
	osrs_t = 1			#Temperature oversampling x 1
	osrs_p = 1			#Pressure oversampling x 1
	osrs_h = 1			#Humidity oversampling x 1
	mode   = 3			#Normal mode
	t_sb   = 5			#Tstandby 1000ms
	filter = 0			#Filter off
	spi3w_en = 0			#3-wire SPI Disable
変数名 設定項目 意味
osrs_t 気温取得サンプリング数 1 x1
osrs_p 圧力取得サンプリング数 1 x1
osrs_h 湿度取得サンプリング数 1 x1
mode センサ動作モード 3 normal
t_sb スタンバイ時間 5 1000ms
filter IIRfilter 0 off
spi3w_en 3-sire SPI on/off 0 off

この設定ですが,今回私の用途は室内の温度を取得することが前提です。データシートにそのための設定例があるのでそちらに変更したいと思います。

def setup():
	osrs_t = 1			#Temperature oversampling x 1
	osrs_p = 5			#Pressure oversampling x 16
	osrs_h = 2			#Humidity oversampling x 2
	mode   = 3			#Normal mode
	t_sb   = 0			#Tstandby 0.5ms
	filter = 4                      #Filter off
	spi3w_en = 0			#3-wire SPI Disable
変数名 設定項目 意味
osrs_t 気温取得サンプリング数 2 x2
osrs_p 圧力取得サンプリング数 5 x16
osrs_h 湿度取得サンプリング数 1 x1
mode センサ動作モード 3 normal
t_sb スタンバイ時間 0 0.5ms
filter IIRfilter 4 16
spi3w_en 3-sire SPI on/off 0 off
ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode
config_reg    = (t_sb << 5) | (filter << 2) | spi3w_en
ctrl_hum_reg  = osrs_h
writeReg(0xF2,ctrl_hum_reg)
writeReg(0xF4,ctrl_meas_reg)
writeReg(0xF5,config_reg)

次にこれらの設定値を以下のメモリーマップに従って格納しています。
f:id:kagemomiji:20160814192920p:plain
それぞれアドレスに格納する値を設定し、writeReg関数でレジスタに書き込みをを行っています。

修正プログラム

以下に私の修正したbme280読み込み修正プログラムを公開しています。
利用される方はご自由にお使いください。
github.com

Raspberry Pi3 と K09421(BME280使用)のI2C接続

Raspberry Pi3 のI2Cを有効化

Raspberry Pi 3は設定を何もしないとI2C通信ができるようになっていません。
なので設定からI2C通信を有効化します。

$ sudo raspi-config

とコンソールに入力します。
f:id:kagemomiji:20160811154956p:plain
するとこのような画面が表示されます。
この中から 9 Advanced Optionsを選択してEnterを押してください。
f:id:kagemomiji:20160811155336p:plain
次にこのような画面が表示されます。その中のA6 I2Cを選択してEnterを押してください。
f:id:kagemomiji:20160811155521p:plain
最後にこの画面が表示され<はい>を選択するとI2Cが有効化されます。
その後最初の画面に戻るのでFinishを選択して終了してください。
以上がI2Cの有効化手順となります。

K09421(BME280使用)の準備

接続するセンサを準備します。

f:id:kagemomiji:20160811160046j:plain:w360

このように袋のなかにセンサ本体とピンが別々にはいっています。これらは半田づけをしてつけなければなりません。

f:id:kagemomiji:20160811160253j:plain:w360

まずこのようにピン6本セットをニッパーなどをつかって切り出します。


f:id:kagemomiji:20160811160413j:plain:w360
できたらこのように組み合わせて半田づけをしていきます。少し弱く細い半田ごてをお進めします。30Wは若干強すぎる気がしました。
f:id:kagemomiji:20160811163012j:plain:w360
つけ終わるとこのような感じになります。
次にセンサのI2C通信を有効化します。
f:id:kagemomiji:20160811160714j:plain:w360
図のように基盤右上のJ3に半田を乗せて導通させます。
これでセンサの準備が完了です。

Raspberry pi3とセンサの配線

K0942は下表のようなピン構成となっています。

ピン番号 信号
1 Vdd
2 GND
3 DNC
4 SDA
5 VccかGNDに接続するアドレス設定用ピン(通常はGND)
6 SCL

そこで配線を図に起こすと以下のようになります。
f:id:kagemomiji:20160811171154p:plain:w360
これをブレッドボードとジャンパ線を使って実装すると以下のようになります。
f:id:kagemomiji:20160811161114j:plain:w360

Raspberry Pi3からセンサの認識

次に接続したセンサが認識されているかを確認します。

$sudo apt-get install i2ctools 

上記コマンドでi2cを扱うためのツールをインストールします。
完了したら以下のコマンドを入力します。

 $ i2cdetect -y 1
  • y は対話式を無効にする。1はi2cのbus番号です。

正常であれば以下の出力が得られます。

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --

これはI2C通信を行っているアドレスを検出して出力するものです。
K0942の取り扱い説明書にはデフォルトアドレスは0x76とのことですから認識されていることが確認できました。

次にこのセンサの値を確認します。
この部分のやり方はよくわかっていないので何かしら簡単にできるツールを探してくることにしました。

www.switch-science.com
こちらのSWITCH SCIENCEでサンプルプログラムのpythongithubで公開されています。
紹介したページの下のほうにgithubへのリンクが張ってあります。

 $sudo apt-get install python-smbus

サンプルプログラムはsmbusを使用するためライブラリをインストールしておきます。

 $git clone https://github.com/SWITCHSCIENCE/BME280.git
 $cd BME280/Python27
 $python BME280_sample.py

サンプルプログラムをダウンロードして起動します。
すると以下のような出力が得られます。

temp:29.10 ℃
pressure:1006.03 hPa
hum: 50.57 %

これでセンサが正常に動作していることが確認できました。
若干値に誤差があるように感じるので校正が必要なようですが保留としたいと思います。
次はセンサ値を定期的に取得してログとして残すことに挑戦します。

Raspberry Pi3 センサの購入とプルアップ/プルダウン

センサ購入

kagemomiji.hateblo.jp

前回Raspberry Pi3とセンサをつなげる際に使用する通信規格を何にするかを考えてI2Cを採用することにしました。

そこで今回、秋月電子さんで以下のSPIとI2C対応の気温・湿度・気圧を取得できるセンサキットK-09421を購入しました。

BME280使用 温湿度・気圧センサモジュールキット: センサ一般 秋月電子通商 電子部品 ネット通販akizukidenshi.com

今回作成するシステムは湿度と温度を用いてエアコンを制御するシステム(詳しくは以下のリンク)でしたので気圧が取得できるのは若干オーバースペックですが、仕事ではないので扱えるデータが増えると割り切って使ってみたいと思います。

Raspberry pi 3でエアコン制御システム構築の構想とGPIO仕様について - Raspberry Pi3 で家庭用IoTkagemomiji.hateblo.jp

GPIOのプルアップ/ダウンについて

今回購入したセンサキットK-09421は基盤自体に通信線のプルアップ抵抗が乗っていて、はんだでジャンパさせることで有効化できます。
GPIOでI2Cに使用するのはGPIO2,GPIO3です。この端子におけるPullUp、もしくはPullDownがどうなっているのか調べてみました。
pinout.xyz
上記ページからGPIO2,GPIO3は1.8kΩのプルアップ抵抗が入っているようです。なのでセンサ側でPullUpする必要はありません。

ちなみにその他のGPIOはプログラム上どちらかに設定できるということでしたが実際に現状どうなっているのかわからりません。
なので確認するすべがないか探してみました。
結論から言うと以下のコマンドをraspbianのターミナル上で使用することで確認できます。

 $sudo raspi-gpio funcs

詳細を以下のコマンドで確認します。

 $ raspi-gpio help
raspi-gpio funcs [GPIO]
Note that omitting [GPIO] from raspi-gpio get prints all GPIOs.
raspi-gpio funcs will dump all the possible GPIO alt funcions in CSV format
or if [GPIO] is specified the alternate funcs just for that specific GPIO.

とあり raspi-gpio funcでGPIOの選択可能な機能がCSV形式で出力するコマンドのようです。
また、さらに下の記述でGPIOのpull up/downの抵抗は50kΩということも読み取れます。

 $sudo raspi-gpio funcs | less

何も設定していない時の出力は以下のようになります。(GPIO1~10まで)

GPIO DEFAULT PULL ALT0 ALT1 ALT2 ALT3 ALT4 ALT5
0 UP SDA0 SA5 PCLK AVEOUT_VCLK AVEIN_VCLK -
1 UP SCL0 SA4 DE AVEOUT_DSYNC AVEIN_DSYNC -
2 UP SDA1 SA3 LCD_VSYNC AVEOUT_VSYNC AVEIN_VSYNC -
3 UP SCL1 SA2 LCD_HSYNC AVEOUT_HSYNC AVEIN_HSYNC -
4 UP GPCLK0 SA1 DPI_D0 AVEOUT_VID0 AVEIN_VID0 ARM_TDI
5 UP GPCLK1 SA0 DPI_D1 AVEOUT_VID1 AVEIN_VID1 ARM_TDO
6 UP GPCLK2 SOE_N_SE DPI_D2 AVEOUT_VID2 AVEIN_VID2 ARM_RTCK
7 UP SPI0_CE1_N SWE_N_SRW_N DPI_D3 AVEOUT_VID3 AVEIN_VID3 -
8 UP SPI0_CE0_N SD0 DPI_D4 AVEOUT_VID4 AVEIN_VID4 -
9 DOWN SPI0_MISO SD1 DPI_D5 AVEOUT_VID5 AVEIN_VID5 -
10 DOWN SPI0_MOSI SD2 DPI_D6 AVEOUT_VID6 AVEIN_VID6 -

GPIO0~8はデフフォルトPullUp 9,10はPullDownのようです。ちなみに44,45はNoneという記述があるようですのでPullUp,PullDown,Noneの3状態が存在するようです。Noneの時は気を付けなければショートさせてしまいますので接続するまえにこのPullの欄は確認しましょう。

今回はここまでです。
次はセンサを接続して値を読み込たいと思います。