Arduino

ruzne programy,konverze dat, digitalizace, atd...
Uživatelský avatar
Radhard
Příspěvky: 287
Registrován: 1. 7. 2020, 10:19
Bydliště: Praha
Kontaktovat uživatele:

7. 10. 2020, 1:55

Mr. MR píše: 7. 10. 2020, 7:18 Jde mi o to, že chci pomocí arduina dělat více věcí současně a pochopil jsem, že správný přístup je OOP. Právě že mi taky přijde, že je OOP na to co potřebuju overkill. Chtěl bych měřit teplotu, tu vyhodnotit pomocí PID a topit, současně hlídat tlačítka a několik čidel. Všechno pak ještě v nějakém časovém plánu.

Řeknu příklad:
přístroj je zapnutý,
tlačítkem odstartuju program,
zapne se topení s PID,
až dojde k ustálení teploty zapnu další věci, většinou s různým spožděním,
pomocí čidel sleduju probíhající proces
pokud nastane chyba proces ukončím
pokud stisknu tlačítko, proces ukončím
proces po ukončení stále potřebuje dochlazení, takže je stále co hlídat
o všech procesech informace na LCD

Nechci se utopit v kilometrovém kódu, proto bych chtěl oddělit jednotlivé úkoly a proměnné.
Chtěl bych mít PID extra, časování jednotlivých procesů extra, informace na LCD extra, a ještě by bylo fajn mít možnost si pro odladění vypisovat data na seriový port, abych věděl, jak se co chová. Zkusil jsem, že hodně jde udělat s funkcemi, nicméně z funkce nedostanu více, než jednu proměnnou (mám na to použít struktury?).

Hodně také pomáhají záložky, kdy si kód rozdělím - každou funkci na jednu záložku. Navíc se mi líbí myšlenka, že si v jednom programu "vymazlím" třeba to PID, a pak to budu používat v různých projektech a nebudu do toho už muset rýt, prostě jen zadám, odkud bere informace o teplotě, kterým pinem spíná topení a odladím si konstanty. Děsím se, že bych musel neustále kvůli drobnostem procházet celý kód. Navíc jedntlivé funkce budu přidávat postupně a chtěl bych je mít odladěné, optimálně kažkou zvlášť.

Vymýšlím blbost? Jak se to dělá správně?
První věc co bych udělal já, tak bych se na celé arduino vy... a psal to normálně v C.

Pokud seš šikovnej programátor, hodně věcí lze pořešit v jednom hlavním vlákně nebo přerušení pomocí stavových automatů. Tento princip používá například Quantum Leap Rtos.
Někdy je ale těžké napsat algoritmus tak aby nezdržoval a ještě byl solidně čitelný (programátorsky) a tak je lepší to spouštět v samostatných vláknech.

I taková ATMEGA zvládne v pohodě multitasking.
Uživatelský avatar
Radhard
Příspěvky: 287
Registrován: 1. 7. 2020, 10:19
Bydliště: Praha
Kontaktovat uživatele:

7. 10. 2020, 1:57

Modularita je základ.
atlan
Příspěvky: 3343
Registrován: 7. 2. 2011, 9:12

7. 10. 2020, 6:54

A? Vola sa to podprogramy alebo kniznice.
V maine potom budes mat 40riadkov kodu. N8c neriesitelne vCku.
Mex
Příspěvky: 10288
Registrován: 6. 2. 2014, 10:29

7. 10. 2020, 7:52

Objektové programování v C++ ti nepřinese nic navíc, co by se nedalo realizovat v neobjektovém prostředí. Takže neplatí, že "chci dělat více věcí současně" - použiju objekty. To spolu nijak nesouvisí.

Příklad z praxe: celý Linux je napsaný v neobjektovém C. Jeho zakladatel Linus Torvalds se kdysi vyjádřil, že objektové C++ nemělo vůbec vzniknout. Linux je dneska nasazen na všech 500 nejvýkonnějších počítačích světa.

Základem je správně si rozvrhnout úlohu na jednotlivé bloky. Jestli je pak budeš realizovat ve formě objektů a volat jeho metody, nebo je uděláš jako modul v C a budeš volat jeho funkce, případně to ještě i předem přeložíš a budeš to používat jako knihovnu, kterou k programu nalinkuješ - všemi postupy můžeš dosáhnout stejného výsledku.

