====== Zadání ====== V prostředí Mbed vytvořte program pro vývojovou desku NXP FRDM-K64F, který bude realizovat řízení automatizovaného skleníku a jeho komunikaci s PC pomocí ethernetového rozhraní. Program bude zahrnovat ovládání a řízení periferií včetně návrhu komunikačního rozhraní s příkazy. ---- ====== Úvod ====== Cílem tohoto projektu je co nejvíce automatizovat obsluhu a udržování běžného zahradního skleníku. Obsahem návrhu je kompletní software řízení a obsluhy, ale také co nejjednodušší hardware pro příslušné periferie. Základní myšlenkou je možnost kontroly a nastavení jednotlivých vlastností skleníku z pohodlí domova, za pomoci ethernetového rozhraní a běžného PC. Pomocí definovaných příkazů je tak možné jak kontrolovat vlhkost půdy, teplotu a osvětlení ve skleníku, tak nastavit jejich limitní hodnoty pro spuštění zavlažování, odvětrávání/vytápění skleníku a přisvětlení zářivkami. {{ :2017:xkrivs00:img_20180115_063847_7.jpg?400 |}} ---- ====== Vývojový hardware ====== FRDM-K64F Key Features *Microcontroller: *Kinetis MK64FN1M0VLL12 in 100LQFP microcontroller featuring ARM® Cortex™-M4 32-bit core with DSP instructions and Floating Point Unit (FPU) working @ 120 MHz max CPU frequency *Memory: *1024 KB program flash memory, 256 KB RAM, and FlexBus external bus interface *System peripherals: *Multiple low-power modes, low-leakage wake-up unit, 16-channel DMA controller *Clocks: *3x Internal Reference Clocks: 32KHz, 4MHz and 48MHz, 2x Crystal inputs: 3-32MHz (XTAL0) and 32kHz (XTAL32/RTC), PLL and FL *Analog modules: *2x 16-bit SAR ADCs up 800ksps (12-bit mode), 2x 12-bit DACs, 3x Analog comparators, Voltage reference 1.13V *Communication interfaces: *1x 10/100 Mbit/s Ethernet MAC controller with MII/RMII interface IEEE1588 capable, 1x USB 2.0 Full-/Low-Speed Device/Host/OTG controller with embedded 3.3V/120mA Vreg, and USB device Crystal-less operation, 1x Controller Area Network (CAN) module, 3x SPI modules, 3x I2C modules. Support for up to 1 Mbit/s, 6x UART modules, 1x Secure Digital Host Controller (SDHC), 1x I2S module *Timers: *2x 8-channel Flex-Timers (PWM/Motor control), 2x 2-channel FlexTimers (PWM/Quad decoder), 32-bit PITs and 16 bit low-power timers, Real-Time Clock (RTC), Programmable delay block *Security and integrity modules: *Hardware CRC and random-number generator modules, Hardware encryption supporting DES, 3DES, AES, MD5, SHA-1 and SHA-256 algorithms *Operating characteristics: *Voltage range: 1.71 to 3.6 V, Flash write voltage range: 1.71 to 3.6 V ---- ====== Vývoj HW ====== Požadavkem na celou konstrukci automatizovaného skleníku byla především jednoduchost implementace/instalace, ale také cena celého systému. Vzhledem k velkému možství některých součástek v inventáři zadavatele, byly proto použity především ony. Seznam použitých komponetů: * Mikrokontrolér FRDM-K64F * Servo Hitec HS-485HB * NTC Termistor Eclipsera 1488979094 * Arduino modul pro měření intenzity světla BH1750 * Půdní Vlhkoměr Modul pro Arduino Odkazy na stránky výrobců/prodejců: *http://hitecrcd.com/products/servos/sport-servos/analog-sport-servos/hs-485hb-deluxe-hd-ball-bearing-servo/product *https://www.nxp.com/products/processors-and-microcontrollers/arm-based-processors-and-mcus/kinetis-cortex-m-mcus/k-seriesperformancem4/k2x-usb/freedom-development-platform-for-kinetis-k64-k63-and-k24-mcus:FRDM-K64F *https://arduino-shop.cz/arduino/1574-ntc-termistor-10k-1-3950-1m-vodotesna-sonda-1488979094.html *https://arduino-shop.cz/arduino/902-arduino-mereni-intenzity-svetla-1420672425.html *https://arduino-shop.cz/arduino/1399-pudni-vlhkomer-modul-pro-arduino-1474354607.html ---- ====== Software ====== V hlavním souboru main.cpp probíhá inicializace celého programu pomocí jednotlivých knihoven. Pro celý program byly použity veřejně dostupné knihovny mbed(Rev. 109), mbed-rtos(Rev. 95), EthernetInterface(Rev. 49) a také knihovna BH1750 dostupná na stránce https://os.mbed.com/users/vrabec/code/BH1750/ , kterou však bylo z důvodu jednoduchosti použití třeba značně modifikovat. Pro ostatní periferie byla vytvořena nová c++ knihovna Peripherals, která zahrnuje patřičnou inicializaci periferií (analogové vstupy, digitální a PWM výstupy, ...) a následně práci s nimi. Použité knihovny v soubotu main.c * #include "mbed.h" * #include "rtos.h" * #include "EthernetInterface.h" * #include "BH1750.h" * #include "Peripherals.h" === Knihovna Peripherals === Zapouzdřuje do tříd veškeré vlastnosti pro snímání vlhkosti a teploty, a ovládání akčních členů (servomotorem řízené otevírání ventilace a reléově ovládané zavlažování/ventilace/topení). Skládá se z Peripherals.h: class Humid { public: Humid(PinName AInp = A0, PinName AOut = D0); uint8_t readHumidity(void); void setLimit(uint8_t lim); void setCurrentLimit(void); uint8_t getLimit(void); private: AnalogIn ain; DigitalOut dop; uint8_t Limit; }; class Temp { public: Temp(PinName AInp = A1); float readTemperature(void); void setHighLimit(float lim); void setLowLimit(float lim); void setVentLimit(float lim); void setCurrentHighLimit(void); void setCurrentLowLimit(void); void setCurrentVentLimit(void); float getHighLimit(void); float getLowLimit(void); float getVentLimit(void); private: AnalogIn ain; float HLimit, LLimit, VLimit; }; class Vent { public: Vent(PinName PWM = D9); void open(uint8_t percent = 100); void close(void); private: PwmOut pwm; uint8_t percentage; }; class Fan { public: Fan(PinName DOut = D4); void Start(void); void Stop(void); private: DigitalOut fan; }; class Heat { public: Heat(PinName DOut = D5); void Start(void); void Stop(void); private: DigitalOut heater; }; class Water { public: Water(PinName DOut = D6); void Start(void); void Stop(void); private: DigitalOut sprinkler; }; class Light { public: Light(PinName DOut = D7); void Start(void); void Stop(void); private: DigitalOut lighting; }; Peripherals.cpp: Humid::Humid(PinName AInp, PinName AOut):ain(AInp),dop(AOut) { Limit = 110; dop = 0; } uint8_t Humid::readHumidity(void) { dop = 1; wait_ms(10); double humidity = 1-ain.read(); humidity = ((humidity*100)-7)*1.471; uint8_t hum = humidity; dop = 0; return hum; } void Humid::setLimit(uint8_t lim) { Limit = lim; } void Humid::setCurrentLimit(void) { uint8_t humidity = readHumidity(); setLimit(humidity); } uint8_t Humid::getLimit(void) { return Limit; } Temp::Temp(PinName AInp):ain(AInp) { LLimit = -30; HLimit = 125; VLimit = 125; } float Temp::readTemperature(void) { double voltage, resistance, temperature; float temp; int resistor = 9910; int thermistor = 10000; int refTemp = 25; int beta = 3380; voltage = (ain.read())*3.3; resistance = (voltage*resistor)/(3.3-voltage); temperature = (log(resistance/thermistor))/beta; temperature += 1.0 / (refTemp + 273.15); temperature = 1.0 / temperature; temperature -= 273.15; temp = temperature; return temp; } void Temp::setHighLimit(float lim) { HLimit = lim; } void Temp::setLowLimit(float lim) { LLimit = lim; } void Temp::setVentLimit(float lim) { LLimit = lim; } void Temp::setCurrentHighLimit(void) { float temperature = readTemperature(); setHighLimit(temperature); } void Temp::setCurrentLowLimit(void) { float temperature = readTemperature(); setLowLimit(temperature); } void Temp::setCurrentVentLimit(void) { float temperature = readTemperature(); setVentLimit(temperature); } float Temp::getHighLimit(void) { return HLimit; } float Temp::getLowLimit(void) { return LLimit; } float Temp::getVentLimit(void) { return VLimit; } Vent::Vent(PinName PWM):pwm(PWM) { pwm.period(0.020); pwm.pulsewidth(0.0009); percentage = 0; } void Vent::open(uint8_t percent) { uint16_t width = ((6 * percentage)+900); while(width != (6*percent)+900) { if(percentage>percent) { width-=1; pwm.pulsewidth_us(width); wait_ms(10); } else { width+=1; pwm.pulsewidth_us(width); wait_ms(10); } } percentage = percent; } void Vent::close(void) { open(0); } Fan::Fan(PinName DOut):fan(DOut) { fan = 0; } void Fan::Start(void) { fan = 1; } void Fan::Stop(void) { fan = 0; } . . . Za zmínku stojí především třídy Humid a Temp, které nejen zapouzdřují různé proměnné a vstupy, ale také nad nimi provádějí řadu výpočtů. U třídy Humid se jedná pouze o úpravu snímaného napětí ze zesilovače a měření v pulzním režimu (z důvodu elektrolýzy na elektrodách snímače vlhkosti). Třída Temp však již provádí poměrně složité výpočty při převodu snímaného napětí z děliče (tvořen rezistorem a NTC termistorem) na hodnotu okolní teploty. Veškeré další třídy jsou v této knihovně již pouze kopie třídy Fan, které slouží k ovládání reléových výstupů, a které mají jen rozdílné názvy a výstupní piny na desce FRDM-K64F. === hlavní soubor main.h === Soubor main.cpp lze rozdělit do několika částí: * Inicializace (zde probíhá pouze inicializace jednotlivých tříd pro příslušné periferie) #define ECHO_SERVER_PORT 23 BH1750 light(I2C_SDA, I2C_SCL); // Senzor osvětlení Humid humidity; // Senzor vlhkosti Temp temperature; // Snímač teploty Vent ventilation; // Otevírání ventilace Fan conditioning; // Ovládání aktivního větrání Heat heating; // Ovládání topení Water irrigation; // Spouštění zavlažování Light illumination; // Ovládání světel enum STAT // Proměnná pro uložení stavu celého systému { READY = 1, SETTINGS, MANUAL, ERROR }sklenikstav; Stav systému sklenikstav je díky třídám pouze pomocnou proměnnou pro rozhodovací stromy uživatelského rozhraní. * Ovládací vlákna RTOS (zde již probíhá periodická kontrola stavu skleníku, a příslušné reakce periferií) void watering_thread(void) { while(true) { uint8_t hum = humidity.readHumidity(); float avghum = 0; if(hum < humidity.getLimit()) { for(int i=0; i<6; i++) { avghum += humidity.readHumidity(); } avghum /= 6; if(avghum < humidity.getLimit()) { irrigation.Start(); Thread::wait(180000); irrigation.Stop(); } avghum = 0; } Thread::wait(900000); } } void venting_thread() { while(true) { uint8_t temp = temperature.readTemperature(); float avgtemp = 0; if(temp > temperature.getVentLimit()) { for(int i=0; i<6; i++) { avgtemp += temperature.readTemperature(); } avgtemp /= 6; if(avgtemp > temperature.getVentLimit()) { ventilation.open(); conditioning.Start(); heating.Stop(); } avgtemp = 0; } else if(temp > temperature.getHighLimit()) { for(int i=0; i<6; i++) { avgtemp += temperature.readTemperature(); } avgtemp /= 6; if(avgtemp > temperature.getHighLimit()) { ventilation.open(); conditioning.Stop(); heating.Stop(); } avgtemp = 0; } else if(temp < temperature.getLowLimit()) { for(int i=0; i<6; i++) { avgtemp += temperature.readTemperature(); } avgtemp /= 6; if(avgtemp < temperature.getLowLimit()) { ventilation.close(); conditioning.Stop(); heating.Start(); } avgtemp = 0; } else { ventilation.close(); conditioning.Stop(); heating.Stop(); } Thread::wait(100000); } } void lighting_thread() { uint16_t lx[6]; for(int i=0; i<6; i++) { lx[i] = light.singleMeas(); } while(true) { uint16_t avglx = 0; for(int i=0; i<5; i++) { lx[i] = lx[i+1]; } lx[5] = light.singleMeas(); for(int i=0; i<6; i++) { avglx += lx[i]; } avglx /= 6; if(avglx Tato vlákna mají zajišťovat plnou automatičnost skleníku po jeho prvotním nastavení. Bohužel se díky problémům s různými verzemi systému MBED a jeho knihoven nepodařilo (díky nedostatku času) najednou zprovoznit vlákna a ovládání skleníku přes ethernetové rozhraní. Ačkoliv RTOS sám o sobě funguje, nebylo by možné nastavovat skleník on-line, čímž bychom přišli o jakoukoliv možnost okamžitého zásahu, a zároveň by nebyla splněna nejdůležitější část zadání. * Funkce rozhodovacího stromu pro Ethernetové rozhraní skleníku (přímé ovládání) int evaluate(char *str) { switch(sklenikstav) { case READY: if (strcmp(str, "help") == 0) { return 1; } else if (strcmp(str, "stav") == 0) { return 2; } else if (strcmp(str, "set") == 0) { . . . int main(void) { . . . int x = evaluate(message); if(sklenikstav == READY) { if (x == 0) { client.send_all("Neplatny prikaz!\r\n", 18); client.send_all(commandwait, sizeof(commandwait)); } else if (x == 1) { char helpmsg[] = "stav\t\t-Zobrazeni aktualnich informaci\r\nset\t\t-Nastaveni skleniku\r\nman\t\t-Ovladani skleniku\r\nexit/quit\t-Konec\r\n"; client.send_all(helpmsg, sizeof(helpmsg)); client.send_all(commandwait, sizeof(commandwait)); number = 0; . . . } Jak lze vidět, jedna z těchto funkcí se nachází ve funkci main, a druhá je pouze jako pomoc při určování obdržených řetězců. V závislosti na obdrženém textu pak tyto funkce přepínají jak stavy systému (pouze pro potřeby komunikace s uživatelem), tak výstupy mikrokontroléru. Tyto funkce jsou obdobou absolvovaného cvičení č.5, pouze značně složitější, a proto je tu nebudu uvádět celé. Uvedu pouze stavy systému a jejich akceptované příkazy. === Celý kód === https://os.mbed.com/users/civava/code/sklenik-2017/