なんとなく i2c-dev.h とかで定義されているAPIを呼ばないと使えないのかなあと思っていたけど、デバイスファイルの読み書きと ioctl だけで普通に使うことができた。

書きこみ

  • /dev/i2c-1 とかを rw+ で open
  • ioctl で I2C_SLAVE コマンドを使ってスレーブアドレスを指定する
  • write する

I2C 的には START -> SLAVE + W -> DATA送信 -> STOP が行われる。

読みこみ

  • /dev/i2c-1 とかを rw+ で open
  • ioctl で I2C_SLAVE コマンドを使ってスレーブアドレスを指定する
  • 読みこみたいアドレスを write する
  • read す

I2C 的には START -> SLAVE + W -> DATA送信 -> ReSTART -> SLAVE + R -> DATA受信 -> STOP が行われる。

たぶん一度 write/read するたびに Repeated Start が送られるのかな。

Ruby での例

Ruby でやる場合以下のような感じでいけた。デバイスによってはうまくいかないかもしれない。

class I2CDevice
	# ioctl command
	# Ref. https://www.kernel.org/pub/linux/kernel/people/marcelo/linux-2.4/include/linux/i2c.h
	I2C_SLAVE       = 0x0703

	attr_accessor :address

	def initialize(address)
		@address = address
	end

	def i2cget(address)
		i2c = File.open("/dev/i2c-1", "r+")
		i2c.ioctl(I2C_SLAVE, @address)
		i2c.write(address.chr)
		ret = i2c.read(1).ord
		i2c.close
		ret
	end

	def i2cset(*data)
		i2c = File.open("/dev/i2c-1", "r+")
		i2c.ioctl(I2C_SLAVE, @address)
		i2c.write(data.pack("C*"))
		i2c.close
	end
end

  1. トップ
  2. tech
  3. Linux 上の LL で I2C する (Ruby)
  1. トップ
  2. i2c
  3. Linux 上の LL で I2C する (Ruby)
  1. トップ
  2. raspberrypi
  3. Linux 上の LL で I2C する (Ruby)
  1. トップ
  2. ruby
  3. Linux 上の LL で I2C する (Ruby)

HTTP 0.9

RFC (rfc:1945) は1996年 (HTTP 1.0と同じRFCで説明されてる)

最初の HTTP。GET メソッドしかなかった。レスポンスヘッダもない (ボディーだけ)。

HTTP 1.0

RFC (rfc:1945) は1996年

  • メソッドに GET POST HEAD が追加された
  • ヘッダが追加された

HTTP 1.1

RFC (rfc:2068 → rfc2616) は1997年

HTTP 1.0 に Keep Alive とかを追加したやつ。

  • バーチャルホストに対応 (Host ヘッダが必須に)
  • Keep Alive が可能
    • 1つのTCPセッションで複数のファイルのやりとりができる
  • リクエストパイプラインが可能
    • リクエストを複数個レスポンスを待たずに送ることができる
    • サーバ側で対応が必要なのであまり有効活用されてない

HTTPS

RFC (rfc:2817, rfc:2818) は2000年

HTTP 1.0 または 1.1 を TLS (暗号化層) にのっけたもの (暗号化通信)

(SSL は TLS の元になったプロトコルの名前)

WebSocket

RFC (rfc:6455) は2011年。HTTP を拡張したもの、と言っていいか微妙だけど、HTTP でセッションを初期化して 101 Switching Protocols で専用プロトコルに切り替わる関連技術的なもの。

  • ブラウザでソケットのようにサーバと自由に送受信ができるようにするのが目的
  • サーバプッシュが可能
  • 普通 JS と一緒に使う

SPDY

Google が作ったプロトコル。現在は SPDY Protocol - Draft 3.1 が最新。

  • レンテンシを低くするのが目的
  • HTTPS のように TLS (暗号化層) にのっかってるので、常に暗号化通信
  • HTTP は SPDY プロトコル上にのっかる感じに
  • サーバープッシュが可能
  • サーバーヒント
    • 予め必要そうなファイルをサーバから送りつけることができる

