2015年7月26日 星期日

{ Arduino + DS18B20 } 如何取得多顆 DS18B20 溫度感測器序號和溫度值

此篇網頁是 DS18B20 說明應用網頁的其中一篇,除了微處理器使用 Arduino 之外,都是使用於讀取多個 DS18B20 溫度感測器序號和溫度值。

Arduino 使用 Serial Monitor 和整合型 LCD 輸出結果,ESP8266 則燒錄 NodeMCU 韌體使用 UART 輸出 ( NodeMCU 韌體燒錄可參閱這篇網頁說明 )。不同的是 ESP8266 可以整合 WiFi 網路做成 IoT ( 物聯網 ) 應用,而且只需要 ESP8266 本身就可做到,不再需要其他微處理器;若使用 AT 韌體就需要其他微處理器做控制中心。
*********************************************************************************

整合型 LCD可至露天賣場訂購:
*********************************************************************************


電路圖

DS18B20 使用單線式通訊與外部裝置做溝通,有兩種接線方式可供選擇 ( 不清楚的請看資料手冊或是露天賣場商品網頁中的介紹  ),電路圖使用正常供電模式 ( 與 DHT11 接線類似 ),可使用在高於 100 °C 的環境上。

DS18B20 每一顆都擁有唯一的 64-bit 序列號 ( ID ) 可供來識別,而且可以多個裝置同時使用單一線路來做通訊,佈線相當的簡單;所需要的外部元件只需要一個提升 ( pull-up ) 電阻 ( 無論使用多少個 DS18B20,只需要一個提升電阻 ),接線的參考電路圖如下所示。

Arduino + DS18B20s 參考電路圖
完成後的接線可參考下面的照片。照片中為了能夠讓使用者了解接線方式,因此特地將每一顆的 DS18B20 再另外由源頭拉線接出,使用者可以自行參考上面電路圖變化自己的接線。
實際接線參考圖 - 1
實際接線中,我使用三顆 ( 兩顆 TO-92 封裝;一顆不銹鋼封裝帶線的) DS18B20 來測溫度與取得序號,而下面是麵包板連接部分的近拍照片。
實際接線參考圖 - 2
中間靠近接電阻的三條線 (  +5V ; VDD ]、 D2 ; DQ ] 和 [ GND ; GND ] 是用來分接給 DS18B20 的。旁邊的三條線 (  +5V ; VDD ]、 D2 ; DQ ] 和 [ GND ; GND ] 是不銹鋼接頭 DS18B20 的連接線。右邊藍白可變電阻是用來給整合型 LCD 條螢幕亮度的線路。綠色線路就是分接出來給另外兩顆 DS18B20 用的連接線。

若手邊沒有整合型 LCD 的話,可以省略掉其電路接線的部分,只要使用 Arduino IDE 中 Serial Monitor 也可以得到每一顆 DS18B20 的序號與偵測到的溫度值。


Arduino 函式庫

Arduino 程式碼中使用下面兩個函式庫 ( 雲端硬碟裡面有,也可以使用下面所提供的連結下載 ):
若不清楚 Arduino 函式庫安裝的請看下面網頁中的說明:


執行結果

DallasTemperature 函式庫需要 OneWire 函式庫才能正常動作。程式碼修改自 DallasTemperature 函式庫 Examples/Multiple 目錄中的 Multiple.pde ( {Arduino Libraries}/Dallas-Temperature-Control/Multiple ),裡面加入了整合型 LCD 的支援與經由序號取得溫度值 ... 等功能,程式執行之後的結果會輸出到 Arduino IDE 的 Serial Monitor 與整合型 LCD 中。

如下圖所示,Serial Monitor 會輸出詳細的資料與說明
Serial Monitor 輸出結果
如下圖所示,整合型 LCD 上面會依序顯示序號與溫度值在螢幕上
整合型 LCD 輸出結果

程式碼 ( 雲端硬碟有完整程式碼,有連結的請自行下載! )
Note:下面將提供完整的程式碼片段,請使用者自行組合;其中一些註解的部分為節省網頁長度已被拿除,但不影響程式最終執行結果。

* 程式碼開頭:

