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.
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ě.
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:
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:
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():
- https://sourceforge.net/projects/abavr/
- https://github.com/ivanseidel/ArduinoThread
- http://playground.arduino.cc/Code/TaskScheduler
- http://playground.arduino.cc/Code/TimerScheduler
- https://github.com/lewisd32/Tasker
- https://github.com/Zuph/AVRQueue/
- https://github.com/chrispbarlow/TTduino
- https://github.com/blanboom/Arduino-Task-Scheduler/
- http://blog.protoneer.co.nz/arduino-task-scheduler/
- https://github.com/cyphar/sched
- https://code.google.com/p/arduino-scoop-cooperative-scheduler-arm-avr/
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:
- co nejjednodušší použití (NE C++ třídám, dědičnosti, složitému API, mnoha parametrům)
- co nejkratší implementace (aby bylo na první pohled zřejmé, že to funguje správně)
- co nejúspornější provoz (malá zabraná jak flash tak RAM paměť)
- 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)
- 100% kompatibila s Arduinem (žádný vlastní časovač či přerušení)
- a samozřejmě bez chyb přetečení časovače po 49 dnech
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.
Výborný počin!
OdpovědětVymazatSuper, 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í.
OdpovědětVymazat1) 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ěď.
ad 1) Tasker nemusíte použít mermo-mocí všude - jsou věci, které jdou naprogramovat lépe i bez něj.
Vymazatad 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.
Nebyl by příklad jednoduchého ovládání topení ?
OdpovědětVymazattopim = (teplota < požadovaná + (topim ? hystereze : 0);
VymazatdigitalWrite(kotel, topim);
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.
VymazatVřelé díky za vaše články. Mají hlavu a patu. Děkuji
Vymazat(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);
}
}
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ětVymazatnejde, ú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ší...
VymazatMimochodem, 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
Vymazatif (! running) return;
a když task chci zastavit, tak nastavím running na false a je to.
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
OdpovědětVymazattu 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 !!!!
}
jak zacházet s hysterezí jsem napsal v komentáři výše již 24. června 2015 v 13:04.
VymazatDobrý den, bude Tasker funkčni i na desce Wemos D1 ??
OdpovědětVymazatA 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í.
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ě.
Vymazat"blink1" not declared je chyba nového Arduino IDE, kterou jsem už v příkladu opravil. Stáhněte si novou verzi z githubu.
Tak už jsem tam doplnil i ten yield(), hned na dvě místa.
VymazatPetře, velké poděkování za Tasker
OdpovědětVymazatvynalé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
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 :)
Vymazattak 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 :-)
Vymazatjá se k tomu dostanu, jen mi dejte chvilku, mám toho rozdělaného moc!
VymazatTak 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.
VymazatNicmé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.
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ětVymazatyield() 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.
VymazatMohl jsem použít místo toho delay(0), ale to by bylo asi ještě víc matoucí.
... dodatek: Ovšem v arduino.h je pouze deklarace yield(), takže v kódu nemá žádný smysl.
OdpovědětVymazatnaopak, v kódu má maximální smysl, vysvětlil jsem to výše.
VymazatDí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ětVymazatTo 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().
VymazatJiný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.
Mimochodem, požádal jsem o zahrnutí Taskeru do seznamu Arduino knihoven, takže ho možná brzy najdete v Arduino Library Manageru :-)
OdpovědětVymazatuž tam je!
VymazatAhoj, 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?
OdpovědětVymazatDíky, Tomáš :)
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.
VymazatTomáši, tu zatracenou DallasTemperature nepotřebujete.
VymazatStačí 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)
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.
VymazatJasně, takhle to Tasker dělá...
VymazatAle obvykle opravdu stačí teplota na 10bit
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.
VymazatNicmé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.
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.
VymazatPrá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ětVymazatV jiném vašem článku o inteligentním termostatu využíváte tasker pro snímání klávesnice s konstrukci
OdpovědětVymazattasker.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?
přesně na to (tzv. debouncing) ho používám:
Vymazatvoid 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
}
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ětVymazatno jasně, to je výhoda setTimeout(), zavolá jen jednou. A pak už si řídíte, za jak dlouho zavoláte funkci (nikoliv proces) znovu.
VymazatUž jsem pochopil. Děkuji.
OdpovědětVymazatTento komentář byl odstraněn autorem.
OdpovědětVymazatAhoj Petře,
OdpovědětVymazatprosí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
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.
VymazatJsem to ale vůl. Máš pravdu, už to funguje. Diky
VymazatZdraví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í?
VymazatAHoj Petre, jen maly dotaz, lze nejak overit jestli nastaveny timout u taskeru uz expiroval? tzn jestli dany task jeste bezi?
OdpovědětVymazatPtam 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.
Zatím to nejde, brzy to půjde.
VymazatDo 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.
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 :)
VymazatJeste se zeptam par drobnosti:
Vymazat1, 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.
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á.
VymazatTasker.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.
diky za ozrejmeni
Vymazat1, 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.
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.
OdpovědětVymazatTasker 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ě.
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 :)
VymazatTakze tak. Drzim palce, vyborna prace a vybornej blog.
To mi řekni, čím jsem ti rozsvítil a co teď vidíš jinak než dřív.
VymazatMimochodem, 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...
"aby se mi hodiny nezpožďovaly"
VymazatJa 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.
Tak jsem dal tasker.run, abych to prestal delat "blbe" a cely dopoledne jsem zjistoval proc mi prestaly fungovat Seriovy porty. :)
Vymazatano, 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?
VymazatAsi v Taskeru2 run vůbec nedám a nechám jen loop v loop...
mam mega na prvnim portu mam GSM na druhem a na tretim portu externi cidla jeste jeden Softwarovy tam je RFID ctecka.
VymazatApropos, pokud uz seriovy port nepouzivas, jak resis seriovou komunikaci? ja mam skoro vsechny periferie se kterejma pracuju na rs232 nebo TTL.
VymazatVšechny Arduina, kde mi běží Tasker, jsou na Ethernetu a komunikuji s nimi přes jejich webový server, který taky brzy zveřejním.
VymazatNojo, to ale neresi externi periferie a ne vzdy je po ruce ethernet.
VymazatA 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 :)
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.
VymazatDobry den, nefunguje mi váš příklad u tasker knihovny DallasTemperature.ino .
OdpovědětVymazatČ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
Ještě dodám, mám čidlo na pin 7
Vymazat#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
zkuste getTempC(0) nahradit za getTempCByIndex(0)
VymazatAha 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ětVymazatTento komentář byl odstraněn autorem.
OdpovědětVymazatDobrý 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ětVymazatpokud 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.
VymazatMam vsechno ukladane do globalnich promennych uz ted tak to zkusim tim druhym zpusobem co jste mi poradil. Zatim dekuji
OdpovědětVymazatDobry 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.
OdpovědětVymazatvoid 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
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