LoginSignup
31
29

More than 5 years have passed since last update.

Raspberry Pi + ruby + pi_piper + MCP3208でSPI通信 / アナログ入力

Last updated at Posted at 2014-12-30

問題

RaspberryPiはON/OFFの判断しか出来ないので、光センサーなどのアナログセンサーがそのままでは使用できない。
解決方法として、ADコンバーターを使用してSPI通信によりアナログ入力を検知する方法がある。

準備するもの

  1. RaspberryPi どのタイプでもOK - OSはRaspbianでやりました
  2. ADコンバーター(ここではMCP3208 - 秋月電子で300円)
  3. 何らかのアナログセンサー(ここでは サウンドセンサー - 秋月電子で690円)
  4. ブレッドボード
  5. RaspberryPiをSPI通信可能にする
  6. Rubyを入れる
  7. 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が表示されているか確認。
bash
$ 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 

MCP3208 各ピン名称
各ピン名称

GPIO

mcp3208.png

配線を上から順に言うと

  1. 3.3Vを使う
  2. GNDにつなぐ
  3. アナログセンサーの出力をMCP3208のCH0につなぐ
  4. GNDにつなぐ
  5. 3.3Vにつなぐ
  6. Vddを3.3Vにつなぐ
  7. Vrefを3.3Vにつなぐ
  8. AGND(アナログGND)をGNDにつなぐ
  9. CLK(クロック)をSCLKにつなぐ
  10. Dout(デジタルアウト)をMISOにつなぐ
  11. Din(デジタルイン)をMOSIにつなぐ
  12. CS/SHDNをCS0につなぐ
  13. DGND(デジタルGND)をGNDにつなぐ

MCP3208データシートを読む

ADコンバーターは次のような流れでデータが読み取れる。

  1. どのチャンネルのデータがほしいかを伝える = RaspberryPiからADコンバーターへの入力
  2. そのチャンネルに繋がっているアナログセンサーの値を返してくる = ADコンバーターからRaspberryPiへの出力

これだけ。とても簡単だが、データシートの読み方が教えてもらわないと全く分からない。

RaspberryPiからADコンバーターへの入力

MCP3208データシート 14ページ目の表表 5-2: MCP3208 の構成ビットで上の1番のどのチャンネルのデータをもらうかの設定方法が書いてある。この表は後に説明するビットを立てるか立てないかを説明している。
* 参考:MCP3204の表で「X」とあるのは使わないビットを表す

  1. SINGLE/DIFF
  2. D2
  3. D1
  4. D0

*SINGLE = シングルエンド
*DIFF = Differencial = 差動

参考:シングルエンド入力とは

  1. 今回のセンサーはシングルエンドの物(アナログ出力が1つ)なので、シングルエンドとして「1」を立てる
  2. CH0を使うとして、D2ビット目は「0」
  3. CH0を使うとして、D1ビット目は「0」
  4. 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とかになっている。

ここで問題なのは

  1. 最初の要素にはデータが無いので捨てればいい
  2. 真ん中の要素には右から4ビットまでにしか信頼するべきデータが無い
  3. 最後のビットは全てデータだけど、真ん中の要素と併せて考えないとちゃんとした値が出ない

この為に次のようにして真ん中のビットの処理、更には真ん中のビットと最後のビットを足す工夫が必要になる

# 最初の要素は捨てる
_, 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

その他参考

  1. Raspberry Pi GPIO Tutorial 3: Analog Inputs
  2. ADコンバーターとフォトレジスタを使って部屋の明るさを取得しよう。
  3. nterfacing an SPI ADC (MCP3008) chip to the Raspberry Pi using C++ (spidev)
  4. SPIモジュール(Serial Peripheral InterfaceModule)の使い方
  5. SPI Transfer Modes
31
29
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
29