此部分是屬於執行時的一些設定與定義,使用者在不增加任何功能的情況下,由這邊可調整程式執行的方式
 10 #include <Wire.h>
 11 #include <string.h>
 12 #include <OneWire.h>
 13 #include <DallasTemperature.h>
 14 
 15 // Data wire is plugged into port 2 on the Arduino
 16 #define ONE_WIRE_BUS 2
 17 #define TEMPERATURE_PRECISION 9
 18 #define MAX_DS18B20_DEVICES 10
 19 //*-----
 20 // 若不使用整合型 LCD,只要在下面這一行前面加雙斜線取消掉就可以
 21 #define I2CLCD 0x3C  // I2C address of I2CLCD
 22 #ifdef I2CLCD
 23   void initLCD();
 24   void clearLCD();
 25   void displayCharOnLCD( int line, int column, const char *dp, unsigned char len );
 26 #endif  
 27 //*-----
 28 void printAddress(DeviceAddress deviceAddress);
 29 void discoverOneWireDevices(void);
 30 
 31 // Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
 32 OneWire oneWire(ONE_WIRE_BUS);
 33 
 34 // Pass our oneWire reference to Dallas Temperature. 
 35 DallasTemperature sensors(&oneWire);
 36 
 37 // arrays to hold device addresses
 38 DeviceAddress Thermometer[MAX_DS18B20_DEVICES];
 39 uint8_t noDS = 0;
 40

line 10 - 13:使用到的函式庫以及標頭檔案。
line 16 - 18:這三個定義很重要!首先 line 16 定義了 Arduino 與 DS18B20 通訊的接腳 ( D2 );line 17 定義 DS18B20 的溫度解析度為 9-bit ( 9, 10, 11, 12 bits 可以選擇 );line 18 定義搜尋的 DS18B20 的數量 ( 理論上可以無限大!請視實際需要設定即可 )。
line 21 - 26:如果使用整合型 LCD,line 21 就必須是有效的一行程式碼;反之,若不使用整合型 LCD,就必須在 line 21 前方加雙斜線取消掉。只要 line 21 是有效的程式碼,那麼就運行 line 22 - 25,而整個程式中只要有 #ifdef I2CLCD ... #endif 裡面的程式碼就會被執行。
line28 - 29:函式宣告;程式碼在下方說明。
line 32:OneWire 函式庫初始化並定義通訊接腳。
line 35:DallasTemperature 函示庫初始化,參數是 line 32 宣告的實體 oneWire。
line 38:宣告一個可存放 MAX_DS18B20_DEVICES 數目大小的陣列,且陣列中的每一個元素都可存放 8-bytes ( 64-bit ) 的資料 ( 用來存放每一個 DS18B20 的序號 );DeviceAddress 定義如下:
DallasTemperature.h, line 58
57 
58 typedef uint8_t DeviceAddress[8];
59 
line 40:紀錄取得的 DS18B20 數目,最多為 255 個。

* 初始化設定 ( setup() ):

基本的函式庫初始化與設定都在這個地方,並將設定值與取得的 DS18B20 數量輸出到 Serial Monitor。

 10 
 41 void setup(void)
 42 {
 43   // start serial port
 44   Serial.begin(9600);
 45   Serial.println("Dallas Temperature IC Control Library Demo");
 46 
 47   // Start up the library
 48   sensors.begin();
 49 
 50   // LCD
 51   #ifdef I2CLCD
 52     Wire.begin();
 53     initLCD();
 54     delay(100);
 55     clearLCD();
 56     delay(100);
 57   #endif
 58 
 59   // locate devices on the bus
 60   Serial.print("Locating devices...");
 61   Serial.print("Found ");
 62   noDS = sensors.getDeviceCount();
 63   Serial.print(noDS, DEC);
 64   Serial.println(" devices.");
 65 
 66   // report parasite power requirements
 67   Serial.print("Parasite power is: "); 
 68   if (sensors.isParasitePowerMode()) Serial.println("ON");
 69   else Serial.println("OFF");
 70  
107   discoverOneWireDevices();
108   // show the addresses we found on the bus
109   for(uint8_t i = 0; i < noDS; i++)
110   {
111     Serial.print("Device ");
112     Serial.print(i);
113     Serial.print(" Address: ");
114     printAddress( Thermometer[i] );
115     Serial.println();
116     // set the resolution to 9 bit
117     sensors.setResolution( Thermometer[i], TEMPERATURE_PRECISION);
118     Serial.print("Device ");
119     Serial.print( i );
120     Serial.print(" Resolution: ");
121     Serial.print(sensors.getResolution(Thermometer[i]), DEC); 
122     Serial.println();
123   }
124 }

