Raspberry PiでADコンバータ(MCP3208)を使う

投稿者: | 2014年7月23日

鈴木です。Raspberry Piに新モデルB+というのが登場するらしいですね。GPIOのピンが増えたのと消費電力が下がったのが私にとっては魅力的です。私のRaspberry PiはGPIO不足に悩んでいるのと、電池駆動なので消費電力にはシビアですので。

Raspberry Piでやったことを順番に紹介しようかとも思ったのですが、順番に読む人も少ないでしょうし、時系列気にせず、気が向いたネタから書いていくことにします。今回の記事はADコンバータというものについてです。

Raspberry Piのようなデジタル回路は、回路のどの部分も電圧が0Vか3.3Vのどちらかを示しています。3.3Vでなくて5Vの場合もありますが、とにかく電圧は一定で、その中間の電圧を扱うことはできません。そのため、連続に変化するアナログな電圧値を扱いたい場合は、アナログとデジタルを変換する必要があります。土壌センサーのように結果をアナログ電圧で出力するデバイスを使いたい場合です。

そんな場合に使うのがADコンバータです。ADというのは Analog to Digital の略です。先ほどの土壌センサーはArduino用と書かれていますが、ADコンバータを使えばRaspberry Piでも使えます。ArduinoはADコンバータを内蔵しているので、別途ADコンバータを用意する必要はないらしいです。

私は電池の電圧を測るためにADコンバータを使っています。電池の消耗具合を電圧の低下としてRaspberry Piが認識できるようにしています。とはいえ、まだ認識できるだけでそれを元に何かをしたりはしていないのですが。

で、ADコンバータは12ビット8チャンネルADコンバータ MCP3208を使いました。写真の左がMCP3208で、右が買ったら付いてきたICソケットです。

ADコンバータ MCP3208

12ビットというのはデジタルに変換したときの値が12ビットの精度を持っているという意味で、8チャンネルというのは同時に8箇所のアナログ電圧をデジタルに変換できるという意味です。12ビットなので0から4095までの整数を返します。このADコンバータのシリーズには他にも12ビットの代わりに10ビットのもの(MCP3008)や、8チャンネルの代わりの4チャンネルのもの(MCP3204, MCP3004)もあるようです。使い方はたぶん似たようなものだと思います。

下の写真の左に写っている黒い四角のICチップがADコンバータMCP3208です。ICソケットも使っていますが、ICソケットを使わなくても足が2.54mm間隔でブレッドボードにそのままつなげられます。

ADコンバータ MCP3208
ADコンバータ MCP3208

2つ目の写真では、下の方にMCP3208が写っています。

回路図は以下のようなイメージ。手書きで恐縮ですが。

ADコンバータ 回路図

MCP3208は四角の形状ですが、向きがわかるように一方に半円状の切り欠きがあり、回路図にも書いておきました。

このADコンバータとRaspberry Piをつなぐデジタル信号はSPIという規格を使い、3.3V電源とGNDの他に、SCLK, MISO, MOSI, CE0という4本の線でつなぎます。Raspberry PiのどこのピンにつなげばよいかはGPIOの配置図を見るとわかります。このGPIO配置図の上がRaspberry Pi基板上のSDカード側で、下が黄色の端子が付いている方です。SCLKはGPIO 11、MISOはGPIO 9、MOSIはGPIO 10、CE0はGPIO 8という番号がついており、後述のPythonのコードの中でこの番号を参照しています。回路図の3.3VはこのGPIO配置図でいう3V3と書いてある箇所で、複数あり、どこにつないでもよいです。回路図のGNDはGPIO配置図のGroundと書いてある箇所で、これもやはり複数ありますが、どこにつないでもよいです。

MCP3208のCH0〜CH7のピンは、電圧を測りたい箇所に接続します。8チャンネルなので8個あります。今回はCH7を8本のエネループから抵抗で5分の1に分圧したところにつないでいます。本当は測りたい電圧はエネループのプラス極なので、そこに直接CH7を接続したいのですが、MCP3208に3.3Vを供給しているのでAD変換できる電圧も3.3Vまでで、エネループ8本は電圧が高すぎて測定できないのです。そこで40kΩと10kΩの抵抗を使って、エネループの電圧を5分の1に落とした箇所を測定しています。なぜCH0でなくてCH7なのかというと、単にブレッドボードの配置上の問題でCH7のほうが配線しやすかったからです。回路図に40kΩと書いてある抵抗は、実際には手元に大量にあった10kΩの抵抗を4本直列につないでいます。

写真ではコードに隠れて見づらいのですが、MCP3208のすぐ近くに青い部品が写っています。これは0.1μFのセラミックコンデンサです。ノイズ除去のためにICの3.3VとGNDの間をセラミックコンデンサでつなぐとよいらしいのでつないでみたのですが、あってもなくても挙動の違いを感じられませんでした。このノイズ除去のためのコンデンサをパスコンと呼ぶらしいです。回路図にはパスコンは書いてありません。

後日、別の記事で紹介するつもりですが、回路図にある通り、エネループとRaspberry Pi本体との間にはS7V7F5という電圧レギュレータを挟んでいます。これは今回のMCP3208には直接は関係ありません。

