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

[投稿日] 2016年4月18日
[最終更新] 2016年7月24日

本記事は、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シリーズはデータシートを見ながら同じように使えるはず、です。

参考

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

スポンサーリンク

コメントを残す

メールアドレスが公開されることはありません。