风筝
发表于: 2018-10-30 16:44:52 | 显示全部楼层

在本篇文章中,我们将学习如何制作基于Arduino开发板的无线气象站。我们使用DHT22传感器测量室外温度和湿度,然后使用NRF24L01收发器模块将这些数据无线发送到室内的设备。在室内设备上,还有另外一个用于测量室内温度和湿度的DHT22传感器,以及一个DS3231实时时钟模块,即使在Arduino断电后也可以保持时间。所有这些数据都打印输出在0.96英寸OLED显示屏上。

wireless-weather-station.jpg


Arduino无线气象站电路图

我们来看看电路原理图以及工作原理。

Arduino-Wireless-Weather-Station-Circuit-Diagram.png


下面列出了电路所需的组件:

●    NRF24L01收发器模块

●    DHT22传感器

●    DS3231实时时钟

●    Arduino Nano


实时时钟模块和OLED显示器都使用I2C协议与Arduino开发板进行通信,因此它们连接到Arduino Nano板上的I2C引脚或4号和5号模拟引脚。在NRF24L01收发器模块旁边有一个去耦电容,可以使电源更加稳定。还有一个连接到DHT22数据引脚的上拉电阻,可使得传感器正常工作。


至于电源,我使用12V直流电源适配器用于室内设备供电,而对于室外设备的供电,我使用了两节锂电池来产生约7.5V的电压。通过这种配置,由于室外设备需要定期传输数据,因此可以在电池放电之前运行大约10天,而同时会将Arduino置于睡眠模式,功耗仅为7mA左右。


定制设计PCB

为了保持能更有效的组织电子元件,根据电路图,我使用EasyEDA免费在线电路设计软件设计了一个定制PCB。我们可以注意到,室内和室外设备使用同一块PCB,只有Arduino开发板采用不同的编程代码。

Arduino-Weather-Station-custom-PCB-design.png


一旦我们完成设计,我们就可以轻易地导出用于制造PCB的Gerber文件。您可以在这里查看Arduino无线气象站的EasyEDA项目文件。

然后我们就可以从PCB生产商定制PCB,一般几天后就可以拿到PCB。

arduino-weather-station-pcbs.jpg


然后,我们开始焊接PCB来组装该电路的电子元件。通过这种方式,我们可以在需要时轻松连接和断开组件。

soldering-pin-heards.jpg

接着焊接电容和上拉电阻。完成后,现在我们可以将组件插接到PCB的排母上。

inserting-components-onto-the-pcb.jpg


接下来,我们项目制作外壳。我使用了8mm刻度MDF板和圆锯,将所有部件切割成所需的大小。

cutting-mdf-board-using-a-circular-saw.jpg

为了进行准确的温度和湿度测量,壳体的侧面必须允许空气进入壳体。因此,使用钻头和锉刀,我在室内和室外设备的侧面板上开了几个插槽。

making-slots-on-the-side-panel.jpg

我还为前面板上的OLED显示器开了一个插槽,并切割了一小块铝尺寸,稍后将其作为装饰安装在前面板上。

making-slot-on-the-almuminum-side.jpg

组装箱子时,我使用了木胶和一些夹子,以及一些螺丝。

assembling-the-case-using-wood-glue.jpg


我用喷漆涂了表壳。在室外设备上使用白色涂料,在室内设备上使用黑色涂料。油漆干了后,我只需将PCB插入箱子中。

inserting-the-pcb-into-the-case.jpg

在室内单元的后侧,插入了电源插孔和电源开关,在室外设备中,我使用一个跳线作为电源开关。

indoor-and-outdoor-temperature-and-humidity-measurement.jpg

现在我们的Arduino无线气象站就可以正常运行了。

跳转到指定楼层
风筝
发表于: 2018-10-30 22:35:48 | 显示全部楼层

Arduino无线气象站代码

