ソフトウェア開発って、他人が過去に作ったものに対する発見を重ねるみたいなところがあって、人工的に作られた過去の遺物を発見してうまく解釈するという、なんかよくSFモノにある、失なわれた技術を発掘していくみたいな、そういう感じ。
ソフトウェアは書いた通りにしか動かない。ソフトウェアのどこかの部品が壊れて(バグって)いたら必ず、なにかしら観測可能な状態に落としこめるような環境が整っている。
ソフトウェア開発って、他人が過去に作ったものに対する発見を重ねるみたいなところがあって、人工的に作られた過去の遺物を発見してうまく解釈するという、なんかよくSFモノにある、失なわれた技術を発掘していくみたいな、そういう感じ。
ソフトウェアは書いた通りにしか動かない。ソフトウェアのどこかの部品が壊れて(バグって)いたら必ず、なにかしら観測可能な状態に落としこめるような環境が整っている。
ハードウェア部分は、人間が工夫して設計しているとはいえ、基本は物理現象(電子の動き)を利用していて、それが自分には不思議で面白い。ソフトウェアレイヤーをいじっている限りでは物理現象を利用しているという感覚は一切なく「他人がどういう意図で設計したか?」という、物理というよりは精神なことを考えていることが多い。
ハードウェア、回路レベルの設計は全体的にパズル的で、動いているのが不思議な感じがする。ソフトウェアでこういうパズル的なことをやろうとしたらテストを書かないと殺されると思うけど、頑張ってだいたいうまく動いてる感じで感心すると同時に、こんなので大丈夫なのかと心配になる。
しかし実際には精度良くハードウェアは動くように設計されていて、そういう頑張ってる感じのパズル的に動くロジックの大量の組合せでCPUが構成されて、その上で機械語が実行されていて、機械語を生成するための言語があって、Cから機械語を生成するためのツールチェインがあって、それらを利用してさらに抽象化されたレイヤーで書けるLLがあって、なんだか考えていると、LLでコードを書くのはジェンガの上のほうでコードを書いている感じに思えてくる。
GPIO の操作はいろいろやる方法があるみたいだけど、LL からだと sysfs への IO を行うのが一番簡単っぽい。以下のような感じでかなり簡単に書ける。この程度だとライブラリを使う必要はない。
pin の数値は、RPi Low-level peripherals で書いてあるような GPIO n の n の部分を指定する。(物理的なピンの並びとは関係ない)
この例だと GPIO 25 (22pin) で指定時間ごとに論理が反転する。
#!/usr/bin/env ruby
# coding: utf-8
module GPIO
def self.export(pin)
File.open("/sys/class/gpio/export", "w") do |f|
f.write(pin)
end
end
def self.unexport(pin)
File.open("/sys/class/gpio/unexport", "w") do |f|
f.write(pin)
end
end
def self.direction(pin, direction)
[:in, :out].include?(direction) or raise "direction must be :in or :out"
File.open("/sys/class/gpio/gpio#{pin}/direction", "w") do |f|
f.write(direction)
end
end
def self.read(pin)
File.open("/sys/class/gpio/gpio#{pin}/value", "r") do |f|
f.read.to_i
end
end
def self.write(pin, val)
File.open("/sys/class/gpio/gpio#{pin}/value", "w") do |f|
f.write(val && val.nonzero? ? "1" : "0")
end
end
end
GPIO.export(25)
GPIO.direction(25, :out)
led = true
loop do
GPIO.write(25, led)
led = !led
sleep 0.5
end
無線のPCモールスUSBインターフェイスって結構高価なのが多いんだけど、Raspberry Pi だと GPIO 経由で普通にキーイングできるし、単体で HDMI やコンポジットビデオ出力できるから、これで十分すぎる感じがする。
GPIO に UART があるけど、デフォルトではシリアルコンソールとして使うことが想定されていて、カーネルメッセージとかが流れる。これを無効にして、普通のシリアルポートとして使う。
/etc/inittab を書きかえる。ttyAMA0 の行をコメントアウトする
#Spawn a getty on Raspberry Pi serial line T0:23:respawn:/sbin/getty -L ttyAMA0 115200 vt100
/boot/cmdline.txt を書きかえる。デフォルトだと以下のようになっているので
dwc_otg.lpm_enable=0 console=ttyAMA0,115200 kgdboc=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
して以下のように
dwc_otg.lpm_enable=0 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline rootwait
で reboot すると普通にユーザレベルで自由に使えるようになる。
3.3V なので、RS-232C レベルにするなら 3.3V でも使えるドライバICを使う必要がある。ICL3232 だと100円ぐらい。
FET らへんは GPIO 25 (22pin) をオンにしているときだけスイッチを入れるための仕組みなので RS-232C のためには必須ではない (GND を普通に繋ぐだけ)
なんとなく i2c-dev.h とかで定義されているAPIを呼ばないと使えないのかなあと思っていたけど、デバイスファイルの読み書きと ioctl だけで普通に使うことができた。
I2C 的には START -> SLAVE + W -> DATA送信 -> STOP が行われる。
I2C 的には START -> SLAVE + W -> DATA送信 -> ReSTART -> SLAVE + R -> DATA受信 -> STOP が行われる。
たぶん一度 write/read するたびに Repeated Start が送られるのかな。
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
RFC (rfc:1945) は1996年 (HTTP 1.0と同じRFCで説明されてる)
最初の HTTP。GET メソッドしかなかった。レスポンスヘッダもない (ボディーだけ)。
RFC (rfc:1945) は1996年
RFC (rfc:2068 → rfc2616) は1997年
HTTP 1.0 に Keep Alive とかを追加したやつ。
RFC (rfc:2817, rfc:2818) は2000年
HTTP 1.0 または 1.1 を TLS (暗号化層) にのっけたもの (暗号化通信)
(SSL は TLS の元になったプロトコルの名前)
RFC (rfc:6455) は2011年。HTTP を拡張したもの、と言っていいか微妙だけど、HTTP でセッションを初期化して 101 Switching Protocols で専用プロトコルに切り替わる関連技術的なもの。
Google が作ったプロトコル。現在は SPDY Protocol - Draft 3.1 が最新。
SPDY を元に提唱されてる新しい HTTP のバージョン。SPDY を HTTP 2.0 として標準化しよう、って感じかな。現状では SPDY と一緒。
Google が作ってるあたらしいプロトコル。上記までのような HTTP の互換レイヤーの話ではなく、その下のレイヤーのプロトコル。
技術の発展とともに TCP がネックになることが増えてきたので、UDP を使って TCP+TLS レイヤーを実装しなおすという感じ。
未来における WebSocket の立ち位置がよくわからない。HTTP 2.0 になったとして (SPDY 実装はすすんでいるし、たぶんなるだろう)、WebSocket はどうなるだろう。HTTP 2.0 上で WebSocket を使うのか、あるいは HTTP 2.0 のセッションを JS からコントロールできるような API が整備されるのか。
Raspberry Pi だ! ππだ!! Raspberry Pi は約5000円ぐらいで買うことができるカードサイズの Linux パソコンです。当然スペックとしてはしょぼいのですが、ホームサーバ用途とかには十分な感じです。
まぁそれだけでも安くて便利なボードなのですが、キモはさらに GPIO (General Purpose IO) がついているところです。基板にピンが立っていて、そのピンを Linux 側から制御できるので、マイコンの延長として使うことができます。普通、マイコンといえども性能が良かったり複雑なインターフェイスをつけようとすると、結構コストがかかるので、多少複雑なことをしたい場合、このような安い Linux コンピュータで比較的富豪的に諸問題を解決するのは個人レベルではかなりコストパフォーマンスが良い気がします。
これです。クリアケース付きです。本体の他に
が必要です。
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 だけ繋げて起動します。
reboot が終わったら、とりあえず pi/raspberry でログインしてみて
sudo shutdown -h now
して終了させます。
さっさと SSH したいのでネット設定します。
念のため終了して電源ケーブルを抜いてから USB Wi-Fi をつけます。
でもう一度電源ケーブルをさして起動
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 はいらないので外します。
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 が入っていて、まぁそのまま頑張るほうがよさそうです。
デフォルトでは有効になっていないらしいので有効にします。
/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
確認して、関連ツールを入れておきます。
仕様を確認しておくと
となっています。ハードウェア部分はミスると普通に壊れるので、気をつけて作業する必要があります。
手元に、ワンチップで簡単そうなのは 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 |
校正値が入っていてそれを考慮して計算しないといけないので多少面倒だけど、比較的すぐ書けました。
やった〜! 天気図を見た感じではだいたいあってる感じです。センサー自体の精度が ±1kPa なので、下1桁は信用できない値です。
というのを作ってみた。
AVR に限らないけど、マイコンで時間を測るにはCPUクロックを数えるわけですが、欲しい時間に対して分周比とかを求めるのが面倒なのでかいた。
F_CPU は CPU クロック数、Seek Freq. のほうに欲しい周波数または時間間隔を入れて、Calculate を押すと、各分周比において CTC でいくつを設定すればいいか、あるいはオーバーフローでいけるかどうかとかを出す。
16MHz で 1msec を測りたい場合、
Pre-scaler:1, 16bit Timer CTC:16000 Pre-scaler:8, 16bit Timer CTC:2000 Pre-scaler:64, 8bit Timer CTC:250 Pre-scaler:256, no result for 62.5 Pre-scaler:1024, no result for 15.625
このようにでる。この場合は 8bit タイマーで 64 分周して CTC を 250 に設定したらよい。
買ったSDカードあるいは持っているSDカードの速度がどんなものか測りたいとき、Mac だといまいち良いツールがなかったけど、いつのまにか良さそうなのがでてた。
ただ、SDカードの領域を埋めつくすまで書きこもうとするのでかなり時間がかかるのがイマイチな感じ。意図としてはカード全体がちゃんと動いてるかどうかをチェックという感じだと思う。実際、途中から速度が大幅に変わったりする。
以下のスクリーンショットは手元にあった microSD カード (カード上の表記は Class10) を測定中のもの。最初は 6MB/s だったけど、途中から 11MB/s ぐらいになった。Class10 は綺麗な状態での書き込み10MB/sを保証するので、仕様には適合している。