Blikací LED

daton
Příspěvky: 479
Registrován: 16 bře 2013, 16:12

Blikací LED

Příspěvek od daton » 30 lis 2018, 22:52

Ahoj všem
chtěl bych vytvořit kontrolku z neopixel, která bude signalizovat stavy nejen barvami ale i délkou blikání. Trochu jsem o tom přemýšlel a napadl mne zatím tento kod pro blikání. Kod musí být co nejméně zatěžující pro kod tedy žádné dealy().
Nejprve jsem uvažoval že budu počítat průchod hlavním programem a dle toho budu časovat blikání, bylo by to ale nepřesné i když by to mohlo třeba napovědět něco a průchodu jednotlivými částmi programu. Nakonec jsem ale vsadil na millis(). vytvořil jsem kod a předpokládám, že rozhodně budete mít mnoho lepších nápadů na ten kod, než já a kratších. Proto sem dávám můj návrh, jsou tam dvě smyčky jedna je zakomentovaná pokud se odkomentuje neopixel bliká hodně rychle modrou a pomaleji červenou kupodivu se ty kody neovlivnují i když jsem původně myslel, že to tak bude. Jen je třeba chvilku počkat než to naběhne. Zkusíte někdo napsat kratší kod?

Kód: Vybrat vše

Adafruit_NeoPixel pixels = Adafruit_NeoPixel(1, 3, NEO_GRB + NEO_KHZ800);
unsigned long ms=0;
unsigned long mek=500;
unsigned long tik;

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

void loop() {

tik=millis();

/*
  if (mek>tik){
    pixels.setPixelColor(0, pixels.Color(0, 0, 15)); 
    pixels.show();   
  }
  else{
    pixels.setPixelColor(0, pixels.Color(0, 0, 0)); 
    pixels.show();
  
  if ((tik-100)>(mek)){mek=mek+200;}  }

 */

  if (ms>tik ){
    pixels.setPixelColor(0, pixels.Color(100, 0,0)); 
    pixels.show();    
  }
  else{ 
    pixels.setPixelColor(0, pixels.Color(0, 0, 0));   
    pixels.show();
    
    if ((tik-400)>(ms)){ms=ms+800;} }

Serial.print(ms);
Serial.print("   ");
Serial.println(tik);
  }
PS opravil jsem kod měl jsem tam špatně složenou závorku ted to chodí lépe.

DavidO
Příspěvky: 604
Registrován: 01 kvě 2013, 21:27

Re: Blikací LED

Příspěvek od DavidO » 01 pro 2018, 14:41

daton píše:
30 lis 2018, 22:52
Zkusíte někdo napsat kratší kod?
To záleží na tom, co tím myslíš. Třeba kratší kód by byl

Kód: Vybrat vše

void setup() {
}
void loop() {
}
ale to by jaksi nedělalo to samý :D

Ale teď vážně. Jde o to, jestli chceš, aby byl kratší zdroják anebo aby to zabralo míň místa v tom mikrokontroléru (a tady ještě rozlišit, jestli míň programové paměti nebo míň datové pamětí nebo míň něčeho jiného, třeba času).
Raději bych se ale snažil, aby zdroják byl čitelný, udržovatelný a dělal to, co má, a až teprv potom jak je něco velké (lépěji myslet na to rovnou, což je ovšem těžší). Tak jako tak bych se netrápil velikostí zdrojového kódu, ale velikostí toho, co bude v mikrokontroleru za běhu (a to navíc přiměřeně, protože dokud paměť dostačuje, nebudu dělat nečitelné prasárny, abych ušetřil jeden byte).