Arduino气象站室外设备的代码:

  1. /*
  2.   Arduino Wireless Communication Tutorial
  3.       Outdoor unit - Transmitter
  4.       
  5. by Dejan Nedelkovski, www.HowToMechatronics.com
  6. Libraries:
  7. NRF24L01 - TMRh20/RF24, https://github.com/tmrh20/RF24/
  8. DHT22 - DHTlib, https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib
  9. LowPower - https://github.com/rocketscream/Low-Power
  10. */
  11. #include <SPI.h>
  12. #include <nRF24L01.h>
  13. #include <RF24.h>
  14. #include <dht.h>
  15. #include <LowPower.h>
  16. #define dataPin 8 // DHT22 data pin
  17. dht DHT; // Creates a DHT object
  18. RF24 radio(10, 9); // CE, CSN
  19. const byte address[6] = "00001";
  20. char thChar[32] = "";
  21. String thString = "";
  22. void setup() {
  23.   radio.begin();
  24.   radio.openWritingPipe(address);
  25.   radio.setPALevel(RF24_PA_MIN);
  26.   radio.stopListening();
  27. }
  28. void loop() {
  29.   int readData = DHT.read22(dataPin); // Reads the data from the sensor
  30.   int t = DHT.temperature; // Gets the values of the temperature
  31.   int h = DHT.humidity; // Gets the values of the humidity
  32.   thString = String(t) + String(h);
  33.   thString.toCharArray(thChar, 12);
  34.   // Sent the data wirelessly to the indoor unit
  35.   for (int i = 0; i <= 3; i++) {           // Send the data 3 times
  36.     radio.write(&thChar, sizeof(thChar));
  37.     delay(50);
  38.   }
  39.   // Sleep for 2 minutes, 15*8 = 120s
  40.   for (int sleepCounter = 15; sleepCounter > 0; sleepCounter--)
  41.   {
  42.     LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
  43.   }
  44. }
复制代码

代码描述:室外设备是无线通信的发射部分,因此首先我们需要包括RF24库DHT库,以及用于将Arduino置于睡眠模式的LowPower库。

在定义了它们的实例,模块所连接的引脚和一些变量之后,在setup()函数中我们需要初始化无线通信地址。然后在loop()函数,首先我们读取DHT22传感器的数据(温度和湿度)。最初这些值是整数并且是分开的,因此我将它们转换为单个String变量,将它们放入字符数组中,并使用radio.write()函数将此数据发送到室内设备。使用for循环,我们发送数据3次,以确保接收器在发送时控制器忙时获得数据。

最后,我们将Arduino置于睡眠模式一段特定时间,以最大限度地降低功耗。


