皆様、ご無沙汰しております。代表の國頭です。
「デバッグ事例」のカテゴリでは、ほぼ6年ぶりの更新になりますがどうぞ宜しくお願い致します。
さて、表題にありますように「ArduinoのWireライブラリ」について、ここ数年疑問だった事が解決したので、私の備忘録も兼ねて記事を書いてみたいと思います。
Arduinoについてはご存じの方が多いと思うので、ここでは詳細な説明は省きますが、私はI2CやSPIなどで制御できるデバイスの評価用プラットフォームとして使っています。
つい先日、温湿度センサーである、Sensirion社製「SHT31」を評価する機会がありましたので、その時に得られた知見をご報告したいと思います。
評価用にSHT31が搭載されたモジュールを購入し、ブレッドボードにそのモジュールを取り付け、Arduinoと接続して、評価を始めました(写真1)。
Arduinoには、I2Cを手軽に扱えるよう「Wireライブラリ」が標準で用意され、そのライブラリを使ってSHT31へコマンドを送り、そのコマンドによって指定されたデータが受信できているか確認してみました。
先ずは、SHT31に対して測定コマンドを送ります。SHT31のデータシート(PDFファイルが開きます)を参照し、
- 単発測定モード
- 繰り返し精度レベル:高
- クロックストレッチ:無
としました。
Arduinoのスケッチは以下。
//SHT31にI2C経由で、測定モード設定コマンドを送信する Wire.beginTransmission(SHT31_Address); //シングルショットモードで測定 //コマンドのMSB側。0x24はクロック・ストレッチ無効とする Wire.write(0x24); //コマンドのLSB側。0x00は繰り返し精度「高」 Wire.write(0x00); //コマンド送信終了 Wire.endTransmission();
このコマンドを送った時に、I2CバスのSCL信号とSDA信号がどの様になるか観測してみました。ハードウエアエンジニアの「さが」なのか、リアルな波形を観ないと落ち着かないのです(写真2参照)。
測定には、キーサイトテクノロジーのDSOX2014Aを用い、このオシロスコープのCH1(黄色の波形)には「SCL」、CH2(緑の波形)には「SDA」が入力されています。
DSOX2014Aには、各種シリアルインターフェースに対して、特定の条件に対して選択的にトリガをかけ、その波形をデコードする機能をオプションとして搭載することができます。この機能を使って、I2Cのスタート・コンディションでトリガをかけて波形を観測しその波形をデコードしています(図1参照)。
スケッチで「Wire.beginTransmission(SHT31_Address)」に相当する部分が、「45Wa」です。アドレス45(ここではSHT31)のデバイスへのWrite(W)が行われ、Acknowledge(a)が帰ってきた事を示します。
続いて、表示されている「24a」と「00a」が「Wire.write(0x24)」と「Wire.write(0x00)」に相当する部分で、スケッチで指定したコマンドがSHT31へと送られていることが分かります。
さらに、00aに続いてI2CのSCL及びSDAが両方ともハイレベルとなっています。これは、スケッチの「Wire.endTransmission()」によって、I2Cバスがストップ・コンディションとなり、I2Cの通信を終了させていることが分かります。
この波形によって、ArduinoのIDEで用意されているWireライブラリは、PHILIPS(現:NXPセミコンダクタ)が定めた規格(PDFファイルが開きます)に準拠していることが確認できました。
続いて、データの受信です。
Arduinoのスケッチは以下。
//SHTー31に対して、I2C経由で6バイト分のデータを読むようにリクエスト Wire.requestFrom(SHT31_Address,6); while (Wire.available()<6){ } //読み込み開始 //温度データは16ビットで、8ビットずつ送られてくる。 //最初、上位8ビットが出てくるので、 //ビットシフト演算を使い左8ビットシフトし、変数に格納。 //その変数に、下位8ビットを加算して、16ビットデータを作成。 Ta_in=(Wire.read()<<8); Ta_in=Ta_in+(Wire.read()); //このプログラムでは使わないが、温度データのCRCを変数に格納 Ta_CRC=Wire.read(); //温度データを浮動小数点変数へ格納 Ta_data=Ta_in; //湿度データも16ビットで、8ビットずつ送られてくる。 //最初、上位8ビットが出てくるので、 //ビットシフト演算を使い左8ビットシフトし、変数に格納。 //その変数に、下位8ビットを加算して、16ビットデータを作成。 RH_in=(Wire.read()<<8); RH_in=RH_in+(Wire.read()); //このプログラムでは使わないが、湿度データのCRCを変数に格納 RH_CRC=Wire.read(); // 湿度データを浮動小数点変数へ格納 RH_data=RH_in; Wire.endTransmission();
このスケッチで得られた波形を図2に示します。
「45Ra」は、「Wire.requestFrom(SHT31_Address,6)」によって、ホストからアドレス45を持つスレーブ・デバイスに対してデータをRead(R)する事を伝え、Acknowledge(a)が帰ってきたことを示しています。 その後6バイトのデータが連続してスレーブ・デバイスから出力されていることが分かります。最後の「85~a」でホストはデータの受信を終え、I2Cバスはストップ・コンディションとなり、SCL、SDA共にハイレベルになっています。
この波形で気になるのは、最後に表示されている「45Wa」です。6バイト目のデータ「85~a」でI2Cバスはストップコンディションになり、バスとしての動作は終了しているはずなのですが、なぜかI2Cバスがスタート・コンディションになり、SHT31に対してコマンドを送ろうとしています。ただ、コマンドが何も指定されていない為か、すぐにストップコンディションになって、I2CバスのSCL及びSDAがハイレベルとなり、I2Cバスの通信が終了しています。
スケッチをよくよく観てみると、湿度データのチェックサムを「RH_CRC=Wire.read();」で受け取ってから、Wireライブラリの「Wire.endTransmission();」を送っています。
試しに、このコマンドをコメントアウトしてみました。
// 湿度データを浮動小数点変数へ格納 RH_data=RH_in; //Wire.endTransmission();
この場合のI2Cバスの波形をデジタルオシロスコープで観測してみたところ、図3のようになりました。
どうも、「Wire.endTransmission();」が悪さをしていたようで、
Wire.requestFrom(Slave_Address,Number_of_Byte); while (Wire.available()<Number_of_Byte){ } ~~~~~~~~~~ Variable = Wire.read();
と書けば、Arduinoが指定されたバイト数だけデータを読むと、I2Cバスを自動的にストップ・コンディションにするようです。
「Wire.read();」の直後に「Wire.endTransmission()」を挿入すると、「Wire.beginTransmission(Slave_Address)」と同じ波形が出てくるのは「謎」ですが、スレーブデバイスからデータを読み込むとき、「Wire.endTransmission()」は入れるべきでは無い、という結論に達しました。
私が「Wire.endTransmission()」を入れた理由は、「Wire.endTransmission()」を送る事によって、「I2Cバスがストップ・コンディションになる」と、勝手に思い込んでいたからです。
前々から、この謎の波形が気になっていたのですが、やっとスッキリしました。
今後、I2Cを使ったデータ受信時には、「Wire.endTransmission()」は不要なのだ、と頭に叩き込んで置きたいと思います。
(補足)
なお、「Wire.endTransmission()」を入れても入れなくても、ソフトウエア的には全く問題無いようで、受信したデータをCOMに表示させても文字化け等のトラブルは起きません。図4-1、4-2を参照下さい。