2014年6月18日 星期三

[ wireless-RF ] Arduino 之間或與 Raspberry Pi 之間的 RF 433MHz 通訊

網頁中所使用的零件可至露天賣場訂購:

RF 模組測試 - VirtualWire 函示庫

許多最近購買無線發射接收模組套件的使用者,來信、留言或是線上討論了一些問題,因此我花了一些時間做了個實驗,使用兩組 Arduino ( Arduino Duemilanove 和 Arduino Pro Mini ) 並使用 VirtualWire 函示庫來做為RF 發射與接收效果的測試,而這兩塊 Arduino 板子不一定要跟我用一樣,用手邊現有的就可以了。


RF 發射或接收模組,都是使用 5V 電源輸入。Arduino Pro Mini 板作為發射控制器,RF 發射端的 data 接腳接到 Arduino Pro Mini 板子的 D11 接腳;Arduino Duemilanove 板作為接收控制器,RF 接收端的 data 接腳接到 Arduino Duemilanove 的 D12 接腳。

下面的程式需要安裝 VirtualWire 函式庫,請下載 ( VirtualWire-1.27.zip ) 壓縮檔之後直接解壓縮在 Arduino 的 libraries 目錄下,然後打開 Arduino IDE,此時就會看到在 File \ Examples \ VirtualWire 下看到 client、receiver、transmitter server 四個範例程式,下面是我們用到的兩個用來測試的程式,程式列表如下:

transmitter.pde, Arduino Pro Mini, @5V, digital IO pin number 12 --> [ RF data pin ]
 1 // transmitter.pde
 2 //
 3 // Simple example of how to use VirtualWire to transmit messages
 4 // Implements a simplex (one-way) transmitter with an TX-C1 module
 5 //
 6 // See VirtualWire.h for detailed API docs
 7 // Author: Mike McCauley (mikem@airspayce.com)
 8 // Copyright (C) 2008 Mike McCauley
 9 // $Id: transmitter.pde,v 1.3 2009/03/30 00:07:24 mikem Exp $
10 
11 #include <VirtualWire.h>
12 
13 void setup()
14 {
15     Serial.begin(9600);   // Debugging only
16     Serial.println("setup");
17 
18     // Initialise the IO and ISR
19     // vw_set_ptt_inverted(true); // Required for DR3100
20     vw_setup(2000);  // Bits per sec
21 }
22 
23 void loop()
24 {
25     const char *msg = "hello";
26 
27     digitalWrite(13, true); // Flash a light to show transmitting
28     vw_send((uint8_t *)msg, strlen(msg));
29     vw_wait_tx(); // Wait until the whole message is gone
30     digitalWrite(13, false);
31     delay(200);
32 }

transmitter.pde 在確認發送 "hello" 資料出去之後,會再等待 200 ms 再繼續下一次傳送,資料傳送的速度每秒 2000 bits。

receiver.pde, Arduino Duemilanove, @5V, digital IO pin number 11 --> [ RF data pin ]
 1 // receiver.pde
 2 //
 3 // Simple example of how to use VirtualWire to receive messages
 4 // Implements a simplex (one-way) receiver with an Rx-B1 module
 5 //
 6 // See VirtualWire.h for detailed API docs
 7 // Author: Mike McCauley (mikem@airspayce.com)
 8 // Copyright (C) 2008 Mike McCauley
 9 // $Id: receiver.pde,v 1.3 2009/03/30 00:07:24 mikem Exp $
10 
11 #include <VirtualWire.h>
12 
13 void setup()
14 {
15     Serial.begin(9600); // Debugging only
16     Serial.println("setup");
17 
18     // Initialise the IO and ISR
19     //vw_set_ptt_inverted(true); // Required for DR3100
20     vw_setup(2000);  // Bits per sec
21 
22     vw_rx_start();       // Start the receiver PLL running
23 }
24 
25 void loop()
26 {
27     uint8_t buf[VW_MAX_MESSAGE_LEN];
28     uint8_t buflen = VW_MAX_MESSAGE_LEN;
29 
30     if (vw_get_message(buf, &buflen)) // Non-blocking
31     {
32  int i;
33 
34         digitalWrite(13, true); // Flash a light to show received good message
35  // Message with a good checksum received, dump it.
36  Serial.print("Got: ");
37  
38  for (i = 0; i < buflen; i++)
39  {
40      Serial.print(buf[i], HEX);
41      Serial.print(" ");
42  }
43  Serial.println("");
44         digitalWrite(13, false);
45     }
46 }

