這篇文章來(lái)源于DevicePlus.com英語(yǔ)網(wǎng)站的翻譯稿。
點(diǎn)擊這里閱讀 LoRaLib 開(kāi)發(fā)板 >
為了控制 Arduino長(zhǎng)距離通信教程–LoRenz 開(kāi)發(fā)板中構(gòu)建的LoRenz開(kāi)發(fā)板,我開(kāi)發(fā)了LoRaLib——用于SX1278芯片的開(kāi)源Arduino庫(kù)。這個(gè)庫(kù)從零開(kāi)始設(shè)計(jì),目的只有一個(gè):制作易于使用的API,即使是初學(xué)者也可以實(shí)現(xiàn)LoRa通信。該庫(kù)的目標(biāo)是使遠(yuǎn)程通信與串行通信一樣簡(jiǎn)單。
軟件
Arduino IDE
LoRaLib Arduino 庫(kù) (可在 GitHub 上獲得)
LoRaLib 庫(kù)
SX1278有多種不同設(shè)置,允許用戶完全自定義范圍、數(shù)據(jù)速率和功耗,但是最重要的三個(gè)設(shè)置如下所示:
帶寬 SX1278允許的帶寬設(shè)置為7.8 kHz至500 kHz。帶寬值越高,數(shù)據(jù)傳輸越快。然而,這是以降低總靈敏度為代價(jià)的,因此降低了最大范圍。
擴(kuò)頻因子 在LoRa調(diào)制中,每個(gè)信息位由多個(gè)啁啾表示。擴(kuò)頻因子是指每位數(shù)據(jù)有多少啁啾。SX1278支持7種不同的設(shè)置,擴(kuò)頻因子越高,數(shù)據(jù)傳輸越慢,范圍越大。
編碼速率 為了提高傳輸?shù)姆€(wěn)定性,SX1278可以執(zhí)行錯(cuò)誤檢查功能。此錯(cuò)誤檢查的度量稱為編碼速率,可以設(shè)定四個(gè)值。編碼速率設(shè)為最低的4/5時(shí),傳輸不太穩(wěn)定,速度稍快。編碼速率設(shè)為最高的4/8時(shí),鏈路更可靠,但代價(jià)是數(shù)據(jù)傳輸速率較慢。
庫(kù)的默認(rèn)設(shè)置為:帶寬為500 kHz、編碼速率為4/5和擴(kuò)頻因子為12。這些設(shè)置是范圍、穩(wěn)定性和數(shù)據(jù)速率之間的合理平衡。當(dāng)然,這些設(shè)置可以通過(guò)函數(shù)隨時(shí)更改。
該庫(kù)內(nèi)置數(shù)據(jù)包類和尋址系統(tǒng)。地址長(zhǎng)度為8字節(jié),那么最大的尋址數(shù)量就是1.8千億億(1.8 × 10^19)。這個(gè)數(shù)值大的離譜。相比之下,NASA估計(jì)我們銀河系中的恒星數(shù)量?jī)H為“4億”(4×10^11)。每個(gè)數(shù)據(jù)包由源地址、目標(biāo)地址和最多240字節(jié)的有效負(fù)載組成。當(dāng)然,該庫(kù)還提供了幾種讀取和寫(xiě)入分組數(shù)據(jù)的方法。
讓我們來(lái)看一下使用這個(gè)庫(kù)是多么容易。假設(shè)我們有兩個(gè)帶有SX1278模塊的LoRenz開(kāi)發(fā)板。它們相距幾百米,所以我們可以使用默認(rèn)設(shè)置。首先,我們必須包含庫(kù)頭文件。然后,我們用默認(rèn)設(shè)置創(chuàng)建 LoRa 類的一個(gè)實(shí)例,用目標(biāo)地址和消息創(chuàng)建 packet 類的一個(gè)實(shí)例。源地址由庫(kù)自動(dòng)生成并寫(xiě)入Arduino EEPROM。要檢查所有內(nèi)容是否已正確保存,我們會(huì)讀取數(shù)據(jù)包信息并將其打印到串行端口。接下來(lái),我們只需調(diào)用 tx() 函數(shù)即可。一會(huì)兒之后……完成!只需一個(gè)命令,我們的數(shù)據(jù)包就傳送成功了!
// include the library #include // create instance of LoRa class with default settings LoRa lora; // create instance of packet class // destination: "20:05:55:FE:E1:92:8B:95" // data: "Hello World !" packet pack("20:05:55:FE:E1:92:8B:95", "Hello World!"); void setup() { Serial.begin(9600); // initialize the LoRa module with default settings lora.init(); // create a string to store the packet information char str[24]; // print the source of the packet pack.getSourceStr(str); Serial.println(str); // print the destination of the packet pack.getDestinationStr(str); Serial.println(str); // print the length of the packet Serial.println(pack.length); // print the data of the packet Serial.println(pack.data); } void loop() { Serial.print("Sending packet "); // start transmitting the packet uint8_t state = lora.tx(pack); if(state == 0) { // if the function returned 0, a packet was successfully transmitted Serial.println(" success!"); } else if(state == 1) { // if the function returned 1, the packet was longer than 256 bytes Serial.println(" too long!"); } // wait a second before transmitting again delay(1000); }
當(dāng)然,我們需要第二套配有LoRenz 開(kāi)發(fā)板的Arduino來(lái)接收該數(shù)據(jù)包。 系統(tǒng)設(shè)置不變,只是這次我們調(diào)用 rx() 函數(shù),然后打印接收到的數(shù)據(jù)包。此函數(shù)將等待數(shù)據(jù)包,如果數(shù)據(jù)沒(méi)有在某個(gè)時(shí)間內(nèi)到達(dá),該函數(shù)將超時(shí),以便您的代碼不會(huì)完全掛起。該庫(kù)甚至還會(huì)檢查傳輸?shù)臄?shù)據(jù)包是否已損壞,如果是,則將其丟棄。
// include the library #include // create instances of LoRa and packet classes with default settings LoRa lora; packet pack; void setup() { Serial.begin(9600); // initialize the LoRa module with default settings lora.init(); } void loop() { Serial.print("Waiting for incoming transmission ... "); // start receiving single packet uint8_t state = lora.rx(pack); if(state == 0) { // if the function returned 0, a packet was successfully received Serial.println("success!"); // create a string to store the packet information char str[24]; // print the source of the packet pack.getSourceStr(str); Serial.println(str); // print the destination of the packet pack.getDestinationStr(str); Serial.println(str); // print the length of the packet Serial.println(pack.length); // print the data of the packet Serial.println(pack.data); } else if(state == 1) { // if the function returned 1, no packet was received before timeout Serial.println("timeout!"); } else if(state == 2) { // if the function returned 2, a packet was received, but is malformed Serial.println("CRC error!"); } }
當(dāng)然,這只是最基本的例子。庫(kù)本身可以做更多事情,而且我還在繼續(xù)開(kāi)發(fā)更多的功能。有關(guān)該庫(kù)和所有其他功能的更深入信息,請(qǐng)參閱我的 GitHub 以及那里托管的文檔。
Arduino 加密
本文結(jié)束之前,我還想討論一下Arduino的加密。我在上一篇文章中提到了這個(gè)問(wèn)題。現(xiàn)在,我們發(fā)送的所有數(shù)據(jù)都是未加密的。這意味著擁有相同配置、使用相同模塊和相同設(shè)置的任何人都能攔截和閱讀我們的消息。攻擊者甚至可以發(fā)送自己的消息,而我們卻無(wú)法分辨。顯然,這并不安全。
最簡(jiǎn)單的解決方案就是使用某種加密。具體地,我決定使用 Rijndael 密碼。沒(méi)聽(tīng)說(shuō)過(guò)吧?這是因?yàn)檫@個(gè)名字是荷蘭語(yǔ),因此不好記憶和發(fā)音。密碼本身實(shí)際上非常普遍,但名稱更加引人注目:AES。它是一種對(duì)稱密碼,可在加密速度和安全性之間提供出色的平衡。此外,Arduino還提供了幾個(gè)AES庫(kù)!本項(xiàng)目使用的庫(kù)是Davy Landman開(kāi)發(fā)的AESLib(可從 GitHub 上獲得)。
如上所述,AES是一種對(duì)稱密碼 – 這意味著它使用相同的密鑰來(lái)加密和解密消息。現(xiàn)在,我們只有兩個(gè)設(shè)備,因此將密鑰硬編碼到Arduino中非常容易。當(dāng)然,如果我們想要?jiǎng)討B(tài)添加更多設(shè)備并創(chuàng)建某種無(wú)線網(wǎng)絡(luò),我們必須以某種方式實(shí)現(xiàn)安全密鑰交換,例如使用Diffie-Hellman交換。但是我們現(xiàn)在不會(huì)深入這個(gè)領(lǐng)域,我們只需將密鑰硬編碼到我們的Arduino程序中即可。
那么我們應(yīng)該如何修改上一章的代碼呢?修改并不多,說(shuō)實(shí)話,我們只需添加密鑰以及一個(gè)加密或解密數(shù)據(jù)包中的數(shù)據(jù)。這是發(fā)射機(jī)部分,加密通過(guò) aes128_enc_single() 函數(shù)完成。
// include the libraries #include #include // create instance of LoRa class with default settings LoRa lora; // create instance of packet class // destination: "20:05:55:FE:E1:92:8B:95" // data: "Hello World !" packet pack("20:05:55:FE:E1:92:8B:95", "Hello World! "); // our secret 16-byte long key uint8_t key[] = {0x2C, 0x66, 0x54, 0x94, 0xE3, 0xAE, 0xC7, 0x32, 0xC4, 0x66, 0xC8, 0xBE, 0xF3, 0x71, 0x22, 0x36}; void setup() { Serial.begin(9600); // initialize the LoRa module with default settings lora.init(); // create strings to store the packet information char src[24]; char dest[24]; // print the source of the packet pack.getSourceStr(src); Serial.print("Source:ttt"); Serial.println(src); // print the destination of the packet pack.getDestinationStr(dest); Serial.print("Destination:tt"); Serial.println(dest); // print the length of the packet Serial.print("Total # of bytes:t"); Serial.println(pack.length); // print the contents of unencrypted packet Serial.println("-------- Plain text ---------"); Serial.println(pack.data); // encrypt the data aes128_enc_single(key, pack.data); // print the contents of encrypted packet Serial.println("--- Encrypted with AES128 ---"); Serial.println(pack.data); } void loop() { Serial.print("Sending packet "); // start transmitting the packet uint8_t state = lora.tx(pack); if(state == 0) { // if the function returned 0, a packet was successfully transmitted Serial.println(" success!"); } else if(state == 1) { // if the function returned 1, the packet was longer than 256 bytes Serial.println(" too long!"); } // wait a second before transmitting again delay(1000); }
接收機(jī)部分如下所示,解密通過(guò)相同密鑰和函數(shù) aes128_dec_single() 完成。
// include the libraries #include #include // create instances of LoRa and packet classes with default settings LoRa lora; packet pack; // our secret 16-byte long key uint8_t key[] = {0x2C, 0x66, 0x54, 0x94, 0xE3, 0xAE, 0xC7, 0x32, 0xC4, 0x66, 0xC8, 0xBE, 0xF3, 0x71, 0x22, 0x36}; void setup() { Serial.begin(9600); // initialize the LoRa module with default settings lora.init(); } void loop() { Serial.print("Waiting for incoming transmission ... "); // start receiving single packet uint8_t state = lora.rx(pack); if(state == 0) { // if the function returned 0, a packet was successfully received Serial.println("success!"); // create strings to store the packet information char src[24]; char dest[24]; // print the source of the packet pack.getSourceStr(src); Serial.print("Source:ttt"); Serial.println(src); // print the destination of the packet pack.getDestinationStr(dest); Serial.print("Destination:tt"); Serial.println(dest); // print the length of the packet Serial.print("Total # of bytes:t"); Serial.println(pack.length); // print the contents of encrypted packet Serial.print("Encrypted (AES128):t"); Serial.println(pack.data); // decrypt the data aes128_dec_single(key, pack.data); // print the contents of unencrypted packet Serial.print("Plain text:tt"); Serial.println(pack.data); } else if(state == 1) { // if the function returned 1, no packet was received before timeout Serial.println("timeout!"); } else if(state == 2) { // if the function returned 2, a packet was received, but is malformed Serial.println("CRC error!"); } }
使用了密鑰之后,我們的消息現(xiàn)在是安全的。如果有人偷聽(tīng)我們的談話,他無(wú)法看到除地址之外的任何內(nèi)容,每個(gè)數(shù)據(jù)包中都是240字節(jié)的亂碼。同樣,如果攻擊者試圖傳輸他自己的消息,我們會(huì)立即知道,因?yàn)樗麄鬏數(shù)南⒉粫?huì)加密。
在Arduino上用AES加密非常簡(jiǎn)單,所以我推薦使用該加密方法。這不僅僅是一個(gè)很好的編程實(shí)踐。您永遠(yuǎn)不知道誰(shuí)以及為什么可能會(huì)偷聽(tīng)您看似無(wú)辜的對(duì)話。
結(jié)論
現(xiàn)在,Arduino遠(yuǎn)程無(wú)線通信的短暫旅途就要結(jié)束了。如果您開(kāi)發(fā)自己的LoRenz開(kāi)發(fā)板并將其應(yīng)用于一些很酷的Arduino項(xiàng)目,請(qǐng)告訴我!如果您有改進(jìn)LoRenz開(kāi)發(fā)板和LoRaLib庫(kù)的想法,請(qǐng)?jiān)贕itHub上與我分享。
我對(duì)開(kāi)發(fā)板進(jìn)行了測(cè)試,當(dāng)帶寬為500kHz、擴(kuò)展因子為12、編碼率為4/8時(shí),在無(wú)障礙環(huán)境中我能夠?qū)崿F(xiàn)超過(guò)500米的可靠傳輸;在茂密的森林中傳輸距離則超過(guò)200米。所有這一切都只是通過(guò)一根10cm天線實(shí)現(xiàn)的,而且發(fā)射器電源也只是廉價(jià)的9V電池而已(接收器從USB端口接電,最終通過(guò)Arduino板載穩(wěn)壓器供電)。這個(gè)距離還可以更長(zhǎng)(通過(guò)降低帶寬),但是這會(huì)導(dǎo)致傳輸速度顯著降低,在上述設(shè)置不變的情況下傳輸速度大約為1 kbps。
然而,對(duì)于我將來(lái)的項(xiàng)目,這些距離綽綽有余。請(qǐng)?jiān)谏缃幻襟w上關(guān)注DevicePlus,這樣您就不會(huì)錯(cuò)過(guò)任何有趣的內(nèi)容!
審核編輯:湯梓紅
-
通信
+關(guān)注
關(guān)注
18文章
6039瀏覽量
136106 -
開(kāi)發(fā)板
+關(guān)注
關(guān)注
25文章
5074瀏覽量
97661 -
Arduino
+關(guān)注
關(guān)注
188文章
6471瀏覽量
187288
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論