Arduino气象站室内设备代码:

  1. /*
  2.   Arduino Wireless Communication Tutorial
  3.         Indoor unit  - Receiver
  4.   by Dejan Nedelkovski, www.HowToMechatronics.com
  5. Libraries:
  6. DS3231 - http://www.rinkydinkelectronics.com/library.php?id=73
  7. U8G2 - https://github.com/olikraus/u8g2
  8. */
  9. #include <SPI.h>
  10. #include <nRF24L01.h>
  11. #include <RF24.h>
  12. #include <dht.h>
  13. #include <DS3231.h>
  14. #include <U8g2lib.h>
  15. #include <Wire.h>
  16. #define dataPin 8 // DHT22 sensor
  17. dht DHT; // Creats a DHT object
  18. DS3231  rtc(SDA, SCL);
  19. U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
  20. RF24 radio(10, 9); // CE, CSN
  21. const byte address[6] = "00001";
  22. char text[6] = "";
  23. int readDHT22, t, h;
  24. String inTemp, inHum, outTemp, outHum;
  25. String rtcTime, rtcDate;
  26. int draw_state = 0;
  27. unsigned long previousMillis = 0;
  28. long interval = 3000;
  29. #define Temperature_20Icon_width 27
  30. #define Temperature_20Icon_height 47

  31. void setup() {
  32.   radio.begin();
  33.   radio.openReadingPipe(0, address);
  34.   radio.setPALevel(RF24_PA_MIN);
  35.   radio.startListening();
  36.   u8g2.begin();
  37.   rtc.begin();
  38. }
  39. void loop() {
  40.   if (radio.available()) {
  41.     radio.read(&text, sizeof(text)); // Read incoming data
  42.     outTemp = String(text[0]) + String(text[1]) + char(176) + "C"; // Outdoor Temperature
  43.     outHum = String(text[2]) + String(text[3]) + "%"; // Outdoor Humidity
  44.   }
  45.   unsigned long currentMillis = millis();
  46.   if (currentMillis - previousMillis > interval) {
  47.     previousMillis = currentMillis;
  48.     u8g2.firstPage();
  49.     do {
  50.       switch (draw_state ) {
  51.         case 0: drawDate(); break;
  52.         case 1: drawInTemperature(); break;
  53.         case 2: drawInHumidity(); break;
  54.         case 3: drawOutTemperature(); break;
  55.         case 4: drawOutHumidity(); break;
  56.       }
  57.     } while ( u8g2.nextPage() );
  58.     draw_state++;
  59.     if (draw_state > 4) {
  60.       draw_state = 0;
  61.     }
  62.   }
  63. }
  64. void drawDate() {
  65.   String dowa = rtc.getDOWStr();
  66.   dowa.remove(3);
  67.   rtcDate = dowa + " " + rtc.getDateStr();
  68.   u8g2.setFont(u8g2_font_timB14_tr);
  69.   u8g2.setCursor(0, 15);
  70.   rtcTime = rtc.getTimeStr(); // DS3231 RTC time
  71.   rtcTime.remove(5);
  72.   u8g2.print(rtcDate);
  73.   u8g2.setFont(u8g2_font_fub30_tf);
  74.   u8g2.setCursor(8, 58);
  75.   u8g2.print(rtcTime);
  76. }
  77. void drawInTemperature() {
  78.   readDHT22 = DHT.read22(dataPin); // Reads the data from the sensor
  79.   t = DHT.temperature; // Gets the values of the temperature
  80.   inTemp = String(t) + char(176) + "C";
  81.   u8g2.setFont(u8g2_font_helvR14_tr);
  82.   u8g2.setCursor(24, 15);
  83.   u8g2.print("INDOOR");
  84.   u8g2.setFont(u8g2_font_fub30_tf);
  85.   u8g2.setCursor(36, 58);
  86.   u8g2.print(inTemp);
  87.   u8g2.drawXBMP( 0, 17, Temperature_20Icon_width, Temperature_20Icon_height, Temperature_20Icon_bits);
  88. }
  89. void drawInHumidity() {
  90.   h = DHT.humidity; // Gets the values of the humidity
  91.   inHum = String(h) + "%";
  92.   u8g2.setFont(u8g2_font_helvR14_tr);
  93.   u8g2.setCursor(24, 15);
  94.   u8g2.print("INDOOR");
  95.   u8g2.setFont(u8g2_font_fub30_tf);
  96.   u8g2.setCursor(36, 58);
  97.   u8g2.print(inHum);
  98.   u8g2.drawXBMP( 0, 17, Humidity_20Icon_width, Humidity_20Icon_height, Humidity_20Icon_bits);
  99. }
  100. void drawOutTemperature() {
  101.   u8g2.setFont(u8g2_font_helvR14_tr);
  102.   u8g2.setCursor(12, 15);
  103.   u8g2.print("OUTDOOR");
  104.   u8g2.setFont(u8g2_font_fub30_tf);
  105.   u8g2.setCursor(36, 58);
  106.   u8g2.print(outTemp);
  107.   u8g2.drawXBMP( 0, 17, Temperature_20Icon_width, Temperature_20Icon_height, Temperature_20Icon_bits);
  108. }
  109. void drawOutHumidity() {
  110.   u8g2.setFont(u8g2_font_helvR14_tr);
  111.   u8g2.setCursor(12, 15);
  112.   u8g2.print("OUTDOOR");
  113.   u8g2.setFont(u8g2_font_fub30_tf);
  114.   u8g2.setCursor(36, 58);
  115.   u8g2.print(outHum);
  116.   u8g2.drawXBMP( 0, 17, Humidity_20Icon_width, Humidity_20Icon_height, Humidity_20Icon_bits);
  117. }
