使用 Arduino 监控和存储家庭区域的温度统计信息
构建一个或多个此类设备,以便您可以随着时间的推移监控家中温度的变化。
引言
温度可能相当主观,因为出于不同原因,每个人对温度的感受可能与他们自身的情况有关(喝热饮或冷饮,在炎热天气跑步后进屋,在寒冷天气外出后进屋等)。
背景
通常,家用恒温器仅安装在一个位置,并且仅在该位置测量温度。然而,家中的另一个房间或区域可能会为居住者带来完全不同的温度体验。我认为构建一个简单的设备会很好,该设备可以
- 显示当前温度
- 将时间和温度存储在 SD 卡上,以便轻松导入电子表格,从而我们可以绘制温度随时间变化的图表
尝试将主观感受转化为数据
所有这些数据都可以帮助我们将“这里感觉冷/热”的主观想法转化为真实数据,使我们能够知道这是一种“感觉”还是现实。
这是您完成此项目所需的一切的快照。
零件清单
最好有一个零件列表和大概价格,这样您可以轻松确定从哪里购买零件以及项目将花费多少钱。
- Arduino Uno - 我使用的是 Elegoo 克隆版(https://amzn.to/2JOoiIp),约 11 美元
- I2C LCD - (https://amzn.to/2NBWl8S)我在我的另一篇文章中向您展示了如何使用它(使用 20x4 LCD 和 Nano 了解您的代码在做什么[^]),约 10 美元
- 主要温度组件 - 我用的是 Seeed 套件中的一个。http://wiki.seeedstudio.com/Grove-Temperature_Sensor_V1.2/^ 约 2.80 美元
- (Micro) SD 卡读写器 - 该设备也使用 I2C (https://amzn.to/2A5nxLd),5 个 8.99 美元
- 面包板 - 我用的是这些(https://amzn.to/2uVHteA),3 个约 8.50 美元
- (Micro) SD 卡(任意大小) - 我的 16GB,因为它们是我能找到的最小/最便宜的。(https://amzn.to/2LyLQpp)约 7.60 美元
- SD 卡适配器 - 用于将文件传输到计算机,然后导入电子表格进行分析
分步进行
我在构建项目时使用的一些指导原则是:
- 先做最简单的部分
- 添加功能层
对我而言,这意味着让 I2C LCD 工作。原因有三个:
- I2C LCD 易于设置。
- 它允许我测试一个基本的 Arduino 草图
- 在构建项目的过程中,我可以使用 LCD 输出——如果需要,作为调试工具。
连接和测试 I2C LCD
这是在面包板上连接好的电路。
I2C LCD 的引脚与 Arduino Uno 的引脚的连接不是很清楚,因此示意图视图可以更清楚地说明连接方式。
现在您可以看到 VCC 和 GND 引脚已从 Uno 连接到 LCD,LCD 上的 SDA 引脚连接到引脚 A4(模拟 4),SCL 引脚连接到 A5。
这是我们将使用的第一个草图,以确保 LCD 工作正常并且您的电路设置正确。
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F, 20, 4);
void setup() {
lcd.begin();
lcd.setCursor(2,0);
lcd.print("Hello, LCD!");
}
void loop() {
}
您可以看到,这个第一个简单的草图在 loop()
方法中甚至不包含任何代码。草图所做的只是在屏幕上打印“Hello, LCD!
”。但是,这让我们知道我们已准备好打印任何接收到的数据。
您只需从 Arduino IDE 上传草图,屏幕上就会有输出。
这是它运行时的快照。
获取代码并尝试
您可以在本文档的顶部获取代码 (TempSensor_v001.zip) 并自行尝试。
添加热敏电阻
我们准备将温度读取模块(热敏电阻)添加到我们的电路中。
它是一个非常简单的组件。在上图的中间部分,可以看到组件底部白色塑料件上有四个(水平)插出的引脚。它们(从下到上)标记为:
- SIG (信号)
- NC (未连接/未使用)
- VCC (输入电压)
- GND (接地)
连接器束
这些 Seeed 模块设置起来易于使用,因此它们还提供了一个连接器束,允许您通过将其插入上图中组件左侧所示的塑料插座来插入所有四根电线。
插入连接器束后,它看起来将如下所示:
Seeed 还确保了颜色编码很方便,因此红色始终是 VCC,黑色是 GND,黄色和白色通常是某种数据线。在这种情况下,热敏电阻只需要一根额外的线,因此 NC(未连接)与白线对齐,我们不会将任何东西连接到该线。
将连接器束连接到面包板
连接器束的另一端具有相同的连接器,并且每根电线都终止于一个母头连接器,如图所示。
我只需剥去一些我的爱好线,然后将末端推入连接器。然后,我将把这些电线连接到面包板。您可以在下图看到插入电线后的连接器样子:
现在,我将红色线连接到 VCC(面包板电源轨),黑色线连接到地(面包板接地轨),黄色线连接到面包板上的一个列。之后,我将带有黄色线的面包板列连接到 Arduino Uno A0 引脚(模拟 0)。
同样,这并不完全清楚连接方式,因此这里是整个电路的示意图,以便您确切地看到连接的内容。
电路就这么多了。现在我们只需要添加一些代码来读取已通电的温度传感器的值。
读取当前温度的代码
我从产品网站上的示例中获取了代码:http://wiki.seeedstudio.com/Grove-Temperature_Sensor_V1.2/#software^
代码的两个区别
但是,我以两种方式修改了原始代码:
- 他们的代码显示在串行监视器上(需要将 Arduino 连接到计算机),但我们的代码当然会将值显示在 LCD 上。
- 原始代码以摄氏度显示值,但我添加了一行转换为华氏度。
获取代码
这是完整的代码列表。您可以下载 TempSensor_v002.zip 来获取代码。
#include <LiquidCrystal_I2C.h>
#include <math.h>
const int B = 4275; // B value of the thermistor
const int R0 = 100000; // R0 = 100k
const int pinTempSensor = A0; // Grove - Temperature Sensor connect to A0
LiquidCrystal_I2C lcd(0x3F, 20, 4);
void setup() {
lcd.begin();
lcd.setCursor(2,0);
lcd.print("Hello, LCD!");
}
void loop() {
int a = analogRead(pinTempSensor);
float R = 1023.0/a-1.0;
R = R0*R;
float temperature = 1.0/(log(R/R0)/B+1/298.15)-273.15; // convert to temperature
// via datasheet
temperature = (temperature * 1.8) + 32; // convert temp to Fahrenheit
lcd.setCursor(2,1);
lcd.print(temperature);
}
每次程序执行循环时,它都会获取当前温度传感器的值并在 LCD 上显示。
温度传感器的 Sig(数据引脚)连接到 Arduino 的 A0。
温度传感器如何测量环境温度的基础知识
代码首先读取引脚 A0 的模拟值,该值范围为 0-1023。该值存储在 a
变量中。
Arduino 的模拟引脚(以及底层的 Atmega328 MCU)可以读取电压,该电压被分成 1024 个值。随着传感器周围的环境温度下降,传感器的电阻会增加。这反过来会降低 A0 引脚上的电压值,从而降低范围(0-1024)中的值。
以下行的数学运算可以让我们知道温度(摄氏度)与传感器内部发生的电阻的关系。
float temperature = 1.0/(log(R/R0)/B+1/298.15)-273.15;
一旦我们有了摄氏温度,就可以很容易地应用转换公式将其转换为华氏度。
之后,我们只需在 LCD 上显示该值。
但是,现在我们需要一种方法来存储我们随时间获得的温度值。SD 卡读写器价格便宜,而且可以让我们将数据传输到计算机进行进一步分析,因此我们将在其中实现一个。
SD 卡读写器
我将在下面添加 SD 卡读写器正面和背面的特写。它非常易于使用,因为它也使用 I2C。
我已经将 16GB SD 卡加载到弹簧加载的卡座中(位于读写器左侧)。您可以看到卡片略微向左突出。
您可以看到引脚已为我们使用进行了清晰标记。
SD 卡读写器使用 SPI(串行外设接口)
每当您看到一个组件使用 MOSI、MISO、SCK 和 CS(有时也标记为 SS)时,您就知道它使用 SPI(串行外设接口)进行数据通信。
SCK 是串行时钟,它设置主设备将发送数据的脉冲(速度)。它基本上是一种确保主设备和从设备理解数据发送时序的方法。
CS(芯片选择)或 SS(从设备选择)是用于确定哪个从设备将接收数据的引脚。
此接口允许多个连接到这四个引脚的设备与主设备通信。这可以节省 Arduino 的数据引脚,但允许您连接多个设备。
您可以在 https://www.arduino.cc/en/reference/SPI^ 阅读更多关于 SPI 的信息。
我们将按照以下方式将这四个引脚连接到我们的 Arduino 引脚:
- MOSI (主设备输出/从设备输入) => D11
- MISO (主设备输入/从设备输出) => D12
- SCK (串行时钟) => D13
- CS (芯片选择) => D10
更新的示意图
这里有一个更新的示意图,以便您更轻松地了解所有连接方式。
我稍微调整了位置(与之前的示意图相比),以便线条与新添加的组件重叠更少。
此组件的引脚使其易于与面包板配合使用。我只是将其插入板中,以便每个引脚在面包板上都有自己的列。
现在,只需用跳线进行连接即可。
然后,我们可以添加一些代码来测试写入 SD 卡。
我们将添加两个新的包含项,它们将为我们提供初始化和使用 SD 卡读写器的函数。
#include <SPI.h>
#include <SD.h>
接下来,我们将声明几个要使用的变量。
File TempData;
const int SD_CS = 10;
第一行声明我们要写入的文件的文件句柄。
第二行创建一个用于 SD 卡读写器 CS 引脚的变量,并将其与 Arduino 的数字引脚 10 连接。我们这样做是为了能够初始化我们将用于写入文件的 SD 对象。
我们将以下代码添加到 setup()
函数中:
if (SD.begin(SD_CS)) {
lcd.setCursor(2,2);
lcd.print("SD card init");
}
如果 SD 卡读写器接线正确,这将会在 LCD 上显示“SD card init”。
获取代码并尝试:TempSensor_v003.zip
在本文档的顶部获取 TempSensor_v003.zip 并尝试一下。
如果一切正常,您将看到类似以下的内容:
如果成功,则表示您已正确接线,并已准备好继续。
将温度数据写入文件
让我们添加写入文件的代码。
我将把这段代码添加到一个函数中,然后让 loop()
方法每次调用该函数。我将该函数命名为 writeToSD
。
void writeToSD(float temperature){
TempData = SD.open("temps.csv", FILE_WRITE);
if (TempData) {
lcd.setCursor(2,2);
lcd.print("file WRITE success");
TempData.print(millis());
TempData.print(",");
TempData.println(temperature);
TempData.close();
}
else{
lcd.setCursor(2,3);
lcd.print("file WRITE fail ");
}
}
写入 SD 卡的代码并不多。它非常简单,并且方法由我们在源文件顶部包含的 Arduinio SD 库提供。
这是在文本编辑器中打开文件时看到的数据。它只是简单的逗号分隔值:
- 第一列是程序开始以来的毫秒数
- 第二列是当时的温度
这是每 500 毫秒收集一次数据,所以温度变化不大。以下是有关处理文件的更多详细信息。
打开/创建用于写入的文件
我们需要做的第一件事是将文件句柄设置为指向我们想要写入的实际文件。我们可以通过调用 SD.open()
方法来实现。您可以看到,我已经提供了字符串名称 "temps.csv"
作为第一个参数,以及预定义值 FILE_WRITE
来指示我想以写入模式打开文件。
文件已创建
如果文件存在,它将被打开以供写入(在末尾追加)。如果文件不存在,它将被创建,然后以写入模式打开。
文件打开成功
如果文件成功打开(并在必要时创建),TempData
文件句柄将不会是 null
。这意味着我们可以简单地编写:
if (TempData)
如果文件句柄不是 null
,则可以使用 print()
和 println()
方法对其进行写入。这两个方法之间的唯一区别是 println()
在将数据写入文件后会在行尾添加 CrLf
(回车/换行)。
写入后关闭文件的重要性
最后,在将数据写入文件后,调用 close()
方法非常重要,以确保字节从内存中刷新并写入磁盘文件(SD 卡上的文件)。
就是这样。其余代码只是在 LCD 屏幕上提供一些输出,以防万一出现问题,并为我们提供有关正在发生的事情的状态。
文件名的 8.3 格式的重要性
当我第一次设置代码时,我使用了以下行来打开文件:
TempData = SD.open("temperature.csv", FILE_WRITE)
我不知道那一行是问题所在,但是当我尝试写入文件时,它总是失败。我花了一段时间才弄清楚,然后我猜测也许这个文件系统只允许 8.3 个文件名。我查了一下,果然是问题所在。由于我提供了一个较长的文件名,open()
调用失败,TempData
为 null
,然后写入也失败了。但这并不容易确定,因为我们没有 Arduino 调试器。
我们需要解决的下一件事是以特定间隔写入数据。我们不想在每次 loop()
执行时都写入 SD 卡,因为数据将无用。
仅按时间间隔写入数据
我们只想每 X 秒写入一次数据,以限制我们写入 SD 卡的数据量。millis()
函数返回自草图在 Arduino 上运行以来的毫秒数。
为了解决这个问题,我们可以每次通过 loop()
时调用 delay(ms)
。但是,这会暂停整个草图,效果不大。相反,我希望草图仅每 X 秒调用一次 writeToSD()
函数。
我将让它每分钟写入一次数据,所以在顶部,我将初始化两个变量来执行此操作:
const long SLEEP_TIME = 60000; // write to card every minute
long multiplier = 0;
SLEEP_TIME
变量就是一个常量,因此我们可以轻松地在一个地方更改值。这是每次调用 writeToSD()
之间的时间间隔。这就是每次调用 writeToSD()
之间的时间。
我称之为 multiplier
的下一个变量只是一个从零开始的计数器,它将帮助我们每 SLEEP_TIME
秒调用一次 writeToSD()
。有了这两个变量,我们就可以使用以下代码来确保 writeToSD()
仅每隔一段时间执行一次。
if (millis() > (multiplier * SLEEP_TIME)){
writeToSD(temperature);
multiplier++;
}
第一次循环时,multiplier 将是 0
,而 millis()
将大于零,这意味着草图将立即写入它读取的第一个温度到 SD 卡。然后它将增加 multiplier。
下次循环时,millis()
将小于 multiplier
* SLEEP_TIME
(1 * 60000),因此它不会再次写入,直到 millis() 运行至少一分钟。它将继续每分钟执行一次,因为 multiplier
每次都会递增。
最终代码:获取 TempSensor_v004.zip
获取代码并尝试一下。这是它在我的桌子上运行的画面:
进一步扩展
现在,如果您愿意,可以通过使设备更便携来进一步扩展。我在这里不涵盖其他细节——为了使此项目更短——但您可以做一些额外的工作,使其可以通过电池运行。
您还可以添加一个实时时钟——Atmega328 芯片没有这个功能——这样您就可以将时钟时间写入文件,从而更轻松地跟踪时间与温度的对应关系。您可以购买实时时钟作为组件,连接到您的 Arduino(请参阅 Amazon 链接:https://amzn.to/2OqmR6v^)。该组件使用 SPI,与 SD 卡一样,所以您已经知道如何连接它。
涉及的主题
现在您知道读取温度传感器数据的简单方法了。您还学到了一些关于:
- Arduino 的文件读写库及其相关挑战
- SPI - SD 卡使用的串行外设接口(MOSI、MISO、CS 和 SCK)。每当您看到这四个连接被使用时,您就会知道该设备正在使用 SPI。
需要注意的要点
- SD 卡读写器没有任何 LED 指示它是否通电或正在读取/写入,这使得在出现问题时很难确定是哪里出了问题。如果它没有写入,您需要使用 LCD
print()
语句进行调试。另外,密切注意您的接线方式。 - 确保您的温度传感器不要离正在运行的 Arduino 板太近,因为 Arduino 板会散发一些热量,这可能会影响您的读数。
- 如果您想要更多数据,只需更改
SLEEP_TIME
变量,使其成为一个较小的时间间隔。例如,将其更改为 5000 毫秒,草图将每 5 秒写入一次数据。 - SD 卡格式化可能会对您的 SD 卡产生奇怪的影响。如果您在写入卡时遇到奇怪的现象,请在此处查看:here^。
- 另外有趣的是,我需要将
multiplier
和SLEEP_TIME
变量设置为long
类型,因为我将它们相乘,结果发现乘积大于int
,并且当超过该int
值时程序会停止,即使我实际上并没有将它们存储在int
变量中。相反,程序会自动分配一个int
大小的临时内存用于存储乘积,当它无法容纳该值时,语句(if (millis() > (multiplier * SLEEP_TIME))
)就会失败。这很合乎逻辑,但却出乎我的意料。
历史
- 2018 年 7 月 28 日:首次发布