【ラズパイ】A/Dコンバータ MCP3208と通信するコードの解説
- 2022.08.12
- IT
ラズパイでADコンバーターMCP3208を使いたく、コードを探したのですが、
どれも分かりにくい、というより、何をしているのか分からなかったので、その解説です。
2進数の計算とチップの仕様とSPI通信を分からないといけないので、難しいですね。
解説するコード2つ
1つは、仕組みを理解するのに有用だったコード。
2つ目は、使うのに便利なコードです。
(僕は書いていない)
1つ目の仕組みを理解するのに役立ったコード
Qiitaのこのコードです。
すでに解説書いてくれていますが、これすら分からない読解力だったので。。
下記が僕のコメント付きのコードです。
“””
”””
のコメントが追記部分です。
print文が()なかったので追加してます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
import RPi.GPIO as GPIO from time import sleep def initialize(): #制御手順1 GPIO.output(SPICE0,GPIO.HIGH) #このHigh、Lowで1クロックを再現 """ SPICE0,は、SPI通信で、どのセンサーと通信するのかの情報のやり取りをします。 まずは、HIGHを送って初期化して、 GPIO.output(SPICE0,GPIO.LOW) の部分で、LOWを送り続け、その間はそのセンサーと通信します。 """ GPIO.output(SPICLK,GPIO.LOW) """ SPICLKは、SPI通信でデータを送受信するさいの、ラズパイとセンサーの同期のために使います。 LOW(データのやり取り待ち)→HGH(データのやり取り)→LOW(データのやり取り終了) という感じです。 """ def sendToMCP3208(): #制御手順3 GPIO.output(SPICE0,GPIO.LOW) """ initialize()でも書きましたが、ここで、LOWとすることで、ラズパイはLOWとしたMCP3208との通信を行います。 """ Transmitted_Data1 = 0b00000110 #10ビット中この8ビットを最初に入力する """ MCP3208のデータシートを見ると分かるのですが、MSB(Most Significant Bit一番左の桁)から5番目の桁0までは、 送らなくてもOKなものですが、理解のために送るとのことです。 次の桁の1は、スタートビット1、次の桁の1は、Single-endedモードの1、 LSB(Least Significant Bit)(右最後の桁)の0は、チャンネルを指定する3ビットの3ビット目です。 チャンネルが8個あり、2進数を3ビット(000)を用いて、選択します。 次のTransmitted_Data2のMSBから2桁で、3桁の残りの2桁を送信します。 例えば、MCP3208の7ch(0ch始まり)の情報を取得したい場合は、 Transmitted_Data1 = 0b00000111 Transmitted_Data2 = 0b11000000 とします。 Transmitted_Data1のLSBの1とTransmitted_Data2のMSBから2桁の11です。 """ for i in range(8): """ ここで8桁送信します。 SPI通信の仕組み的に受信も同時に行うのですが、受信データは不定のデータなので受け取る意味がありません。 なので、送信だけしています。 """ if Transmitted_Data1 & 0b10000000: #MSBが1ならTrue GPIO.output(SPIMOSI,GPIO.HIGH) #1をセット else: GPIO.output(SPIMOSI,GPIO.LOW)#0をセット """ Transmitted_Data1 & 0b10000000では、 &演算をして、0以外であれば、Trueとして、GPIO.output(SPIMOSI,GPIO.HIGH) #1をセット が行われます。 &演算なので、0b10000000との各桁を比較して同じ桁が同じ値であれば、その値となります。 0b10 & 0b11 = 0b10 """ GPIO.output(SPICLK,GPIO.HIGH)#クロック出力して1bit入力 GPIO.output(SPICLK,GPIO.LOW) Transmitted_Data1 = Transmitted_Data1 << 1 #1bit左シフトする事で、if判定時に次のビットを操作対象にできる """ ここでは、1桁ずつ送信する処理をおこなっています。 GPIO.output(SPICLK,GPIO.HIGH)で送信です。 Transmitted_Data1 = Transmitted_Data1 << 1 をすると、LSB右の桁に0が追加されます。 Transmitted_Data1 = 0b00000110 であれば、 Transmitted_Data1 = 0b00001100 となります。 これをFor文で、1回回るたびに、1の位置が左にズレていきます。 目的は、 Transmitted_Data1 =0→0→0→0→0→1→1→0 と1つずつ情報を送ることです。1つずつ左にずらして行くことで、送りたい情報を送ります。 """ #制御手順4 Transmitted_Data2 = 0b00000000 #10bit中の2ビット、上位2ビットだけ使用 """ ここでは、データシート上でいう、2バイト目、のデータを送ります。 データシートの仕様は下記のようになっています。 送信データは、MSBと次の桁で、CH番号の続きを。残りの6桁は、特に意味はない、という仕様です。 受信データは、最初の3桁が不定、次の4桁目が0で、残りの4桁がセンサーからのデータです。 センサーからのデータは12桁送られて来ますが、その最初の4桁がここで送られてきます。 for i in range(2): において、CH番号の続きの部分だけ送ります。 """ for i in range(2): if Transmitted_Data2 & 0b10000000: GPIO.output(SPIMOSI,GPIO.HIGH) else: GPIO.output(SPIMOSI,GPIO.LOW) GPIO.output(SPICLK,GPIO.HIGH) GPIO.output(SPICLK,GPIO.LOW) Transmitted_Data2 = Transmitted_Data2 << 1 def receiveFromMCP3208(): #制御手順5 """ ここで、上記for i in range(2):で、送受信し損ねた途中のデータを送受信します。 データシート上では、受信データは最後の12Bitに情報が詰まっているという仕様になっているので、 1桁ずつ受信していきます。 データシートの仕様では、送信側は、Don't care bitと書かれているので、送ろうが送るまいが変わらないようです。 """ Received_Data = 0b000000000000 #戻り値の12bitデータ変数 for i in range(13): #iは0→12まで1ずつインクリメントされる GPIO.output(SPICLK,GPIO.HIGH) GPIO.output(SPICLK,GPIO.LOW) """ SPICLK,GPIO.HIGH→SPICLK,GPIO.LOWが送受信の合図です。 """ if i == 0: #nullビットなので対象外処理 """ 使用上受信データの11桁目はNullが入るBitです。 """ pass elif i >=1 and i <= 11: #通常処理 Received_Data = Received_Data | GPIO.input(SPIMISO) Received_Data = Received_Data << 1 """ 桁を詰めて、情報を詰めていきます。 """ elif i== 12: #最終ビットの場合左ビットシフトをしない Received_Data = Received_Data | GPIO.input(SPIMISO) return Received_Data def close(): #制御手順6 GPIO.output(SPICE0,GPIO.HIGH) """ SPICE0にHIGHを送ることで、MCP3208の通信は終わりという合図です。 """ SPICLK = 11 SPIMOSI = 10 SPIMISO = 9 SPICE0 = 8 GPIO.setmode(GPIO.BCM) GPIO.setup(SPICLK,GPIO.OUT) GPIO.setup(SPIMOSI,GPIO.OUT) GPIO.setup(SPIMISO,GPIO.IN) GPIO.setup(SPICE0,GPIO.OUT) while True: initialize() sendToMCP3208() value = receiveFromMCP3208() print(value) close() sleep(0.2) |
2つ目は、使うのに便利なコード
読者特典のコードなので、公開して良いのかわからないのですが、誰でもダウンロードできる状態なので、載せています。
(演習用のサンプルファイルのダウンロードは、こちら という部分の06-01-print.pyというものです)
NGであればご教授お願いします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 |
# -*- coding: utf-8 -*- import RPi.GPIO as GPIO from time import sleep # MCP3208からSPI通信で12ビットのデジタル値を取得。0から7の8チャンネル使用可 def readadc(adcnum, clockpin, mosipin, misopin, cspin): """chは0〜7までしか指定できません。""" if adcnum > 7 or adcnum < 0: return -1 GPIO.output(cspin, GPIO.HIGH) GPIO.output(clockpin, GPIO.LOW) GPIO.output(cspin, GPIO.LOW) """ cspinで通信先を選び、clockpinで通信を開始します。 """ commandout = adcnum commandout |= 0x18 # スタートビット+シングルエンドビット commandout <<= 3 # LSBから8ビット目を送信するようにする """ commandout |= 0x18 は下記と同じです。i+=1と同じ感じです。 commandout = commandout | 0x18 同様に commandout <<= 3 は下記と同じです。 commandout = commandout << 3 で、 0x18 = 0b011000 = 0d24 です。 adcnumでADコンバーターの受信したいCh番号を仮に3(0b000011)とすると、 commandout |= 0x18によって、 commandout = 0b011011 となり、上位の桁に11が追加されます。 そして、 commandout <<= 3 によって、LSB右の桁に0が3つが追加されて、 commandout = 0b11011000 となります。 これは、何を指すのか、というと、下記のとおりです。 データシートの仕様では、最初に送信する5桁は送らなくてもOKで、その次の桁から、 スタートビット1、次の桁の1は、Single-endedモードの1、CH番号を3桁で送信する、という仕様になっています。 なので、commandout = 0b11011000として、 スタートビット1、次の桁の1は、Single-endedモードの1、CH番号を3桁で011(=3)を送信する、というふうになっています。 """ for i in range(5): # LSBから数えて8ビット目から4ビット目までを送信 if commandout & 0x80: GPIO.output(mosipin, GPIO.HIGH) else: GPIO.output(mosipin, GPIO.LOW) commandout <<= 1 GPIO.output(clockpin, GPIO.HIGH) GPIO.output(clockpin, GPIO.LOW) adcout = 0 """ commandout = 0b11011000 のうち最初の五桁11011を送信しました。 必要な情報を送信できたので、もう後は、受信するだけです。 """ # 13ビット読む(ヌルビット+12ビットデータ) for i in range(13): GPIO.output(clockpin, GPIO.HIGH) GPIO.output(clockpin, GPIO.LOW) adcout <<= 1 if i>0 and GPIO.input(misopin)==GPIO.HIGH: adcout |= 0x1 GPIO.output(cspin, GPIO.HIGH) """ センサーデータが含まれているのはは、3バイト全体24桁のうち、後半の12桁です。 前半の5桁は最初から飛ばしていて、5桁を送信したので、ココで受信するのは、 11桁からです。 なので、i>0として、 ヌルビットも含めて、計算して、返却しています。 """ return adcout GPIO.setmode(GPIO.BCM) # ピンの名前を変数として定義 SPICLK = 11 SPIMOSI = 10 SPIMISO = 9 SPICS = 8 # SPI通信用の入出力を定義 GPIO.setup(SPICLK, GPIO.OUT) GPIO.setup(SPIMOSI, GPIO.OUT) GPIO.setup(SPIMISO, GPIO.IN) GPIO.setup(SPICS, GPIO.OUT) try: while True: inputVal0 = readadc(0, SPICLK, SPIMOSI, SPIMISO, SPICS) print(inputVal0) sleep(0.2) except KeyboardInterrupt: pass GPIO.cleanup() |
チップの仕様ので説明
MCP3208の仕様書は、こちら(PDF)です。
2つ目のコードは、ここから送受信と紫の◯で囲った部分から送受信しています。
1つ目のコードは、最初の桁から送受信しています。
まとめ
2進数なのでわかりにくいですが、1つずつ追っていくと分かるかなとおもいます。