Individální projekty MPOA

Mikroprocesory s architekturou ARM

Uživatelské nástroje

Nástroje pro tento web


2017:greenhouse-ctrl

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.


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ů:


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<light.getLimit())
        {
            illumination.Start();
        }
 
        else
        {
            illumination.Stop();
        }
 
        Thread::wait(300000);
    }
}

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

2017/greenhouse-ctrl.txt · Poslední úprava: 2018/01/15 07:28 autor: Josef Křivský