reveiver.pde 設定 Bits per sec 為 2000 與 transmitter.pde 一樣,只要正確接收傳送過來的資料,就會將每個字元以 16 進位的方式,輸出到 Serial Monitor 上。如下圖所示,將 "hello" 以 68 65 6C 6C 6F 來做表示。

receiver.pde 接收到的資料以16進位的方式顯示在 Serial Monitor 上


測試:

記得在賣場的介紹中說過,下面這兩組無線模組,發射與接收端可以交換著使用,不限定一定要同組才能做通訊。為了測試上的方便,左邊這一組稱為 RF433M ( -T:發射;-R:接收 ),右邊這一組稱為 ASK ( -T:發射;-R:接收 )。
左邊 RF433M:( 接收、發射 );右邊:ASK ( 發射、接收 )
我的測試方法很簡單,固定一組接收模組接收訊號並放置在一樓的樓梯旁邊的桌子,然後由 1 樓至 4 樓分別使用不同的發射端發射訊號,再由樓下一人監看接收端燈的亮滅速度。

ASK-R

1F
2F
3F
4F
RF433M-T
V
V
V
V
ASK-T
V
V
V
V

RF433M-R

1F
2F
3F
4F
RF433M-T
V
V
X
X
ASK-T
V
V
V
V

結果就如上表所示,ASK 這一組無線發射接收模組的穿牆效果很好,無論是作為接收或是發射都能達到應有的效果,這也是 ASK 這一組與眾不同之處。所以若是要用在遮蔽物比較多的場合,ASK 這一組會是一個好的選擇。

另外,說到 VirtualWire 函式庫,它的傳送方式有點像是網路的 UDP 通訊,但只傳送字元陣列不能傳輸數字,因此要傳輸數字格式,例如 12.34,就必須將其拆成 '1', '2', '.', '3', '4' 傳送,這是在傳送數據時比較麻煩的地方。但列舉一段為何使用 VirtualWire 函式庫而不使用 UART 的討論:
As discussed in the RFM documentation, ASK receivers require a burst of training pulses to synchronize the transmitter and receiver, and also requires good balance between 0s and 1s in the message stream in order to maintain the DC balance of the message. UARTs do not provide these. They work a bit with ASK wireless, but not as well as this code.
也就是使用 VirtualWire 函式庫會比使用 UART 來的好。


Arduino <-- ( RF433M or ASK ) --> Raspberry Pi 之間的通訊:

Arduino 與 Raspberry Pi 使用 RF433M 模組的通訊 ( ASK 模組同樣也適用 ),我在網路上找到兩篇網頁,裡面所用的函式庫也可以用來控制市面上的一些使用 315 MHz 和 433 MHz 的無線插座。但書到底其實就是逆向工程,先將訊號擷取出來做分析,然後找出規則再傳送編碼回去控制插座開或關,傳送的過程並不是只有一次,而是將控制插座開或關的編碼傳送多次以確保遠端收到訊號,詳細可以去看一下下面談到的程式碼或是參考部落格的這篇文章 "[ Wireless-RF] 使用樹莓派模擬 HT12E 遙控器編碼晶片的編碼格式"。

在這兩個網頁中,作者已經將接線與程式碼解釋得很清楚,只要搞清楚 Arduino 與 Raspberry Pi 之間如何使用 433MHz 模組做資料傳輸,不管是賣場的 RF433M 或是 ASK 模組,要怎麼應用就看自己如何發揮了  !!! 例如傳遞現在外界環境中的溫度、大氣壓力、照度、濕度...等。

接下來,我將兩個網頁中的內容大抵說明一下,做個引導 ! 程式中所需要用到的程式可以到下面連結先下載:

  1. RCSwitch 函式庫 ( Arduino 版本 ):作為發射與接收時使用的函式庫。
  2. RCSwitch 函式庫 ( Raspberry Pi 版本 ):做為發射端時使用的函式庫。
  3. 433Utils:混合上面兩個函式庫的 Raspberry Pi 和 Arduino 的 RF 工具程式,包括 Arduino 的 sketch 檔案與 Raspberry Pi 的命令列程式。Raspberry Pi 使用 433Utils 之前必須先安裝 WiringPi 函式庫,然後編譯產生所需要的程式 ( WiringPi 安裝可參考 Dragon 的網頁,或是直接參考下面網頁中的安裝說明 )。


* 433Mhtz RF communication between Arduino and Raspberry Pi: Arduino as receiver

