2017年8月28日 星期一

*2*nRF24L01+*2* SHT31 單點無線溫溼度傳輸

網頁最後修改時間:2020/12/21 

在之前的兩篇 0, 1 的網頁,已經針對選擇哪一個頻道做為通訊,以及傳送端與接收端選擇何種天線的 nRF24L01+ 無線模組做過說明。有了這些資訊之後,接下來就是開始無線通訊的部分,這篇先由單點無線通訊開始。
參考接線圖:
下面是無線溫溼度傳輸的發射端與接收端的參考接線圖。接線的時候盡量選擇手邊可用的相同材料即可,不限定一定要跟網頁一樣,因為這篇網頁的重點在無線數據的傳輸上,而不是取得 SHT31 的數據。

微控制器可選擇不一樣的 Arduino 開發板。若沒有相同的溫溼度感測器,使用其他的也可以 (改一下函式庫和相關程式碼),甚至手動建立數據也可以。發射端與接收端接在 <A1><A2> 接腳上面的開關圖示,代表是否選擇 UART 除錯輸出、使用整合型 LCD 顯示日期時間和溫溼度數據;這裡可以直接使用跳線替代,不接則不會輸出任何數據。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 發射端參考接線圖:
發射端參考接線圖
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 發射端接線材料:
請注意到,我們這邊所使用的無線模組,都是有加裝轉接板的!如果不接轉接板跟著接線會燒掉無線模組。
發射端實際接線完成參考圖

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 接收端參考接線圖:
接收端參考接線圖
/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 接收端接線材料:
請注意到,我們這邊所使用的無線模組,都是有加裝轉接板的!如果不接轉接板跟著接線會燒掉無線模組。
接收端實際接線完成參考圖
Arduino 函式庫:
下面兩個 Arduino 函式庫,在編譯前必須先行安裝的,請至函式庫下方的連結網頁中下載;其餘的直接在程式碼中實現。
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
這篇網頁使用 Arduino IDE v1.8.4 編譯上傳程式碼!
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

Arduino 程式碼:
下面兩個程式碼不做逐行解釋,在這裡只做整體說明,需要詳細說明的話,請自行再閱讀零件資料手冊與函式庫的說明。

在這兩個程式中,不管是發射端與接收端都使用 nRF24L01+ 做無線通訊,所以兩者的的無線頻率與基本組態設定都必須一樣,否則無法互相通訊!預設的情況下,nRF24L01+ 六個 data pipe 的自動確認 (Auto  Acknowledgment) 功能都是開啟的,所以在發射端只要負責將數據準備好傳送 (write) 出去,接收端則是等待 (available())數據過來 (read) 後進行計算,其他的不需要去管,也不需要再做處理,nRF24L01+ 自己會搞定!至於附加的週邊,只是為了顯示處理後的數據而已,用與不用取決於實際應用的場合上,可自行再做選擇!

nRF24L01+ 可接收來自 6 個同頻率不同位址的無線數據,由於 data pipe 0 額外也用於傳送,所以在接收端的設定是使用 data pipe 1 來做數據接收,保留 data pipe 0 作為之後程式用。當 nRF24L01+ 無線模組有用到傳送與接收的功能時,如果又選擇 data pipe 0 作為接收,那麼每一次在傳送或是接收數據時,都必須重新設定傳送或是接收的位址,否則就會導致通訊失敗。這樣設定有點煩,簡單與程式碼容易了解起見,發射端與接收端分別使用不同的 data pipe。

接著,就直接進入到程式碼的部分。

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 發射端程式碼:
在發射端的程式碼主要是取得 SHT31-D 的溫溼度 raw data (原始數據,未經過計算),然後直接使用 nRF24L01+ 傳送出去。此時,若接收端不存在的話,開啟 UART 除錯就會出現 Tx failed 的文字訊息出現;從這裡也可以知道,自動確認是由 nRF24L01+ 自己完成。

在不開啟 UART 除錯功能的情況下,發射端除了訊息不輸出之外,也不再進一步的計算 SHT31-D 的溫溼度,這樣可減少 MCU 處理的時間增加空閒時間,讓 MCU 進入到睡眠的狀態節省電力 (在此先保留此功能)。由於是測試,所以每次通訊的時間設得比較短,只有 1000 ms。實際應用上,每次通訊可以設幾十秒或是幾分鐘一次,數據傳送出去之後,裝置就進入到睡眠狀態已節省電力,這樣的方式就很適合用在電池供應的裝置上。