line 44:設定 Arduino 硬體 UART 的通訊速率。使用 USB 連接 Arduino 板子時,可以將訊息由此輸出到 Serial Monitor 中。
line 48:開始使用 DallasTemperature 函式庫。
line 51 - 57:如果 line 21 是有效的,就執行整合型 LCD 的初始化。
line 59 - 64:開始尋找連接到 line 16 接腳上面所有的 DS18B20,並輸出相關訊息。
line 67 - 69:詢問並輸出 DS18B20 使用的電源模式是否為 "寄生供電模式" ?
line 107:開始尋找通訊線上所有的 DS18B20 並且取得它們的 ID,並儲存在 Thermometer 陣列中 ( discoverOneWireDevices 函式下面再解釋 )。
line 109 - 124:設定在 line 107 函式所取得的所有的 DS18B20 解析度,設定後再取出輸出到 Serial Monitor 中 ( line 104 printAddress 函式下面再解釋 )

* 重複執行的部分 ( loop() ):

loop() 函式中,會一直重複的讀取所有 DS18B20 的溫度,然後每隔三秒輸出一個 DS18B20 的再序號與溫度。如果不需要顯示在 LCD 上,那麼 line 207 的延遲時間是可以拿掉,這樣可以很快地得到所有的 DS18B20 的序號和溫度值。

195 void loop(void)
196 { 
197   // call sensors.requestTemperatures() to issue a global temperature 
198   // request to all devices on the bus
199   Serial.print("Requesting temperatures...");
200   sensors.requestTemperatures();
201   Serial.println("DONE");
202 
203   // print the device information
204   for( uint8_t i = 0; i < noDS; i++)
205   {
206     printData( Thermometer[i] );
207     delay(3000);    
208   }
209 }

line 200:取得所有在通訊接腳上所有 DS18B20 的溫度值。
line 206:以序號取得溫度值,並輸出即顯示出來。( printData 函式下面再解釋 )。

* 副程式部分

各個副程式的名稱就說明該程式碼的用途:

126 // function to print a device address
127 void printAddress(DeviceAddress deviceAddress)
128 {
129   for (uint8_t i = 0; i < 8; i++)
130   {
131     // zero pad the address if necessary
132     if (deviceAddress[i] < 16) Serial.print("0");
133     Serial.print(deviceAddress[i], HEX);
134   }
135 
136   // LCD
137   #ifdef I2CLCD
138     char chrbuf[17];
139     for( uint8_t i = 0; i < 16; i+=2)
140     {
141       chrbuf[i] = 0x30 + deviceAddress[i/2] / 16;
142       if( chrbuf[i] > 0x39 ) chrbuf[i] += 7;
143       chrbuf[i+1] = 0x30 + deviceAddress[i/2] % 16;
144       if( chrbuf[i+1] > 0x39 ) chrbuf[i+1] += 7;
145     }
146     displayCharOnLCD( 1, 1, chrbuf, 16 );
147   #endif
148 }
149 
150 // function to print the temperature for a device
151 void printTemperature(DeviceAddress deviceAddress)
152 {
153   char chrbuf[17];
154   char prx[3];
155   int val_int, val_fra;
156   float tempC = sensors.getTempC(deviceAddress);
157   Serial.print("Temp C: ");
158   Serial.print(tempC);  
159   Serial.print(" Temp F: ");
160   Serial.print(DallasTemperature::toFahrenheit(tempC));
168   #ifdef I2CLCD
169     displayCharOnLCD( 2, 1, "Temp: ", 6);
170     dtostrf( tempC, 8, 4, chrbuf );
171     displayCharOnLCD( 2, 7, chrbuf, 8 );
172     sprintf( prx, "%cC", 0xdf );
173     displayCharOnLCD( 2, 15, prx, 2 );
174   #endif
175 }
176 
177 // function to print a device's resolution
178 void printResolution(DeviceAddress deviceAddress)
179 {
180   Serial.print("Resolution: ");
181   Serial.print(sensors.getResolution(deviceAddress));
182   Serial.println();    
183 }
184 
185 // main function to print information about a device
186 void printData(DeviceAddress deviceAddress)
187 {
188   Serial.print("Device Address: ");
189   printAddress(deviceAddress);
190   Serial.print(" ");
191   printTemperature(deviceAddress);
192   Serial.println();
193 }
194 

printAddress ( line 127 - 148 ):經由給定的 DeviceAddress,然後將裡面 8-bytes 的資料使用文字的方式輸出以及顯示在 LCD 上。

printTemperature ( line 151 - 175 ):經由給定的 DeviceAddress,取出通訊接腳中該 DS18B20 的溫度值並轉換為 °C 和 °F 輸出與顯示在 LCD 上。

printResolution ( line 178 - 183 ):取得現在給定的 DeviceAddress 所指定的 DS18B20 的溫度解析度並輸出。

printData ( line 186 - 193 ):呼叫 printAddress printTemperature 兩個函式輸出資料。