發射端:Raspberry Pi
接收端:Arduino

Raspberry Pi 作為發射端,使用 RCSwitch 函式庫撰寫發射端的程式 ( source code:433Utils / RPi_utils / codesend.cpp )。

設定 GPIO #0 ( line 24 ) 接到 RF Module 的 DATA 接腳;接收命令列參數並轉換為數字傳遞給 code ( line 27 );使用 RCSwitch 函式庫之前,先初始化 wiringPi 函示庫 ( line 29 );初始化 RCSwitch 函式庫,並設定資料傳送的腳位為輸出 ( line 31 ... line 32 );傳送 24-bit 長度的 code ( line 34 )

433Utils / RPi_utils / codesend.cpp
 1 /*
 2 
 3  'codesend' hacked from 'send' by @justy
 4  
 5  - The provided rc_switch 'send' command uses the form systemCode, unitCode, command
 6    which is not suitable for our purposes.  Instead, we call 
 7    send(code, length); // where length is always 24 and code is simply the code
 8    we find using the RF_sniffer.ino Arduino sketch.
 9 
10  Usage: ./codesend decimalcode
11  (Use RF_Sniffer.ino to check that RF signals are being produced by the RPi's transmitter)
12  */
13 
14 #include "RCSwitch.h"
15 #include <stdlib.h>
16 #include <stdio.h>
17      
18 
19 int main(int argc, char *argv[]) {
20     
21     // This pin is not the first pin on the RPi GPIO header!
22     // Consult https://projects.drogon.net/raspberry-pi/wiringpi/pins/
23     // for more information.
24     int PIN = 0;
25     
26     // Parse the firt parameter to this command as an integer
27     int code = atoi(argv[1]);
28     
29     if (wiringPiSetup () == -1) return 1;
30  printf("sending code[%i]\n", code);
31  RCSwitch mySwitch = RCSwitch();
32  mySwitch.enableTransmit(PIN);
33     
34     mySwitch.send(code, 24);
35     
36  return 0;
37 
38 }

433Utils / RPi_utils / RCSwitch.cpp, enableTransmit()
 95 /**
 96  * Enable transmissions
 97  *
 98  * @param nTransmitterPin    Arduino Pin to which the sender is connected to
 99  */
100 void RCSwitch::enableTransmit(int nTransmitterPin) {
101   this->nTransmitterPin = nTransmitterPin;
102   pinMode(this->nTransmitterPin, OUTPUT);
103 }

send() 在傳送之前會將數字資料轉換為 length 長度 bit 的二進制 ASC Code 的 '0' 或 '1',前方沒用到的 bit 全部補 '0',最後將資料整合再傳送。

433Utils / RPi_utils / RCSwitch.cpp, send()
317 void RCSwitch::send(unsigned long Code, unsigned int length) {
318   this->send( this->dec2binWzerofill(Code, length) );
319 }

433Utils / RPi_utils / RCSwitch.cpp, enableReceive()
444 void RCSwitch::enableReceive() {
445   if (this->nReceiverInterrupt != -1) {
446     RCSwitch::nReceivedValue = NULL;
447     RCSwitch::nReceivedBitlength = NULL;
448     wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt);
449   }
450 }

433Utils / RPi_utils / RCSwitch.cpp, dec2binWzerofill()
 1 /**
 2   * Turns a decimal value to its binary representation
 3   */
 4 char* RCSwitch::dec2binWzerofill(unsigned long Dec, unsigned int bitLength){
 5   static char bin[64];
 6   unsigned int i=0;
 7 
 8   while (Dec > 0) {
 9     bin[32+i++] = ((Dec & 1) > 0) ? '1' : '0';
10     Dec = Dec >> 1;
11   }
12 
13   for (unsigned int j = 0; j< bitLength; j++) {
14     if (j >= bitLength - i) {
15       bin[j] = bin[ 31 + i - (j - (bitLength - i)) ];
16     }else {
17       bin[j] = '0';
18     }
19   }
20   bin[bitLength] = '\0';
21   
22   return bin;
23 }


Arduino 作為接收端,使用 RCSwitch 函式庫撰寫接收端的程式 ( source code:433Utils / Aduino_sketches / RF_Sniffer / RF_Sniffer.ino )。

程式設定 Arduino 的 Serial Port 速度為 9600 bps ( line 13 ),接收到的資料會在這邊做顯示 ( line 27 ... line 33 )。