HTTP 2.0

SPDY を元に提唱されてる新しい HTTP のバージョン。SPDY を HTTP 2.0 として標準化しよう、って感じかな。現状では SPDY と一緒。

QUIC

Google が作ってるあたらしいプロトコル。上記までのような HTTP の互換レイヤーの話ではなく、その下のレイヤーのプロトコル。

  • レンテンシを低くするのが目的
  • TCP の代替

技術の発展とともに TCP がネックになることが増えてきたので、UDP を使って TCP+TLS レイヤーを実装しなおすという感じ。

わからないところ

未来における WebSocket の立ち位置がよくわからない。HTTP 2.0 になったとして (SPDY 実装はすすんでいるし、たぶんなるだろう)、WebSocket はどうなるだろう。HTTP 2.0 上で WebSocket を使うのか、あるいは HTTP 2.0 のセッションを JS からコントロールできるような API が整備されるのか。

  1. トップ
  2. tech
  3. SPDY とか新しいプロトコルがよくわからないので簡単にまとめた
  1. トップ
  2. http
  3. SPDY とか新しいプロトコルがよくわからないので簡単にまとめた

Raspberry Pi だ! ππだ!! Raspberry Pi は約5000円ぐらいで買うことができるカードサイズの Linux パソコンです。当然スペックとしてはしょぼいのですが、ホームサーバ用途とかには十分な感じです。

まぁそれだけでも安くて便利なボードなのですが、キモはさらに GPIO (General Purpose IO) がついているところです。基板にピンが立っていて、そのピンを Linux 側から制御できるので、マイコンの延長として使うことができます。普通、マイコンといえども性能が良かったり複雑なインターフェイスをつけようとすると、結構コストがかかるので、多少複雑なことをしたい場合、このような安い Linux コンピュータで比較的富豪的に諸問題を解決するのは個人レベルではかなりコストパフォーマンスが良い気がします。

Raspberry Pi Type B 512MB ケースセット (clear) -

5.0 / 5.0

これです。クリアケース付きです。本体の他に

  • 4GB Class4 以上 SD カード
  • microUSB 5V 0.7A以上のACアダプタ (容量が多いほうがよい)
  • USB Wi-Fi アダプタ

が必要です。

SD カードの速度

Raspberry Pi 側は UHS-1 とか SDXC には対応していないので、仕様上 Class10 の SDHC 32GB というのが最大限富豪な選択になります。。

http://elinux.org/RPi_SD_cards を見ると、だいたい Read Write ともに 20MB/sec 程度でればいい方みたい (異常に早い報告もあるけどよくわからない)。

イメージの書きこみ

オンラインインストールのものではなく、普通にイメージを落としてきて Mac から書きこみました。特別別途ソフトがいるとかはないので楽です。

diskutil list
diskutil unmountDisk /dev/disk99
sudo dd bs=1m if=2013-09-25-wheezy-raspbian.img of=/dev/rdisk99
sudo diskutil eject /dev/rdisk99

指定するのはあくまでディスクであって、パーティションではないところに注意が必要です (/dev/disk2s1 とかはパーティション)。df で出てくるのはマウント済みのパーティション一覧で紛らわしいので、diskutil list を使ったほうが良いです。dd で指定するディスクファイルは r (raw) をつけて指定します。

dd の指定をミスると指定ディスクの内容が消えて悲しいのでこわい。

dd でイメージ作ってるけど、初期設定でパーティションを拡張するようになっているので、でかいカードを使っておいても損はしない。

初期設定

最初はキーボードと HDMI だけ繋げて起動します。

  1. Expand Filesystem を実行して、SDカード領域全域を使えるようになる。これは一瞬で終わります。
  2. Internationalization Options → Locale で en_US.UTF-8 をスペースで選択して、デフォルトを en_US.UTF-8 にする
  3. Internationalization Options → Timezone を Asia/Tokyo にする。
  4. Advanced Options → SSH を Enable にする。
  5. Finish して reboot をする。