Raspberry PiとMCP3208をつなぐSPIという規格は、Raspberry Piのデバイスドライバの設定ファイルを書き換えるだけで簡単に使えるようになるらしいです。デバイスドライバが読み込まれると /dev/spidev0.0, /dev/spidev0.1 というファイルができます。試したところ確かにファイルができていました。

しかし、実際に使ってみいようとしたところ、ADコンバータの値が常に0の値になってしまうような感じで、うまく使えませんでした。きっと何か間違えているのだと思いますが、原因がよくわかりませんでした。

なのでデバイスドライバを使わずに、SPIの規格通りにデジタル信号をGPIOで入出力するスクリプトコードを書きました。とはいってもサンプルコードがインターネット上にたくさんありましたので、それらを参考にしています。

コード例は以下のとおりです。

import sys
import time
import RPi.GPIO as GPIO

# GPIOの番号の定義。
# 上記回路図では、Raspberry Pi上の特定のピンに接続するかのように説明しましたが、
# たぶんここで定義する番号と接続するGPIOの番号が合っていれば動く気がする。
spi_clk  = 11
spi_mosi = 10
spi_miso = 9
spi_ss   = 8

# RPiモジュールの設定
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)

# GPIOデバイスの設定
GPIO.setup(spi_mosi, GPIO.OUT)
GPIO.setup(spi_miso, GPIO.IN)
GPIO.setup(spi_clk,  GPIO.OUT)
GPIO.setup(spi_ss,   GPIO.OUT)

# 0.1秒インターバルの永久ループ
while True:
    time.sleep(0.1)

    # 8チャンネル分のループ
    for ch in range(8):
        GPIO.output(spi_ss,   False)
        GPIO.output(spi_clk,  False)
        GPIO.output(spi_mosi, False)
        GPIO.output(spi_clk,  True)
        GPIO.output(spi_clk,  False)

        # 測定するチャンネルの指定をADコンバータに送信
        cmd = (ch | 0x18) << 3
        for i in range(5):
            if (cmd & 0x80):
                GPIO.output(spi_mosi, True)
            else:
                GPIO.output(spi_mosi, False)
            cmd <<= 1
            GPIO.output(spi_clk, True)
            GPIO.output(spi_clk, False)
        GPIO.output(spi_clk, True)
        GPIO.output(spi_clk, False)
        GPIO.output(spi_clk, True)
        GPIO.output(spi_clk, False)

        # 12ビットの測定結果をADコンバータから受信
        value = 0
        for i in range(12):
            value <<= 1
            GPIO.output(spi_clk, True)
            if (GPIO.input(spi_miso)):
                value |= 0x1
            GPIO.output(spi_clk, False)

        # 測定結果を標準出力
        if ch > 0:
            sys.stdout.write(" ")
        GPIO.output(spi_ss, True)
        sys.stdout.write(str(value))

    sys.stdout.write("\n")

インポートしている RPi というモジュールは、GPIOをPythonから操作するためのもので、Raspberry Piで標準のOSとなっているRaspbianに初めから入っているようです。

GPIOを触るので、このPythonコードはroot権限で実行する必要があります。実行すると、0.1秒のインターバルを置いて永久ループで値を読み出し続けます。Ctrl-Cを押して終了です。1回の読み出しで8チャンネル分の数字を整数で取得して、標準出力に吐き出します。整数は12ビットですので、0から4095です。結果が\(n\)の場合の実際の電圧は \( \frac{3.3 n}{4096} \) です、たぶん。いや、分母は\(4095\)なのかもしれませんが、よくわかりません。ただ、3.3VというのもRaspberry Piの3.3V出力に誤差がありますし、ADコンバータの変換誤差もありますので、細かいところは気にしないほうがよさそうです。

pi@raspberrypi ~ $ sudo python adc.py
0 12 11 22 11 18 9 2320
0 11 13 13 6 12 3 2321
0 11 9 12 7 10 3 2320
0 10 11 16 7 12 5 2324
0 10 12 13 10 10 7 2314
0 11 12 11 10 8 4 2325
0 10 12 12 7 11 8 2324
0 10 11 16 7 10 7 2320
0 11 11 16 7 10 5 2320
0 11 12 10 12 5 6 2239
0 9 7 12 5 6 8 2324
0 11 12 13 8 9 5 2322
0 10 9 12 5 8 5 2322
0 10 12 11 10 8 4 2320
0 11 12 10 12 8 6 2319
0 10 9 10 9 7 7 2322
0 5 7 15 8 9 3 2324
0 12 8 14 10 13 5 2326
0 9 8 11 8 7 8 2315
0 8 3 14 8 8 6 2323
0 9 8 11 8 9 5 2319
0 9 8 11 9 8 5 2317
0 10 12 13 6 13 6 2322

出力行の一番右がCH7の値です。数値が揺れているのでこれぐらいの誤差はあるんだなと考えています。CH7以外も0でない数値が表示されていますが、なにも接続しないとこのようになるみたいで、GNDに接続するとちゃんと0になります。上記実行例だとCH0が安定して0になっていますが、このときCH0もなにも接続していなかったのでちょっと不思議です。

次の記事では、気が変わらなければ電池でRaspberry Piを動かす方法を紹介しようと思います。土壌センサーの使い方はきっと別のスタッフが紹介すると思います。では。