本記事は、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 で出力しています(下図)。
受信した信号は [ 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通信の最大周波数の設定が必要になったそうです.
参考
下記の記事を参考にしました。ありがとうございました。
コメント
コメント一覧 (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以上で動作はするようです(ただしサンプリング周波数の最大値が低下する)。
ご参考までに。。。