Raspberry Pi でI2C: 温度センサーを使う

Raspberry PiでI2Cデバイスをいくつか使ってみたので、何回かに分けて紹介したいと思います。

まずは秋月のADT7410を使用した温度センサーモジュールを接続し、温度データを読めるようになるまでの道のりを紹介します。
ただしこのデバイスRaspberry Piとは相性が悪く、ネイティブのI2Cドライバを使用したアクセスにはかなりの制限が避けられませんでした。
しかし回避策も一応存在しますのでそれも合わせて紹介します。

準備

バイス接続

GND, VDD(3.3V), SCL, SDA を接続します。Raspberry Pi のピンは以下のものを使用します。

とりあえず接続だけした状態です。
配線については追々きれいにしていきます。
下の方に伸びて緑色のスイッチにつながっている2本の線はシャットダウンスイッチなので今回は関係ありません。

センサーモジュールのボード上でSCL, SCAのプルアップができますが、
プロセッサ内でプルアップされているようなので(データシート確認していませんが)
バイス側では不要のようです。

また同様にI2Cスレーブアドレス(デフォルト0x48)の変更もできますので必要に応じてパッドをショートさせて設定してください。

カーネルモジュール

i2c-dev と、依存するモジュールとして i2c-bcm2708 が必要ですが、
i2c-bcm2708 はblacklist.confに入っていて自動ではロードしないようになっていますので、これを解除します。

/etc/modprobe.d/raspi-blacklist.conf
blacklist i2c-bcm2708 をコメントアウト

# blacklist spi and i2c by default (many users don't need them)

blacklist spi-bcm2708
#blacklist i2c-bcm2708

その上で、i2c-devをロードします。

% sudo modprobe i2c-dev

起動時に自動でロードさせたい場合は /etc/modules に i2c-dev を追加します。

必要パッケージのインストール

i2c-toolsをインストールします。

% sudo apt-get install i2c-tools

以上で準備は完了です。

バイスへのアクセス確認

バイス検出

i2cdetectでデバイスを検出してみます。コマンドラインオプション最後の'1'はI2Cバス番号ですが、
Raspberry Piのハードウェアリビジョンによって0か1か変わりますので注意してください。
(R1:0, R2:1)

% sudo /usr/sbin/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: -- -- -- -- -- -- -- -- 

I2Cアドレス0x48になにかいることがわかります。

レジスタread

ADT7410のデータシートによると、温度データは2byteでレジスタアドレス0x00がMSB, 0x01がLSB(Big Endian的な配置)です。
i2cgetを使ってアドレス0x00からwordアクセスしてみます。(後述しますが、0x00, 0x01へのバイトアクセス2回では動きませんのでご注意)

% sudo /usr/sbin/i2cget -y 1 0x48 0x00 w
0x180d

これをバイトスワップして(9.7)フォーマット(signed)で評価すると温度データ(摂氏)になります。
バイトスワップ→0x0d18
(9.7)で評価→26.1875(度C)

問題点

「0x00, 0x01へのバイトアクセス2回では動きません」と書きましたが、実際それを行うと以下のように0x00,0x01で全く同じデータが観測されてしまいます。

% sudo /usr/sbin/i2cget -y 1 0x48 0x00 b
0x0d
% sudo /usr/sbin/i2cget -y 1 0x48 0x01 b
0x0d

またi2cdumpで全レジスタを読み出してみても、以下のように明らかにおかしなデータとなります。

% sudo /usr/sbin/i2cdump -y 1 0x48 b
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c    ????????????????
10: 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c    ????????????????
20: 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 0c 00    ???????????????.
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 0c 0c 0c    .............???

読み出し単位を変えると少し変化しますが、最大でも4byteの繰り返しです。

% sudo /usr/sbin/i2cdump -y 1 0x48 i
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
10: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
20: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
30: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
40: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
50: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
60: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
70: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
80: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
90: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
a0: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
b0: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
c0: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
d0: 0c 58 80 00 0c 58 80 00 0c 58 80 00 0c 58 80 00    ?X?.?X?.?X?.?X?.
e0: 0c 50 80 00 0c 50 80 00 0c 50 80 00 0c 50 80 00    ?P?.?P?.?P?.?P?.
f0: 0c 50 80 00 0c 50 80 00 0c 50 80 00 0c 50 80 00    ?P?.?P?.?P?.?P?.

これはどうやらADT7410がI2CのRepeated Start Conditionという動作を要求する一方で、
Raspberry PiのプロセッサBCM2835のI2Cモジュールがそれに非対応ということが原因のようです。

きちんと理解していませんが、Stop Conditionを挟むとアドレスがリセットされてしまう、とかそんな感じ?でしょうか

これはもうハードウェアの問題なのでソフトウェアではどうしようもありません。
以下のサイトでは外付けロジックでSCLの論理を変えちゃおうという試みについて書かれていますのでどうしてもという方は参照してください。
http://www.circuitwizard.de/raspi-i2c-fix/raspi-i2c-fix.html

HiPi (I2C bit banging)

上記問題の1つの解決方法が、perlのHiPiというモジュールを使う方法です。
これはハードウェアのI2C機能を使わず、SCL,SDAピンをGPIOとして扱って全てソフトウェア的にロジックをコントロールしてしまおうというものです(この手のものをbit bangingと呼ぶようです)。
当然のことながらハードウェアコントロールに比べるとむちゃくちゃ遅いのですが、
それでもこちらはRepeated Start Conditionに対応しているのでとりあえず任意のアドレスにアクセスできます。

