1-Wireプロトコル

1-Wireプロトコルを使ったデバイスやセンサー間の通信を学びます。


AUTHOR: Arduino Community、LAST REVISION: 2022/11/11 17:56


この記事は、2022/09/28にHannes Siebeneicher氏により改定されました。

i
コントローラ/ペリフェラルは、以前はマスター/スレーブとして知られていました。Arduinoではこの用語は用いません。以前はマスターと呼ばれていたデバイスは、コントローラと呼び、スレーブと呼ばれていたデバイスはペリフェラルと呼びます。

1-Wire通信はコントローラデバイスとペリフェラルデバイス間を1本の電線で操作するプロトコルです。この記事では、ArduinoでOneWireライブラリを利用して、1-Wireプロトコルを使う基本を紹介します。以下のセクションでは、1-Wireプロトコルとインターフェイス、電源、デバイスのアドレス指定、デバイスの読み取り、ライブラリの歴史を説明します。

最新バージョン

最新版のライブラリは、Paul Stoffregen氏のサイトにあります。

OneWireは現在、Paul Stoffregen氏により維持管理されています。バグを見つけたり、ライブラリに対する改善点がある場合は、paul@pjrc.comにメールしてください。最新版のOneWireを使っていることを確認してください。

Busは、OneWireライブラリのサブクラスです。Busクラスは、アナログピンに接続されている1-Wireバスをスキャンし、ROMを配列に格納します。バスクラスでは、異なる1-Wireセンサー(DS18B20、DS2438)からデータを取得するために、いくつかのメソッドが利用できます。

1-Wireプロトコル

Dallas Semiconductor社(現在はMaxim社)は、同社が所有権を持つ1-Wireプロトコルで制御できるデバイスファミリーを製造しています。Dallas 1-Wire(商標)ドライバを利用するプログラマには課金されません。