复制代码

代码描述:在另一侧,室内设备或接收器,我们需要包括两个库,一个用于DS3231实时时钟模块,另一个用于OLED显示器 -U8G2库。 与前一个一样,我们需要为下面的程序定义实例,引脚和一些变量。 此外,我们还需要将温度和湿度图标定义为位图。


温度图标位图:

  1. #define Temperature_20Icon_width 27
  2. #define Temperature_20Icon_height 47
  3. static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM = {
  4.   0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00,
  5.   0xc0, 0xe1, 0x00, 0x00, 0xe0, 0xc0, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03,
  6.   0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x79, 0x00,
  7.   0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03,
  8.   0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x8c, 0x79, 0x00,
  9.   0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03,
  10.   0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x79, 0x00,
  11.   0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03,
  12.   0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00,
  13.   0x70, 0x9e, 0x03, 0x00, 0x38, 0x1e, 0x07, 0x00, 0x18, 0x3e, 0x0e, 0x00,
  14.   0x1c, 0x3f, 0x0c, 0x00, 0x0c, 0x7f, 0x18, 0x00, 0x8c, 0xff, 0x18, 0x00,
  15.   0x8e, 0xff, 0x38, 0x00, 0xc6, 0xff, 0x31, 0x00, 0xc6, 0xff, 0x31, 0x00,
  16.   0xc6, 0xff, 0x31, 0x00, 0x8e, 0xff, 0x38, 0x00, 0x8c, 0xff, 0x18, 0x00,
  17.   0x0c, 0x7f, 0x1c, 0x00, 0x3c, 0x1c, 0x0e, 0x00, 0x78, 0x00, 0x06, 0x00,
  18.   0xe0, 0x80, 0x07, 0x00, 0xe0, 0xff, 0x03, 0x00, 0x80, 0xff, 0x00, 0x00,
  19.   0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  20. };
复制代码

我们可以使用GIMP,一个开源图像编辑器,通过它我们可以绘制任何东西,然后将其导出为位图(.xbm)。

gimp.jpg

然后我们可以使用记事本打开这个文件,将位图复制到Arduino代码中。

请注意,在这里我们可以使用PROGMEM变量修饰符将位图定义为常量,这样位图将存储在闪存而不是Arduino板的SRAM中。

  1. static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM // Save in the Flash memory
  2. static unsigned char Temperature_20Icon_bits[] // Save in the SRAM
复制代码

setup函数中,我们需要初始化无线通信以及初始化OLED显示器和实时时钟模块。

然后在loop函数,我们不断检查是否有通过NRF24L01模块读取的输入数据。如果为true,则使用radio.read()函数读取它,并将前两个字符存储到温度String变量中,将接下来的两个字符存储到湿度String变量中。

然后我们使用millis()函数,以显示间隔变量定义的显示器上的各种数据,我将其设置为3秒。我们使用millis()函数,因为以这种方式可以重复执行其余代码,而且如果使用delay()函数,程序会等待那段时间,这样我们可能会错过从室外设备传入的数据。

接下来,使用U8G2库的firstPage()nextPage()函数,我们打印使用自定义函数定义的五个不同界面。

drawDate()自定义函数从实时时钟模块获取日期和时间信息,并将其正确打印在显示器上。 drawInTemperature()函数读取室内温度并在显示屏上正确显示。实际上,在显示器上的所有屏幕的打印采用相同的方法。


以上就是本篇文章的全部内容,我希望你喜欢这个Arduino项目,并学到一些新东西。如果遇到问题,请随时在本帖下面进行回复。

回复

使用道具 举报

碌碌无为
发表于: 2020-8-1 11:41:27 | 显示全部楼层

感谢楼主分享。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

主题 700 | 回复: 1483



手机版|

GMT+8, 2024-5-11 04:26 , Processed in 0.045002 second(s), 8 queries , Gzip On, MemCache On. Powered by Discuz! X3.5

YiBoard一板网 © 2015-2022 地址:河北省石家庄市长安区高营大街 ( 冀ICP备18020117号 )

快速回复 返回顶部 返回列表