213 void discoverOneWireDevices() {
214   uint8_t i;
215   uint8_t devs = 0;
216 
217   Serial.print("Looking for 1-Wire devices...\n\r");
218   while(oneWire.search( Thermometer[devs] )) {
219     Serial.print("\n\rFound \'1-Wire\' device [");
220     Serial.print( devs );
221     Serial.print("]with address:\n\r");
222     for( i = 0; i < 8; i++) {
223       Serial.print("0x");
224       if (Thermometer[devs][i] < 16) {
225         Serial.print('0');
226       }
227       Serial.print(Thermometer[devs][i], HEX);
228       if (i < 7) {
229         Serial.print(", ");
230       }
231     }
232     if ( OneWire::crc8( Thermometer[devs], 7) != Thermometer[devs][7]) {
233         Serial.print("CRC is not valid!\n");
234         return;
235     }
236     else
237       devs++;
238   }
239   Serial.print("\n\r\n\rThat's it.\r\n");
240   oneWire.reset_search();
241   return;
242 }

discoverOnwWoreDevice ( line 213 - 244 ):這函式用來搜尋接在通訊接腳 ( Arduino D2 ) 上所有的 DS18B20,紀錄並輸出,而這函式只在程式運行時執行一次。每次要搜尋之前,必須執行 line 217:reset_search 一次,然後再搜尋 ( line 220),直到搜尋結束;每次只要搜尋到,就會輸出搜尋到的裝置順序號碼與其唯一的序號 ( line221 - 232 ),最後確認取得的資料 ( CRC check ) 是否正確,正確就繼續輸出下一筆的資料。

* 整合型 LCD 使用的程式

下面是整合型 LCD ( 使用 I2C 模式 ) 使用到的函式,主要是用來初始化 ( initLCD, line 248 - 257 )、清除螢幕 ( clearLCD, line 25 9 -268 ) 和從指定的位置上顯示字元 ( displayCharOnLCD, line 270 - 286 )。使用的方式請直接參考程式碼就可以。

244 /************************************************************************************
245 * LCD
246 ************************************************************************************/
247 #ifdef I2CLCD
248   void initLCD()
249   {
250     Wire.beginTransmission(I2CLCD);
251     Wire.write( 0x00 ); // N x commands
252     Wire.write( 0x38 ); // Function set
253     Wire.write( 0x0C ); // Display ON/OFF
254     Wire.write( 0x01 ); // Clear display
255     Wire.write( 0x06 ); // Entry mode set
256     Wire.endTransmission();
257   }
258 
259   void clearLCD()
260   {
261 
262     Wire.beginTransmission(I2CLCD);
263 
264     Wire.write( 0x80 ); // One command
265     Wire.write( 0x01 ); // Clear display  
266 
267     Wire.endTransmission();
268   }
269 
270   void displayCharOnLCD( int line, int column, const char *dp, unsigned char len )
271   {
272     unsigned char i;
273 
274     Wire.beginTransmission(I2CLCD);
275 
276     Wire.write( 0x80 );
277     Wire.write( 0x80 + ( line - 1 ) * 0x40 + ( column - 1 ) );
278     Wire.write( 0x40 );
279 
280     for( i = 0; i < len; i++)
281     {
282       Wire.write( *dp++ );
283     }
284 
285     Wire.endTransmission();
286   }
287 #endif

以上就是所有程式碼的說明。

* 程式測試說明:

線路都弄好之後,就直接使用 Arduino IDE 上傳 sketch。

多顆 DS18B20 使用時,可以先個別插在線路上以取得唯一的 64-bit 序號,只要按下板子上面的 RESET 按鈕,就可以重複這個動作。如果使用者的應用需要在不同距離上面,那麼剛剛說的部份就很重要,因為可以用序號取得溫度而且也可以做為該位置溫度的識別。

取得序號之後,就可以將全部使用到的 DS18B20 插上去,開始取得所有 DS18B20 的溫度值,一直循環下去。



結論:

這是個簡單使用 DS18B20 的例子,使用 Arduino 做為控制板取得所有單線上面所有的 DS18B20 序號與溫度值,使用者可以使用這個程式的框架,加入對於一片區域的溫度監控再產生相對應的動作,或是做為其他的應用。

若是使用者對於 IoT ( Internet of Thing, 物聯網 ) 有著濃厚興趣,可以參考另一篇使用 ESP8266 讀取 DS18B20 的部落格網頁 ( 使用 ESP8266 ( 燒錄 NodeMCU 韌體,使用 Lua 撰寫程式 ),多加入了,可使用瀏覽器查看連接的 DS18B20 溫度,敬請拭目以待!!!

沒有留言:

張貼留言