Pár poznámek ke tvému kódu (je to víc věcí, tak napíšu každou zvlášť místo abych je rovnou složil dohromady do výsledného kódu; bude tak snad jasnější jeden každý princip. Taky to píšu nasucho, takže tam možná bude nějaká chybka, ale jde mi o princip):
  • Celkem zbytečně neustále nastavuješ barvu jakou to má svítit. Představ si, že by to běželo tak rychle, že by se loop otočila miliardkrát za vteřinu, pak bys miliardkrát za vteřinu nastavil buď 100 nebo 0 (protože otestuješ, jestli má být LED rozsvícená a když jo, nastavíš jí 100 a když ne, nastavíš jí 0). Místo toho bych testoval, jestli mám něco změnit a teprve když jo, tak bych to změnil. Takže by to pak jen jednou na 400ms nastavilo buď tak nebo tak (tj. 2,5x za vteřinu místo miliardkrát). (Je samozřejmě možné vést filozofickou debatu na téma "no a co, tak se to nastavuje mockrát, ale když se to stihne, tak to přece nevadí")
    Asi bych to napsal nějak takhle:

    Kód: Vybrat vše

    void loop() {
      static unsigned long udelejNeco; // cas, kdy se ma neco udelat
      static boolean cervenaZapnuta; // je zrovna cervena zapnuta?
      tik=millis();
      if(tik>udelejNeco){
        if(cervenaZapnuta){
          pixels.setPixelColor(0, pixels.Color(0, 0,0));   // zhasnout cervenou
        } else {
          pixels.setPixelColor(0, pixels.Color(100, 0,0));   // rozsvitit cervenou
        }
        cervenaZapnuta = ! cervenaZapnuta; // otocim indikator zapnutosti
        pixels.show(); // necham to provest
        udelejNeco += 400; // priste neco budu delat zase za dany cas
      }
    }
    
  • Pokud bys to chtěl nějak asymetricky (např. červená svítí 200ms a nesvítí 400ms), tak by se ten posun na další čas udelejNeco += 400; (to je totéž, jako bys napsal udelejNeco = udelejNeco + 400;) dal dovnitř toho ifu a přičítalo by se patřičně různé číslo.
  • Hodnoty pro to, jak dlouho svítí/nesvítí, bych si radši nadefinoval jako konstanty, např.

    Kód: Vybrat vše

     #define CERVENA_ZAPNUTA 400  
     #define CERVENA_VYPNUTA 400 
     #define MODRA_ZAPNUTA 100 
     #define MODRA_VYPNUTA 100
    protože pak bude kód čitelnější. Navíc těch 800 radši napíšu jako CERVENA_ZAPNUTA + CERVENA_VYPNUTA a bude na první pohled jasné, co to znamená.
  • Když ve tvém kódu odkomentuješ tu část pro modrou (a není to smyčka, jen jeden podmíněný příkaz if, aby to byla smyčka, muselo by to běžet opakovaně - buď for nebo while), tak ti stejně vždycky bude svítit jen jedna barva, ale logicky by občas měly svítit obě zároveň. Správnější by bylo nastavovat v té které části jen modrou nebo červenou složku té barvy a tu druhou nechat tak, jak je.
  • Z hlediska použitých typů proměnných tam je chyba, která ale nakonec dobře dopadne: tik i ms jsou bez znamínka, ms je na začátku nula. Když se to celé spustí, bude napoprvé tik taky nula. Podmínka v testu if(ms>tik) není tedy splněna a proto se bude vykonávat else větev. Tam se testuje if ((tik-400)>(ms)) a tady se to zamotá: matematicky je 0-400 rovno -400, ale to v bezznamínkových číslech nejde, "zatočí" se to dokola přes nejvyšší hodnotu. No ale dobře to dopadne, protože když tik je unsigned long a je rovno nule, tak tik-400 vyjde 4294966896 a to je větší než 0 a tedy se ms o těch 800 posune.
  • Pozor na seriovou linku, posílání přes ní je pomalé. Při inicializaci Serial.begin(9600); to znamená, že za vteřinu můžeš přenést max. ani ne kilo i kdyby ten procesor nedělal už nic jiného (když zavoláš Serial.print, tak se to vrátí až když odešle všechny znaky). To samozřejmě může významně ovlivnit to, co ten procesor má normálně dělat, protože každé poslání ho bude zdržovat v závislosti na délce toho posílaného. Co s tím je na delší povídání, pro teď prosím přijmi jen dvě praktické rady: inicializuj to s vyšší rychlostí (běžné AVR snese i 115200, ESP sice prakticky nevím, ale divil bych se, kdyby to nesneslo taky) a stejně to moc nepoužívej.
  • Místo vypisování několika mezer v Serial.print(" "); radši používej pro odsazení tabulátor \t, výstup bude hezčí.