Kdybych to dělal já, tak bych použil nejjednodušší řešení, že bych jednotlivé nezávislé části vždy realizoval jako samostatné soubory (např. pid.c), a pak v hlavním programu bych na začátku uvedl, které moduly se mají do daného programu natáhnout direktivou #include "pid.c".
Program to bude malý, takže rychlost překladu nijak zásadně neovlivní, že to budeš překládat pořád dookola. A nemusíš z toho tedy dělat knihovny, které by se pak už nepřekládaly ale jen linkovaly.
Uživatelský avatar
Mr. MR
Příspěvky: 756
Registrován: 31. 5. 2020, 10:05

7. 10. 2020, 7:57

Nejsem ani programátor ani šikovný :shock: takže byh rád zůstal u wiringu, alespoň prozatím. Pokud to dobře chápu, tak například tady https://learn.adafruit.com/sous-vide-po ... -enchilada je použito pár knihoven a zbytek programu je strukturovaný do funkcí, které jsou poté volány pomocí switch case. Je toto optimální uspořádání? Dá se to udělat přehlednější? Projekt, který bych chtěl vytvořit má podobně složité zadání jak ten hrnec, jen nepotřebuju tak obsáhlý uživatelský interface ale zas budu sledovat vce veličin a ovládat více výstupů.
Připrav se, hrajem...
https://www.youtube.com/watch?v=HzjNAnEfvxc
Uživatelský avatar
Radhard
Příspěvky: 287
Registrován: 1. 7. 2020, 10:19
Bydliště: Praha
Kontaktovat uživatele:

7. 10. 2020, 8:18

(Switch - Case) model může klidně být. Prostě záleží na tom jaké odezvy od programu chceš mít.

Klasická začátečnická chyba je, že procesor věčně hnije v nějakým wait() , ale nestíhá nic udělat, protože je blbě navržená architektura programu.

Využívej přerušení ( tlačítka, user intreface, měření, regulace, timeouty).

Využívej SWITCH-CASE (stavové automaty).

Využívej fronty. (data ke zpracování / odeslání).

Nepiš takovej "špageti" kód jako to na co jsi dával odkaz :-/ (zamotáš se v tom).
prcek
Příspěvky: 692
Registrován: 31. 10. 2016, 2:26

7. 10. 2020, 8:34

OOP tvuj problem neresi, mozna nejaka knihovna ma jen predpripravenou hlavni smycku programu a funkce jako obsluhy "udalosti".
Mr. MR píše: 7. 10. 2020, 7:57 Pokud to dobře chápu, tak například tady https://learn.adafruit.com/sous-vide-po ... -enchilada je použito pár knihoven a zbytek programu je strukturovaný do funkcí, které jsou poté volány pomocí switch case. Je toto optimální uspořádání?
Bezna praxe v C v pripade stavoveho automatu - ten switch case jen ve smycce rozhoduje podle opState, kterou z tech funkci zavola,
je to prehlednejsi, nez
if (opState == stavA) {
funkceA();
} else if (opState == stavB) {
funkceB();
}
...
Mr. MR píše: 7. 10. 2020, 7:57 Dá se to udělat přehlednější? Projekt, který bych chtěl vytvořit má podobně složité zadání jak ten hrnec, jen nepotřebuju tak obsáhlý uživatelský interface ale zas budu sledovat vce veličin a ovládat více výstupů.
Asi to moc prehledneji zapsat nejde (chce to praxi v programovani, pak uz ti to neprijde divne).
Takove ty obecne poucky o programovani a algoritmizaci znas? Jako rozdelit si problem na dilci mensi (ejhle funkce), ty potom jeste na mensi (ano funkce mohou volat jine funkce) atp. Kdyz si funkce a promenne pojmenujes dostatecne rozumne a budes pouzivat komentare, tak se v tom vyznas. Zacinat od jednoducheho ke slozitemu.
Navic si myslim, ze je rozumne to napsat predevsim citelne (rozumej logicka struktura programu a vyhybani se neprehlednym zapisum) a optimalizovat rychlost az tam, kde to bude nutne - pises neco o regulaci topeni - to vetsinou neni neco, kde bezi o ms.
Jinak mi tvuj pristup prijde spravny
- modularni reseni
- ladit kazdou cast oddelene (pozor aby se ti pak nepobily o nejake funkcni registry atp.)