reboot が終わったら、とりあえず pi/raspberry でログインしてみて

 sudo shutdown -h now

して終了させます。

Wi-Fi 設定

さっさと SSH したいのでネット設定します。

念のため終了して電源ケーブルを抜いてから USB Wi-Fi をつけます。

 -

3.0 / 5.0

でもう一度電源ケーブルをさして起動

IP は固定で指定します (SSH しやすいように)

$  sudo vi /etc/network/interfaces
auto lo
iface lo inet loopback
iface eth0 inet dhcp

allow-hotplug wlan0
iface wlan0 inet static
address 192.168.0.250
netmask 255.255.255.0
gateway 192.168.0.1
wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

# WiFi アダプタが異常に遅い現象が出るのでオフ
wireless-power off

iface default inet dhcp

めんどうなのでパスは直書き。

$ sudo su
# wpa_passphrase ESSID password >> /etc/wpa_supplicant/wpa_supplicant.conf
# exit

でもって

$ sudo ifdown wlan0
$ sudo ifup wlan0

以下のようなエラーが出た場合、less /var/log/daemon.log を見るとエラってる部分がわかるはず。

wpa_supplicant: /sbin/wpa_supplicant daemon failed to start

うまくいった場合何も表示されない。daemon.log を見てうまくいっているか確認し、ifconfig を実行する以下のように IP アドレスが割当られます。

pi@raspberrypi ~ $ ifconfig
eth0      Link encap:Ethernet  HWaddr b8:27:eb:01:5c:f2  
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:16436  Metric:1
          RX packets:22 errors:0 dropped:0 overruns:0 frame:0
          TX packets:22 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:2434 (2.3 KiB)  TX bytes:2434 (2.3 KiB)

wlan0     Link encap:Ethernet  HWaddr b0:c7:45:a9:9e:91  
          inet addr:192.168.0.250  Bcast:192.168.0.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:176 errors:0 dropped:0 overruns:0 frame:0
          TX packets:112 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:36200 (35.3 KiB)  TX bytes:17924 (17.5 KiB)

これで SSH できるようになったはずなので、手元のマシンからログインします。

$ ssh pi@192.68.0.250
pi@192.168.0.250's password: 
Linux raspberrypi 3.6.11+ #538 PREEMPT Fri Aug 30 20:42:08 BST 2013 armv6l

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Sep 26 07:18:58 2013
pi@raspberrypi ~ $

~/.ssh/authorized_keys に手元のマシンのキーを追加して公開鍵認証にしとく。これで、もはやキーボードと HDMI はいらないので外します。

dns-sd 化

IP アドレス固定にしているので必須ではないけど、手元のヒストリとかが綺麗になるし、便利なのでやっておく

$ sudo apt-get install avahi-daemon
$ sudo insserv avahi-daemon

で avahi-daemon を入れて、起動スクリプトを登録する。

以下のファイルを作って SSH をアナウンスするようにする。

$ sudo vim /etc/avahi/services/multiple.service
<?xml version="1.0" standalone='no'?>
<!DOCTYPE service-group SYSTEM "avahi-service.dtd">
<service-group>
        <name replace-wildcards="yes">%h</name>
        <service>
                <type>_device-info._tcp</type>
                <port>0</port>
                <txt-record>model=RaspberryPi</txt-record>
        </service>
        <service>
                <type>_ssh._tcp</type>
                <port>22</port>
        </service>
</service-group>

それで avahi-daemon を再起動

$ sudo /etc/init.d/avahi-daemon restart

これで名前でアクセスできるようになる。

$ dns-sd -B _ssh._tcp
$ ssh -c arcfour pi@raspberrypi.local

アップデート

まずこれをやらないとこわいのでやっておきます。upgrade で結構時間がかかります。1時間ぐらい。