以下の手順でインストールします。

% perl -MCPAN -e 'install "LWP:Simple"'
% wget http://raspberry.znix.com/hipifiles/hipi-install
% perl hipi-install

アドレス0x00,0x01を読んでみます。

% sudo hipi-i2c r 1 0x48 0x00 1
12
% sudo hipi-i2c r 1 0x48 0x01 1
96

それらしい値が取得できました。(繰り返しますが、むちゃくちゃ遅いです)

またi2c-toolsではどうやっても読めなかった、アドレス0x0b(チップのID)を読んでみます。

% sudo hipi-i2c r 1 0x48 0x0b 1
203

データシートによると 0b11001xxx(0xc8-0xcf)が読めるはずですので、203=0xcbということでOKです。

幸いなことに、頻繁にアクセスする温度データは0x00-0x01にあるため
i2c-toolsのwordアクセス(ひいてはnativeドライバのAPI)で読み出せますし、
設定を変えるため諸々のレジスタにアクセスする場合はHiPiを使用することができるため、
結果的には一応フル機能使えるということになりそうです。

まとめ

以上でI2C温度センサーが使用できるようになりました。
一口にI2Cと言っても本デバイスのように相性が悪いものもあるみたいですが、この手の工作は多少の困難があった方が楽しいですよね (^^;

おまけ

ADT7410は温度スレッショルドを設定して割り込みを出す機能がありますので、
チップからRaspberry PiのGPIOに配線すればそのような機能も使うことができると思います。

国内IPのみからsshを受け付ける設定(Fedora firewalld)

自宅なりレンタルなり、自前でサーバーを立ち上げて公開するとあっという間に世界中からアタックがやってきます。
うちでは会社や出先からちょっと設定をいじったりソースを取り出したりしたいなという時のために自宅鯖のsshを開けていて、
アタックがあったらメールでお知らせみたいな運用をしばらくはしていたのですが、多すぎて辟易してしまいました。
アタック元を見るとほとんど海外、とくにcn,kr,twあたりというのが実情でありまして、
sshを開けている目的からすると365日全世界にオープンにしておく必要はないので今は国内IP以外はfirewallではじいています。

そんなわけで、今回もまたFedoraのfirewalldネタとして書いてみることにします。

(実際は、自分だけしか使わないなら例えばポート変えちゃってもいいんですけどね)

国内IPの判別

世界の国別 IPv4 アドレス割り当てリスト http://nami.jp/ipv4bycc/
で使いやすく整理していただいているものをありがたく使わせて頂きます。
というかここでやっていることが上記サイトの例の応用にすぎません。

firewalldによる特定IPのアクセプト/リジェクト

細かい設定は、firewall-cmd --direct を通じて行います。オプションはiptablesと同様です。
例)

firewall-cmd --direct --add-rule ipv4 filter INPUT 1 -m conntrack --ctstate NEW -m tcp -p tcp --dport 22 -s 192.168.0.0/16 -j ACCEPT

方針

22番ポートに関して、以下の方針でfirewallを組みます。

  • 基本ポリシーは、REJECT
  • ローカルネットワークのIP(192.168.0.0)はACCEPT
  • 国内IPはACCEPT

スクリプト

上記方針を実現するため、 /usr/local/sbin/allow-ssh-from-jp.sh を以下の内容で作成します。

#!/bin/sh

WORK_DIR=/var/firewall

verbose_exec()
{
        echo $*
        $*
}

#
# cd to working dir
#
if [ ! -d $WORK_DIR ]; then
        mkdir -p $WORK_DIR
fi
cd $WORK_DIR

#
# if -dl option is set, download cidr.txt.gz
#
if [ "$1" = "-dl" ]; then
        if [ -f cidr.txt.gz ]; then
                mv cidr.txt.gz cidr-old.txt.gz
        fi
        wget http://nami.jp/ipv4bycc/cidr.txt.gz
        if [ $? -ne 0 ]; then
                rm cidr.txt.gz
        fi
fi

if [ ! -f cidr.txt.gz ]; then
        echo cidr.txt.gz not found.
        exit 1
fi

#
# remove all current rules regarding port 22
#
firewall-cmd --direct --get-rules ipv4 filter INPUT | grep '\--dport 22' | while read rule; do
        verbose_exec firewall-cmd --direct --remove-rule ipv4 filter INPUT $rule
done

#
# accept local network
#
verbose_exec firewall-cmd --direct --add-rule ipv4 filter INPUT 1 -m conntrack --ctstate NEW -m tcp -p tcp --dport 22 -s 192.168.0.0/16 -j ACCEPT

#
# reject all (priority=3)
#
verbose_exec firewall-cmd --direct --add-rule ipv4 filter INPUT 3 -m conntrack --ctstate NEW -m tcp -p tcp --dport 22 -j REJECT

#
# accept from JP (priority=2)
#
zcat cidr.txt.gz | sed -n 's/^JP\t//p' | while read address; do
        verbose_exec firewall-cmd --direct --add-rule ipv4 filter INPUT 2 -m conntrack --ctstate NEW -m tcp -p tcp --dport 22 -s $address -j ACCEPT
done

IPの割り当て状況は変わりますので、このスクリプトを定期的に実行してfirewallを更新していく必要があると思いますが、
リスト提供元のサーバ側に迷惑のかからないように頻度にはご注意ください。
また、スクリプト実行に結構な時間がかかります(手元で数分程度)。起動スクリプトに組み込む場合はバックグラウンドで実行する等の工夫が必要です。