středa 18. března 2015

Tasker - "multitasking" na Arduinu

Poměrně rychle jsem přijal programovací styl navrhovaný Arduino IDE, tj. dvě hlavní funkce - setup() a loop(). Do setup() dám inicializační věci a do loop() dám samotné jádro programu, které provádí nějakou periodickou činnost. Dokud jde jen o jednu činnost, například blikání LEDkou v sekundovém rytmu, je to jednoduché: rozsvítím LED, počkám půl sekundy díky volání funkce delay(500), zhasnu, počkám další půlsekundu dík dalšímu volání delay(500) a je to:

void loop() {
    digitalWrite(13, HIGH);
    delay(500);
    digitalWrite(13, LOW);
    delay(500);
}

Bezva, ale co když potřebuji vykonávat činnosti dvě a navíc různé? Třeba během blikání LED chci ještě číst teploty z čidel. Anebo kreslit na displej. Anebo odpovídat na požadavky web klientů. Anebo tisíc jiných věcí. Co pak? Jak během delay(), kdy program stojí a čeká, vykonávat jinou činnost?

Když jsem pracoval na řídicím programu pro můj vytuněný domácí termostat, docela mě zarazilo, kolik chvílemi až protichůdných požadavků musí zvládat vyřizovat v podstatě naráz:
  • měřit teploty z obou větví digitálních čidel DS18B20, třeba jednou za tři sekundy
  • měřit teplotu a vlhkost z čidla DHT11, nesmí častěji než jednou za sekundu
  • podle teplot a dalších údajů spínat kotel, stačí jednou za pět sekund
  • řídit podsvícení displeje, to je potřeba nejméně dvakrát za sekundu
  • kontrolovat, jestli se někdo nedotkl displeje, to je potřeba co nejčastěji, aspoň 10x za sekundu
  • překreslovat displej, nejlépe přesně jednou za sekundu, pokud se ho někdo nedotkl, jinak dřív
  • sledovat požadavky na interní webserver, to stačí tak 2-3x za sekundu
  • občas ujistit watchdog, že ještě žiju a nemá mě zresetovat (aspoň jednou za 4 sekundy)
Mimochodem, stejné problémy vyvstanou třeba při řízení robotického auta (což mám se synem taky v řešení) - tam je potřeba číst údaje z různých snímačů (vzdálenosti, světla, rychlosti), poslouchat dálkové ovládání a zároveň neustále řídit motory, do toho sem-tam blikat blinkry, svítit světly, přehrávat hlasy či muziku a podobně.

Na tohle všechno by byl ideální nějaký multitasking/multithreading, ne? Po více než dvaceti letech strávených v multitáskových operačních systémech to k němu člověka tak nějak táhne.  Ono se to všechno sice dá nacpat do té funkce loop(), ale je potřeba u toho moooc přemýšlet a hlavně - nikdy nevolat delay()!

Takže můj termostat měl všechny výše uvedené činnosti napsané za sebou v loop() cyklu jednu za druhou, ale zároveň jsem si musel definovat hromadu proměnných, které si pamatují, kterou z těch výše popsaných událostí jsem kdy zavolal a pak je nutné kontrolovat, jestli už je čas ji zavolat znovu - a pokud ještě není, tak ji nevolat a pokračovat v cyklu dál.

Nemusím vysvětlovat, že zdrojový kód ty dodatečné podmínky a přeskoky docela znepřehledňují. Navíc čtení z digitálních čidel DS18B20 napájených parazitně je potřeba provádět vlastně nadvakrát - nejdřív je nutno požádat o změření teploty, pak počkat 750 milisekund (pro 12bitovou přesnost) a pak teprve přečíst samotné teploty z čidel. To je další komplikace.

A když jsem teď začal znovu rozvíjet ten můj termostat a chtěl jsem tam přidat další funkci, která by na definovaný interval, třeba 30 minut, změnila nějaké nastavení a po uplynutí času ho zase sama vrátila zpět (ano, jde o větrání), uvědomil jsem si, že už to dál nechci takto plácat. A začal jsem hledat nějakou knihovnu, která by toto spouštění úloh v čase řešila za mě.

Poměrně brzy jsem našel celou řadu hotových řešení přímo pro Arduino (či pro nejbližší ATMEL procesory), které všechny slibovaly modré z nebe a světový mír. Vlastně je tu všechny můžu vypsat, ať ušetřím jiným hledačům čas:
No, strávil jsem docela dost času procházením těch existujících řešení, našel v nich chyby v návrhu i implementaci a nakonec jsem se rozhodl: napíšu si vlastní!

Upřímně řečeno, většinou lidi znovuvynalézající kolo nepovažuju za nejmoudřejší, ale v tomto případě jsem si byl docela jistý, že žádné z 11 dostupných řešení nefunguje tak, jak potřebuju, a že tedy bude OK věnovat část mé životní energie na řešení dvanácté, napsané mně na míru. Stanovil jsem si přitom následující zásady:
  1. co nejjednodušší použití (NE C++ třídám, dědičnosti, složitému API, mnoha parametrům)
  2. co nejkratší implementace (aby bylo na první pohled zřejmé, že to funguje správně)
  3. co nejúspornější provoz (malá zabraná jak flash tak RAM paměť)
  4. co nejpružnější a nejmocnější vlastnosti (předávání parametrů do volaných funkcí, ideálně dynamické vytváření úkolů za běhu)
  5. 100% kompatibila s Arduinem (žádný vlastní časovač či přerušení)
  6. a samozřejmě bez chyb přetečení časovače po 49 dnech