$ sudo apt-get update
$ sudo apt-get upgrade

ここまできたらあとは普通に自分の環境をつくります。

環境作り

zsh は重いのでいれず、手元の dotfiles は .bashrc も入れてあるのでそれを使います。

$ ssh-keygen # して key を github に登録
$ cd
$ git clone git@github.com:cho45/dotfiles.git
$ cd dotfiles
$ ruby setup.rb
$ sudo apt-get install screen libncurses5-dev vim
$ screen -S main
$ curl -L http://cpanmin.us | perl - --sudo App::cpanminus

### 以下は時間がかかりすぎるのでやめた
$ mkdir tmp
$ cd tmp
$ wget ftp://ftp.vim.org/pub/vim/unix/vim-7.4.tar.bz2
$ tar xjvf vim-7.4.tar.bz2
$ cd vim-7.4
$ ./configure --prefix=/usr/local/vim7 --enable-multibyte --enable-gpm --enable-cscope --with-features=huge --enable-fontset --disable-gui --without-x --disable-xim --disable-perlinterp
$ make # かなり時間がかかる
$ sudo make install

raspi はさすがに遅いので vim のコンパイルとかはしないほうが良いみたいです。

perl はデフォルトで 5.14.2 が入っているので十分で、cpanm さえ入れればよさそうです。ruby は 1.9.4p194 が入っていて、まぁそのまま頑張るほうがよさそうです。

I2C できるように

デフォルトでは有効になっていないらしいので有効にします。

/etc/modules に以下を追加

i2c-dev


/etc/modprobe.d/raspi-blacklist.conf の以下の部分のコメントアウトする

# blacklist i2c-bcm2708

でリブートして

$ cat /var/log/dmesg | grep i2c
$  sudo apt-get install i2c-tools

確認して、関連ツールを入れておきます。

I2C 確認のため MPL115A2 (気圧計) を読む

仕様を確認しておくと

  • 3.3V
  • プルアップ抵抗は実装済み
  • 3.3V ピンから 50mA しかとれない
  • 5V ピンは供給電源から本体の電流(700mA)を引いたぶんだけ使える。1A のアダプタなら 300mA
    • しかしレギュレータで 3.3V 安定化するか、入出力部分はレベル変換が必要

となっています。ハードウェア部分はミスると普通に壊れるので、気をつけて作業する必要があります。

手元に、ワンチップで簡単そうなのは MPL115A2 という気圧計しかないのでこれを頑張って読むことにしました。3.3V で使えるし、消費電力もほとんどないので簡単そうです。

http://elinux.org/RPi_Low-level_peripherals を参考に結線すると i2cdetect にでてきます。このアドレスは仕様書に書かれてるのと一緒です。

$ 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: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: 60 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    

sudo してるのは /dev/i2c* のパーミッションが一般ユーザに許可されていないからですが、面倒なので pi ユーザも許可するように変えます。/dev/i2c* は i2c グループが所有者になっているのでi2c グループに pi ユーザを追加します。そのうち必要になりそうなのも追加しとく。

 sudo usermod -G i2c,spi,gpio,dialout pi

いっかい exit でログインしなおさないと反映されないっぽいです。

でもって MPL115A2 の仕様書通りに値を読みだして計算します。今回は Ruby で書いてみました。