#include <Wire.h>
#include <SPI.h>
#include <RF24.h>
#include <printf.h>
/** SHT31-D */
const int SHT31_IIC_ADDRESS = 0x44;
unsigned char rawdata[6];
/** nRF24L01+ */
#define CEPIN 7
#define CSNPIN 8
// RF 頻道
const uint8_t RF_CHANNEL = 100;
RF24 rf24( CEPIN, CSNPIN );
// 通訊位址
const uint8_t receiverAddress[] = "Node0";
//----------------------------------------- end of eRF24L01+
/** debug */
#define DEBUGOUTPUT A1
bool isdebug;
//----------------------------------------- end of SHT31-D
/** time */
unsigned long previousMills = 0;
const unsigned long interval = 1000; // milliseconds
//----------------------------------------- end of time
void setup() {
Serial.begin( 115200 );
printf_begin();
pinMode( DEBUGOUTPUT, INPUT_PULLUP );
isdebug = !digitalRead( DEBUGOUTPUT );
/** SHT31-D */
Wire.begin();
delay(10);
if( !checki2cdevice( SHT31_IIC_ADDRESS ) ) {
if( isdebug) Serial.println( F("SHT31-D not found!") );
while(1) delay(1);
}
softResetSHT31();
/** nRF24L01+ */
if( !rf24.isChipConnected() ) {
if( isdebug ) Serial.println( F("nRF24L01+ not found!") );
while(1) delay(1);
}
// RF 相關參數設定
rf24.setPayloadSize( sizeof(rawdata) );
rf24.setChannel( RF_CHANNEL );
rf24.setPALevel( RF24_PA_HIGH );
rf24.setDataRate( RF24_250KBPS );
rf24.openWritingPipe( receiverAddress );
// 除錯模式下,輸出無線模組的設定
if( isdebug ) rf24.printDetails();
}
void loop() {
// 每隔 intercal ms 傳送一次資料
unsigned long currentMillis = millis();
if( currentMillis - previousMills >= interval ){
previousMills = currentMillis;
/** 取得溫濕度的 raw data */
memset( &rawdata, 0, sizeof(rawdata) );
if( readRawDataSHT31() ) {
if( !rf24.write( &rawdata, sizeof(rawdata) ) && isdebug ) {
Serial.println( F(" Tx failed") );
}
if( isdebug ) {
// 計算溫溼度值,不確認 CRC 正確性
int temp = (rawdata[0] * 256) + rawdata[1];
float cTemp = -45.0 + (175.0 * temp / 65535.0);
float fTemp = (cTemp * 1.8) + 32.0;
float humidity = (100.0 * ((rawdata[3] * 256.0) + rawdata[4])) / 65535.0;
Serial.print( F("Temp: ") );
Serial.print( cTemp );
Serial.print( " C (");
Serial.print( fTemp );
Serial.print( F(" F), Humi: "));
Serial.print( humidity );
Serial.println( "%RH");
}
}
}
}
bool checki2cdevice( uint8_t i2caddr ) {
Wire.beginTransmission( i2caddr );
if( Wire.endTransmission() == 0 ) return true;
return false;
}
/****************************************************************************************
* SHT31-D
* **************************************************************************************/
/**
* [readRawDataSHT31 讀取 SHT31-D 未經計算的原始資料]
*
* @return [取得 6-byte 的資料]
*/
bool readRawDataSHT31() {
Wire.beginTransmission( SHT31_IIC_ADDRESS );
Wire.write( 0x2C );
Wire.write( 0x06 );
Wire.endTransmission();
delay(300);
Wire.beginTransmission( SHT31_IIC_ADDRESS );
Wire.endTransmission();
Wire.requestFrom( SHT31_IIC_ADDRESS, 6 );
if( Wire.available() == 6 ) {
rawdata[0] = Wire.read(); rawdata[1] = Wire.read(); rawdata[2] = Wire.read();
rawdata[3] = Wire.read(); rawdata[4] = Wire.read(); rawdata[5] = Wire.read();
return true;
}
return false;
}
void softResetSHT31() {
Wire.beginTransmission( SHT31_IIC_ADDRESS );
Wire.write( 0x30 );
Wire.write( 0xA2 );
Wire.endTransmission();
delay(10);
}
//-------------------------------------------------------------------------------------
發射端程式碼下載

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 接收端程式碼:
相對於發射端,接收端負責的東西就比較多,除了接收數據並進行運算之外,由於不使用網路上傳數據,所以除了選擇由 UART 輸出計算後的溫溼度之外,也提供整合型 LCD 顯示的方式,都是由外部接腳做控制。

