我如何制作功能齐全的 Arduino 气象站

已发表: 2022-03-11

更新:在本文发表后,我们的 Arduino 气象站的工作仍在继续,最终发布了开放气象站 (OWS)。 查看更多更新、资源以及代码和新教程。

这是怎么回事?

风筝冲浪是世界上最容易上瘾的运动之一。 它所需要的只是一个风筝板、一个水体和一些配件。 这是接触大自然、解放思想和锻炼身体的好方法。 另外,你真的可以为它发疯。

所以有什么问题?

哦,我忘了一个基本要求:风。 这就是我们的问题所在:除非您住在您最喜欢的风筝冲浪地点附近,否则您永远不知道是否会有风。

我住在阿根廷的科尔多瓦,距离我风筝冲浪的湖约 130 公里(约 80 英里)。 这大约是两个小时的车程,我可以应付。 但我无法处理天气预报不准确的事实。 在我住的地方,良好的风力条件只持续几个小时。 您要做的最后一件事是清理周一的日程安排去风筝冲浪,然后在两个小时的车程后发现自己在无风的湖面上诅咒众神。

我需要实时了解我最喜欢的风筝冲浪地点的风况。 所以我决定建立自己的气象站。

在恶劣的环境中实时测量天气

目标是向家里的浏览器提供实时天气数据:

arduino气象站整体流程

在我进入细节之前,让我们花点时间考虑一下像这样的项目中涉及的关键问题和注意事项:

  • 如何创建一个对小偷既没有价值也没有吸引力的气象站?
  • 如何将硬件成本和开发时间降至最低?
  • 如何实时测量和访问天气数据并以有用的方式显示?
    • 所需测量:风和阵风、风向、雨量、大气压力、温度、湿度
    • 将电台连接到互联网
    • 存储和检索本地天气数据
    • 气象站和服务器之间的通信
  • 如何将维护减少到(几乎)为零?
    • 管理软件挂起
    • 管理连接丢失
    • 管理能源供应损失

向我的小朋友问好!

文件

您可能会认为手套的存在是为了让车站看起来更友好; 但它实际上是用来测试气压传感器(充气手套内手套压力增加)。 在右侧,您可以看到车站的最终位置,它位于附近的一座塔顶。

我还设计和编写了一个关于风筝冲浪的网站,其中包括一个站点测量的实时图,以帮助风筝冲浪社区。 最后,我在 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 音频采样