RCSwitch 接收的方式需要用到中斷,以 Arduino Duemilanove 為例,有兩個中斷 ( INT0, INT1 ) 可以用 ( 位於板子的右下角,也就是 Digital IO pin 號碼 2 和 3 )。程式使用 INT0 負責接收傳送過來的訊號,因此 line 14 函式裡面的參數設為 0,並且在每次訊號發生變化時都會呼叫中斷處理程式。

Arduino Duemilanove 接腳圖, 來源:http://pighixxx.tumblr.com//image/57805840162

一但有資料進來之後,就可以開始開始接收資料 ( line 19 );先確認資料是否始無效的字元輸入 ( line 23 ... line 24 ),如果不是就可以接收並顯示資料的相關資訊 ( line 25 ... line 34 ),所有的資料都可以使用 Arduino 的 Serial Monitor 觀看。

433Utils / Aduino_sketches / RF_Sniffer / RF_Sniffer.ino
 1 /*
 2   RF_Sniffer
 3   
 4   Hacked from http://code.google.com/p/rc-switch/
 5   
 6   by @justy to provide a handy RF code sniffer
 7 */
 8 
 9 #include <RCSwitch.h>
10 RCSwitch mySwitch = RCSwitch();
11 
12 void setup() {
13   Serial.begin(9600);
14   mySwitch.enableReceive(0);  // Receiver on inerrupt 0 => that is pin #2
15 }
16 
17 void loop() {
18   
19   if (mySwitch.available()) {
20     
21     int value = mySwitch.getReceivedValue();
22     
23     if (value == 0) {
24       Serial.print("Unknown encoding");
25     } else {
26    
27      Serial.print("Received ");
28       Serial.print( mySwitch.getReceivedValue() );
29       Serial.print(" / ");
30       Serial.print( mySwitch.getReceivedBitlength() );
31       Serial.print("bit ");
32       Serial.print("Protocol: ");
33       Serial.println( mySwitch.getReceivedProtocol() );
34     }
35     
36     mySwitch.resetAvailable();
37     
38   }
39 
40 }

下面的程式位於 Arduino 函式庫 libraries 目錄下,但必須要有安裝 RCSwitch 函式庫。 
{Arduino libraries} / RCSwitch / RCSwitch.cpp
587 /**
588  * Enable receiving data
589  */
590 void RCSwitch::enableReceive(int interrupt) {
591   this->nReceiverInterrupt = interrupt;
592   this->enableReceive();
593 }
594 
595 void RCSwitch::enableReceive() {
596   if (this->nReceiverInterrupt != -1) {
597     RCSwitch::nReceivedValue = NULL;
598     RCSwitch::nReceivedBitlength = NULL;600
599     attachInterrupt(this->nReceiverInterrupt, handleInterrupt, CHANGE);
600   }
601 }


433Mhtz RF communication between Arduino and Raspberry Pi: Raspberry Pi as receiver

發射端:Arduino
接收端:Raspberry Pi

Arduino 做為發射端,使用 RCSwitch 函式庫撰寫發射端的程式。程式修改自 { Arduino libries } / RCSwitch / examples / SendDemo / SendDemo.pde,但忽略其中幾行程式碼,只留下傳送數字的代碼;不過還是可以直接使用 SendDemo.pde 做測試,不需要使用連結網頁上的。

程式設定 Digital IO Pin 號碼 10 接到發射模組的 DATA 接腳 ( line 17 );然後重複傳送四種開關 ON / OFF 的程式碼出去 ( line 30 ... line 57 ),連結網頁中只使用到 ( line 38 ... line 43, line 56 )。

{Arduino libraries} / RCSwitch /examples/ SendDemo / SendDemo.pde
 1 /*
 2   Example for different sending methods
 3   
 4   http://code.google.com/p/rc-switch/
 5   
 6 */
 7 
 8 #include <RCSwitch.h>
 9 