- v C mohou funkce dostat jako parametr ukazatel na strukturu, nebo ho vratit
- potrebujes strukturu, nebo staci pole?
- Kdysi jsem cetl, ze nejaky programator (mozna spis analytik) zacina kazdy program vymyslenim datovych struktur a vztahy mezi nimi. Funkce potom jen manipuluji s daty v techto strukturach. Vetsinou nepisu nic sloziteho, ale obcas mi tento pristup dava dost dobrou sanci si rozmyslet, jake informace kde udrzuji, jestli je potrebuju globalni, nebo mi staci lokalni, jestli se mi nahodou vic nehodi zretezeny seznam misto pole, jestli nekde stejna data zbytecne neprochazim porad dokola atp.
- urcite existuje nejake pravidlo, ze funkce delsi nez X radku by se mela rozdelit, ono to potom usnadnuje ladeni a prochazeni programu, kdyz prochazis tu hlavni smycku a vis, ze je to neco jako
tlacitka = zjisti_stav_tlacitek()
if (tlacitka && TLACITKO_STOP) {
zatahni_za_zachranou_brzdu();
}
...
a nemusis se brodit kodem ke zjistovani stavu tlacitek a nemusis hadat, co je ta magicka konstanta 0x0020
tak se debuguje mnohem lepe
(uff koncim se slohovkou, kdo to ma cist)
--
Všechno je snadné, než to zkusíš sám.
Uživatelský avatar
Mr. MR
Příspěvky: 756
Registrován: 31. 5. 2020, 10:05

8. 10. 2020, 7:30

Ahoj, díky za odpovědi. Na pokusy s oop se vykašlu a použiju funkce a switch case. Zjistil jsem, že hodně proměnných nemusí být globálních, schovají se tak do funkce a tím na ně můžu zapomenout. Tady je třeba jedna funkce pro výpočet PID, blbé je, že z ní dostanu jen jednu hodnotu, hotovou sumu PID. Normálně by mi to stačilo, ale já potřebuju pro odladění průběžně přes seroivý port číst i hodnoty P, I a D, takže to zkrátka rozsekám na menší funkce. Takře např: budu mít funkci vypočti_P, tuto funkci pak budu volat ve funkcích zobraz_hodnoty_PID_na_serial a suma_P_I_D. Chápu to dobře?

Ještě jednou: všechny úkoly musím atomizovat na takovou úroveň, abych neměl potřebu z funkce získávat více než jednu hodnotu. Je to tak?

Kód: Vybrat vše


long calculate_pid (double read_temperature) {


  //Next we calculate the error between the setpoint and the real value
  PID_error = set_temperature - read_temperature;

  //Calculate the P value
  PID_p = kp * PID_error;

  //Calculate the I value in a range on +-3
  if (-2 < PID_error < 2)  {
    PID_i = PID_i + (ki * PID_error);
  }

  //bezpecnostni opatreni proti nekonecnemu nacitani chyby
  if (PID_i >= PID_interval) {
    PID_i = PID_interval;
  }

  // For derivative we need real time to calculate speed change rate - ale v mém případě je perioda pevně daná, proto to počítat nemusím, změna se děje vždy po dvou sekundách
  // Now we can calculate the D value
  PID_d = kd * (PID_error - previous_error);

  // filteredReading = .75 * filteredReading + .25 * sample;
  filteredPID_d = 0.75 * filteredPID_d + 0.25 * PID_d;

  //Final total PID value is the sum of P + I + D
  PID_value = PID_p + PID_i + filteredPID_d;

  // bezpecnostni opatreni
  if (PID_value < 0)
  {
    PID_value = 0;
  }
  if (PID_value > PID_interval)
  {
    PID_value = PID_interval;
  }

  // toto je pojistka proti sumu derivacni slozky
  if (read_temperature >= (set_temperature + 10))PID_value = 0;

  //Remember to store the previous error for next loop.
  previous_error = PID_error;

  // toto je proti pocatecnimu uletu vyhlazovane hodnoty PID_d
  if (whi < 1) {
    filteredPID_d = 0;
  }

  whi++;


  return PID_value;
}

Připrav se, hrajem...
https://www.youtube.com/watch?v=HzjNAnEfvxc
prcek
Příspěvky: 692
Registrován: 31. 10. 2016, 2:26

8. 10. 2020, 8:42

Mr. MR píše: 8. 10. 2020, 7:30 Ahoj, díky za odpovědi. Na pokusy s oop se vykašlu a použiju funkce a switch case.
Tohle neni vhodne na vsechno, ale na stavove automaty ano.
Mr. MR píše: 8. 10. 2020, 7:30 Zjistil jsem, že hodně proměnných nemusí být globálních, schovají se tak do funkce a tím na ně můžu zapomenout.
Obecne doporucovana praxe je minimum globalnich promennych, takze fajn.
Mr. MR píše: 8. 10. 2020, 7:30 Tady je třeba jedna funkce pro výpočet PID, blbé je, že z ní dostanu jen jednu hodnotu, hotovou sumu PID. Normálně by mi to stačilo, ale já potřebuju pro odladění průběžně přes seroivý port číst i hodnoty P, I a D, takže to zkrátka rozsekám na menší funkce. Takře např: budu mít funkci vypočti_P, tuto funkci pak budu volat ve funkcích zobraz_hodnoty_PID_na_serial a suma_P_I_D. Chápu to dobře?