開啟 UART 輸出的情況下,幾乎所有的訊息都會輸出,但是 DS3231 所提供的日期和時間不會;開啟使用整合型 LCD,則螢幕第一行分時顯示現在日期和時間,第二則顯示最近一次接收並計算之後的溫溼度值。

#include <Wire.h>
#include <SPI.h>
#include <RF24.h>
#include <printf.h>
#include <RTClib.h>
/** 整合型 LCD */
#define LCD_IIC_ADDRESS 0x3C
char lcdbuf[17];
//----------------------------------------- end of 整合型 LCD
/** DS3231 */
RTC_DS3231 rtc;
//----------------------------------------- end of DS3231
/** nRF24L01+ */
#define CEPIN 7
#define CSNPIN 8
// RF 頻道
const uint8_t RF_CHANNEL = 100;
RF24 rf24( CEPIN, CSNPIN );
// 通訊位址
const uint8_t thisNodeAddress[] = "Node0";
//----------------------------------------- end of nRF24L01+
/** debug */
const uint8_t DEBUGOUTPUT = A1;
const uint8_t IICLCDOUTPUT = A2;
bool isdebug, isiiclcd;
//----------------------------------------- end of debug
/** time update */
unsigned long previousMills = 0;
const long interval = 1000; // milliseconds
//----------------------------------------- end of time update
unsigned char rawdata[6];
void setup() {
Serial.begin( 115200 );
Wire.begin();
printf_begin();
pinMode( DEBUGOUTPUT, INPUT_PULLUP );
pinMode( IICLCDOUTPUT, INPUT_PULLUP );
isdebug = !digitalRead( DEBUGOUTPUT );
isiiclcd = !digitalRead( IICLCDOUTPUT );
/** RTC DS3231 */
// 須先完成時間的調整
if( !checki2cdevice( DS3231_ADDRESS ) ) {
if( isdebug ) Serial.println( F("DS3231 RTC Module not found!") );
while(1) delay(1);
}
rtc.begin();
/** 整合型 LCD (IIC 模式) */
if( isiiclcd ) {
if( !checki2cdevice( LCD_IIC_ADDRESS ) ) {
if( isdebug ) Serial.println( F("LCD not found!") );
while(1) delay(1);
}
// 整合型 LCD 初始化 @ I2C 模式
initLCD();
delay(100); // delay 100 ms
clearLCD();
delay(100); // delay 100 ms
displayCharOnLCD( 1, 1, "**LCD/RTC ready*", 16 );
delay(1000);
}
/** nRF24L01+ */
rf24.begin();
if( !rf24.isChipConnected() ) {
if( isdebug ) Serial.println( F("nRF24L01+ not found!") );
while(1) delay(1);
}
// RF 相關參數設定
rf24.setPayloadSize( sizeof(rawdata) );
rf24.setChannel( RF_CHANNEL );
rf24.setPALevel( RF24_PA_HIGH );
rf24.setDataRate( RF24_250KBPS );
rf24.openReadingPipe( 1, thisNodeAddress );
rf24.startListening();
if( isiiclcd ) {
displayCharOnLCD( 2, 1, "*nRF24L01+ ready", 16 );
delay(800);
clearLCD();
delay(100);
}
// 除錯模式下,輸出無線模組的設定
if( isdebug ) rf24.printDetails();
}
void loop() {
unsigned long currentMillis = millis();
if( currentMillis - previousMills >= interval ) {
// 更新時間顯示
static uint8_t idx = 0;
previousMills = currentMillis;
DateTime now = rtc.now();
switch( ++idx ) {
case 1:
sprintf( lcdbuf, "*-*%4d/%02d/%02d*-*", now.year(), now.month(), now.day() );
displayCharOnLCD( 1, 1, lcdbuf, 16 );
break;
case 3:
case 4:
case 5:
sprintf( lcdbuf, "*--*%02d:%02d:%02d*--*", now.hour(), now.minute(), now.second() );
displayCharOnLCD( 1, 1, lcdbuf, 16 );
if( idx == 5 ) idx = 0;
default:
;
}
}
if( rf24.available() ) {
memset( &rawdata, 0, sizeof(rawdata) );
rf24.read( &rawdata, sizeof( rawdata ) );
/** 計算 SHT31-D 溫溼度數據 */
int temp = (rawdata[0] * 256) + rawdata[1];
float cTemp = -45.0 + (175.0 * temp / 65535.0);
float fTemp = (cTemp * 1.8) + 32.0;
float humidity = (100.0 * ((rawdata[3] * 256.0) + rawdata[4])) / 65535.0;
/** UART 溫溼度資料輸出 */
if( isdebug ) {
// 計算溫溼度值,不確認 CRC 正確性
Serial.print( F("Temp: ") );
Serial.print( cTemp );
Serial.print( " C (");
Serial.print( fTemp );
Serial.print( F(" F), Humi: "));
Serial.print( humidity );
Serial.println( "%RH");
}
/** 整合型 LCD 溫濕度資料輸出 */
// line 2, format:100.00C 100.0%RH
if( isiiclcd ) {
uint8_t Tfractional, RHfractional;
uint16_t Tnumber, RHnumber;
Tnumber = (int)cTemp;
RHnumber = (int)humidity;
Tfractional = (cTemp - (float)Tnumber ) * 100; // 小數點兩位
RHfractional = (humidity - (float)RHnumber) * 10; // 小數點一位
sprintf( lcdbuf, "%3d.%02dC %3d.%1d%cRH",
Tnumber, Tfractional,
RHnumber, RHfractional, 0x25 );
displayCharOnLCD( 2, 1, lcdbuf, 16 );
}
}
}
bool checki2cdevice( uint8_t i2caddr ) {
Wire.beginTransmission( i2caddr );
if( Wire.endTransmission() == 0 ) return true;
return false;
}
/****************************************************************************************
* LCD
****************************************************************************************/
void initLCD()
{
Wire.beginTransmission( LCD_IIC_ADDRESS );
Wire.write( 0x00 );
Wire.write( 0x38 );
Wire.write( 0x0C );
Wire.write( 0x01 );
Wire.write( 0x06 );
Wire.endTransmission();
}
void clearLCD()
{
Wire.beginTransmission( LCD_IIC_ADDRESS );
Wire.write( 0x80 );
Wire.write( 0x01 );
Wire.endTransmission();
}
void displayCharOnLCD( int line, int column, const char *dp, unsigned char len )
{
unsigned char i;
Wire.beginTransmission( LCD_IIC_ADDRESS );
Wire.write( 0x80 );
Wire.write( 0x80 + (line - 1 ) * 0x40 + ( column - 1 ) );
Wire.write( 0x40 );
for( i = 0; i < len; i++) {
Wire.write( *dp++ );
}
Wire.endTransmission();
}
//-------------------------------------------------------------------------------------
接收端程式碼下載 (2020/12/21 更新)