Nakonec bylo napsání vlastního řešení rychlejší, než jsem čekal. Výsledný kód implementace se vejde na jednu obrazovku monitoru, pro použití stačí zavolat jednu jedinou funkci s navíc intuitivním jménem i parametry, v RAM zabírá jedna úloha jen 14 bajtů včetně parametru předávaného do volané funkce a dokonce je možné vytvářet další úlohy dynamicky za běhu!

Výsledek jsem nazval Tasker pro Arduino a ihned ho použil v mém termostatu. Špagetový kód z funkce loop() jsem rozdělil na jednotlivé malé funkce a volám je jako samostatné úlohy v jejich pravý čas. Zdrojový kód termostatu se tím krásně pročistil a výsledek funguje díky prioritám implementovaným v Taskeru mnohem lépe, než dříve (například vyhodnocení dotykové vrstvy má teď jednu z nejvyšších priorit, aby reakce na dotyk byla okamžitá - tohle mi dřív trochu zlobilo). Super!

Můj Tasker jsem s radostí uvolnil pro všechny na GitHubu: https://github.com/joysfera/arduino-tasker

Nechci zde opisovat či překládat anglicky psaný manuál viditelný hned na tom URL uvedeném výše, ale pár ukázek elegantního řešení typických problémů s mým Taskerem si neodpustím. Pro začátek obligátní blikání LEDkou - na čtyři řádky a bez delay():

Tasker tasker;
void blik(int) { digitalWrite(13, !digitalRead(13)); }
void setup() { tasker.setInterval(blik, 500); }
void loop() { tasker.loop(); }

A ještě třeba nekompletní ukázka možnosti čtení těch DS18B20 periodicky každé tři sekundy, a to bez jakéhokoliv čekání na cokoliv, postaveném na zřetězení úkolů díky možnosti dynamického přidávání nových úkolů za běhu:

Tasker tasker;
OneWire oneWire(4);
DallasTemperature sensor(&oneWire);
float g_teplota;

void ctiSensor(int) {
    g_teplota = sensor.getTempC(0);
}

void ctiTeplotu(int) {
    sensor.requestTemperatures();
    tasker.setTimeout(ctiSensor, 750);
}

void setup() {
    sensor.begin();
    sensor.setWaitForConversion(false);
    tasker.setInterval(ctiTeplotu, 3000);
    tasker.run();
}

void loop() { }

To mi připomíná, že bych měl vyvézt na GitHub i mé úpravy v knihovně DallasTemperature... Snad někdy brzy.

Lepší ukázka využití Taskeru a kompletní popis API je na URL výše. A samozřejmě nejlepší dokumentací je samotný zdrojový kód, o kterém si troufám tvrdit, že je přehledný a dobře čitelný. Tak si to užijte! :)

EDIT 2017/01/27:
Na přání jsem doplnil klíčový řádek do příkladu se čtením čidel od Dallasu, takže už to nebude blokovat. A víte co? Rovnou jsem tam doplnil i tři další řádky, aby ten program byl celý funkční.
Tento příklad najdete i v mém git repozitáři jako druhý example vedle MultiBlink.