Dallas社が「MicroLan(商標)」と名づけた1-Wireネットワークでは、1台のコントローラデバイスが1台以上の1-Wireペリフェラルデバイスと1本のデータ線で通信します。このデータ線はペリフェラルデバイスへの給電にも利用することもできます(1-Wireバスから給電しているデバイスは、“parasitic power mode"で動作しているといわれます)。Tom Boyd氏のguide to 1-Wireは、知りたいこと以上のことを教えてくれます。さらに、疑問に答えてくれたり、興味を掻き立てたりしてくれます。

1-Wire温度センサーは、安価で使いやすく、校正されたデジタル温度を直接読むことができるので、特に人気があります。このセンサーは、センサーとArduino間を長い電線で接続することにも体勢があります。以下のサンプルコードは、Jim Studt氏のOneWire Arduinoライブラリを使い、DS18S20デジタル温度計を例として、1-Wireデバイスとインターフェイスをとる方法を示します。多くの1-Wireチップは、parasitic power modeとnormal power modeのどちらでも動作することができます。

1-Wireインターフェイス

専用バスコントローラ

Dallas/Maxim社や他の多くの企業は、1-Wireネットワークの送受信・監理用の専用バスコントローラを製造しています。ほとんどのコントローラは以下に示されています。

https://owfs.org/index.php?page=bus-masters

これらのデバイスは、1-Wireデバイスとネットワークで、効率的に送受信できるように特別に設計・最適化されています。UART/USARTコントローラのように、これらのデバイスは、バッファの利用やホストプロセッサ(例えば、センサーゲートウェイやマイクロコントローラ)から処理負荷のオフロードを行い、クロック動作をネイティブに処理することで、精度を向上させています。外部プルアップ抵抗が不要であることが多いです。

多くのチップは、特に大規模ネットワークで問題を引き起こす、信号の完全性の喪失やレベル変動、反射や他のバスの問題に対するエラー処理機能を提供します。多くのデバイスには追加機能があり、多くの種類のインターフェイスで提供されています。価格は1ドルから30ドル程度です。

他の大きな利点は、多様な1-Wireデバイス向けに多くのネイティブ機能を提供する1-Wireコントローラ用の多くのデバイスに対する、ファイルシステムの読み書きのサポートです。

UART/USARTコントローラ

ほとんどのUART/USARTは、標準モードでの1-Wireバスに要求される15.4kbpsを超える速度を完全に維持することができます。さらに重要なのは、クロックとバッファリングが個別に処理され、マイクロコントローラやメインプロセッサのメインプロセスからオフロードされることです。この実装はここで議論されています: http://www.maximintegrated.com/en/app-notes/index.mvp/id/214

Bitbangingアプローチ

ネイティブのbっファリングやクロック管理が利用できないときは、1-Wireは汎用IO(GPIO)ピンを使って実装され、ピン状態の手動切替がUART/USARTのエミュレートに利用され、受信データからの信号の再構築も行います。これらはプロセッサの利用効率が低く、他のシステムプロセスとプロセッサを共有する、他のプロセスに影響を与えたり、影響を受けたりします。

Arduinoや互換チップでは、これはOneWireライブラリによって行われます。

Raspberry Piのようなシングルボードコンピュータでは、1-Wireネットワークの受信は、ネイティブサポートを提供するカーネルドライバにより実現可能です。w1-gpioやw1-gpio-therm、w1-gpio-customカーネルモードが、最新のRaspbianのディストリビューションに含まれていて、追加のハードウェアなしに1-Wireデバイスのサブセットとインターフェイスを取れるので、普及しています。しかし、現在、それらは限られたデバイスだけをサポートしていて、ソフトウェアによりバスの大きさに制限があります。

1-Wireデバイスへの給電

チップへの給電方法には2種類あります。一つは「parasitic」オプションで、2本の電線だけがチップに接続されます。もう一つは、チップへの給電用に追加の電線を利用するので、より信頼性の高い操作が可能となるかもしれません(parasiticは多くの場合問題なく動作します)。最初は、特にチップがArduinoから約6メートル(20フィート)以内にあるときは、おそらくparasiticオプションがいいでしょう。以下のコードは、どちらのオプションでも動作します。

Parasite power mode

Parasite power modeで動作する場合は、データ線とGNDの2本の電線だけが必要です。このモードでは、データシートによると、電源線はGNDに接続する必要があります。コントローラでは、4.7kΩのプルアップ抵抗を1-Wireバスに接続する必要があります。電線がHIGH状態のとき、デバイスは内部コンデンサに充電するために電流を引き込みます。

電流は通常とても小さいですが、温度変換やEEPROMへの書き込み時には、1.5mA程度まで上昇することがあります。ペリフェラルデバイスがこれらの操作のどれかを実行しているときは、バスコントローラは操作が完了するまで電力を供給するためにバスをHIGHにし続ける必要があります。DS18S20の温度変換の場合は750msの遅延が必要です。この間、他のデバイスにコマンドを発行したり、ペリフェラルの操作完了をポーリングしたりなどの、どのような操作も実行できません。これをサポートするために、OneWireライブラリは、データが書き込まれた後、バスをHIGHに保持します。

Normal (external supply) mode

外部電源を使うときは、バス線とGN線D、電源線の、3本の電線が必要です。この場合も、バス線には、4.7kΩのプルアップ抵抗が必要です。バスはデータ転送に自由に使えるので、マイクロコントローラは継続して変換しているデバイスをポーリングすることができます。Parasite power modeでは、変換時間(デバイスの機能や精度に依存します)だけ待つ必要があるのとは違い、この方法では、デバイスが完了報告するとすぐに変換要求を完了することができます。

抵抗に関する注意

大規模ネットワークでは、小さい抵抗を試すことができます。

ATmega328/168のデータシートによると、1.6kΩから始めることができ、多くのユーザが大規模ネットワークでは、より小さいほうが、うまくgg動作することを見つけています。

1-Wireデバイスのアドレッシング

各1-Wireデバイスは、8ビットのファミリーコード、48ビットのシリアルナンバー、8ビットのCRCからなる、一意の64ビットROMアドレスを持っています。CRCはデータの正しさの検証に使われます。例えば、以下のコード例では、アドレスを付与されたデバイスがDC18S20温度センサーであるかを、ファミリーコードが0x10かを調べることで検証しています。新しいDS18B20センサーでコード例を使うには、代わりに、ファミリーコードが0x28かどうかを確認する必要があります。また、DS1822では、0x22かどうかを確認する必要があります。

単一デバイスコマンド

一つのペリフェラルデバイスにコマンドを送信する前に、一意のROMを使って、最初にデバイスを選択する必要があります。ROMが見つかれば、後続のコマンドは、選択したデバイスにより反応します。

複数デバイスコマンド

一方、Skip ROMコマンド(0xCC)を発行することで、全てのペリフェラルデバイスにコマンドを発行することができます。複数のデバイスにコマンドを発行する影響を考えることが重要です。ときどき、これは意図的で有効です。例えば、Skip ROMの後に、convert T(0x44)を発行すると、convert Tコマンドを実装している全てのネットワークデバイスが、温度変換を実行するよう指示します。これは、時間を節約し効果的な操作方法です。一方、Read Scratchpadコマンド(0xBE)の発行は、全てのデバイスが同時にスクラッチパッドのデータを報告してしまいます。Skip ROMコマンドを使うときは、全てのデバイスの(例えば、温度変換時の)消費電力も重要です。

1-Wireデバイスのデータを読む

1-Wireデバイスのデータを読むには複数の手順が必要です。デバイスは異なる測定値を報告できるので、詳細はデバイス依存です。例えば、有名なDS18B20は、温度を読み取り報告します。一方、DS2438は電圧と電流、温度を読み取ります。

2つのおもな読み取り手順

変換

内部変換操作を行うためにデバイスにコマンドを発行します。DS18B20では、これは、Convert T(0x44)コマンドです。OneWireライブラリでは、ds.write(0x44)のように記述します。ここで、dsは、OneWireクラスのインスタンスです。このコマンドを実行した後、デバイスは内部のADCを読み取り、この操作が終わると、そのデータをスクラッチパッドレジスタにコピーします。この変換操作に要する時間は精度に依存し、デバイスのデータシートに記述されています。DS18B20は、温度変換に94ミリ秒(9ビットの粒度)から750ms(12ビットの粒度)かかります。変換の実行時は、例えば、OneWireライブラリのds.read()コマンドを使い、デバイスはポーリングされ、変換が正しく行われたのかを確認します。

スクラッチパッドの読取

データが変換されると、結果はスクラッチパッドメモリにコピーされ、その後読み取られます。スクラッチパッドは、変換コマンド無しでも、デバイスの精度や他のデバイス設定オプションや最新のデータを、いつでも読み取ることができることに注意してください。

非同期 vs 同期読み書き

既存の1-Wireデバイスの、特にArduino向けのコードの大部分は、デバイスが複数あるときにも、非常に基本的な、変換・待機・読取アルゴリズムを使っています。これにはいくつか問題があります。

他機能のプログラムタイミング

上述の方法を使うときの最大の問題は、間違いなく、スレッドを利用した測定が行われない限り、ハードコードされた待ち時間が含まれるときに、変換中はデバイスが止まって待たなければならないことです。これは、他に時間のかかる処理があるときに問題になります。また、そのような処理がなくても、多くのプログラムは、ユーザの入力を待ち、データを処理し、温度変換処理の間待つことのできない、他の多くの処理を実行します。前述したように、DS18B20の12bビットの変換プロセスは750msかかります。測定の変換が完了するまでコントローラがまったく何もしないという必要がない限り、待機方式を使う理由はありません。変換コマンドを発行し、変換完了したその後に、測定結果を取得するのにスクラッチパッドの読み取りコマンドを発行しに戻る方が、はるかに効率的です。

複数デバイスでのポーリング速度のためのスケーリング

変換・待機・読取方法の別の大きな問題は、スケールしないことで、特別な理由はありません。全ての変換コマンドは、Skip ROMを発行することで、順次もしくは同時に発行でき、その後順次結果を読み取るようにコマンドを発行できます。ここでの議論を参照してください。 http://interfaceinnovations.org/onewireoptimization.html

要求される変換時間に対する待ち時間の調整

1-Wireデバイスの、最も効率的で速い読取は、読み取るデバイスの変換時間を明に考慮していて、典型的には読み取り分解能の機能です。例えば、以下の例では、データシートでの最大変換時間の750msで典型的な変換には625ms以下の時間がかかることに対して、1000msを与えています。最も重要なのは、ポーリングしようとしている分解能に合わせて値を調整する必要があるということです。例えば、9ビットの分解能では、最大94msかかるので、1000msの待ち時間は意味がありません。上述したように、ポーリングする最も効率的な方法は、デバイスのポーリングのための読取タイムスロットを設けることです。この方法では、結果が準備できたことを正確に知ることができ、すぐに取得できます。

歴史

2007年に、Jim Studt氏が、1-Wireデバイスを簡単に利用するための、元となるOneWireライブラリを作成しました。Jimの元のバージョンはArduino-007でだけ動作し、CRCの計算に、大きい(256バイト)ルックアップテーブルを必要としていました。後にこれは、Arduino-008以降で動作するように更新されました。最新のバージョンでは、CRCルックアップテーブルがなくなり、arduino-0010で試験されています。

OneWireライブラリには、検索機能を使ったときに無限ループを引き起こすバグがありましたが、バージョン2.0で、Robin James氏の改良型検索機能をマージし、Paul Stoffregen氏の改良型I/Oルーチン(稀に発生する通信エラーの修正)を取り込み、さらに、いくつかの小さい最適化が行われました。

バージョン2.1では、Arduino 1.0-betaとの互換性や、改善した温度の例(Paul Stoffregen氏)、DS250x PROMの例(Guillermo Lovato氏)、chipKit互換(Jason Dangel氏)、CRC16、便利な関数、DS2408の例(Glenn Trewitt氏)が追加されました。

Miles Burton氏は、ここから、Dallas Temperature Control Libraryを派生させました。

コード例

  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
#include <OneWire.h>


// DS18S20 Temperature chip i/o

OneWire ds(10);  // on pin 10


void setup(void) {

  // initialize inputs/outputs

  // start serial port

  Serial.begin(9600);

}


void loop(void) {

  byte i;

  byte present = 0;

  byte data[12];

  byte addr[8];


  ds.reset_search();

  if ( !ds.search(addr)) {

      Serial.print("No more addresses.\n");

      ds.reset_search();

      return;

  }


  Serial.print("R=");

  for( i = 0; i < 8; i++) {

    Serial.print(addr[i], HEX);

    Serial.print(" ");

  }


  if ( OneWire::crc8( addr, 7) != addr[7]) {

      Serial.print("CRC is not valid!\n");

      return;

  }


  if ( addr[0] == 0x10) {

      Serial.print("Device is a DS18S20 family device.\n");

  }

  else if ( addr[0] == 0x28) {

      Serial.print("Device is a DS18B20 family device.\n");

  }

  else {

      Serial.print("Device family is not recognized: 0x");

      Serial.println(addr[0],HEX);

      return;

  }


  ds.reset();

  ds.select(addr);

  ds.write(0x44,1);         // start conversion, with parasite power on at the end


  delay(1000);     // maybe 750ms is enough, maybe not

  // we might do a ds.depower() here, but the reset will take care of it.


  present = ds.reset();

  ds.select(addr);    

  ds.write(0xBE);         // Read Scratchpad


  Serial.print("P=");

  Serial.print(present,HEX);

  Serial.print(" ");

  for ( i = 0; i < 9; i++) {           // we need 9 bytes

    data[i] = ds.read();

    Serial.print(data[i], HEX);

    Serial.print(" ");

  }

  Serial.print(" CRC=");

  Serial.print( OneWire::crc8( data, 8), HEX);

  Serial.println();

}

16進数を意味のある表記に変換(温度)

16進コードを温度に変換するために、まずDS18S20かDS18B20シリーズのセンサーを使っているのかを認識する必要があります。温度を読み取るコードはDS18B20とDS1822とでは少し異なります。DS18B20とDS1822は12ビットの温度の値(0.0625どの精度)を返すのに対し、DS18S20とDS1820は、9ビットの値(0.5度の精度)を返すからです。

まず、いくつかの変数を定義する必要があります。上のloop()のすぐ下に書いてください。

1
int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;

その後、DS18B20シリーズ向けには、Serial.println()の下に、以下のコードを追加する必要があります。

 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
LowByte = data[0];

  HighByte = data[1];

  TReading = (HighByte << 8) + LowByte;

  SignBit = TReading & 0x8000;  // test most sig bit

  if (SignBit) // negative

  {

    TReading = (TReading ^ 0xffff) + 1; // 2's comp

  }

  Tc_100 = (6 * TReading) + TReading / 4;    // multiply by (100 * 0.0625) or 6.25


  Whole = Tc_100 / 100;  // separate off the whole and fractional portions

  Fract = Tc_100 % 100;


  if (SignBit) // If its negative

  {

     Serial.print("-");

  }

  Serial.print(Whole);

  Serial.print(".");

  if (Fract < 10)

  {

     Serial.print("0");

  }

  Serial.print(Fract);


  Serial.print("\n");

このコードブロックは、温度を摂氏に変換し、シリアル出力に値を表示します。

解像度0.5度でのDS1820用コード

上記の例は、DS1820のBタイプでだけ動作します。以下は、低分解能のDS1820と値をLCDに表示する複数のセンサーで動作します。以下の例はArduinoの9番ピンで動作します。各自の用法に合わせて適切なピンに変更してください。DS1820の1番ピンと3番ピンはGNDに接続する必要があります。例では、5kΩの抵抗がDS1820の2番ピンからVCC(+5V)に接続しています。LCDとAduinoの接続については、LiquidCrystalのドキュメントを参照してください。

  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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <OneWire.h>

#include <LiquidCrystal.h>

// LCD=======================================================

//initialize the library with the numbers of the interface pins

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

#define LCD_WIDTH 20

#define LCD_HEIGHT 2


/* DS18S20 Temperature chip i/o */


OneWire  ds(9);  // on pin 9

#define MAX_DS1820_SENSORS 2

byte addr[MAX_DS1820_SENSORS][8];

void setup(void)

{

  lcd.begin(LCD_WIDTH, LCD_HEIGHT,1);

  lcd.setCursor(0,0);

  lcd.print("DS1820 Test");

  if (!ds.search(addr[0]))

  {

    lcd.setCursor(0,0);

    lcd.print("No more addresses.");

    ds.reset_search();

    delay(250);

    return;

  }

  if ( !ds.search(addr[1]))

  {

    lcd.setCursor(0,0);

    lcd.print("No more addresses.");

    ds.reset_search();

    delay(250);

    return;

  }

}

int HighByte, LowByte, TReading, SignBit, Tc_100, Whole, Fract;

char buf[20];


void loop(void)

{

  byte i, sensor;

  byte present = 0;

  byte data[12];


  for (sensor=0;sensor<MAX_DS1820_SENSORS;sensor++)

  {

    if ( OneWire::crc8( addr[sensor], 7) != addr[sensor][7])

    {

      lcd.setCursor(0,0);

      lcd.print("CRC is not valid");

      return;

    }


    if ( addr[sensor][0] != 0x10)

    {

      lcd.setCursor(0,0);

      lcd.print("Device is not a DS18S20 family device.");

      return;

    }


    ds.reset();

    ds.select(addr[sensor]);

    ds.write(0x44,1);         // start conversion, with parasite power on at the end


    delay(1000);     // maybe 750ms is enough, maybe not

    // we might do a ds.depower() here, but the reset will take care of it.


    present = ds.reset();

    ds.select(addr[sensor]);    

    ds.write(0xBE);         // Read Scratchpad


    for ( i = 0; i < 9; i++)

    {           // we need 9 bytes

      data[i] = ds.read();

    }


    LowByte = data[0];

    HighByte = data[1];

    TReading = (HighByte << 8) + LowByte;

    SignBit = TReading & 0x8000;  // test most sig bit

    if (SignBit) // negative

    {

      TReading = (TReading ^ 0xffff) + 1; // 2's comp

    }

    Tc_100 = (TReading*100/2);    


    Whole = Tc_100 / 100;  // separate off the whole and fractional portions

    Fract = Tc_100 % 100;


    sprintf(buf, "%d:%c%d.%d\337C     ",sensor,SignBit ? '-' : '+', Whole, Fract < 10 ? 0 : Fract);


    lcd.setCursor(0,sensor%LCD_HEIGHT);

    lcd.print(buf);

  }

}

オリジナルのページ

https://docs.arduino.cc/learn/communication/one-wire

最終更新日

November 19, 2022

inserted by FC2 system