پروژه ایستگاه کنترل هوا با آردوینو
در این برنامه آموزشی یاد میگیریم که چگونه ایستگاه کنترل هوا با آردوینو را بسازیم. شما میتوانید برنامه آموزشی نوشته شده در زیر را بخوانید.
مرور کلی ایستگاه کنترل هوا با آردوینو
دما و رطوبت بیرونی با استفاده از سنسور DHT22 اندازهگیری میشود و این دادهها به طور بیسیم با استفاده از ماژول های فرستنده و گیرنده NRF24L01 به واحد داخلی ارسال میشود. در واحد داخلی، همچنین یک سنسور DHT22 دیگر برای اندازهگیری دما و رطوبت داخلی، و همچنین یک ماژول ساعت زمان واقعی DS3231 وجود دارد که میتواند زمان را حتی اگر آردینو قدرت را از دست بدهد، حفظ کند. همه این دادهها بر روی یک صفحه نمایش 0.96 OLEDچاپ میشوند. در بخش های بعدی در پروژه های دیگر آموزش رباتیک نیز ما از سنسور DHT22 استفاده خواهیم کرد.
نمودار مدار ایستگاه هواشناسی بیسیم آردینو
بیایید نگاهی به نمودار مدار و نحوه کار این پروژه بیندازیم.
هر دو ماژول ساعت زمان و صفحه نمایش OLED از پروتکل I2C برای ارتباط با آردینو استفاده میکنند بنابراین آنها به پینهای I2C یا پینهای آنالوگ شماره ۴ و ۵ در صفحه آردینو نانو متصل میشوند. درست در کنار ماژول فرستنده و گیرنده NRF24L01 یک خازن وجود دارد که منبع تغذیه را برای آن پایدارتر نگه میدارد. همچنین یک مقاومت کششی متصل به پین داده DHT22 وجود دارد تا سنسور به درستی کار کند.
در مورد منبع تغذیه، من از آداپتور توان با ولتاژ12 DC برای واحد داخلی، و از طرف دیگر، برای تأمین توان واحد بیرونی، از دو باتری Li – on که حدود ۵ / ۷ ولتاژ تولید میکردند، استفاده کردم. با این پیکربندی، واحد بیرونی میتواند حدود ۱۰ روز قبل از تخلیه باتریها کار کند، زیرا ما دادهها را به صورت دورهای منتقل میکنیم و در عین حال، آردینو را به حالت خواب میبریم، که در آن مصرف برق تنها حدود ۷ میلی آمپر است.
طراحی سفارشی برد مدار چاپی یا (برد PCB)
به منظور سازمان دهی اجزای الکترونیکی، براساس نمودار مدار، من یک برد مدار چاپی سفارشی را با استفاده از نرمافزار طراحی مدار آنلاین رایگان EasyEAD طراحی کردم. میتوانیم به این نکته توجه کنیم که برد مدار چاپی مشابهی را می توان هم برای بخش داخلی و هم برای بخش بیرونی مورد استفاده قرار داد، تنها برد آردینو باید به گونهای متفاوت برنامهریزی شود.
من کار مونتاژ اجزای الکترونیکی این پروژه را با لحیم کاری پین هدرها به برد مدار چاپی آغاز کردم. به این ترتیب ما میتوانیم به راحتی اجزا را در صورت نیاز به هم متصل و قطع کنیم.
سپس خازن و مقاومت بالا کش را نیز وارد کرده و لحیم کردم. با انجام این مرحله، اکنون میتوانیم اجزا را به پین هدرهای برد مدار چاپی وصل کنیم.
سپس، من به ایجاد موارد برای پروژه ادامه دادم. برای این منظور از تخته ۸ میلیمتری MDF استفاده کردم و با استفاده از اره گرد، تمام قطعات را به اندازه برش دادم.
به منظور داشتن اندازهگیریهای دقیق دما و رطوبت، طرفین محفظه ها باید اجازه ورود هوا به داخل محفظه را بدهند. بنابراین، با استفاده از یک مته و یک شکاف، چندین شکاف درکنار تابلوها در هر دو بخش داخلی و خارجی ایجاد کردم.
من همچنین یک شکاف برای صفحه نمایش OLED بر روی تابلوی جلویی ایجاد کردم، و همچنین، یک قطعه کوچک از آلومینیوم را به اندازه برش دادم که بعداٌ آن را به عنوان یک دکوراسیون بر روی تابلوی جلویی نصب خواهم کرد.
برای مونتاژ محفظه ها از چسب چوب و چند گیره، و همچنین چند پیچ استفاده کردم.
من فایل ها را با استفاده از رنگ اسپری رنگ کردم. من از رنگ سفید برای واحد فضای بیرونی و رنگ سیاه برای واحد فضای داخلی استفاده کردم. پس از خشک شدن رنگ، من به سادگی بردهای مدار چاپی را در محفظه قرار دادم.
در سمت عقب واحد داخلی من یک جک قدرت و یک سوئیچ قدرت قرار دادم، و در واحد بیرونی من از یک سیم جوش ساده به عنوان یک سوئیچ قدرت استفاده کردم.
و اینگونه است که ایستگاه هواشناسی بیسیم آردینو ما در حال حاضر در حال کار است، اما چیزی که در این آموزش باقی مانده است این است که نگاهی به نحوه کار برنامه بیندازیم.
کد ایستگاه هواشناسی بیسیم آردینو
کد واحد بیرونی ایستگاه هواشناسی آردینو:
/* Arduino Wireless Communication Tutorial Outdoor unit - Transmitter by Dejan Nedelkovski, www.HowToMechatronics.com Libraries: NRF24L01 - TMRh20/RF24, https://github.com/tmrh20/RF24/ DHT22 - DHTlib, https://github.com/RobTillaart/Arduino/tree/master/libraries/DHTlib LowPower - https://github.com/rocketscream/Low-Power */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #include <dht.h> #include <LowPower.h> #define dataPin 8 // DHT22 data pin dht DHT; // Creates a DHT object RF24 radio(10, 9); // CE, CSN const byte address[6] = "00001"; char thChar[32] = ""; String thString = ""; void setup() { radio.begin(); radio.openWritingPipe(address); radio.setPALevel(RF24_PA_MIN); radio.stopListening(); } void loop() { int readData = DHT.read22(dataPin); // Reads the data from the sensor int t = DHT.temperature; // Gets the values of the temperature int h = DHT.humidity; // Gets the values of the humidity thString = String(t) + String(h); thString.toCharArray(thChar, 12); // Sent the data wirelessly to the indoor unit for (int i = 0; i <= 3; i++) { // Send the data 3 times radio.write(&thChar, sizeof(thChar)); delay(50); } // Sleep for 2 minutes, 15*8 = 120s for (int sleepCounter = 15; sleepCounter > 0; sleepCounter--) { LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); } }
شرح: واحد فضای بیرونی فرستنده ارتباطات بیسیم است، بنابراین در اینجا ابتدا باید کتابخانه RF24، کتابخانه DHT و همچنین کتابخانه LowPower که برای خاموش کردن آردینو استفاده میشود را در نظر بگیریم.
پس از تعریف نمونه های آنها، پین هایی که ماژولها به آنها متصل شدهاند و برخی متغیرها در بخش راهاندازی، ما باید آدرس ارتباطات بیسیم را راهاندازی کنیم. سپس در بخش حلقه، اولاٌ دادههای سنسور DHT22 یا دما و رطوبت را میخوانیم. در ابتدا این مقادیر اعداد صحیح و مجزا هستند، بنابراین من آنها را به یک متغیر رشته واحد تبدیل میکنم، آنها را در دسته بندی کاراکتر قرار میدهم، و با استفاده از تابع write() رادیویی، این داده ها را به واحد داخلی ارسال می کنم. با استفاده از حلقه، ما دادهها را ۳ بار به منظور اطمینان از اینکه گیرنده دادهها را در صورتی که کنترلر در زمان ارسال مشغول باشد دریافت خواهد کرد، ارسال میکنیم.
در پایان ما آردینو را برای یک دوره زمانی خاص به منظور به حداقل رساندن مصرف برق به حالت خواب میبریم.
کد واحد داخلی ایستگاه هواشناسی آردینو:
/* Arduino Wireless Communication Tutorial Indoor unit - Receiver by Dejan Nedelkovski, www.HowToMechatronics.com Libraries: DS3231 - http://www.rinkydinkelectronics.com/library.php?id=73 U8G2 - https://github.com/olikraus/u8g2 */ #include <SPI.h> #include <nRF24L01.h> #include <RF24.h> #include <dht.h> #include <DS3231.h> #include <U8g2lib.h> #include <Wire.h> #define dataPin 8 // DHT22 sensor dht DHT; // Creats a DHT object DS3231 rtc(SDA, SCL); U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); RF24 radio(10, 9); // CE, CSN const byte address[6] = "00001"; char text[6] = ""; int readDHT22, t, h; String inTemp, inHum, outTemp, outHum; String rtcTime, rtcDate; int draw_state = 0; unsigned long previousMillis = 0; long interval = 3000; #define Temperature_20Icon_width 27 #define Temperature_20Icon_height 47 static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0xc0, 0xe1, 0x00, 0x00, 0xe0, 0xc0, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x79, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x8c, 0x79, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x79, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x70, 0x9e, 0x03, 0x00, 0x38, 0x1e, 0x07, 0x00, 0x18, 0x3e, 0x0e, 0x00, 0x1c, 0x3f, 0x0c, 0x00, 0x0c, 0x7f, 0x18, 0x00, 0x8c, 0xff, 0x18, 0x00, 0x8e, 0xff, 0x38, 0x00, 0xc6, 0xff, 0x31, 0x00, 0xc6, 0xff, 0x31, 0x00, 0xc6, 0xff, 0x31, 0x00, 0x8e, 0xff, 0x38, 0x00, 0x8c, 0xff, 0x18, 0x00, 0x0c, 0x7f, 0x1c, 0x00, 0x3c, 0x1c, 0x0e, 0x00, 0x78, 0x00, 0x06, 0x00, 0xe0, 0x80, 0x07, 0x00, 0xe0, 0xff, 0x03, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; #define Humidity_20Icon_width 27 #define Humidity_20Icon_height 47 static const unsigned char Humidity_20Icon_bits[] U8X8_PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xdc, 0x00, 0x00, 0x00, 0xdc, 0x01, 0x00, 0x00, 0x8e, 0x01, 0x00, 0x00, 0x86, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x03, 0x07, 0x00, 0x80, 0x03, 0x06, 0x00, 0x80, 0x01, 0x0c, 0x00, 0xc0, 0x01, 0x1c, 0x00, 0xc0, 0x00, 0x18, 0x00, 0xe0, 0x00, 0x38, 0x00, 0x60, 0x00, 0x30, 0x00, 0x70, 0x00, 0x70, 0x00, 0x30, 0x00, 0xe0, 0x00, 0x38, 0x00, 0xc0, 0x00, 0x18, 0x00, 0xc0, 0x01, 0x1c, 0x00, 0x80, 0x01, 0x0c, 0x00, 0x80, 0x03, 0x0e, 0x00, 0x80, 0x03, 0x06, 0x00, 0x00, 0x03, 0x06, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, 0x07, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06, 0x03, 0x00, 0x00, 0x06, 0x63, 0x00, 0x00, 0x06, 0x63, 0x00, 0x00, 0x06, 0x63, 0x00, 0x00, 0x06, 0xe3, 0x00, 0x00, 0x06, 0xc7, 0x00, 0x00, 0x06, 0xc6, 0x01, 0x00, 0x07, 0x86, 0x03, 0x00, 0x03, 0x0e, 0x1f, 0x00, 0x03, 0x0e, 0x1e, 0x80, 0x01, 0x1c, 0x00, 0xc0, 0x01, 0x38, 0x00, 0xe0, 0x00, 0x78, 0x00, 0x70, 0x00, 0xf0, 0x00, 0x38, 0x00, 0xe0, 0x07, 0x1f, 0x00, 0x80, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00 }; void setup() { radio.begin(); radio.openReadingPipe(0, address); radio.setPALevel(RF24_PA_MIN); radio.startListening(); u8g2.begin(); rtc.begin(); } void loop() { if (radio.available()) { radio.read(&text, sizeof(text)); // Read incoming data outTemp = String(text[0]) + String(text[1]) + char(176) + "C"; // Outdoor Temperature outHum = String(text[2]) + String(text[3]) + "%"; // Outdoor Humidity } unsigned long currentMillis = millis(); if (currentMillis - previousMillis > interval) { previousMillis = currentMillis; u8g2.firstPage(); do { switch (draw_state ) { case 0: drawDate(); break; case 1: drawInTemperature(); break; case 2: drawInHumidity(); break; case 3: drawOutTemperature(); break; case 4: drawOutHumidity(); break; } } while ( u8g2.nextPage() ); draw_state++; if (draw_state > 4) { draw_state = 0; } } } void drawDate() { String dowa = rtc.getDOWStr(); dowa.remove(3); rtcDate = dowa + " " + rtc.getDateStr(); u8g2.setFont(u8g2_font_timB14_tr); u8g2.setCursor(0, 15); rtcTime = rtc.getTimeStr(); // DS3231 RTC time rtcTime.remove(5); u8g2.print(rtcDate); u8g2.setFont(u8g2_font_fub30_tf); u8g2.setCursor(8, 58); u8g2.print(rtcTime); } void drawInTemperature() { readDHT22 = DHT.read22(dataPin); // Reads the data from the sensor t = DHT.temperature; // Gets the values of the temperature inTemp = String(t) + char(176) + "C"; u8g2.setFont(u8g2_font_helvR14_tr); u8g2.setCursor(24, 15); u8g2.print("INDOOR"); u8g2.setFont(u8g2_font_fub30_tf); u8g2.setCursor(36, 58); u8g2.print(inTemp); u8g2.drawXBMP( 0, 17, Temperature_20Icon_width, Temperature_20Icon_height, Temperature_20Icon_bits); } void drawInHumidity() { h = DHT.humidity; // Gets the values of the humidity inHum = String(h) + "%"; u8g2.setFont(u8g2_font_helvR14_tr); u8g2.setCursor(24, 15); u8g2.print("INDOOR"); u8g2.setFont(u8g2_font_fub30_tf); u8g2.setCursor(36, 58); u8g2.print(inHum); u8g2.drawXBMP( 0, 17, Humidity_20Icon_width, Humidity_20Icon_height, Humidity_20Icon_bits); } void drawOutTemperature() { u8g2.setFont(u8g2_font_helvR14_tr); u8g2.setCursor(12, 15); u8g2.print("OUTDOOR"); u8g2.setFont(u8g2_font_fub30_tf); u8g2.setCursor(36, 58); u8g2.print(outTemp); u8g2.drawXBMP( 0, 17, Temperature_20Icon_width, Temperature_20Icon_height, Temperature_20Icon_bits); } void drawOutHumidity() { u8g2.setFont(u8g2_font_helvR14_tr); u8g2.setCursor(12, 15); u8g2.print("OUTDOOR"); u8g2.setFont(u8g2_font_fub30_tf); u8g2.setCursor(36, 58); u8g2.print(outHum); u8g2.drawXBMP( 0, 17, Humidity_20Icon_width, Humidity_20Icon_height, Humidity_20Icon_bits); }
شرح: از طرف دیگر، در واحد داخلی یا گیرنده، باید دو کتابخانه دیگر را نیز در نظر بگیریم، یکی برای ماژول ساعت زمان واقعی DS3231 و دیگری برای صفحه نمایش OLED،کتابخانه .U8G2 همانند گذشته، ما نیاز به تعریف نمونهها، پینها و برخی متغیرها برای برنامه زیر داریم. همچنین در اینجا ما نیاز به تعریف آیکونهای دما و رطوبت به عنوان bitmaps داریم.
آیکون دما bitmap
#define Temperature_20Icon_width 27 #define Temperature_20Icon_height 47 static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0x00, 0x00, 0x80, 0x7f, 0x00, 0x00, 0xc0, 0xe1, 0x00, 0x00, 0xe0, 0xc0, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x79, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0xf9, 0x03, 0x60, 0x80, 0x01, 0x00, 0x60, 0x80, 0x01, 0x00, 0x60, 0x8c, 0x79, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x79, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0xf9, 0x03, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x60, 0x9e, 0x01, 0x00, 0x70, 0x9e, 0x03, 0x00, 0x38, 0x1e, 0x07, 0x00, 0x18, 0x3e, 0x0e, 0x00, 0x1c, 0x3f, 0x0c, 0x00, 0x0c, 0x7f, 0x18, 0x00, 0x8c, 0xff, 0x18, 0x00, 0x8e, 0xff, 0x38, 0x00, 0xc6, 0xff, 0x31, 0x00, 0xc6, 0xff, 0x31, 0x00, 0xc6, 0xff, 0x31, 0x00, 0x8e, 0xff, 0x38, 0x00, 0x8c, 0xff, 0x18, 0x00, 0x0c, 0x7f, 0x1c, 0x00, 0x3c, 0x1c, 0x0e, 0x00, 0x78, 0x00, 0x06, 0x00, 0xe0, 0x80, 0x07, 0x00, 0xe0, 0xff, 0x03, 0x00, 0x80, 0xff, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
برای این منظور میتوانیم از GIMP، یک ویرایشگر تصویر منبع باز، استفاده کنیم، که از طریق آن میتوانیم هر چیزی را ترسیم کرده و سپس آن را به صورت bitmap(xbm) صادر کنیم.
سپس ما میتوانیم این فایل را با استفاده از یک نوت پد باز کنیم و از آنجا میتوانیم بیت مپ را در داخل کد آردینو کپی کنیم.
توجه داشته باشید که در اینجا ما میتوانیم بیت مپ را با استفاده از تعدیلکننده متغیر PROGMEM به صورت ثابت تعریف کنیم، و به این ترتیب bitmap در حافظه فلاش به جای حافظه رم استاتیک برد آردوینو ذخیره خواهد شد.
static const unsigned char Temperature_20Icon_bits[] U8X8_PROGMEM // Save in the Flash memory static unsigned char Temperature_20Icon_bits[] // Save in the SRAM
در بخش راهاندازی، ما نیاز به مقداردهی اولیه ارتباطات بیسیم و همچنین مقداردهی اولیه نمایش OLED و ماژول ساعت زمان واقعی داریم.
سپس در بخش حلقه ما به طور مداوم بررسی میکنیم که آیا یک داده ورودی در دسترس برای خواندن از طریق ماژول های NRF24L01 وجود دارد. اگر درست باشد، با استفاده از تابع ()read رادیویی آن را میخوانیم و دو کاراکتر اول را در متغیر تغییر دما و دو کاراکتر بعدی را در متغیر تغییر رطوبت ذخیره میکنیم.
سپس از تابع ()millis به منظور نمایش دادههای مختلف بر روی صفحه نمایش در فواصل تعریفشده با متغیر فاصلهای که آن را بر روی ۳ ثانیه تنظیم کردم، استفاده میکنیم. ما از تابع ()millis استفاده میکنیم زیرا به این ترتیب بقیه کد میتواند به طور مکرر اجرا شود، در صورتی که ما از تابع () delayاستفاده کنیم، برنامه برای آن دوره منتظر میماند بنابراین به این ترتیب ما احتمالاٌ دادههای ورودی از واحد فضای بیرونی را از دست میدهیم.
سپس، با استفاده از عملکرد ()firstPage و ()nextPage کتابخانه U8G2، پنج صفحه نمایش مختلف را چاپ میکنیم که با توابع سفارشی تعریف شدهاند.
()drawDate تابع سفارشی تاریخ و اطلاعات زمان را از ماژول ساعت زمان واقعی میگیرد و آن را به طور مناسب بر روی صفحه نمایش چاپ میکند. تابع ()drawIn Temperature دمای داخلی را میخواند و آن را به طور مناسب بر روی صفحه نمایش چاپ میکند. در واقع، از همین روش برای چاپ تمام صفحات نمایش استفاده میشود.
بنابراین، امیدوارم که شما از این پروژه آردینو لذت برده باشید و چیز جدیدی یاد بگیرید. لطفا در قسمت نظرات هر سوالی را بپرسید.