73 komentářů:

  1. Super, mě osobně to zaujalo, žádné složité nastavování proměnných, nevadí přetečení časovače, priority..... Přestože jsem nováček, rychle jsem kód pochopil a naprogramoval řízení přímotopů a vzduchotechniky. Prostudoval jsem Váš kód a návod a jsou zde 3 oblasti kterým nerozumím. Chci Vás požádat o vysvětlení.

    1) po vypnutí topení, omezuji opětovné zapnutí až do uplynutí požadované doby. Chci tak omezit počet sepnutí relé. Jak v tomto případě využít Váš multitasking?

    Část kódu:

    if (atKuchyn <= (EEPROM.read(2) - hysterze) && EEPROM.read(1) != 0 && millis() >= casVypnutiKuchyn) // zapne topeni pokud je aktualni teplota nizsi nez pozadovana - hysterze a soucasne uplynula doba od posledniho vypnuti
    {
    digitalWrite (KUCHYN, HIGH); // zapne topeni
    }
    else if (atKuchyn >= (EEPROM.read(2) + hysterze)) // vypne topeni pokud je aktualni teplota vyzsi nez pozadovana + hysterze
    {
    digitalWrite (KUCHYN, LOW); // vypne topeni
    casVypnutiKuchyn = millis() + prodleva; //nastavi cas kdy lze nejdrive zapnout topeni
    }

    2) v multitaskingu nechám povoleny priority a nastavím více procesů s krátkou dobou opakování. Je zde riziko, že nebude zbývat čas na další činnosti. Co se stane s ostatními procesy? Je možné že se nikdy neprovedou?

    3) není potřeba všechny procesy časovat a je možné je ponechat v loop. Jak zajistit aby se tento kód provedl?

    Předem děkuji za odpověď.

    OdpovědětVymazat
    Odpovědi
    1. ad 1) Tasker nemusíte použít mermo-mocí všude - jsou věci, které jdou naprogramovat lépe i bez něj.

      ad 2) ano, možná, že se nikdy neprovedou. Mají-li nižší prioritu a není-li na ně čas, tak holt mají smůlu.

      ad 3) když nezavoláte tasker.run(), tak se normálně opakovaně volá loop() jak jsme z Arduina zvyklí. V něm máte svůj kód jako předtím, než jste poznal Tasker, a stačí tam přidat volání tasker.loop(). Mám to v jednom z příkladů použito.

      Vymazat
  2. Nebyl by příklad jednoduchého ovládání topení ?

    OdpovědětVymazat
    Odpovědi
    1. topim = (teplota < požadovaná + (topim ? hystereze : 0);
      digitalWrite(kotel, topim);

      Vymazat
    2. SUPER! Zatím jsem se C++ vyhýbal, ale když vidím to elegantní naplnění proměnné topim a ty neuvěřitelné možnosti s Arduinem, tak se asi budu muset velmi hluboce nad sebou zamyslet a něco s tím udělat.

      Vymazat
    3. Vřelé díky za vaše články. Mají hlavu a patu. Děkuji

      (topim ? hystereze : 0).....................tuhle část programu jsem nechápal, tak jsem si to vyzkoušel

      topim = (teplota < pozadovana + (topim ? hystereze : 0)) .....přičte k požadované hysterezi při vzestupu a to co je za : přičte k požadované při sestupu

      (topim ? hystereze : 0))
      teplota= 20 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 21 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 22 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 23 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 24 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 25 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 26 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 27 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 28 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 28 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 27 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 26 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 25 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 24 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 23 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 22 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 21 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 20 topim= 1 pozadovana= 24 hystereze= 3
      (topim ? hystereze : 1))
      teplota= 20 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 21 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 22 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 23 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 24 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 25 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 26 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 27 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 28 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 28 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 27 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 26 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 25 topim= 0 pozadovana= 24 hystereze= 3
      teplota= 24 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 23 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 22 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 21 topim= 1 pozadovana= 24 hystereze= 3
      teplota= 20 topim= 1 pozadovana= 24 hystereze= 3
      boolean topim;
      byte teplota=20;
      byte kotel=3;
      byte pozadovana=24;
      byte hystereze=3;

      void setup() {
      Serial.begin(9600);
      }

      void loop() {
      for (int i=20; i <= 28; i++){
      teplota=i;
      Serial.print("teplota= ");Serial.print(teplota);
      topim = (teplota < pozadovana + (topim ? hystereze : 2));
      Serial.print(" topim= ");Serial.print(topim);
      Serial.print(" pozadovana= ");Serial.print(pozadovana);
      Serial.print(" hystereze= ");Serial.println(hystereze);
      //digitalWrite(kotel, topim);
      delay (1000);
      }
      for (int i=28; i >= 20; i--){
      teplota=i;
      Serial.print("teplota= ");Serial.print(teplota);
      topim = (teplota < pozadovana + (topim ? hystereze : 2));
      Serial.print(" topim= ");Serial.print(topim);
      Serial.print(" pozadovana= ");Serial.print(pozadovana);
      Serial.print(" hystereze= ");Serial.println(hystereze);
      //digitalWrite(kotel, topim);
      delay (1000);
      }
      }

      Vymazat
  3. Je možné tasker nějak zastavit? Respektive pouze jeden task? Potřeboval bych to použít podobně jako Timer ve VB.NET. Ten jde zapnout/vypnout a i za běhu (já za běhu nepotřebuji) změnit interval.

    OdpovědětVymazat
    Odpovědi
    1. nejde, úmyslně. Tasky by musely vracet ID, aby to šlo. Původně jsem to zvažoval, ale dospěl jsem k přesvědčení, že by to vše příliš zesložitilo, a tak jsem to tam nedal. Většina těch ostatních knihoven to podporuje, ale jsou řádově složitější...

      Vymazat
    2. Mimochodem, někde v dokumentaci pro to doporučuji si ten task prostě přeskakovat, pokud ho člověk nepotřebuje. Tj. mít hned na začátku úlohy
      if (! running) return;
      a když task chci zastavit, tak nastavím running na false a je to.

      Vymazat
  4. dobry den studujem ako zaciatocnik vas prispevok , teoreticky to chapem, ale v praxi mam vyrobeny kod na spinanie teploty čerpadla, ale nedari sa mi nastavit hysterezu povedzme 3 stupne celzia. viete mi poradit ? vopred dakujem

    tu je moj kod :


    //program pre kotol UK , LCD 16x2 . jednokanálové relé, snímač teploty Dallas temperature waterproof DS18B20
    //lcd je zapojené na I2C zbernici, výstupy zbernice komunikacny port SCL a SDA,GND , 5V
    //snímač teploty je cez modul zapojený na gnd, 5v, a pin 2
    //jednokanálové relé je zapnuté na gnd, 5v a pin 8
    //20.12.2016

    #include
    #include "LCD.h" // For LCD
    #include "LiquidCrystal_I2C.h" // Added library*

    //Set the pins on the I2C chip used for LCD connections
    //ADDR,EN,R/W,RS,D4,D5,D6,D7
    LiquidCrystal_I2C lcd(0x3F,2,1,0,4,5,6,7); // 0x27 0x3F is the default I2C bus address of the backpack-see article
    int DS18S20_Pin = 2; //DS18S20 Signalny pin na digital 2, takto napojime dalsie cidlo bez modulu na pin 3 (2,3);

    int relay =8;

    //Temperature chip i/o
    OneWire ds(DS18S20_Pin); // zapojene na digitalny pin 2

    void setup(void) {

    // Set off LCD module
    lcd.begin (16,2); // 16 x 2 LCD module
    lcd.setBacklightPin(3,POSITIVE); // BL, BL_POL
    lcd.setBacklight(HIGH);

    Serial.begin(9600);
    pinMode(8, OUTPUT); //slovensky: nastav rele pin 8 ako výstup
    }

    void loop(void) {
    float temperature = getTemp();



    //float tempF = (temperature * 9.0)/ 5.0 + 32.0;
    Serial.println(temperature); // názov volania cidla
    lcd.setCursor (0,0); // horný riadok displeja, teplota čerpadlo viz riadok niž
    lcd.print("Teplota Cerpadlo");




    if (temperature >25) { //podmienka pre zapínanie čerpadla ak je teplota vyššia, ako 22 st C zapni
    digitalWrite(8,HIGH); //pust napatie na pin 8
    lcd.setCursor (7,1); //vypíš na druhom riadok od pozície 7 ON viz riadok niž druhy riadok zacina 15.1
    lcd.print(" ON");
    } else {
    digitalWrite(8,LOW); // ked skonci horna podmienka teplota vyššia ako 22 st C teda je nižšia ako 22 st C vypne sa repadlo
    lcd.setCursor (7,1);
    lcd.print(" OFF"); // tu potrebujem prida histerziu zap 22 vyp 20 !!!!!!!

    }

    delay(100); //spomal opakovanie aby bol citatelny vysledok volanej hodnoty teploty 1000= 1s

    }


    float getTemp(){
    //vracia teplotu z čidla v stupnoch celzia



    byte data[12];
    byte addr[8];

    if ( !ds.search(addr)) {
    //žiadne ďalšie senzory na reťazce, resetovanie vyhľadávanie
    ds.reset_search();
    return -1000;
    }

    if ( OneWire::crc8( addr, 7) != addr[7]) {
    Serial.println("CRC is not valid!");
    return -1000;
    }

    if ( addr[0] != 0x10 && addr[0] != 0x28) {
    Serial.print("Device is not recognized");
    return -1000;
    }

    ds.reset();
    ds.select(addr);
    ds.write(0x44,1); // start conversion, with parasite power on at the end

    byte present = ds.reset();
    ds.select(addr);
    ds.write(0xBE); // Read Scratchpad


    for (int i = 0; i < 9; i++) { // we need 9 bytes
    data[i] = ds.read();
    }

    ds.reset_search();

    byte MSB = data[1];
    byte LSB = data[0];

    float tempRead = ((MSB << 8) | LSB); //using two's compliment
    float TemperatureSum = tempRead / 16;
    lcd.setCursor (0,1);
    lcd.print(TemperatureSum);
    lcd.println("*C..");

    return TemperatureSum;


    // pridat dalsie cidlo potrebujem !!!!
    // ako kalibrovat ????? ked premeriava
    // nastavit len 1 desatinne cislo, alebo ziadne !!!!



    }

    OdpovědětVymazat
    Odpovědi
    1. jak zacházet s hysterezí jsem napsal v komentáři výše již 24. června 2015 v 13:04.

      Vymazat
  5. Dobrý den, bude Tasker funkčni i na desce Wemos D1 ??

    A druhý dotaz, při kompilaci příkladu MultiBlink (s deskou arduino uno) vyhodí ide (ver.1.8.) chybu :
    exit status 1
    'blink1' was not declared in this scope

    Kde jsem asi udělal chybu? Knihovna Tasker je vidět v seznamu, ale není instalovaná ze zipu, jen jsem ji kopiroval dle instrukcí.

    OdpovědětVymazat
    Odpovědi
    1. Ano, bude tam skvěle funkční. Mám v plánu ho ještě rozšířit o takovou drobnost, která jej výrazně vylepší. Zatím asi bude potřeba volat yield() ručně.

      "blink1" not declared je chyba nového Arduino IDE, kterou jsem už v příkladu opravil. Stáhněte si novou verzi z githubu.

      Vymazat
    2. Tak už jsem tam doplnil i ten yield(), hned na dvě místa.

      Vymazat
  6. Petře, velké poděkování za Tasker
    vynalézal jsem kolo a natrefil na Váš Tasker
    Pokud ještě budete ochoten vyvézt úpravy v knihovně DallasTemperature, tak Vám snad osobně dovezu flašu něčeho dobrého

    OdpovědětVymazat
    Odpovědi
    1. beru Vás za slovo :) Podívám se, co jsem do knihovny DallasTemperature přidal před těmi třemi lety, jestli to tam dnes ještě není, a pokud ne, tak to vyvezu :)

      Vymazat
    2. tak jsem obešel celou DallasTemperature knihovnu a zůstal jen na úrovni OneWire, což je základní práce s DS čidly, ale za Tasker si odměnu zasloužíte. Určitě se někde sejdeme :-)

      Vymazat
    3. já se k tomu dostanu, jen mi dejte chvilku, mám toho rozdělaného moc!

      Vymazat
    4. Tak jsem právě vyvezl své 3,5 roku staré úpravy knihovny DallasTemperatures. Moji verzi najdete na https://github.com/joysfera/Arduino-Temperature-Control-Library a má navíc kromě nějakých čistek především možnost podržet libovolný počet čidel v paměti, takže se pak při každé operaci nemusí znovu hledat na sběrnici.
      Nicméně tyto moje úpravy nejsou pro spolupráci s Taskerem nezbytné, je to jen pro pohodlnější a rychlejší práci s hodně čidly na sběrnici.

      Vymazat
  7. Díky, skvěle hutný kód. Co jsem se ale natrápil s yield()! Ať jsem googlil, jak googlil, yield se zmiňuje spíš v iteracích a v jednom případě jsem to našel u multitaskingu - ovšem tam to využívalo ještě třídu definovanou v nějaké hlavičce. Než jsem zjistil, že zdejší yield() je z arduinové hlavičky, už mi z toho cukalo v koutku... :-)

    OdpovědětVymazat
    Odpovědi
    1. yield() je ve většině případů definovaný jako no-op, prázdnota, žádná instrukce. Používá se u větších systémů, než je Uno (např. u HW, který musí zároveň obsluhovat komunikaci s něčím), kde vlastně předává čas operačnímu systému. Takže i když na Arduino Uno to nemá žádný význam, raději jsem to zahrnul, kdyby tu knihovnu použil někdo na těch nových ARMových potvorách, nebo třeba na ESP8266.

      Mohl jsem použít místo toho delay(0), ale to by bylo asi ještě víc matoucí.

      Vymazat
  8. ... dodatek: Ovšem v arduino.h je pouze deklarace yield(), takže v kódu nemá žádný smysl.

    OdpovědětVymazat
    Odpovědi
    1. naopak, v kódu má maximální smysl, vysvětlil jsem to výše.

      Vymazat
  9. Díky za vysvětlení. Pochopil jsem, že jste yield použil z "ideového" důvodu. Jen mě nejdřív mátla jeho praktická nadbytečnost v daném kódu, tedy jen s danou hlavičkou. Díky vám jsem do problému více pronikl.

    OdpovědětVymazat
    Odpovědi
    1. To není ideový, ale ryze praktický důvod. Tasker lze použít na všech Arduino platformách, přičemž řada z nich potřebuje provozovat třeba WiFi či jiné periférie a potřebuje, abyste jim na chvíli "půjčil" CPU. A to přesně dělá yield().
      Jinými slovy, Tasker bez yield() na třeba ESP8266 způsobí do 8 sekund restart celého počítače, protože watchdog znervózní, že 8 sekund nedostal půjčený CPU.

      Vymazat
  10. Mimochodem, požádal jsem o zahrnutí Taskeru do seznamu Arduino knihoven, takže ho možná brzy najdete v Arduino Library Manageru :-)

    OdpovědětVymazat
  11. Ahoj, mohl bych tě požádat o radu jak se zbavit při dotazu na teplotu sensors.requestTemperatures() toho nepříjemného zaseknutí na cca 750ms? Jsem začátečník, můj názor je že tohle nemá s taskerem co dočinění (tasker to jen načasuje) ale potřebuju k tomu tvoji úpravu kniovny DallasTemperature. Je to tak nebo se mýlím?
    Díky, Tomáš :)

    OdpovědětVymazat
    Odpovědi
    1. Než uveřejním svoji tři roky starou verzi, musím se podívat, co je nového v aktuální "veřejné" verzi, porovnat, a případně moje změny přidat do té "veřejné" verze a pak zveřejnit tu svoji - to abych neštěpil vývoj.

      Vymazat
    2. Tomáši, tu zatracenou DallasTemperature nepotřebujete.
      Stačí jen OneWire knihovna a na její úrovni si ubrat rozlišení z 12bit třeba jen na 10bit.
      A už jste jen na 200ms (187.5)

      Vymazat
    3. zkracovat ten interval je zbytečné a nikam nevedoucí - správné je právě použít Tasker, započít konverzi teploty a jít dělat něco jiného. Tasker sám se po 750 ms vrátí a teplotu vyčte - tak, jak to mám v příkladech.

      Vymazat
    4. Jasně, takhle to Tasker dělá...
      Ale obvykle opravdu stačí teplota na 10bit

      Vymazat
    5. Tomáši, tak jsem právě vyvezl své 3,5 roku staré úpravy knihovny DallasTemperatures. Moji verzi najdete na https://github.com/joysfera/Arduino-Temperature-Control-Library a má navíc kromě nějakých čistek především možnost podržet libovolný počet čidel v paměti, takže se pak při každé operaci nemusí znovu hledat na sběrnici.
      Nicméně tyto moje úpravy nejsou pro spolupráci s Taskerem nezbytné, je to jen pro pohodlnější a rychlejší práci s hodně čidly na sběrnici.

      Vymazat
    6. Ještě jedna věc (ta důležitá, tj. odpověď na tvoji půvoní otázku): to nepříjemné zaseknutí se má vypnout pomocí setWaitForConversion(false) - doplnil jsem to do příkladu v blogpostu.

      Vymazat
  12. Právě jsem zeditoval příklad v blogpostu tak, že je plně funkční, a zároveň jsem ho doplnil i mezi příklady do knihovny na githubu.

    OdpovědětVymazat
  13. V jiném vašem článku o inteligentním termostatu využíváte tasker pro snímání klávesnice s konstrukci

    tasker.setTimeout(check_touch, 100);

    ten setTimeout mě mate. Jak to funguje? Řeším podobný problém a chtěl jsem tasker mimo jiné i využít pro odstranění přechodových stavů u tlačítek. Nebo je to špatný nápad?

    OdpovědětVymazat
    Odpovědi
    1. přesně na to (tzv. debouncing) ho používám:

      void check_touch(int)
      {
      if (! bylDotyk()) {
      tasker.setTimeout(check_touch, 100);
      return;
      }
      // zde zpracuji dotyk
      tasker.setTimeout(check_touch, 1500); // debouncing - dalsi dotyk nejdrive za 1,5 sekundy
      }

      Vymazat
  14. Předpokládám, že nedochází k rekurzi a že proces se přerazí Není mi ale jasné jak to funguje v hlavní smyčce. Je to tak, že proces se poprvé spustí po 100ms z hlavní smyčky a pak následně se vždy už spouští po 100ms/1500ms už přímo z toho procesu?

    OdpovědětVymazat
    Odpovědi
    1. no jasně, to je výhoda setTimeout(), zavolá jen jednou. A pak už si řídíte, za jak dlouho zavoláte funkci (nikoliv proces) znovu.

      Vymazat
  15. Už jsem pochopil. Děkuji.

    OdpovědětVymazat
  16. Tento komentář byl odstraněn autorem.

    OdpovědětVymazat
  17. Ahoj Petře,

    prosím tě, řeším tu drobný problém s Taskerem. Našel jsem na internetu program pro arduino pro vyčítání informací z plynového kotle BAXI. Program jsem si doplnil o zasílání dat do mysql databáze na internetu. V těle programu je časování řešené pomocí millis, ale tvé řešení se mi víc líbí a navíc už ho mám vyzkoušené a osvědčené na jiném projektu. Bohužel nemohu přijít na to, proč mi nejde zkompilovat program po doplnění řádku "tasker.setInterval(ethernet, 60000);" a pořád mi to vypisuje chybu

    Arduino: 1.6.8 (Windows 7), Vývojová deska: "Arduino/Genuino Mega or Mega 2560, ATmega2560 (Mega 2560)"

    C:\Users\user\Desktop\bsb_lan-master\BSB_lan\BSB_lan.ino: In function 'void setup()':

    BSB_lan:4526: error: invalid conversion from 'void (*)()' to 'TaskCallback {aka void (*)(int)}' [-fpermissive]

    tasker.setInterval(ethernet, 60000);

    ^

    In file included from C:\Users\user\Desktop\bsb_lan-master\BSB_lan\BSB_lan.ino:231:0:

    C:\Users\user\Documents\Arduino\libraries\Tasker/Tasker.h:53:6: error: initializing argument 1 of 'bool Tasker::setInterval(TaskCallback, long unsigned int, int, byte)' [-fpermissive]

    bool Tasker::setInterval(TaskCallback func, unsigned long interval, int param, byte prio)

    ^

    exit status 1
    invalid conversion from 'void (*)()' to 'TaskCallback {aka void (*)(int)}' [-fpermissive]

    This report would have more information with
    "Show verbose output during compilation"
    option enabled in File -> Preferences.

    kód funkce ethernet je zde:
    void ethernet(){
    double log_values[numLogValues];
    for (int i=0; i < numLogValues; i++) {
    if (log_parameters[i] > 0) {
    log_values[i] = strtod(query(log_parameters[i],log_parameters[i],1),NULL);
    }
    }

    if (client.connect("topeni.php5.cz", 80))
    {
    // vypíše text na serial monitor
    Serial.println("Pripojeni probehlo v poradku");
    client.print("GET /index.php?");
    for (int i=0; i < numLogValues; i++) {
    if (log_parameters[i] > 0 && log_parameters[i] != 20002 && log_parameters[i] != 30000) {
    client.print(log_parameters[i]);
    client.print("=");
    }
    if (log_parameters[i] > 0 && log_parameters[i] < 20000) {
    client.print(log_values[i]);
    client.print("&&");
    }
    }

    client.println(" HTTP/1.1");
    client.println("Host: topeni.php5.cz");
    client.println("Connection: close");
    client.println();
    client.println();
    client.stop();
    client.flush();
    }
    }

    Nenapadá tě náhodou co by to mohlo dělat?

    Díky

    OdpovědětVymazat
    Odpovědi
    1. nad tím není potřeba přemýšlet - kompiler tam přímo píše, kde je problém. Všechny funkce volané Taskerem mají mít právě jeden parameter typu int.

      Vymazat
    2. Jsem to ale vůl. Máš pravdu, už to funguje. Diky

      Vymazat
    3. Zdravím, zajímá mě komunikace s kotlem BAXI. Lze s ním komunikovat nějak více, než jen zapínat a vypínat topení?

      Vymazat
  18. AHoj Petre, jen maly dotaz, lze nejak overit jestli nastaveny timout u taskeru uz expiroval? tzn jestli dany task jeste bezi?

    Ptam se proto, ze jsem zjistil, ze kdyz mam nastavennou zmenu timeoutu v samotne funkci - typicky tvuj Multiblink MujTasker.setTimeout(Tasker_LED_PWR, digitalRead(37) ? 9900 : 100); no a shodou okolnosti zrovna na tasker nezbude priorita tak uz se asi neprovede a dalsi Timeout se tim padem nenastavi.

    OdpovědětVymazat
    Odpovědi
    1. Zatím to nejde, brzy to půjde.
      Do té doby (než vydám Tasker 2.0) doporučuji buďto vypnout prioritizaci v Taskeru, anebo posunout důležité tasky víc na začátek fronty.

      Vymazat
    2. diky za rychlou odpoved, zrovna tady si jednoduse overim, jestli dioda sviti, a kdy ztak ho pustim znovu, ale bude to fajn jestli bude v2 :)

      Vymazat
    3. Jeste se zeptam par drobnosti:
      1, je nejaky funkcni ropzdil mezi tasker.loop a tasker.run?
      2, je bezpecne menit setInterval za behu?

      Proc se ptam?
      Pouzivam tasker.loop
      1, mam pomerne vytitzenou proceduru a vsiml jsem si nekolika detailu. Jak si tak blikam diodou 1x za sec abych videl, ze ziju, tak napriklad pri delsim cteni z serial je viditelne, ze se samotny interval bliknuti prodlouzi a potom ma tendenci ten cas dohnat, tedy blikne 1x za 2 sec(pracuju) a pak 3x za 2 sec(mam volno) ocekaval bych pouze natazeni prvniho intervalu.
      2, mam dva stavy v kazdem mi dioda blika jinak rychle. pri prechodu z 10s na 1s je to v poradku, kdyz ale zmenim stav opacne uz se neprepne, teda spis blika tak 8+2, nebo 6+4 a pod, jako kdyby na najednou bezely 2 taskery soucasne s se stejnou frekvenci a spousteli stejnou funkci posunute, jinak si to neumim vysvetlit.

      Vymazat
    4. Mezi tasker.loop a tasker.run je zásadní rozdíl v tom, že tasker.run nikdy neskončí, volá se jako poslední příkaz ve funkci setup(), dál už se nikdy nic nevykoná.
      Tasker.loop() jsem přidal pro lidi, kteří se bez hlavní Arduino funkce loop() nedokáží obejít - osobně ji nepoužívám, protože umožňuje dál prasit starým a špatným Arduino loop() způsobem.

      setInterval() nemění interval, ale PŘIDÁVÁ další úlohu do seznamu úloh. Trochu mě děsí, že to není zřejmé z dokumentace.

      Vymazat
    5. diky za ozrejmeni
      1, samotne pouziti run a loop je mi jasne, smeroval jsem k tomu casovemu dobehnuti zpozdenych funkci. Mimochodem pokud do prazdneho Arduino loop() vlozim pouze jediny prikaz tasker.loop(), tak to ve vyslededku musi byt stejne jako tasker.run(), nebo se pletu?

      2, ja jsem to tak nepochopil, ale potvrzuje to to odpozorovane chovani.

      Vymazat
  19. loop v loop jsem psal v dokumentaci, že nahrazuje run. Je to téměř totéž, ale ne stejné, protože Arduino samo dělá v loop ještě další věci.

    Tasker je úmyslně naprogramovaný tak, aby když si v nějakém tasku volaném co 1000 milisekund zobrazuju čas, tak aby se mi hodiny nezpožďovaly. To je prostě hlavní fíčura. Je chybou uživatele knihovny poskládat tasky tak, že ty dlouhotrvající má nahoře, a ještě navíc má zapnutou prioritizaci a pak se diví, že se něco nestihne zavolat.
    Tasker by se dal změnou pěti písmen předělat tak, že přestane dodržovat reálný čas a úlohy začne zpožďovat. Každý si ho tak může upravit, vždyť je na pár řádků a naprosto přehledný.

    pozorovat chování setInterval v momentě, kdy zlobí i jiné věci, je zavádějící. Stačí se podívat do zdrojáku a hned je vidět, co to doopravdy dělá.

    V příští velké verzi toto chování asi změním, protože vidím, že to lidé používají špatně.

    OdpovědětVymazat
    Odpovědi
    1. No vidis, staci napsat jednu vetu jinak a veci do sebe najednou zapadnou. Omlouvam se, za takovy "hloupy" dotazy. Ja 20 let programuju servrove aplikace na windows ve visual basicu a mam proste zazite jine postupy a jinak tvorene aplikace. tohle je muj prvni vetsi projekt na tehle platforme, jsem zvyklej mit komfortnich x GB RAM, a nejakou globalni promennou navic neresim. A ver mi, ze kdyz jsem poprve videt tvou konstrukci: tasker.setTimeout(blink1, led ? 300 : 700); myslel jsem, ze se vratim na zakladku :)
      Takze tak. Drzim palce, vyborna prace a vybornej blog.

      Vymazat
    2. To mi řekni, čím jsem ti rozsvítil a co teď vidíš jinak než dřív.

      Mimochodem, k tomu setIntervalu - stačí ho zavolat dvakrát pro stejnou funkci s jiným parametrem a hned uvidíš, že se daná funkce volá skutečně jako dvě různé úlohy. Na to právě je tam ten (int) jako parametr - aby to člověk mohl odlišit. Přišlo mi to původně cool, ale všechny ty komentáře výše mi ukázaly, že to lidi moc nepobrali. Škoda. Budu muset udělat Tasker2 bez tohoto parametru...

      Vymazat
    3. "aby se mi hodiny nezpožďovaly"

      Ja to od zacatku pochopil uplne spatne. myslel jsem si, ze je to jen

      if (millis() > cas_posledniho_spusteni + x) {

      s jednoduchou deklaraci a jednoznacnym casovym intervalem. takze me se hodiny pochopitelne porad zpozdovaly. taky to bylo to prvni co me prekvapilo a na co jsem se puvodne ptal, proc mi ty opozdene funkce jakoby "dobehnou". no vychazel jsem z mylneho predpokladu, ze neco podobehe na arduinu neni ani mozny.

      Vymazat
    4. Tak jsem dal tasker.run, abych to prestal delat "blbe" a cely dopoledne jsem zjistoval proc mi prestaly fungovat Seriovy porty. :)

      Vymazat
    5. ano, to je to, jak jsem psal, že Arduino dělá v loop() ještě nějaké věci navíc. Konkrétně se stará o sériový port u Arduino Micro. Máš něco podobného? U Pro Mini to potřeba není, si myslím. Anebo už sériový port nepoužívám?
      Asi v Taskeru2 run vůbec nedám a nechám jen loop v loop...

      Vymazat
    6. mam mega na prvnim portu mam GSM na druhem a na tretim portu externi cidla jeste jeden Softwarovy tam je RFID ctecka.

      Vymazat
    7. Apropos, pokud uz seriovy port nepouzivas, jak resis seriovou komunikaci? ja mam skoro vsechny periferie se kterejma pracuju na rs232 nebo TTL.

      Vymazat
    8. Všechny Arduina, kde mi běží Tasker, jsou na Ethernetu a komunikuji s nimi přes jejich webový server, který taky brzy zveřejním.

      Vymazat
    9. Nojo, to ale neresi externi periferie a ne vzdy je po ruce ethernet.

      A neves hlavu kluli nekolika induviduiim, kteri jsme napoprve nepochopili tvuj tasker. Predstav si kolik tisicu lidi po celym svete bude nastavnejch az zmizi jejich oblibene .run(). O tech nevis, protoze pochopili a nepisou ti :)

      Vymazat
    10. upřímně řečeno si celý den lámu hlavu nad tím, jestli můžu změnit stávající Tasker nekompatibilním způsobem, kvůli kterému by si lidi museli přepsat programy, nebo jestli musím raději udělat nový projekt Tasker2.

      Vymazat
  20. Dobry den, nefunguje mi váš příklad u tasker knihovny DallasTemperature.ino .
    Čidlo mám zapojené klasicky 3 vodičove s rezistorem. Na serial monitoru pořád vypisuje - 127 . Mám i vaši knihovnu pro dallas čidla. Když však i s vaší knihovnou vyzkouším jiné příklady na pro dallas 18b20 tak mi teplota normálně měří. Používám, ale vaš tasker tak jsem chtěl použít váš příklad na čtení teplot.
    Kde může být prosím chyba?
    Děkuji za radu

    OdpovědětVymazat
    Odpovědi
    1. Ještě dodám, mám čidlo na pin 7

      #include "Tasker.h"
      #include "OneWire.h"
      #include "DallasTemperature.h"

      Tasker tasker;
      OneWire oneWire(7);
      DallasTemperature sensor(&oneWire);

      void readSensor() {
      // read the actual temperature after it's been converted
      float temperature = sensor.getTempC(0);
      // do what you need with the temperature here
      Serial.print(temperature);
      }

      void startConversion() {
      // start temperature conversion (does not block)
      sensor.requestTemperatures();
      // schedule reading the actual temperature in 750 milliseconds
      tasker.setTimeout(readSensor, 750);
      }

      void setup() {
      Serial.begin(9600);
      sensor.begin();
      // do not block during temperature conversion
      sensor.setWaitForConversion(false);
      // read temperature every 5 seconds
      tasker.setInterval(startConversion, 5000);
      }

      void loop() {
      tasker.loop();
      }

      Při vypojení čidla vypíše místo teploty -127, teplotu 0.00

      Vymazat
    2. zkuste getTempC(0) nahradit za getTempCByIndex(0)

      Vymazat
  21. Aha to mne nenapadlo, dekuju moc za radu. A take za tasker. Zatim stim zacinam jako zacatecnik a je lepsi si takhle spoustet jednotlive funkce ( metody ), nez to mit v loop kde jsem pak nevedel co k cemu patri.

    OdpovědětVymazat
  22. Tento komentář byl odstraněn autorem.

    OdpovědětVymazat
  23. Dobrý den, potřeboval bych poradit s displejem 20×4 pres tasker. Mam program na ovládání akvária pomocí taskeru ( cidla teploty, rtc, stmivání a rozedníváni led světel atd ). S displejem umím ďělat. Mám vše v samostatných metodách, tak jak doporučujete a je to prehlednejsi. Vše funguje pomocí tasker.setinterval. Nevím, ale jak do toho zakomponovat zobrazeni jednotlicych hodnot na displeji. Jestli vytvorit dalsi metodu treba void displej a do ni to napsat ( nevim jestli je to mozne ) nebo to napsat rovnou do do jednotlive casti kodu. Jde mi o to ze kazdou hodnotu merim v jiny cas. Neco jednou za 750ms a neco treba jednou za 45 vterin. A nevim jak se to bude chovat pri zobrazeni na displeji. Kdybyste mohl poskytnout na par radku jednouduchy postup byl bych moc vdecny. Jsem stale zacatecnik. Dekuji moc

    OdpovědětVymazat
    Odpovědi
    1. pokud můžete aktualizovat části displeje zvlášť, tak zobrazujte v těch rutinách, ve kterých měříte. Pokud musíte vždy překreslit celý displej najednou, tak si naměřené údaje ukládejte do globálních proměnných a ze všech měřících rutin pak volejte rutinu na obnovu údajů na displeji s hodnotami z globálních proměnných.

      Vymazat
  24. Mam vsechno ukladane do globalnich promennych uz ted tak to zkusim tim druhym zpusobem co jste mi poradil. Zatim dekuji

    OdpovědětVymazat
  25. Dobry den, potreboval bych dalsi radu. Potrebuji vyresit tlacitka na ovladani menu. Jak to udelat. S tlacikama zatim moc kamarad nejsem. Nemate nekde cast kodu jak to pres tasker vyresit? Vcetne ochrany pred zachvevy atd. Nasel jsem tu na foru cast kodu pro tlacitka ale neni cely a nejsem schopnej ho dopsat.

    void check_touch(int)
    {
    if (! bylDotyk()) {
    tasker.setTimeout(check_touch, 100);
    return;
    }
    // zde zpracuji dotyk
    tasker.setTimeout(check_touch, 1500); // debouncing - dalsi dotyk nejdrive za 1,5 sekundy
    }

    Tuhle cast chapu ale neni cela. Navodu na tlacitka je na internetu hodne, ale ne timto zpusobem. Jde mi o dalsi promnenne atd potrebne k tomu, aby tento kod fungoval.
    Dekuji moc za pomoc

    OdpovědětVymazat
    Odpovědi
    1. hotový kód nemám, ale pro tlačítka existuje mnoho Arduino knihoven, které všechno zvládají, včetně dlouhého stisku, dvojstisku atd.

      Vymazat