Raspberry Pi でSPI通信のAD変換 MCP3008 を使う

本記事は、Raspberry Pi で SPI通信できるAD変換の MCP3008 を利用することを目的とした記事です。MCP300xシリーズは、ラズパイ本だとよく出てくるので定番ぽいです。調べていると出てくる謎めいた式data = ((adc[1]&3) << 8) + adc[2]をちゃんと解説します。

目次

SPI方式のADC MCP3008

以下のようなスペック。データシートは「21295d.pdf」。

  • 量子化ビット数:10bit
  • 最大サンプリング周波数:200kHz(2.7V では 75kHz)
  • 入力8ch(差動なら4ch)
  • 動作電圧 2.7 - 5.5V

量子化は10bitですが、サンプリング周波数がMax 200kHzです。レートを落とせば8chまで使えます。OSがLinuxですし、連続的にではなく、単発あるいは断続的に取得する用途になると思うので、ch数の多さは魅力かもですね。安いし。例によってスイッチサイエンスさんから手軽に購入できます「Products - スイッチサイエンス」。Amazonでも。

SPIを使う準備

SPIが使えるように準備をします。

# カーネルのバージョンが3.18以上であることを確認。もしそれ以下なら rpi-update
$ uname -r
# SPI を有効化
$ sudo raspi-config
-> 9 Advanced Option -> A6 SPI より有効化、また有効化をデフォルトに設定(対話式)
$ sudo reboot
$ lsmod | grep spi
-> spi_bcm2835 がロードされていることを確認
# SPI通信に必要なPythonライブラリをインストール(入ってるかも)
$ sudo apt-get install python-spidev

ハードウェアの準備

SPIは4線式で、またMCP3008は接続するところが多いので、ちょっと面倒です。「Raspberry PiのPythonからTMP36のアナログ温度センサとMCP3008のADコンバータを使う - Qiita」の記事がたいへん参考になります。ここでは以下のように接続しました。

  • RPi(5V, 2pin) < -- > MCP([Vdd,Vref],[16pin,15pin])
  • RPi(GND, 6pin) < -- > MCP([AGND,DGND],[14pin,9pin])
  • RPi(SCLK, 23pin) < -- > MCP(CLK,13pin)
  • RPi(MISO, 21pin) < -- > MCP(Dout, 12pin)
  • RPi(MOSI, 19pin) < -- > MCP(Din, 11pin)
  • RPi(CE, 24pin) < -- > MCP(CS, 10pin)

Vrefに入力した電圧以下の電圧を測定します。今回は5Vを入れたので、5V以下を測ります。量子化10bitなので、分解能は5V/2^10で約5mVです。SPIの用語等についても少し。CLK信号は、信号のやりとりのタイミングを決める基準となる信号です。MISOは味噌…ではなく、Master Input Slave Outputで、意味はそのまま。MOSIはその逆です。CE/CS は Chip Select だそうですが、WikipediaだとSS(Slave Select)になってますね(「シリアル・ペリフェラル・インタフェース - Wikipedia」)。AGNDはアナログ信号のグランド、DGNDはデジタル信号のグランドです。本当は分けたほうがよいのですが、まぁテストなので。

今回、Raspberry Pi の 3.3V 電圧を、100kオームの抵抗を挟んで、MCP3008の1pin、すなわち1chの入力信号としました。この3.3Vを測ります。

スクリプト

スクリプトとその解説。スクリプトは「「Raspberry PiのPythonからTMP36のアナログ温度センサとMCP3008のADコンバータを使う - Qiita」を参考にさせていただきました。

スクリプトの実行

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import spidev
spi = spidev.SpiDev()
# spi.open(bus,device)
spi.open(0,0)
# 解説参照
def readAdc(channel):
    adc = spi.xfer2([1,(8+channel)<<4,0])
    print(adc)
    data = ((adc[1]&3) << 8) + adc[2]
    return data

def convertVolts(data, vref):
    volts = (data * vref) / float(1023)
    volts = round(volts,4)
    return volts

if __name__ == '__main__':
    # 1ch -> 0, ..., 8ch -> 7
    ch = 0
    data = readAdc(ch)
    print("adc : {:8} ".format(data))
    # MCP3008 の Vref に入れた電圧. ここでは 5V
    v = 5.0
    volts = convertVolts(data, v)
    print("volts: {:8.2f}".format(volts))

↑を~/spi/test.py として保存し、実行した結果↓

pi@raspberrypi:~/spi$ python test.py
[0, 2, 186]
adc :      698
volts:     3.41

次節で解説。

解説

mainにある、readAdc(ch) は、ch のデバイス(ここではMCP3008)と送受信のやりとりをする関数です。spi.xfer2 で、データを送信し、それと同じだけのデータを受信しています。adc = spi.xfer2([1,(8+channel)<<4,0])は、00000001 1xxx0000 00000000 を送信しているのと同じです。最初の 00000001 はスタートビット。1xxx は ch によります。たとえば、1chなら0なので、1xxx - > 1000 だし、2ch は 1 なので、1xxx -> 1001 となります。また、1xxx の1は、シングルエンドという意味で、ここを0にすると差動方式です。最後の 1xxx から後ろは特に意味がありません。このあたりの詳細は、データシートの5節を見ると、テーブルとタイミング図がのっています。

