問題
RaspberryPiはON/OFFの判断しか出来ないので、光センサーなどのアナログセンサーがそのままでは使用できない。
解決方法として、ADコンバーターを使用してSPI通信によりアナログ入力を検知する方法がある。
準備するもの
- RaspberryPi どのタイプでもOK - OSはRaspbianでやりました
- ADコンバーター(ここではMCP3208 - 秋月電子で300円)
- 何らかのアナログセンサー(ここでは サウンドセンサー - 秋月電子で690円)
- ブレッドボード
- RaspberryPiをSPI通信可能にする
- Rubyを入れる
- pi_piperを入れる
SPI通信有効化
初期の状態ではSPI通信がOFFになっているので、sudo raspi-config
をして、SPIを有効にする。
参考:raspi-config オフィシャル解説
参考:raspi-config SPI有効化の説明
8 Advanced Options
から
A5 SPI Enable/Disable automatic loading of SPI kernel module (needed for e.g. PiFace)
を選択して有効にする。
その後再起動。
$ sudo reboot
再起動後、下のコマンドでspidev0.0、0.1が表示されているか確認。
$ ls /dev/spi*
/dev/spidev0.0 /dev/spidev0.1
上記だけではまだSPIが使えない。下のブラックリストファイルを開き、spi-bcm2708
の行がコメントアウトされていないので、コメントアウトしspi-bcm2708
をブラックリストから外す=有効化
sudo nano /etc/modprobe.d/raspi-blacklist.conf
# blacklist spi and i2c by default (many users don't need them)
#blacklist spi-bcm2708
その後再度再起動。
$ sudo reboot
下のコマンドを打ち、spi_bcm2708
が表示されればSPI通信有効化完了。
$ lsmod | grep spi_
配線方法
MCP3208は8チャンネル12ビット。8種類のアナログセンサーを繋げられて、12ビットの解像度=111111111111、つまりアナログセンサーを0から4095までの数値で表現できる。
より少ないチャンネル数、より低い解像度のADコンバーターもあるが、値段はほとんど変わらない。
> 0b111111111111
=> 4095
> 4095.to_s(2)
=> "111111111111"
> "111111111111".to_i(2)
=> 4095
> "0b111111111111".to_i(2)
=> 4095
配線を上から順に言うと
- 3.3Vを使う
- GNDにつなぐ
- アナログセンサーの出力をMCP3208のCH0につなぐ
- GNDにつなぐ
- 3.3Vにつなぐ
- Vddを3.3Vにつなぐ
- Vrefを3.3Vにつなぐ
- AGND(アナログGND)をGNDにつなぐ
- CLK(クロック)をSCLKにつなぐ
- Dout(デジタルアウト)をMISOにつなぐ
- Din(デジタルイン)をMOSIにつなぐ
- CS/SHDNをCS0につなぐ
- DGND(デジタルGND)をGNDにつなぐ
MCP3208データシートを読む
ADコンバーターは次のような流れでデータが読み取れる。
- どのチャンネルのデータがほしいかを伝える = RaspberryPiからADコンバーターへの入力
- そのチャンネルに繋がっているアナログセンサーの値を返してくる = ADコンバーターからRaspberryPiへの出力
これだけ。とても簡単だが、データシートの読み方が教えてもらわないと全く分からない。
RaspberryPiからADコンバーターへの入力
MCP3208データシート 14ページ目の表表 5-2: MCP3208 の構成ビット
で上の1番のどのチャンネルのデータをもらうかの設定方法が書いてある。この表は後に説明するビットを立てるか立てないかを説明している。
- 参考:MCP3204の表で「X」とあるのは使わないビットを表す
- SINGLE/DIFF
- D2
- D1
- D0
*SINGLE = シングルエンド
*DIFF = Differencial = 差動
- 今回のセンサーはシングルエンドの物(アナログ出力が1つ)なので、シングルエンドとして「1」を立てる
- CH0を使うとして、D2ビット目は「0」
- CH0を使うとして、D1ビット目は「0」
- CH0を使うとして、D0ビット目は「0」
実際にどのようにビットを立てるのかというと事が、16ページに書いてある。
この表は初めは全く意味が分からない。本来はCS(チップセレクト)、SCLK(クロック)などと併せてデータを流していくのだと思うけども、pi_piperの方でうまくやってくれるので、データ入力部分だけを考えればOK。
ここで重要なのは8ビットのデータを配列で3要素流すと言う事。
CH0を使うとしてつまりはこういう事
#開始ビット、シングルエンドビット、D2はビットなし
#D1,D0はビットなし
#最後の要素は常にゼロ
[0b00000110, 0b00000000, 0b00000000]
これをpi_piperのSPIオブジェクトから書き込めば良い=ADコンバーターへの入力
ADコンバーターからRaspberryPiへの出力
入力が間違いなければ、すぐに下のような出力が返ってくる。
これもつまりは8ビットのデータが配列で3要素返ってくることを意味している。
ここでB11からB0までのビットがある=12ビットある=0から4095の解像度があると言う意味になる。
- 参考:低い解像度の場合はそのデータシートを見ると配列が2要素とかでビット数が10とかになっている。
ここで問題なのは
- 最初の要素にはデータが無いので捨てればいい
- 真ん中の要素には右から4ビットまでにしか信頼するべきデータが無い
- 最後のビットは全てデータだけど、真ん中の要素と併せて考えないとちゃんとした値が出ない
この為に次のようにして真ん中のビットの処理、更には真ん中のビットと最後のビットを足す工夫が必要になる
# 最初の要素は捨てる
_, center, last = spi.write [.....]
# centerが 10100011 だったとして
# 00001111 をアンド演算すると
# ---------
# 00000011 となり、綺麗に1から4ビット目だけのデータが取れる
center = center & 0b00001111 # これによりゴミがある可能性のある、5から8ビット目は無視できる
# centerは2バイト目のデータなのに、今のままでは1バイト目の数値
# この為8ビット左にずらし、2バイト目の数値に変える
# 例
# 00000011 = 3 # center
# 00000001 = 1 # last
# 単純に centerとlastを足すと 4 になってしまうので、centerを8ビットずらし
# 00000011 00000000 = 768 # center
# lastと足せば
# 00000011 00000001 = 769
center = center << 8
value = center + last
出てきた値は解像度に対しての数値なので解像度に対してのパーセンテージ計算をする必要がある。これによりどの程度アナログセンサーが反応しているかが分かる。
RESOLUTION_AT_12BIT = 0b111111111111.to_f.freeze
percentage = value / RESOLUTION_AT_12BIT * 100
まとめ
require "pi_piper"
RESOLUTION_AT_12BIT = 0b111111111111.to_f.freeze
PiPiper::Spi.begin do |spi|
loop do
_, center, last = spi.write [0b00000110, 0b00000000, 0b00000000]
center = center & 0b00001111
center = center << 8
value = center + last
percentage = value / RESOLUTION_AT_12BIT * 100
puts "#{percentage} %"
sleep 0.1
end
end
参考:複数チャンネル版
require "pi_piper"
RESOLUTION_AT_12BIT = 0b111111111111.to_f.freeze
channels = {
ch0: [0, 0, 0],
ch1: [0, 0, 1],
}
PiPiper::Spi.begin do |spi|
loop do
channels.each do |channel_name, data|
_, center, last = spi.write ["0b0000011#{data[0]}".to_i(2), "0b#{data[1]}#{data[2]}000000".to_i(2), 0b00000000]
center = center & 0b00001111
center = center << 8
value = center + last
percentage = value / RESOLUTION_AT_12BIT * 100
puts "#{channel_name}: #{percentage} %"
end
sleep 0.1
end
end