#!/usr/bin/env ruby
# coding: utf-8
class I2CDevice
# ioctl command
# Ref. https://www.kernel.org/pub/linux/kernel/people/marcelo/linux-2.4/include/linux/i2c.h
I2C_RETRIES = 0x0701
I2C_TIMEOUT = 0x0702
I2C_SLAVE = 0x0703
I2C_SLAVE_FORCE = 0x0706
I2C_TENBIT = 0x0704
I2C_FUNCS = 0x0705
I2C_RDWR = 0x0707
I2C_SMBUS = 0x0720
I2C_UDELAY = 0x0705
I2C_MDELAY = 0x0706
attr_accessor :address
def initialize(address)
@address = address
end
def i2cget(address, length=1)
i2c = File.open("/dev/i2c-1", "r+")
i2c.ioctl(I2C_SLAVE, @address)
i2c.write(address.chr)
ret = i2c.read(length)
i2c.close
ret
end
def i2cset(*data)
i2c = File.open("/dev/i2c-1", "r+")
i2c.ioctl(I2C_SLAVE, @address)
i2c.write(data.pack("C*"))
i2c.close
end
end
class ACM1602NI < I2CDevice
MAP = Hash[
[
"。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚".split(//).map {|c|
c.force_encoding(Encoding::BINARY)
},
(0xa1..0xdf).map {|c|
c.chr
}
].transpose
]
def initialize
super(0x50)
initialize_lcd
end
undef i2cget
def initialize_lcd
# function set
i2cset(0, 0b00111100)
sleep 53e-6
# display on/off control
i2cset(0, 0b00001100)
sleep 53e-6
clear
end
def clear
i2cset(0, 0b00000001)
sleep 2.16e-3
end
def put_line(line, str)
str.force_encoding(Encoding::BINARY)
str.gsub!(/#{MAP.keys.join('|')}/, MAP)
str = "%- 16s" % str
# set ddram address
i2cset(0, 0b10000000 + (0x40 * line))
sleep 53e-6
i2cset(*str.unpack("C*").map {|i| [0x80, i] }.flatten)
sleep 53e-6
end
# Usage:
# lcd.define_character(0, [
# 0,1,1,1,0,
# 1,0,0,0,1,
# 1,1,0,1,1,
# 1,0,1,0,1,
# 1,1,0,1,1,
# 1,0,0,0,1,
# 1,0,0,0,1,
# 0,1,1,1,0,
# ])
def define_character(n, array)
raise "n < 8" unless n < 8
raise "array size must be 40 (5x8)" unless array.size == 40
array = array.each_slice(5).map {|i|
i.inject {|r,i| (r << 1) + i }
}
i2cset(0, 0b01000000 + (8 * n))
sleep 53e-6
i2cset(*array.map {|i| [0x80, i] }.flatten)
sleep 53e-6
end
end
class MPL115A2 < I2CDevice
def initialize
super(0x60)
coefficient = i2cget(0x04, 8).unpack("n*")
@a0 = fixed_point(coefficient[0], 12)
@b1 = fixed_point(coefficient[1], 2)
@b2 = fixed_point(coefficient[2], 1)
@c12 = fixed_point(coefficient[3], 0) / (1<<9)
p [@a0, @b1, @b2, @c12]
end
def fixed_point(fixed, int_bits)
msb = 15
deno = (1<<(msb-int_bits)).to_f
if (fixed & (1<<15)).zero?
fixed / deno
else
-( ( (~fixed & 0xffff) + 1) / deno )
end
end
def calculate_hPa
i2cset(0x12, 0x01) # CONVERT
sleep 0.003
data = i2cget(0x00, 4).unpack("n*")
p_adc = (data[0]) >> 6
t_adc = (data[1]) >> 6
p_comp = @a0 + (@b1 + @c12 * t_adc) * p_adc + @b2 * t_adc
hPa = p_comp * ( (1150 - 500) / 1023.0) + 500;
end
end
mpl = MPL115A2.new
loop do
puts "%d hPa" % mpl.calculate_hPa
sleep 0.5
end
view raw rb.rb hosted with ❤ by GitHub

校正値が入っていてそれを考慮して計算しないといけないので多少面倒だけど、比較的すぐ書けました。

やった〜! 天気図を見た感じではだいたいあってる感じです。センサー自体の精度が ±1kPa なので、下1桁は信用できない値です。

  1. トップ
  2. raspberrypi
  3. Raspberry Pi がきたのでセットアップして I2C デバイスを読むところまで
  1. トップ
  2. tech
  3. Raspberry Pi がきたのでセットアップして I2C デバイスを読むところまで