さて、同じだけのデータも返ってきて、それが変数 adc に格納されます。データシートを見ると、スタートビットから4bitだけ送信したあと、1.5 bit分の長さでサンプリングし、その結果を Dout から 10bit で出力しています(下図)。

17
データシートより抜粋

受信した信号は [ 0, 2, 186 ]、すなわち 00000000 00000010 10111010 です。タイミング図を見て、必要なところを取り出し、整形しましょう。その部分が data = ((adc[1]&3) << 8) + adc[2] に相当します。

まず最初のadc[0] = 0 、つまり 00000000 はスタートビットのところなので、無意味です。次の adc[1] = 2、つまり00000010 は、意味がありそうですが、数えると、最初の 4bit はこちらがチャネルなどの情報を相手に送っているところで、次の1bitはサンプリングの時間です。で、その次の1bit が Null で、その次の10bitがデータ本体ということになります。すなわち、00000010 のうち、上位6bit は必要ありません。それを式として表現すると、2進数11との論理積、つまり adc[1]&3 となります。これで下位2bitだけ取り出せました。この下位2bitは、データ全体では 9,10bit 目に相当するので、8bit 左にシフトし、その値に最後の1byteをくっつけると、求める値になります。この値が data で、readAdc() の返り値です。今回の例では、698 です。

さて、返り値 698 が実際に何 V であるかを計算するのが、convertVolts() です。これは簡単で、 Vref の電圧を量子化ビット分で除算した値で掛け算してやれば、求める電圧になります。ここでは、量子化10bitなので、 698 * 5.0V / 1023 となり、その計算結果が 3.41 V です。だいたいこんなところでしょうか。

見方がわかれば、MCP300xシリーズはデータシートを見ながら同じように使えるはず、です。

(コメントより)通信が失敗する場合

コメント欄より、以下のような情報をいただきました。私が確認できておらず申し訳ないですが、ご参考までに。

通信が失敗する方へ:

spi.open(0,0)
の直後に
spi.max_speed_hz = 1000000
という行を挿入するとうまくいく場合があるようです.

「カラー図解 最新 Raspberry Piで学ぶ電子工作」補足情報 https://raspibb1.blogspot.com/ の
「PDF11:spidevを用いてAD変換を行うプログラムをkernel 4.9.43以降で使う場合」
の項にあるとおり, kernel 4.9.43以降はkernelの変更によりSPI通信の最大周波数の設定が必要になったそうです.

参考

下記の記事を参考にしました。ありがとうございました。

本サイトはUXを悪くするevilなGoogle広告を消しました。応援してくださる方は おすすめガジェット を見ていってください!
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (6件)

  • 通信が失敗する方へ:

    spi.open(0,0)
    の直後に
    spi.max_speed_hz = 1000000
    という行を挿入するとうまくいく場合があるようです.

    「カラー図解 最新 Raspberry Piで学ぶ電子工作」補足情報 https://raspibb1.blogspot.com/
    「PDF11:spidevを用いてAD変換を行うプログラムをkernel 4.9.43以降で使う場合」
    の項にあるとおり, kernel 4.9.43以降はkernelの変更によりSPI通信の最大周波数の設定が必要になったそうです.

    • 情報ありがとうございます。記事に反映させていただきました。
      最近はWeb系にいって、先人たちに追いつかねばとそちらのことばかり、ラズパイ周りはすっかりご無沙汰になってしまいました……。

  • Raspberry Pi及びPythonを勉強中の者です。
    貴方のおかげで電子工作の際に頻出するbitの部分の演算子を理解することができました。
    非常に参考になりました。ありがとうございます。

    • お役にたててなによりです。勉強頑張ってください。
      だいぶ昔の記事ですがbitの演算は変わらないから大丈夫ですね😊

  • 現在Raspberry Pi と MCP3008 もしくは MCP3208 でのSPI接続を模索しております。
    記事は大変参考になりました。とても分かりやすいです。
    1点気になる点があります。

    MCP3008の仕様が読めないので無粋な質問かもですが、
    本記事では「RPi(5V, 2pin) MCP([Vdd,Vref],[16pin,15pin])」の記載によりRaspiから5Vを供給する事としておりますがRaspiのGPIOの取り扱い最大電圧が3.3Vとの認識です。
    SPI通信に係る信号線の電圧は3.3Vを超えない認識でよいでしょうか?
    あるいは、
    MCP3008に接続する計測デバイス(センサー等)が3.3V以下であれば問題ないでしょうか?

    • こんにちは。コメントありがとうございます。記事がお役に立てていれば幸いです。

      最近Raspberry Piをいじっていないので調べ直しましたが、記事中の2pinはGPIOピンではなく電源ピンかと思います。

      Raspberry Pi Documentation – Raspberry Pi hardware
      https://www.raspberrypi.com/documentation/computers/raspberry-pi.html

      ちなみに、データシートによると電圧2.7V以上で動作はするようです(ただしサンプリング周波数の最大値が低下する)。
      ご参考までに。。。

コメントする

目次