daton
Příspěvky: 479
Registrován: 16 bře 2013, 16:12

Re: Blikací LED

Příspěvek od daton » 01 pro 2018, 17:05

Moc děkuji za výklad i zdůraznění chyb kterých jsem se dopustil. Máš pravdu a tvůj kod je v tomto lepší.
S tím místem jsem myslel místo v paměti arduina protože se mi ho zoufale nedostává a už nevím kde bych ušetřil aby se mi tam vešel program. Jsem na 98% obsazení paměti a ze zkušeností vím že pokud se dostanu nad 95% tak arduino již není stabilní. Tak že se snažím i za cenu krásy resp škaredosti kodu šetřit místem.
Přemýšlel jsem že to zkompiluji na některém starším ide ty byly podstatně úspornější a když se vygeneroval kod napříkla 1.3 ide a 1.87 tak tam došlo k dost velké úspoře místa nevím proč ale je to tak.
Tak že zkusím si tvlj kod rozpitvat a použít do toho mého projektu a uvidíme až zkompiluji jestli se něco ušetřilo :-)

daton
Příspěvky: 479
Registrován: 16 bře 2013, 16:12

Re: Blikací LED

Příspěvek od daton » 01 pro 2018, 17:27

Ještě dotaz ve tém kodu je

Kód: Vybrat vše

 cervenaZapnuta = ! cervenaZapnuta; 
jako otočím příznak, toto použití jsem ještě neviděl znamená to že například když dám
cervenaZapnuta
jako integer tak to samo bude generovat nějakou číslici která bude jednou například 1 a jednou 0 nebo to má dát jako boolen a tedy že jednou to bude true a podruhe false a bude se to určovat samo?
Dík za objasnění

DavidO
Příspěvky: 604
Registrován: 01 kvě 2013, 21:27

Re: Blikací LED

Příspěvek od DavidO » 01 pro 2018, 18:01

! je logická negace. S hodnotou typu bool udělá co bys čekal - z true udělá false a z false udělá true.
Typ bool je normální celočíselný typ (je to jen alias standardního typu _Bool), a true a false jsou nadefinované jako konstanty 1 a 0 (viz soubor stdbool.h někde v hloubi stromu Arduina).

Podmínky v C++ a tedy i v Arduinu (a v C taky) fungujou tak, že se ten výraz co tvoří podmínku vyhodnotí jako číslo, a když vyjde 0, tak podmínka není splněná, když cokoli jiného, splněná je. Proto se taky píše třeba if(promenna){ necoUdelej(); } a znamená to, že se zkontroluje, jestli hodnota v proměnné promenna je 0 nebo není. Když není, tak je podmínka splněná a něco se udělá. A nemusíš psát if(promenna!=0). Různá porovnávání (>, < a tak) jsou definovaný tak, že jejich hodnotou je celé číslo a to tak, že to v podmínkách funguje: 0 nesplněno, 1 splněno. A negace funguje na celá čísla tak, že z nuly udělá jedničku a z čehokoli jiného než nula udělá nulu.
Ono to je ještě v definicích C a C++ trošku košatější a v různých verzích maličko různě, ale v zásadě se to chová tak jak jsem napsal. Kromě toho je v Arduinu ještě definovaný typ boolean, což je jen alias typu bool a v dokumentaci se píše, že to není standardní typ a máš radši používat bool (já si myslím, že ho tam dali jednak aby to bylo "slovně čitelné" a možná i proto, že v Pascalu se ten typ jmenuje boolean tak aby to měli lidi případně jednodušší, ale ono je to nakonec spíš matoucí).