10 RCSwitch mySwitch = RCSwitch();
11 
12 void setup() {
13 
14   Serial.begin(9600);
15   
16   // Transmitter is connected to Arduino Pin #10  
17   mySwitch.enableTransmit(10);
18 
19   // Optional set pulse length.
20   // mySwitch.setPulseLength(320);
21   
22   // Optional set protocol (default is 1, will work for most outlets)
23   // mySwitch.setProtocol(2);
24   
25   // Optional set number of transmission repetitions.
26   // mySwitch.setRepeatTransmit(15);
27   
28 }
29 
30 void loop() {
31 
32   /* See Example: TypeA_WithDIPSwitches */
33   mySwitch.switchOn("11111", "00010");
34   delay(1000);
35   mySwitch.switchOn("11111", "00010");
36   delay(1000);
37 
38   /* Same switch as above, but using decimal code */
39   mySwitch.send(5393, 24);
40   delay(1000);  
41   mySwitch.send(5396, 24);
42   delay(1000);  
43 
44   /* Same switch as above, but using binary code */
45   mySwitch.send("000000000001010100010001");
46   delay(1000);  
47   mySwitch.send("000000000001010100010100");
48   delay(1000);
49 
50   /* Same switch as above, but tri-state code */ 
51   mySwitch.sendTriState("00000FFF0F0F");
52   delay(1000);  
53   mySwitch.sendTriState("00000FFF0FF0");
54   delay(1000);
55 
56   delay(20000);
57 }


Raspberry Pi 作為接收端,使用 Pi 版本的 RCSwitch 函式庫撰寫接收端的程式 RFSniffer:RFSniffer.cpp 是使用 Raspberry Pi 版本的 RCSwitch 函式庫寫的程式,使用 WiringPi 的中斷處理程式負責處理 DATA 接腳接收到的訊號,程式碼與 Arduino 版本的差不多。

使用 Pi 版本的 RCSwitch 函式庫之前,必須初始化 wiringPI 函式庫 ( RFSniffer.cpp, line 25 ... line 26 );接收端使用 GPIO #2 與 RF 接收模組的 DATA 接腳連接,並且設定這支接腳只要準位做變化 ( 不管是高或低 ) 都會引發中斷 ( RCSwitch.cpp, line 439 ... line 450 )

433Utils / RPi_utils / RFSniffer.cpp
 1 /*
 2   RF_Sniffer
 3   
 4   Hacked from http://code.google.com/p/rc-switch/
 5   
 6   by @justy to provide a handy RF code sniffer
 7 */
 8 
 9 #include "RCSwitch.h"
10 #include <stdlib.h>
11 #include <stdio.h>
12      
13      
14 RCSwitch mySwitch;
15  
16 
17 
18 int main(int argc, char *argv[]) {
19   
20      // This pin is not the first pin on the RPi GPIO header!
21      // Consult https://projects.drogon.net/raspberry-pi/wiringpi/pins/
22      // for more information.
23      int PIN = 2;
24      
25      if(wiringPiSetup() == -1)
26        return 0;
27 
28      mySwitch = RCSwitch();
29      mySwitch.enableReceive(PIN);  // Receiver on inerrupt 0 => that is pin #2
30      
31     
32      while(1) {
33   
34       if (mySwitch.available()) {
35     
36         int value = mySwitch.getReceivedValue();
37     
38         if (value == 0) {
39           printf("Unknown encoding");
40         } else {    
41    
42           printf("Received %i\n", mySwitch.getReceivedValue() );
43         }
44     
45         mySwitch.resetAvailable();
46     
47       }
48       
49   
50   }
51 
52   exit(0);
53 
54 
55 }


433Utils / RPi_utils / RCSwitch.cpp
436 /**
437  * Enable receiving data
438  */
439 void RCSwitch::enableReceive(int interrupt) {
440   this->nReceiverInterrupt = interrupt;
441   this->enableReceive();
442 }
443 
444 void RCSwitch::enableReceive() {
445   if (this->nReceiverInterrupt != -1) {
446     RCSwitch::nReceivedValue = NULL;
447     RCSwitch::nReceivedBitlength = NULL;
448     wiringPiISR(this->nReceiverInterrupt, INT_EDGE_BOTH, &handleInterrupt);
449   }
450 }


結論:

以上就是兩個連結網頁中程式碼的說明,照著連結網頁中的接線並載入相關的程式,就可以實現 Arduino 與 Raspberr Pi 之間的無線通訊,不管是使用 RF433M 或是 ASK 哪一種無線發射接收模組都可達到相同的功能。

在這篇網頁的開頭,也做了 RF433M 與 ASK 兩種無線模組的發射與接收的實驗,這是在我自家的環境下所做的,實際應用上必須實地做測試才會知道效果好的什麼程度,環境不同測試的效果也會不同 !!!

希望大家在使用無線發射接收模組時,可以更加的順手 !!!


<<相關資料與使用說明連結>>

2 則留言:

  1. 我想請問發出的訊號跟接收的訊後有可能會不一樣嗎?

    回覆刪除
    回覆
    1. 收到什麼就會輸出什麼,不一樣就是受到干擾或是收到資料之後的處理不正確導致。

      刪除