我如何製作功能齊全的 Arduino 氣象站
已發表: 2022-03-11更新:在本文發表後,我們的 Arduino 氣象站的工作仍在繼續,最終發布了開放氣象站 (OWS)。 查看更多更新、資源以及代碼和新教程。
這是怎麼回事?
風箏衝浪是世界上最容易上癮的運動之一。 它所需要的只是一個風箏板、一個水體和一些配件。 這是接觸大自然、解放思想和鍛煉身體的好方法。 另外,你真的可以為它發瘋。
所以有什麼問題?
哦,我忘了一個基本要求:風。 這就是我們的問題所在:除非您住在您最喜歡的風箏衝浪地點附近,否則您永遠不知道是否會有風。
我住在阿根廷的科爾多瓦,距離我風箏衝浪的湖約 130 公里(約 80 英里)。 這大約是兩個小時的車程,我可以應付。 但我無法處理天氣預報不准確的事實。 在我住的地方,良好的風力條件只持續幾個小時。 您要做的最後一件事是清理週一的日程安排去風箏衝浪,然後在兩個小時的車程後發現自己在無風的湖面上詛咒眾神。
我需要實時了解我最喜歡的風箏衝浪地點的風況。 所以我決定建立自己的氣象站。
在惡劣的環境中實時測量天氣
目標是向家裡的瀏覽器提供實時天氣數據:
在我進入細節之前,讓我們花點時間考慮一下像這樣的項目中涉及的關鍵問題和注意事項:
- 如何創建一個對小偷既沒有價值也沒有吸引力的氣象站?
- 如何將硬件成本和開發時間降至最低?
- 如何實時測量和訪問天氣數據並以有用的方式顯示?
- 所需測量:風和陣風、風向、雨量、大氣壓力、溫度、濕度
- 將電台連接到互聯網
- 存儲和檢索本地天氣數據
- 氣象站和服務器之間的通信
- 如何將維護減少到(幾乎)為零?
- 管理軟件掛起
- 管理連接丟失
- 管理能源供應損失
向我的小朋友問好!
您可能會認為手套的存在是為了讓車站看起來更友好; 但它實際上是用來測試氣壓傳感器(充氣手套內手套壓力增加)。 在右側,您可以看到車站的最終位置,它位於附近的一座塔頂。
我還設計和編寫了一個關於風箏衝浪的網站,其中包括一個站點測量的實時圖,以幫助風箏衝浪社區。 最後,我在 Facebook 上創建了一個風箏衝浪小組。
棒極了! 那麼你是怎麼做到的呢?
好吧,我將依次解決每一點:
“我怎樣才能創建一個對小偷既沒有價值也沒有吸引力的氣象站?”
這是一個關鍵因素,並且在許多方面推動了設計過程的其餘部分。 大多數低於 2000 美元的預製電台都需要通過 USB 連接到計算機。 如果小偷認出車站旁邊有一台電腦,那事情就完蛋了,因為更換電腦和車站的費用將超出我的個人預算。 因此,我決定以較低的成本從頭開始測試幾個硬件平台來實現該站點。
“我怎樣才能將硬件成本和開發時間降到最低?”
我一個人承擔了這個副項目的成本,並在業餘時間完成了所有工作,所以這當然是一個大問題。 我從流行的PIC32和一些預組裝的微芯片以太網模塊開始,但成本並沒有我預期的那麼低,而且硬件組裝和擴展涉及太多開銷。 然後,我開始研究 Arduino:一種使用 C 語言進行電子原型設計的開源硬件和軟件。 這正是我想要的,我可以在 DealeXtreme 上購買模塊。 我只需花費 15 美元和兩天的時間就可以開始玩了。
當然,Arduino 也有其局限性:只有 2KBytes 的 RAM 和 32Kbytes 用於我編譯的軟件——這並沒有為花哨的字符串或無用的變量1留下太多空間。
“如何實時測量和訪問天氣數據並以有用的方式顯示?”
目前我站可以測量:風速、陣風、風向、溫度、濕度、雨量、氣壓。 溫度、濕度和壓力由幾個庫處理,這讓生活變得更加輕鬆。
測量風速和雨量有點混亂。 通過打開和關閉一個開關(簧片開關)來操作傳感器。 因此,我需要實現硬件中斷,以便在傳感器觸發輸入時立即捕獲它。 也就是說,我需要調用一些方法:
attachInterrupt(RAINGAUGE_PIN, countRainCycles, FALLING);
一旦開關經歷由閉合或斷開電路產生的下降沿,此中斷將中斷正常代碼執行並調用 countAnemometerCycles 或 countRainCycles 函數。 開關的每次觸發都會增加一些變量。 (稍後,您權衡這些變量以考慮單位轉換。)
void countRainCycles() { rainCyclesCounter++; // This is easy! And it actually works. }
但沒那麼快! 由於任何硬件開關固有的開關彈跳效應,此過程會生成數百個錯誤觸發。 幸運的是,這個問題有硬件和軟件解決方案。
關於彈跳效果
彈跳效應是由於開關物理地打開或關閉其“觸點”而產生的,這些觸點與電路的其餘部分建立了聯繫。 當觸點開始分離(打開開關)或結合(關閉開關)時,可能會產生一些小電弧,以及電路中的機械彈性,將觸發電路在幾毫秒內打開和關閉。 當您撥動電燈開關時,這種效果並不明顯; 但是當您將中斷附加到信號的下降沿時,這種彈跳效果會觸發大量中斷。 更多在這裡。
我在軟件中實現了硬件去抖電路和類似版本。 但是您究竟是如何實現軟件去抖動的呢? 簡單! 在第一個預期觸發發生後,“等待”足夠的時間讓反彈穩定下來,然後再開始偵聽新的中斷。 這可以通過幾行 C 來完成:
void countRainCycles() { if (nextTimeRainIterrupt == 0 || nextTimeRainIterrupt < millis()) { rainCyclesCounter++; // The interrupts counter nextTimeRainIterrupt = millis() + 100; // Wait 100msecs before next trigger } }
millis() 函數返回自 Arduino 開啟以來的當前執行時間(以毫秒為單位)。 還值得注意的是,這些變量必須定義為 volatile 以指示編譯器不要優化執行,從而避免硬件中斷期間的不准確值。
不知何故,我需要工作站存儲累積的數據並定期將這些測量值發送到 MySQL 數據庫。 所以我添加了一個帶有 SD 插槽的以太網模塊來記錄值並在用戶(服務器)連接到工作站時檢索它們。 在使用 ADSL 連接在家進行測試期間,它的效果非常好——但當我使用 3G 互聯網(使用 3G 調製解調器)“在現場”測試它時,我幾乎失去了頭髮,因為當我試圖檢索測量! 經過大量測試,我終於發現互聯網上提供的描述“向連接的客戶端提供”數據的示例並沒有考慮到連接可能很差,以至於到客戶端的連接可能會在數據包傳輸過程中丟失,導致輸出緩衝區會溢出。 但是為什麼斷開的連接會導致緩衝區溢出呢? 好吧,假設傳輸會話開始並且站開始用數據填充輸出緩衝區。 理想情況下,客戶端消耗這個緩衝區的速度比它被填滿的速度要快。 但是,當連接 3G 調製解調器時,情況並非如此! 與客戶端的連接太差了,所以緩衝區填滿的速度比消耗的快,這導致緩衝區溢出和站點突然重新啟動。

為了解決這個問題,我需要在 Arduino 提供的以太網庫中添加一個函數,如下所示:
int EthernetClient::free() { if (_sock != MAX_SOCK_NUM) return W5100.getTXFreeSize(_sock); return 0; }
然後,在嘗試用更多數據填充緩衝區之前,我能夠檢查客戶端是否在緩衝區中有一些空間:
while (file.available() > 0) { if (client.free() > 0) { // This was key to solving the issue c = file.read(); client.print((char)c); } else { // No free buffer? Ok, I'll wait a couple of millis... delay(50); } } file.close();
順便說一句,如果您對 Arduino 編程感興趣,這裡有一個很棒的指南。
另一個有趣的任務是 LIFO 日誌的實現。 為什麼這是必要的? 好吧,通常情況下,當我將測量結果保存到給定文件時,方法很簡單:打開文件,將新樣本附加到末尾,然後關閉文件。 但是假設我想獲取最新的 1000 個測量值,按時間順序排序。 這些測量值在文件末尾; 所以我應該打開文件,將光標移到末尾,輸出最新的測量值,然後將文件光標返回到上一個測量值,並通過尋找樣本分隔符來檢測從哪裡開始和停止輸出。 Arduino 既沒有足夠的 RAM 也沒有足夠的處理器能力來快速執行這個過程,所以我需要另一種方法。 相反,我決定將文件以相反的順序輸出到服務器,然後在服務器端恢復字符串文字:
unsigned long filePosition = file.size(); file.seek(filePosition); while (filePosition >= 0) { if (client.free() > 0){ file.seek(filePosition); c = file.peek(); if (c != -1) { client.print((char)c); } if (filePosition <= 0) { break; } filePosition--; } }
具有 PHP 開發人員的任何經驗,都可以輕鬆獲得字符順序正確的最新示例:
// $output has the reversed string measures, each sample is delimited by ; $rows = split(";", trim($output)); array_walk_recursive($rows, 'reverseString'); if (strlen($rows[0]) == 0) { array_shift($rows); // Remove the first line if empty } function reverseString(&$row, $key) { $row = trim(strrev($row)); } // $rows is now the array of the latest samples :)
在服務器端,我設置了一個 cron 進程,每兩分鐘獲取一次最新的測量值,並將數據插入 MySQL 引擎。 為了顯示數據,我創建了 www.kitesurfcordoba.com.ar 並使用 jQuery 自動更新圖表(這些圖表本身是使用 pChart v2.0 生成的,這是一個很棒的開源庫)。
還有很多其他必要的技巧才能讓事情正常進行,與軟件和硬件工程相關,但我已經拖得夠久了——所以讓我們談談最小化維護。
“我怎樣才能將維護減少到(幾乎)零?”
這是一個主要問題,因為我到達車站肯定不容易——如果我願意開車兩個小時只是為了修理一個小故障,那麼我一開始就不必讓她進來(我之前沒提過這個,但畢竟我們都經歷過,車站其實是一個“她”,她的名字叫Dorothy)。
那麼我們在這裡討論的是哪些類型的錯誤呢? 好吧,例如:軟件可能會掛起,網絡可能會失去連接,能源供應可能會失敗(確實如此)等等。
本質上,該站需要執行盡可能多的自我恢復。 這就是為什麼我同時使用軟看門狗和硬看門狗。 對於那些不熟悉的人來說,看門狗是一種軟件或硬件,用於檢查系統是否正常運行,如果沒有,則嘗試使其恢復活力。 Arduino 有一個可以使用的嵌入式看門狗。 我將其設置為等待 8 秒:如果通話時間超過該時間限制,軟件看門狗將重置板。
wdt_enable(WDTO_8S); // "wdt" stands for "watchdog timer"
我喜歡這個功能。 但是,有時電路板會重置而以太網模塊不會。 為什麼? 嗯,這是一個相對實惠的原型板,而不是一個非常昂貴的防故障設備(你當然不應該用它來製造起搏器)。 為了克服這個缺點,我不得不通過將硬件復位輸入交叉連接到電路板上的數字輸出來破解 Arduino。 為了避免重置循環,還必須添加幾行代碼:
void setup() { digitalWrite(RESET_ARDUINO_PIN, HIGH); // Set it to HIGH immediately on boot pinMode(RESET_ARDUINO_PIN, OUTPUT); // We declare it an output ONLY AFTER it's HIGH digitalWrite(RESET_ARDUINO_PIN, HIGH); // Default to HIGH, set to LOW to HARD RESET ...
之後,我只需調用digitalWrite(RESET_ARDUINO_PIN, LOW)
就可以對 Arduino 及其上的所有模塊(包括以太網模塊)進行硬件重置,幾秒鐘後 Dorothy 就恢復了生機。
此外,電路板在能量損失後會自動重啟。 如果互聯網連接失敗,我們會利用 SD 卡的存儲功能(數據可以在卡上存儲一周以上,服務器可以提取舊數據以恢復任何丟失的樣本)。 結合所有這些功能為我們提供了一個非常強大的氣象站,可以在其監測的惡劣條件下生存。 總的來說,這件事花了我大約 300 美元。
最後
該站自 2012 年 12 月以來一直在工作。迄今為止,它還沒有失敗(或者如果失敗了,該站恢復得足夠快,以至於風箏衝浪社區和我都沒有註意到)。 大約有 500 名風箏衝浪者在前往現場之前定期檢查氣象站。 因此,除了解決一些棘手的技術挑戰的獎勵之外,我還有機會為一群人提供更愉快的風箏衝浪體驗。
1最初,我使用的是 Arduino Uno。 後來,由於需要增加 RAM 和閃存,我改用 Arduino Mega。
相關:使用 ESP32 音頻採樣