Ještě jednou: všechny úkoly musím atomizovat na takovou úroveň, abych neměl potřebu z funkce získávat více než jednu hodnotu. Je to tak?
V rozporu s mou predchozi odpovedi bud tak, nebo pouzit globalni promenne, nebo predavat/vracet ukazatel na strukturu.
Ja bych pro ladeni pouzil asi ty globalni promenne (nikdy jsem tu nebyl a ta castka taky nesedi)

stejne je to smycka

Kód: Vybrat vše

while true {
    nacti();
    if debug {
        posli_na_seriak();
    }
    spocitej();
    if debug {
        posli_na_seriak();
    }
    nastav();
}
--
Všechno je snadné, než to zkusíš sám.
prcek
Příspěvky: 692
Registrován: 31. 10. 2016, 2:26

8. 10. 2020, 8:58

Jeste poznamka k tem globalnim promennym, ony nejsou spatne a na nektere veci jsou potreba, jen neni dobre je pouzivat na vsechno.
Priklad uziti je, kdyz neco v hlavni smycce a potrebuje reagovat na asynchronni udalosti, ale nemusi to byt uplne hned (treba ovladaci klavesnice, asi mimo tlacitka stop). To si pak v obsluze preruseni, jen nekam nastavis hodnotu stisknuteho tlacitka, a jede se dal. V hlavni smycce potom nectes primo tlacitka, ale tuto ulozenou hodnotu. Takze globalni promenna slouzi jako vyrovnavaci pamet mezi obsluhou preruseni a hlavni smyckou.
--
Všechno je snadné, než to zkusíš sám.
Mex
Příspěvky: 10288
Registrován: 6. 2. 2014, 10:29

8. 10. 2020, 5:23

Vracet více hodnot z nějaké funkce jde více způsoby.
Jak už psal prcek, tak můžeš výsledek vracet v nějaké globálce. Nebo vrátit ukazatel na nějakou strukturu s výsledky. Případně můžeš volat tu funkci s parametry, kde některé budou vstupní a některé výstupní (ty výstupní pak při volání předáváš ukazatelem).

Teoretické poučky říkají, že globálky se mají nepoužívat, nebo používat co nejmíň. Stejně jako třeba příkaz "goto".
Jenže obě pravidla často mají v praxi mnohem více negativ než pozitiv. Zbytečně natahují a znepřehledňují program (i když by teoreticky měly fungovat opačně).

Takhle pravidla stanovují teoretici programování, kteří berou do úvahy i to, že se na daném programu bude podílet 826 programátorů ze 17 zemí. Tak aby se nějaký programátor v Číně netrefil do globálky progamátora z Dánska, tak je lepší je raději nepoužívat.
Jenže to je teorie, která platí v teoretických podmínkách (mám nekonečně rychlý procesor, nekonečně mnoho paměti, nekonečný čas na programování atd.).

Je to něco podobného, jako školní teoretické příklady výpočtů ne fyzice, kde v zadání vždy bylo "tření zanedbáme". Ale všichni víme, že v praxi to tak nefunguje.

Ještě poznámka: pokud předáváš globálkou nějaká data z přerušení do hlavního programu, tak je třeba zajistit, aby se vždy ta druhá strana dozvěděla, že došlo ke změně hodnoty a nepoužívala nějakou nakešovanou starou hodnotu (tj. aby s tím počítal kompilátor, který překládá ten program).
Je na to klíčové sloho "volatile", které je třeba použít u deklarace té proměnné.
Např:
volatile int pocet_stisku_tlacitka.
lubbez
Příspěvky: 3148
Registrován: 21. 6. 2012, 9:26
Bydliště: Praha

8. 10. 2020, 6:07

