今回は2つのK型熱電対温度センサモジュールを並列接続する例を説明します。
●MISO信号の問題
最初、2つの温度モジュールのSCK信号、MISO信号は並列接続し、SS信号は個別にして交互に切り替えてアクセスするようにしたのですが、どうやっても出力が0のままで読み出せませんでした。
片方のMISO信号を外して片方ずつ作動させると問題なく読み出せることから、両モジュールのMISOがぶつかっていることに気がつきました。
通常MISOの出力ポートはスリー・ステート出力になっていて、SS信号がディスエーブルの時はフロート(ハイ・インピーダンス)状態になるはずなのですが、この温度モジュールでは”L”レベルが出力されたままになっています。
従って、2個のモジュールのMISOを直結すると、非選択側のデバイスの”L”レベル出力が選択側デバイスの出力信号を打ち消してしまい、結果的に”L”レベルとなります。これを読み出すと全ビットが0となってしまいます。
常識的に考えて、MAX31855のMISO出力はスリー・ステート出力かオープン・ドレイン出力になっていると思いますが(データシート上で明記されていないので推測)、この温度モジュールではMAX31855のMISO出力に何らかのレベル変換素子が実装されていて、それが悪さをしているようです。素子の種類や回路図がないのでこれも推測です。
ちなみに温度モジュール1個とSDカードI/Fを並列でつなぐ場合でも同様の問題が起こります。なお、第2回記事のサンプル・プログラムのように、SPIデバイスが温度モジュール1個だけならコンフリクトの相手がないので問題はありません。
未確認ですが、たぶん、3.3V版の方にはレベル変換素子は実装されていないようなので、このようなコンフリクトの問題はないと思います。
●MISO信号のコンフリクト対策
対策は簡単です。MISO出力にスリー・ステート・バッファかオープン・ドレイン(またはオープン・コレクタ)のバッファを付ければ解決します。SPIの各信号は単方向なので、バッファ回路は単純です。スリー・ステートの場合はSS信号をイネーブル信号にすれば良いでしょう。
今回は単純に汎用トランジスタ1個と抵抗器数本を使い、オープン・コレクタ出力にすることで対応しました。
図1はトランジスタによるバッファ回路の例です。温度モジュール毎にトランジスタ回路を付ける必要があります。
(a)はオープン・エミッタ方式です。この場合、論理は反転しませんのでプログラムでSPIデータを反転する必要はありません。ただ、低い抵抗器でプルダウンしないといけないので、次の(b)の方がよいと思います。
(b)はオープン・コレクタ方式ですが、この場合、論理が反転するためソフトウェアでSPIデータを受信した時に全ビットを反転してやる必要があります。今回用意したサンプル・スケッチではこちらの方法で使っています。
●2個並列時の制御の概要
基本的にはSS信号を交互に切り替えながら、選択された側から温度を読み出すだけです。SS信号は汎用の出力ポートを使用しているため、アクセスの直前にその出力ポートを”L”レベルにしてデータを読み出し、それが終わったら”H”レベルに戻して非選択状態にしておきます。3個以上ある場合も同じです。
2個使用のサンプル・スケッチを用意してあります。接続は図2のようにしています。MISOはオープン・コレクタにしているので、SPIで読み出した時にビット反転しています。
このプログラムを実行すると、温度値が2個ずつ、1秒周期でシリアル出力されます。結果はArduino-IDEのシリアル・モニタで確認できます(CTRL+SHIFT+Mで起動)。
<サンプルスケッチ ReadTempW.zip>
/* 熱電対コンバータ 温度読み出しサンプル copyright (c) 2013 T.N www.wsnak.com 13/01/14 新規 13/01/15 Wバージョン 熱電対センサモジュールSPI#1 MISO (D12) CLK (D13) CS (D8) 熱電対センサモジュールSPI#2 MISO (D12) CLK (D13) CS (D9) 注意 MISOはオープンコレクタ出力にはなっていないので、並列 接続時はトランジスタのオープン・エミッタ回路などのバッファ を入れる必要あり */ #include <SPI.h> #define SPI_CS_ON digitalWrite(SPI_cs, LOW) // /cs = L #define SPI_CS_OFF digitalWrite(SPI_cs, HIGH) // /cs = H #define SPI_CS2_ON digitalWrite(SPI_cs2, LOW) // /cs = L #define SPI_CS2_OFF digitalWrite(SPI_cs2, HIGH) // /cs = H const int SPI_cs = 8; // 熱電対センサモジュール CSポート番号 const int SPI_cs2 = 9; // 熱電対センサモジュール CSポート番号 byte spiDat[4]; char strBuf[16]; // ------------------------- // 初期化 // ------------------------- void setup() { Serial.begin(19200); // シリアルポート初期化 pinMode(SPI_cs, OUTPUT); // センサモジュール#1 cs pinMode(SPI_cs2, OUTPUT); // センサモジュール#2 cs SPI_CS_OFF; SPI_CS2_OFF; SPI.begin(); SPI.setClockDivider(SPI_CLOCK_DIV4); // SPIクロック分周比 SPI.setBitOrder(MSBFIRST); // 送信ビット順番 SPI.setDataMode(SPI_MODE1); // SPIデータモード切替 } // ------------------------- // メインループ // ------------------------- void loop() { word val; // 温度センサ1 val = getSpiData(0); getTemp(val, strBuf); Serial.print(strBuf); // 温度センサ2 Serial.print(" "); val = getSpiData(1); getTemp(val, strBuf); Serial.println(strBuf); delay(1000); // 1秒 } // // 温度モジュールからSPIでデータを読み出す // word getSpiData(byte num) { byte i; if(num == 0) { SPI_CS_ON; for(i = 0; i < 2; i++) { // 2バイトだけ受信 // spiDat[i] = SPI.transfer(0); // ダミーデータ送信(1バイト受信) spiDat[i] = ~SPI.transfer(0); // ダミーデータ送信(1バイト受信)(ビット反転) } SPI_CS_OFF; } else { SPI_CS2_ON; for(i = 0; i < 2; i++) { // 2バイトだけ受信 // spiDat[i] = SPI.transfer(0); // ダミーデータ送信(1バイト受信) spiDat[i] = ~SPI.transfer(0); // ダミーデータ送信(1バイト受信)(ビット反転) } SPI_CS2_OFF; } return ((word)spiDat[0] << 8) | (word)spiDat[1]; } // // SPIで読み出したデータを温度値の文字列に変換する // void getTemp(word spival16, char *str) { word val, vali; byte sign, vals; val = spival16 >> 2; if(val & 0x2000) { // 符号ビットをチェック // 負数(2の補数) val = ~val + 1; // 正数に戻す val &= 0x3FFF; // 数値抽出 sign = true; // 負数マーク } else { // 正数 sign = false; } // 整数部分と小数部分に分ける vali = val >> 2; // 整数部(上位11ビット) vals = (byte)val & 0x03; // 小数部(下位2ビット) vals = vals * 25; // 25=0.25*100 小数部を整数化 // 表示用文字列作成 if(sign) { // 負数 sprintf(str, "-%d.%-2d", vali, vals); } else { // 整数 sprintf(str, "+%d.%-2d", vali, vals); } }
この記事は2013年1~2月頃にCQ出版のブログへ掲載したものを少し修正したものです。
参考文献