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

### 問題

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

### 準備するもの

1. RaspberryPi どのタイプでもOK - OSはRaspbianでやりました
2. ADコンバーター（ここでは[MCP3208](http://akizukidenshi.com/catalog/g/gI-00238/) - 秋月電子で300円）
3. 何らかのアナログセンサー（ここでは [サウンドセンサー](http://akizukidenshi.com/catalog/g/gM-07038/) - 秋月電子で690円）
4. ブレッドボード
5. RaspberryPiをSPI通信可能にする
6. Rubyを入れる
7. [pi_piper](https://github.com/jwhitehorn/pi_piper)を入れる

### SPI通信有効化
初期の状態ではSPI通信がOFFになっているので、`sudo raspi-config`をして、SPIを有効にする。
[参考：raspi-config オフィシャル解説](http://www.raspberrypi.org/documentation/configuration/raspi-config.md)
[参考：raspi-config SPI有効化の説明](http://www.raspberrypi-spy.co.uk/2014/08/enabling-the-spi-interface-on-the-raspberry-pi/)

`8 Advanced Options`
から
`A5 SPI Enable/Disable automatic loading of SPI kernel module (needed for e.g. PiFace)`
を選択して有効にする。

その後再起動。

```bash
$ sudo reboot
```

再起動後、下のコマンドでspidev0.0、0.1が表示されているか確認。
```bash
$ ls /dev/spi*
/dev/spidev0.0  /dev/spidev0.1
```

上記だけではまだSPIが使えない。下のブラックリストファイルを開き、`spi-bcm2708`の行がコメントアウトされていないので、コメントアウトし`spi-bcm2708`をブラックリストから外す＝有効化

```bash
sudo nano /etc/modprobe.d/raspi-blacklist.conf
```

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

#blacklist spi-bcm2708
```

その後再度再起動。

```bash
$ sudo reboot
```

下のコマンドを打ち、`spi_bcm2708`が表示されればSPI通信有効化完了。

```bash
$ lsmod | grep spi_
```



### 配線方法

MCP3208は8チャンネル12ビット。8種類のアナログセンサーを繋げられて、12ビットの解像度＝111111111111、つまりアナログセンサーを0から4095までの数値で表現できる。
より少ないチャンネル数、より低い解像度のADコンバーターもあるが、値段はほとんど変わらない。

```rb
> 0b111111111111
=> 4095 
> 4095.to_s(2)
=> "111111111111"
> "111111111111".to_i(2)
=> 4095 
> "0b111111111111".to_i(2)
=> 4095 
```

[MCP3208 各ピン名称](http://ww1.microchip.com/downloads/en/DeviceDoc/21298b.pdf)
![各ピン名称](http://i.gyazo.com/06cc52ccfc4aacb058bc14f5220818a4.png)

![GPIO](http://i.gyazo.com/bb3a28f41fab4e1d7970910ea3228f00.png)

![mcp3208.png](https://qiita-image-store.s3.amazonaws.com/0/16757/2ebf0f5f-a10b-ae28-4af2-221a7f17a71d.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データシート](http://ww1.microchip.com/downloads/en/DeviceDoc/21298b.pdf)を読む

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

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

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


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

[MCP3208データシート](http://ww1.microchip.com/downloads/en/DeviceDoc/21298b.pdf) 14ページ目の表`表 5-2: MCP3208 の構成ビット`で上の1番のどのチャンネルのデータをもらうかの設定方法が書いてある。この表は後に説明するビットを立てるか立てないかを説明している。
* 参考：MCP3204の表で「X」とあるのは使わないビットを表す

![](http://i.gyazo.com/72ea3fb620389b843eaee9cd06477653.png)

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

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

[参考：シングルエンド入力とは](http://www.contec.co.jp/product/device/analog/basic.html)

1. 今回のセンサーはシングルエンドの物(アナログ出力が1つ)なので、シングルエンドとして「1」を立てる
2. CH0を使うとして、D2ビット目は「0」
3. CH0を使うとして、D1ビット目は「0」
4. CH0を使うとして、D0ビット目は「0」

実際にどのようにビットを立てるのかというと事が、16ページに書いてある。
この表は初めは全く意味が分からない。本来はCS(チップセレクト)、SCLK(クロック)などと併せてデータを流していくのだと思うけども、pi_piperの方でうまくやってくれるので、データ入力部分だけを考えればOK。

![](http://i.gyazo.com/1a1ec96da7c2d09d357548551107f7d5.png)

ここで重要なのは8ビットのデータを配列で3要素流すと言う事。
![](http://i.gyazo.com/0191a9e15ebf6b1ac6e0e1f65ef6b4e3.png)

**CH0を使うとしてつまりはこういう事**

```rb
        #開始ビット、シングルエンドビット、D2はビットなし
             #D1,D0はビットなし
                         #最後の要素は常にゼロ
[0b00000110, 0b00000000, 0b00000000]
```

これをpi_piperのSPIオブジェクトから書き込めば良い＝ADコンバーターへの入力

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

入力が間違いなければ、すぐに下のような出力が返ってくる。

![](http://i.gyazo.com/97662d304edf81ab612b37f7844120d8.png)
![](http://i.gyazo.com/c301a55f774b34e100baf2e4efa303e6.png)

これもつまりは8ビットのデータが配列で3要素返ってくることを意味している。
ここでB11からB0までのビットがある＝12ビットある＝0から4095の解像度があると言う意味になる。
* 参考：低い解像度の場合はそのデータシートを見ると配列が2要素とかでビット数が10とかになっている。

ここで問題なのは

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

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

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

出てきた値は解像度に対しての数値なので解像度に対してのパーセンテージ計算をする必要がある。これによりどの程度アナログセンサーが反応しているかが分かる。

```rb
RESOLUTION_AT_12BIT = 0b111111111111.to_f.freeze
percentage = value / RESOLUTION_AT_12BIT * 100
```

### まとめ

```rb
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
```

参考：複数チャンネル版

```rb
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](https://www.youtube.com/watch?v=oW0mTwFck_c)
2. [ADコンバーターとフォトレジスタを使って部屋の明るさを取得しよう。](http://lchikaamazon.hatenablog.com/entry/2013/11/30/232006)
3. [nterfacing an SPI ADC (MCP3008) chip to the Raspberry Pi using C++ (spidev)](http://hertaville.com/2013/07/24/interfacing-an-spi-adc-mcp3008-chip-to-the-raspberry-pi-using-c/)
4. [SPIモジュール（Serial Peripheral InterfaceModule）の使い方](http://www.asahi-net.or.jp/~gt3n-tnk/SPI1.html)
5. [SPI Transfer Modes](http://dlnware.com/theory/SPI-Transfer-Modes)