Když tady radí odborníci, tak bych měl bejt asi zticha, ale nedá mi to. Základem všeho je nějakej funkční (vývojovej) diagram. Sice většinou nepřežije první kompilaci, ale alespoň se má človek od čeho odrazit. Docela důležité je vymyslet obvodové řešení a uvědomit si omezení některých vývodů procesoru. Já používám většinou procesor AVR324 nebo 124. Na většinu i poměrně složitých aplikací bohatě stačí. Problém je projekt, kdy v reálném čase musí probíhat dva složitější synchronizované procesy. Pak holt použiješ ty arduina dvě. Až na drobné rutinky s počítadly, kde používám lokální proměnné, jedu většinou na globálních. Mám je pěkně na začátku programu a mám naprostý přehled co je použité a jak se to jmenuje. Texty, když je jich víc, dávám do eeprom. Řídím se při kompilaci volnou pamětí, pokud hodně ubejvá, přejdu důsledněji na lokální. 324 má jen dva vstupy HW přerušení a ty si šetřím na důležité věci. Samozřejmě Mega s podovné procesory mají víc vývodů, většina vývodů může být jako interrupt, ale stále jedou jenom na 16MHz. Doporučuju si časověji náročné rutiny otestovat třeba osciloskopem a hned uvidíš, jak se realita liší od teoretického návrhu. Na druhou stranu, se zbytečně přeceňuje nastavování vývodů přímo přes registry a wiringem.
Uživatelský avatar
Mr. MR
Příspěvky: 756
Registrován: 31. 5. 2020, 10:05

8. 10. 2020, 9:01

Děkuju všem za rady, vidím, že toho budu muset ještě dost dostudovat. Ten program nakonec nebude snad tak složitý, ale na realizaci nespěchám a chci to využít, abych se něco naučil. Až s tím pokročím, tak sem nějakou verzi pověsím, abyste mi to zkritizovali :lol:
Připrav se, hrajem...
https://www.youtube.com/watch?v=HzjNAnEfvxc
Uživatelský avatar
Cjuz
Příspěvky: 2422
Registrován: 17. 2. 2013, 6:27
Bydliště: Předklášteří
Kontaktovat uživatele:

8. 10. 2020, 9:43

Ohledně toho abys spouštěl to co potřebuješ a kdy potřebuješ používá se "vnitřní časování" millis.
Více méně ve smyčce loop se je točí sekce bez zpoždění a hlídají si kdy se mají provést.
Jakmile dosáhnou svojí periody tak udělají co je třeba, nastaví si další čas a vše pokračuje.
Tímto můžeš co nejrychleji skenovat vstupy, 1x za 50ms zapsat výstupy, 1x250ms obnovit displej, mezitím blikat LED s periodou 0,5s apod.
A přitom nikde není delay.
Na konci poznávacího procesu je omyl zcela vyvrácen a my nevíme nic. Zato to víme správně.
Uživatelský avatar
Mr. MR
Příspěvky: 756
Registrován: 31. 5. 2020, 10:05

8. 10. 2020, 10:48

Delay už nepoužívám, z toho jsem naštěstí brzo vyrostl.
Funkční diagram jsem zkoušel vytvořit, celkem to pomáhá.
Přikládám ukázku, jak jsem pochopil práci s funkcemi. Ještě jsem to netestoval, takže tam asi budou chyby.

Kód: Vybrat vše

float calc_error(float set_temperature) {
  return set_temperature - read_thermocouple();
}



float PID_p (float _kp) {
  return _kp * calc_error();
}



float PID_i(float _ki) {
  float _ki;

  if (-2 < calc_error() < 2)  {
    _ki = _ki + (_ki * PID_error);
  }

  if (_ki >= PID_interval) {
    _ki = PID_interval;
  }

  return _ki;
}



float PID_d (float _kd) {
  static float previous_error = 0;
  return  _kd * (calc_error() - previous_error);
  previous_error = calc_error();
}



float PID_value() {
  static float value = 0;
  value = PID_p(kp) + PID_i(ki) + PID_d(kd);

  if (value < 0) || (value > PID_interval) {
    value = 0;
  }

  return value;
}
pozn. k tomu intervalu, nebudu řídit topení pomocí pwm ale on/off v nějakém (třeba vteřinovém) intervalu


a ještě jedna

Kód: Vybrat vše

void spark() {

  // casovani spark

  const long spark_on = 50;     //zadat
  const long spark_off = 500;   //zadat

  static long spark_time = 0;

  static boolean spark_state = LOW;


  if ((millis() >= spark_time) && (spark_state == LOW)) {
    digitalWrite(spark_pin, HIGH);
    spark_time += spark_on;
    spark_state = HIGH;
  }


  else if ((millis() >= spark_time) && (spark_state == HIGH)) {
    digitalWrite(spark_pin, LOW);
    spark_time += spark_off;
    spark_state = LOW;
  }

}
Připrav se, hrajem...
https://www.youtube.com/watch?v=HzjNAnEfvxc
Odpovědět

Zpět na „Ostatní software“