A pro úplnost, ještě existuje bitová negace ~ (tilda, vlnka), která vezme výraz a otočí mu jednotlivé bity z 0 na 1 a naopak. Není to logická negace, protože až na číslo složené ze samých bitů 1 ti touhle negací vždycky vyjde z nenulového čísla jiné nenulové číslo. Obvyklá chyba je tyhle dvě negace zaměňovat, to se pak samozřejmě dějou nechtěné věci (podobně jako je blbě když se zamění logický a bitový operátor, třeba & za && anebo | za || a naopak).

DavidO
Příspěvky: 604
Registrován: 01 kvě 2013, 21:27

Re: Blikací LED

Příspěvek od DavidO » 01 pro 2018, 18:10

daton píše:
01 pro 2018, 17:05
S tím místem jsem myslel místo v paměti arduina protože se mi ho zoufale nedostává a už nevím kde bych ušetřil aby se mi tam vešel program. Jsem na 98% obsazení paměti a ze zkušeností vím že pokud se dostanu nad 95% tak arduino již není stabilní.
Dokud se program vejde do Flash, tak ať je velký klidně na 100%. Tam to nevadí. Mnohem horší je, když dochází datová paměť, protože kromě toho, že tam jsou uložené globální proměnné, tak tam je i zásobník, který se používá při volání funkcí na předávání parametrů a ukládání návratové adresy a věcí, co ta funkce potřebuje ale nechce je navenek měnit (na začátku funkce se proto uložej na zásobník a na konci se zase vyzvednou). Takže když je málo datové paměti a přitom se volají funkce hodně hluboko, tak ten zásobník prostě začne přepisovat globální proměnné a začne být veselo. Nevadí volat jednu funkci 1000x postupně za sebou, ale vadilo by volat funkci, která zavolá funkci, která zavolá funkci ... atd. hodně hluboko.

Základní pro šetření datové paměti je používat správně velké typy (např. kde stačí short nepoužívat long int, nepoužívat zbytečně velká pole a pokud možno používat lokální, nikoli globální proměnné (nebo statické lokální), protože pro ně se musí vyhradit místo v datové paměti, zatímco lokální proměnné uvnitř funkce se vytvoří a až funkce skončí, zase zruší.

daton
Příspěvky: 479
Registrován: 16 bře 2013, 16:12

Re: Blikací LED

Příspěvek od daton » 01 pro 2018, 18:50

Dík za rady zkusím tím prohrábnout kód a třeba pošetřím dalších pár cenných bajtíků :-)
Ještě otázka dává mi to tyto hodnoty
Projekt zabírá 30598 bytů (99%) úložného místa pro program. Maximum je 30720 bytů.
Globální proměnné zabírají 738 bytů (36%) dynamické paměti, 1310 bytů zůstává pro lokální proměnné. Maximum je 2048 bytů.
dynamická pamět je jasná
ale to úložné místo je definováno v jaké paměti?

DavidO
Příspěvky: 604
Registrován: 01 kvě 2013, 21:27

Re: Blikací LED

Příspěvek od DavidO » 01 pro 2018, 19:52

Vždyť to tam píšou: "úložného místa pro program"
Tohle je úplně OK. Jestli to je nestabilní, tak chyba je jinde - v programu.

daton
Příspěvky: 479
Registrován: 16 bře 2013, 16:12

Re: Blikací LED

Příspěvek od daton » 01 pro 2018, 20:40

A můžeš mi tedy alespoň naznačit proč je ta velikost v paměti při kompilaci v jednotlivých ide různá?
A promin s tou pamětí mám boje, protože celková pamět jak jsem pochopil od tebe by měla být ta flash pamět, ale zase z jiného webu jsem pochopil, že když máš v programu např. html a chceš zmenšit velikost kodu tak použiješ F tedy Serial.println F("kod html") nó a to F je flash kam se ty příkazy ukládají a je to mimo oblast flash kam se píše program. No a ted z toho mám trochu zmatek kterou flash jsi tedy myslel.