/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*
程式碼與資料都在網頁提供的連結或是網頁中,請自行下載使用與測試!
/*-/--*-*/*/*/*/***//-*-*-**-*/*-*-/*/*/*-*-/-////--/**/**--**/--///--//**----**//--**//**----***//*-**//*

/*--*//**---/*///**---*-*////***--*/*///***----*///--*/*///**--*/*//**--**/*//
* 測試影片:
下面是測試的影片,兩個線路中的 <S1>、<S2><S3> 全部接地,UART 輸出與硬體線路一起動作,動作與測試過程影片中有文字描述

結論:
為什麼要花這麼多的篇幅來寫 nRF24L01+ 這個已經存在很久的無線模組,其實是為了 LoRa (Long Range) 鋪路,兩者都是無線模組 (前者是 2.4GHz,後者是 433 MHz / 866 MHz / 915 MHz ),特別適用於網路不普及也不方便的區域處數據的取得,一但這些數據連接到網路時,不就是 "物聯網" 了嗎 ?

所以呢 ? ESP8266 出現了!

不過寫到這裡之前我已經在想,到底下一篇是要 Arduino + ESP8266 (AT 指令),還是直接使用 ESP8266 來延續這個網頁裡面的東西 ? 記得有人問過我 "Arduino + ESP8266 (AT 指令) 和 Blynk 怎麼結合來用,並將資料顯示在手機上",要不下一篇就這個主題吧!


<< 部落格相關網頁 >>

沒有留言:

張貼留言

留言屬名為"Unknown"或"不明"的用戶,大多這樣的留言都會直接被刪除掉,不會得到任何回覆!

發問問題,請描述清楚你(妳)的問題,別人回答前不會想去 "猜" 問題是什麼?

不知道怎麼發問,請看 [公告] 部落格提問須知 - 如何問問題 !