マスタリングWireライブラリ その2 温度センサMCP9808で温度測定
前回、I2Cインターフェースの基本的なタイミングを見ました。今回からは、デバイスごとにWireライブラリを使って読み書きします。
センサMCP9808の温度レジスタ(ポインタ)の読み出しは、最初に、レジスタを指定した後、読み出しだけを繰り返しました。これはマニュアルにも書かれていた、一度レジスタを設定したら、再度指定する必要はないという理由です。逆に、読み出しのたびにレジスタを指定しても、温度を読み出す動作に変わりはありません。
●MCP9808のおもなスペック
電源電圧は2.7~5.5Vなので、5Vもしくは3.3VのArduinoで利用できます。I2Cインターフェースのデータ転送速度は0~400kHzです。Wireライブラリのデフォルトは100kHzで、高速の400kHzにはWire.setClock(400000)で変更できます。
測定温度範囲は-40~125℃の範囲で確度±0.25°C(typ)です。データシートのAccuracyは日本では精度と訳すことが多いですが、ここでは確度という言葉を使います。typはtypicalの省略形で、代表値のことです。データシートではmin(minimal;最小値)、typ、max(maxim;最大値)が書かれていることが多いです。設計者はminの値に注目します。分解能の最大は+0.0625°C(12ビット時)です。
利用したプリント基板はAdafruitです。スイッチサイエンスなどから入手できます。Adafruitではブレーク・アウト・ボードと呼んでいます。同様な製品はebayでもたくさん見つかります。デバイス単体はDigi-Keyでも購入できます。
●接続
プリント基板上にA0、A1、A2のスレーブ・アドレスを設定する端子があります。回路図から、10kΩでプルダウンされているので、Lowです。データシートから0011000=0x18です。0011は固定されていて、下位3ビットのA0からA2のいずれかをVddにつなぐことでアドレスを変更できます。
SDAとSCLも10kΩでプルアップされています。ArduinoでこのMCP9808だけをつないで利用するときは、このまま使います。ほかにI2Cデバイスを使うときは、取り外したほうがよいでしょう。ラズパイでは本体側で1.8kΩでプルアップされているので、実験時に取り外しました。はんだゴテでチップ抵抗R1とR2を温めて外します。新規に購入した場合は、そのまま使います。チップ抵抗の表面には1002と書かれています。100×10^2=10000Ωです。
取り去った抵抗の代わりに追加したプルアップ抵抗の2本は6.8kΩを使いました。4.7~10kΩの範囲の抵抗値なら何でもかまわないと思います。電力は1/2W、1/4W、1/6Wのいずれでも、炭素皮膜抵抗もしくは金属皮膜抵抗のどちらでもかまいません。電源5VとグラウンドGND間には、0.1uF(104)の積層セラミック・コンデンサと10~47uFのアルミ電解コンデンサを入れます。電解コンデンサはマーキングがあるほうがマイナス端子です。図では半円形のグレーのマーキングがあるので、その端子をGNDへつなぎます。黒色や茶色の電解コンデンサであれば、白い帯のマーキングがあります。コンデンサの耐圧は16~50Vの製品を使います。
二つのコンデンサは、電源のデカップリング用です。ノイズを軽減し、安定な動作が望めます。なくても動きます。
図では空中配線になっていますが、実際はブレッドボードを利用して配線します。
●スケッチとタイミング
最初に、MCP9808スレーブ・アドレス0x18と温度ポインタの0x05を送ったのちストップ・コンディションになります。そのあと、2バイトの読み出しを行うために、再度スタート・コンディションになります。最初に読んだ1バイトが温度データの上位バイト、次が下位バイトであることが確認できます。デバイスによっては、上位と下位が逆になっていることがあります。
ここで、温度ポインタ0x05を送った後Wire.endTransmission();を忘れるとどうなるかを見ます。スケッチでは0x05を送ったように見えますが、実際は読み出しだけが行われています。読み出しでは、正常な値を読み込めているようです。
ここで、MCP9808の電源を入れなおすと次の意味不明な波形が観測できました。先ほど正常な読み出しができていたのは、温度ポインタが一度書き込まれていたので、継続してその指定が有効だったため、温度データが読めていたようです。
Wire.endTransmission();を有効にし、読み出すデータを2バイトから5バイトに変更してみます。温度データ2バイトに続いて3バイト読み込んでいます。I2Cでは、連続読み出しをするとき、スレーブ側のポインタが自動でインクリメントされる思っていました。データシートによれば、温度データ2バイトの次には製造者IDが2バイト、デバイスIDが続きます。しかし、正しくないので、ポインタは正しく指定して2バイトずつ読み出す必要があるようです。
●温度を得る
読み出した2バイトのデータは、次の符号付き2の補数形式です。
temp1にはbit15~bit8が、temp2にはbit7~bit0が入っています。bit15~bit13は不要なので、
temp1 & 0b00011111 |
でマスクします。C言語の演算子&は論理積で、temp1 と0b00011111のどちらのビットも1なら1に、そうでないときは0になります。したがって、上位3ビットは何であっても強制的に0になり、残りの5ビットは、もとの1/0が変化しません。
Arduinoのintは16ビット長です。読み出したのは1バイト=8ビットなので、上位1バイトは全部0でうずめられています。8回左へビット・シフトします(256を乗じても同じ)。そうすると、上位1バイトへ読み出したデータが移動します。
(temp1 & 0b00011111)<<8 |
下位8ビットが空いた状態なので、そこへtemp2を加算すると、符号1ビット+12ビットのデータが作られます。
tempSign = (temp1 & 0b00011111)<<8 + temp2 |
図で説明します。
温度データは符号付き2の補数形式で入っています。bit4が2^0=1℃単位、小数点以下は、bit3が2^-1=0.5℃単位、bit2が2^-2=0.25℃単位、bit1が2^-3=0.125℃単位、bit0が2^-4=0.0625℃単位です。したがって、12ビット・データに0.0625を乗じてスケーリングすると、温度temperature が得られます。tempは符号を除いた12ビット・データです。
temperature = temp * 0.0625 |
2の補数形式では、0は0x000、正のフルスケールは0x7ff、マイナスのフルスケールは0x800です。温度はフルスケールにはなりません。身近な温度のバイナリ値を計算しました。
温度[℃] | バイナリ | 16進 |
---|---|---|
50 | 0011 0010 0000 | 320 |
25 | 0001 1001 0000 | 190 |
0.25 | 0000 0000 0100 | 004 |
0 | 0000 0000 0000 | 000 |
-0.25 | 1111 1111 1100 | ffc |
-5 | 1111 1011 0000 | fb0 |
-25 | 1110 0111 0000 | e70 |
ここまでのスケッチでは、マイナスの温度は考慮しませんでした。bit12は符号です。1ならばマイナスです。任意のビットが1かどうかをテストする命令がないので、13ビット目の符号を含むtempSignを右に12回シフトすると、bit12だけが残るのでif文で判断します。マイナスの2の補数を10進に直すには、ビット反転し1を加えます。12ビットを扱っているので、適時マスクをかけます。
if (tempSign >>12 ) temp = -( (~temp & 0x0fff) +1); |
#include <Wire.h>
unsigned char MCP9808_address = 0x18;
unsigned char Temp_register = 0x05;
void setup() {
Wire.begin();
Serial.begin(9600);
Serial.println("start ");
}
void loop() {
Wire.beginTransmission(MCP9808_address);
Wire.write(Temp_register);
Wire.endTransmission();
Wire.requestFrom(MCP9808_address, 2);
int temp1 = Wire.read();
int temp2 = Wire.read();
int tempSign = ((temp1 & 0b00011111 ) << 8 ) + temp2;
int temp = tempSign & 0x0fff;
if (tempSign >>12 ) temp = -( (~temp & 0x0fff) +1);
float temperature = temp * 0.0625 ;
Serial.println(temperature);
}
(※1)Arduino IDEは1.8.5を利用しています。プログラム・リストは、表示の関係でTabキーが無視されるので、スペースに代えてあります。また、リスト中を2回クリックすると全選択になるので、CTRL-Cでコピーし、テキスト・エディタにCTRL-Vで貼り付けて利用してください。エディタに持っていくと、スペースやリターン・コードなどが化けていることがあるので、一度消して、Arduino IDEのテキスト・エディタで修正してください。