DavidO
Příspěvky: 604
Registrován: 01 kvě 2013, 21:27

Re: Blikací LED

Příspěvek od DavidO » 01 pro 2018, 22:02

Různá velikost paměti použité v "různých IDE" vyplývá z toho, že kromě tvých funkcí setup a loop (a dalších, které si napíšeš), tam je ještě další kód, který zajišťuje řádné fungování systému. No a ten vývojáři Arduina postupně vyvíjejí a opravují. No a že to roste, to je abych tak řekl obvyklé.

Pokud se jedná o AVR (ATmega, tedy malá Arduina jako Uno, Micro a tak), tak tam to vypadá tahle: paměť pro program je Flash, paměť pro data je statická RAM, obvykle označovaná jako SRAM (kterou Arduino poněkud zavádějícím způsobem označuje jako "dynamická paměť", ale na druhou stranu se dá nalézt takový výklad pojmů, že se to dá brát jako správné označení, ale to kdyžtak na Robodoupěti vysvětlím, sem to psát nebudu, nemá to valný smysl). Dále tam je EEPROM, paměťově mapované registry periferií a možnost externí paměti (ta ale na pokud vím žádném Arduinu není). NA ESP to možná je jinak, nevím, jeho architekturu jsem ještě nepotřeboval zkoumat.

Co se týče těch tisků, tak jednak správně se to píše s víc závorkama (Serial.println(F("kod html"));), ale hlavně to s tím F znamená, že ten řetězec se uloží do Flash paměti, odkud se pak, až bude potřeba (při tom volání println), vytáhne, ale nezabírá přitom místo v SRAM. Normálně println očekává data k tisknutí v SRAM, ale tím makrem F() se provede reinterpret cast, což přeloženo do normální řeči znamená, že tomu nasadíš jinej kabát a ve skutečnosti se pak bude volat úplně jiná funkce na tisknutí (ale to tě nemusí příliš zajímat). Jenže ve tvém případě ti dochází programová paměť, ale datové máš dost, což je dobrá situace, tak to neřeš. Programovou paměť klidně využij na 100% a udržuj datovou se slušnou rezervou a bude to v pohodě. Jestli jsi na 36% datové, tak to jsi na tom hodně dobře, co by za to leckdo dal, a pokud neuděláš nějakou kravinu s příliš hlubokým zanořením funkcí, tak to je v cajku (s trochou opatrnosti se sice dá vyžít i se skoro úplně zaplněnou datovou taky, ale to musíš našlapovat fakt jemně). Kdybys nakonec přesto narazil na to, že v programové paměti došlo místo, tak pořád ještě můžeš nějaké řetězce uložit do EEPROM místo do programové Flash. Ale to už je vyšívání z Vyšší dívčí, které bych ti teď neradil zkoumat (j to tak, že někam ten řetězec musíš uložit tak, aby v kontroleru zůstal i když se vypne napájení, a na to jsou jen dvě základní možnosti, programová Flash anebo EEPROM (další možností jsou externí paměť, karty, USB klíčenky apod. ale to sem teď nepatří), takže místo Flash to dáš do EEPROM, ale pak to musíš zase šikovně odtamtud vytahovat).

Ono správné zacházení s řetězci není až tak složité, ale dá se to bohužel snadno zmrvit, protože objektový přístup třídy String není na tomhle malém osmibitovém ořezávátku ideální. Jak ti začne něco docházet ve smyslu je toho málo, tak je potřeba to důkladně pochopit, aby ti to začalo docházet ve smyslu že tomu rozumíš. ;)

Odpovědět

Kdo je online

Uživatelé prohlížející si toto fórum: Žádní registrovaní uživatelé a 1 host