Vlákno názorů k článku Přístupy k programování STM32 od dustin - Když vidím tu smyčku - opravdu má smysl...

  • Článek je starý, nové názory již nelze přidávat.
  • 3. 10. 2017 10:52

    dustin (neregistrovaný)

    Když vidím tu smyčku - opravdu má smysl provozovat takový hardware bez více "běžících" vláken?

  • 3. 10. 2017 11:21

    Petr M (neregistrovaný)

    Na knihovnách od STM evidentně jo. Tam se tenhle způsob používá by defaut, i když si v STMCube vygeneruješ projekt s FreeRTOSem :Q :Q :Q

  • 3. 10. 2017 11:43

    gripennn (neregistrovaný)

    Pokud tím HW myslíte STM32 tak nepochybně ano. Z mého úhlu pohledu jsou STMka užitečná i když na nich neběží žádný sofistikovaný kód a to hlavně kvůli periferiím. Často dělám přesné časování (STM zvládá na jednotky až desítky ns), občas nějaký rychlejší sběr dat (tam člověk ocení DMA a slušnou RAM). Někdy je zase potřeba chrlit z DA převodníku nějaký průběh. A to všechno se schopností rychle reagovat (~1us) na nějaké vnější změny. Žádná rozumnější platforma mě nenapadá (FPGA je na spoustu věcí overkill). A to vše jde většinou ovládat relativně primitivními programy...

  • 3. 10. 2017 11:50

    trumbera (neregistrovaný)

    Ono někdy je použití HAL knihoven je už příliš náročné (jsou nabobtnalé a plné chyb), natož ještě mít něco jako jádro os na vícevláknovou podporu ... to aby jste tam pak dal ještě jeden procesor :-)

    Hodně záleží na aplikaci.

  • 3. 10. 2017 13:26

    dustin (neregistrovaný)

    Přiznám se, že prakticky to teprve budu na STM32F1 v následujících měsících řešit, zatím jsem si s tím jen celkem úspěšně hrál, ale nepředpokládám výkonové problémy s http://www.chibios.org/dokuwiki/doku.php?id=chibios:kb:timing . Bez vláken bych se do svého projektu neodvážil.

    Takováto čísla jsem při testování na Maple mini za $3 taky dostal (např. > 900tis. kontext switchů vláken/s - http://chibios.sourceforge.net/reports/STM32F103-72-GCC.txt , zdroják testu https://github.com/ChibiOS/ChibiOS/blob/master/test/rt/source/test/test_sequence_013.c#L338

  • 3. 10. 2017 15:40

    Ladislav Michl (neregistrovaný)

    Jen tak pro zajímavost, k čemu tam ta vlákna potřebujete? Procesor je jen jeden a všechny vnější události se projeví jako přerušení (i "poll style" může být přerušení časovače). "Hlavní vlákno" pak buď nedělá nic nebo zpracovává v přerušeních a DMA kanály nasbíraná data, případně řeší power management.

  • 3. 10. 2017 16:36

    dustin (neregistrovaný)

    V projektu s dost rozsáhlým GUI (touchscreen TFT) běží řada tasků:
    * příjem a zpracování zpráv z několika sériových portů - na některé se reaguje změnou GUI, některé se jen přeposílají dál na jiné porty
    * čte analogové porty a posílá zprávy na porty
    * čte touchscreen a reaguje na to - sama knihovna UGFX https://ugfx.io/ používá řadu vláken
    * GUI má řadu současně běžících časovaných prvků

    Když třeba vstupním vláknem zpracovávám message obdrženou do mailboxu od IRS sériového portu ze vstupu, mohu být klidně přerušen jiným vláknem, které se právě dočkalo v mailboxu dat od IRS AD konverze, jež vlákno zabalí do zprávy a pošle sériákem někam dál. Obě vlákna mohou mít stejnou prioritu a prohazovat se po uplynutí timeslicu - vykreslování GUI bude pomalejší, ale poběží. Nebo může mít jedno prioritu vyšší (třeba zrovna GUI kvůli plynulosti vykreslení), dle potřeby.

    Každý odchozí port má své vlákno se vstupním mailboxem. Když do něj dostane data, probudí se a zprávu odešle. Vlákna generující zprávy nemusí nic zásadního řešit, prostě zprávu pošlou do mailboxu.

    Zprávy jsou brané z předdefinovaného memory poolu, kam se po odeslání zase vrací. OS má statickou správu paměti, nemá malloc/free. Memory pooly mohou případně vlákno bloknout na nastavený čas, pokud je zrovna prázdný. Vlákno paměť buď stihne dostat, nebo má smůlu a musí se zařídit jinak (třeba zkusit to znovu). Ale to je klasika...

    Nechci vše míchat do jednoho kódu, když mohu pro jednotlivé funkčnosti vytvořit vlákna a miniOS si je sám časuje/přepíná. Vláknům lze nastavovat různé priority, pre-emptivně se přepnou dle potřeby (přijde interrupt od periférie, "vyprší" softwarový timer od scheduleru, přijdou očekávaná data do mailboxu atd.).

    Samozřejmě to vyžaduje správně pracovat se zamykáním, ale to je normální, to samé musím řešit v pythonním prototypu celého řešení (několik STM32 + PC), na kterém zatím dělám.

  • 3. 10. 2017 17:24

    Sten (neregistrovaný)

    Nešlo by to řešit kooperativním multitaskingem založeným na message loop, případně korutinách? To žádná vlákna nepotřebuje. IMO plnohodnotný pre-emptivní multitasking je na takovém procesoru overkill.

  • 3. 10. 2017 17:37

    dustin (neregistrovaný)

    Proč bych si komplikoval práci loopem, když mohu použít OS přímo určený pro STM32, pro jehož výrobce jeho autor pracuje? U takto rozsáhlého programu by mi z loopu hráblo...

  • 3. 10. 2017 20:49

    Ladislav Michl (neregistrovaný)

    Co jste popsal výše vlákna zcela určitě nepotřebuje a nezbývá než popřát, aby Vám nehráblo později, až budete v tom "OS přímo určeném pro STM32" hledat nějaké podivné chování, které se projevuje pouze u zákazníka. STM32F103 a "rozsáhlý program" se tak trochu vylučují, ne? ;-) Pro zajímavost potom napište, zda se zvolené řešení osvědčilo neboť moje zkušenost říká, že u podobných projektů je každá další abstrakce spíše na škodu.

  • 4. 10. 2017 8:20

    dustin (neregistrovaný)

    Každý si ve finále píše nějakou knihovnu funkcí, viz post Petra M výše o vlastním přepínání vláken. Ten "OS" je také jen sada funkcí a maker od člověka, který práci s tímhle MCU rozumí nesrovnatelně víc, než já, a ten OS dělá přes 10 let.

    Potřebuji zpracovávat paralelně běžící tasky se stejnou i různou prioritou (tj. i načasované přehazování) a nemíním to přepínání psát sám, když mohu využít něco hotového odladěného s ochotným supportem (vyzkoušeno). Do 120kB fleš se vejde už docela složitý program.

    Rád se pak podělím o zkušenosti. Zdrojáky budou stejně na githubu.

  • 4. 10. 2017 21:14

    PetrM (neregistrovaný)

    Asi takhle... Mám program s FreeRTOSem na STM32F4, kde běží 15 vláken. Dělá grafiku na TFT 800x480 s touchscreenem, komunikaci po dvou UARTech, měření pěti analogových kanálů, ovládání výstupů, správa několika rozhraní, FLASH disk v externí NOR FLASH a obsluha NFC. Sakum prdum včetně fontů, tabulek zvuků přes PWM generovaných a podobných blblin 130kB FLASH + 40kB RAM.

    A ten komfort, když všechno řeším nezávisle... Třeba audio. Přijde zpráva, vlákno se probere, vezme číslo zvuku, podívá se do tabulky, kde jsou data jako vektory významem (tón, délka), a jeden po druhým pouští do PWM a schrupne si. Mezi tím se děje něco jinýho, ale to nemusím při psaní audia řešit. Co mě zajímá je jenom fronta, do které spadne kód zvuku, jinak je to autonomní program a mimo initSound()m, playSound() a enum s předdefinovanýma zvukama o něm nevím. Je to těžce návykový.

  • 5. 10. 2017 0:29

    dustin (neregistrovaný)

    Přesně takhle to budu psát. Vlákna komunikující frontami, každé si řeší to svoje. Tu část prototypu v pythonu, která bude běžet na MCU, dělám s ohledem na možnosti a principy programování toho MCU. Zatím skoro všechno mám v chibios k dispozici, samozřejmě kromě správy paměti.

  • 5. 10. 2017 9:01

    rrrobo (neregistrovaný)

    A keby ste aj nejake ukazky toho celeho nejake zverejnil, aby sme sa aj my ostatni nieco naucili a nerobili len while(1) { ... zapni led sleep vypni led sleep }?

  • 5. 10. 2017 11:55

    Kiwi (neregistrovaný)

    Je vidět, že o tématu nemáš ani šajna. Nemáš vůbec představu o (ne)náročnosti implementace vláken, dokonce ani nevíš, jak jsou kooperativní a preemptivní multitasking implementované.
    Mimochodem, ty procesory jsou výkonnější než PDP-9, na němž vznikal Unix.

  • 5. 10. 2017 19:59

    Karel (neregistrovaný)

    "Vícevláknový systém" je široký pojem zahrnující celou řadu nástrojů s velmi různou náročností na implementaci. Podle subjektivních požadavků na tu množinu se dá trvrdit cokoliv od "to je triviální" přes "to bude hodně zatěžovat" až po "to ten procesor neumí".

    Pár z těch nástrojů:

    - context switch - snadné - vyvolá se přerušení, uloží se registry jednoho procesu, načtou se registry jiného procesu a obnoví se běh
    - ochrana paměťového prostoru - nemožné bez MMU - aby jeden proces nemohl číst a přepisovat paměť jiného procesu
    - kooperativní multitasking - triviální - svůj kód prošpikujete příkazem "yield" (SWI), což je volání systémové funkce, kterým říkáte "jestli chce běžet někdo jiný, tak teď je vhodná doba"
    - preemptivní multitasking - může být snadné i těžké - pokud obsluha periferií běží v přerušení, pak je tohle stejně snadné jak kooperativní multitasking. Pokud obsluha běží ve vlákně, tak může být problém, když procesor vlákno přeruší ve špatnou chvíli. To se pak řeší pomocí zákazů přerušení apod., což jsme ale krok zpět ke kooperativnímu multitaskingu
    - virtuální paměť - bez MMU velmi těžké - umět část paměti vlákna odsypat někam jinam a uvolnit tak paměť pro jiné vlákno
    - dynamická alokace paměti - snadné, ale - to ale je v ochraně paměťového prostoru - bez MMU je to celé o dobrovolnosti a bezchybnosti
    - překrývání paměti - bez MMU velmi těžké - to aby vlákna mohla běžet ve stejné oblasti. Paměť se musí při context switchi celá překopírovat. S MMU triviální. Pokud píšu programy relokovatelné, tak to řešit nemusím. Stejně tak když každé vlákno bude jinde
    - mezivláknová komunikace - snadné až těžké - podle toho, zda to chci jet v režii vláken (dohodnou se na semaforech) nebo v režii OS
    - dynamické zavádění programů - netriviální plus problémy uvedené výše - natáhnout kus kódu do paměti a ten spustit jako další vlákno

    Takže pokud je požadavek: mám jen jeden program, který obsahuje více vláken, která se mezi sebou dobrovolně přepínají (kooperativní multitasking), přístup ke sdíleným datům si řeší semafory ve vlastní režii a nepotřebují vyšší funkce OS jako je dynamická alokace paměti, pak se to dá napsat snadno i na STM32 a na výkonu se to nepozná. Pokud ale někdo čeká, že bude mít na SD kartě kusy kódu, které bude zavádět do paměti a spouštět, tak to už ho čeká pořádný kus práce. STM32 má Supervisor mód, takže toho dost udělat půjde. Ale nemá MMU. Na to už by byl potřeba Cortex-A.

  • 5. 10. 2017 20:10

    dustin (neregistrovaný)

    Nejsem žádný specialista, ale preemptivní přepínání v tom OS řeší přerušení od hw časovače, v němž běží kód scheduleru. Má to time slicy, příp. umí tickless režim - ví, kdy se má co probudit, tak si časovač nastaví na daný čas. Takže není potřeba, aby se vlákno vzdalo kooperativně, scheduler si je sám přehodí, pokud zrovna neběží v nějaké kritické sekci.

    MMU to samozřejmě nemá, tudíž nemůže alokovat paměť, je to celé dělané staticky.

  • 6. 10. 2017 0:30

    Kiwi (neregistrovaný)

    ad ochrana - Cortexy M0+ a M3 a výš sice nemají MMU, ale mají MPU. Je ovšem otázka, je-li to k něčemu v praxi dobré. Nejsme na desktopech, aby si BFU instalovali a spouštěli neznámé programy, obvykle přesně víme, co v daném zařízení máme a proč a selže-li daný kus softwaru, znamená to znefunkčnění celého zařízení (jinak by tam daný kus kódu vůbec nemusel být). Nějaký význam bych viděl především při ladění, ovšem psát MPU-aware kód jen kvůli tomu, aby mi při ladění vyskočil MemFault, mi připadá jako zbytečné. V embedded světě je důležité dodržovat určitou sebekázeň a zásady, které by na desktopech mohly snad působit poněkud rigidně a zbytečně, ovšem tady nepředstavují zásadní problémy, ale zásadním způsobem právě tyto možné chybové stavy omezují. Mimochodem velké procento potenciálních MemFaultů zachytí i HardFault, který je na všech Cortexech, protože chybná hodnota pointer mnohdy namíří do neobsazené paměti nebo přepíše data v zásobníku, což následně HardFault způsobí. Chce to určitý cvik, zkušenosti a intuici při ladění. Začátečníci z toho mohou mít noční můry, ale po nějakém čase už člověk pozná, odkud vždycky vítr vane.

    ad kooperativní - na první pohled triviální, na druhý méně. Yield je jen vyvolání plánovače, které v preemptivním případě neřeším, zatímco v kooperativním to řešit musím já místo nějaké obsluhy časovače. I v kooperativním multitaskingu musím mít plánovač, který naplánuje další proces k běhu, i zde musím mít rutinu pro přepnutí kontextu. Z hlediska jádra OS je rozdíl minimální, z hlediska aplikačního vývojáře je kooperativní multitasking náročnější. Sice se zjednoduší kritické sekce, ale za to se zkomplikuje vše ostatní, protože aby to celé fungovalo, musí kooperovat každý z procesů, nejen ty, co vzájemně sdílejí nějaké prostředky, jako tomu je u preemptivního multitaskingu. Kooperativní multitasking má IMHO smysl jedině na platformách, kde chybí systém přerušení nebo časovač, což jsou v dnešní době prakticky jen případy různých zákaznických chipů obsahujících procesorová jádra v nějaké minimalističtější variantě.

    ad virtuální paměť - na mikrořadičích mi tato věc připadá poněkud perverzní, nicméně i to je řešitelné čistě programovými prostředky pomocí různých memory poolů apod., pokud by bez toho někdo nemohl žít

    ad dynamická alokace - na MCU je vše otázkou dobrovolnosti a bezchybnost. Je to opravdu úplně jiný svět oproti desktopům a přenášení některých (zlo)zvyků z desktopového prostředí na mikrořadiče je cesta do pekel. Na MCU se obvykle toto nedělá a profi standardy dynamickou alokaci paměti mnohdy explicitně zakazují, což přispívá k předvídatelnosti a testovatelnosti daného řešení. Nicméně i malloc() a free() jsou otázkou dobrovolnosti a bezchybnosti, pokud chceme, aby náš desktopový program fungoval. U mikrořadičů je třeba vnímat to celé en bloc jako jeden program - mikrořadičem něco řídím a nefunguje-li nějaká část jeho programového vybavení, tak to zařízení zkrátka nebude fungovat správně - a tedy stručněji: nebude fungovat. Správně navržený a fungující program ale zase nepotřebuje ochrany paměti.

    ad překrývání - ve skutečnosti to je ještě těžší. Nestačí překopírovat, je třeba mít i nějaký GOT. Dost záleží na překladači, zda-li vůbec umí generovat takový kód. Obecně platí co v minulých bodech - jsme na MCU, ne na desktopech, ergo má-li někdo nutkání toto potřebovat, asi přemýšlí nějak špatně. Jinak sdílení kódu více vlákny je samozřejmě triviální - stačí nemít žádná statická data.

    ad komunikace - ta je snadná tak i tak

    ad dynamické zavádění - viz předchozí body, nicméně někdy se i v tomto světě dynamické zavádění vyžaduje, pak záleží spíš na bezpečnostních standardech, bude-li to náročné nebo jednoduché (viz např. instalace appletů na smart karty dle ETSI).

    MMU v embedded světě je poměrně zbytečná věc; lidé z desktopů všeobecně mají velmi zkreslené představy o tom, jak se dá co řešit, protože na desktopech se dané věci musejí řešit velmi univerzálně a tedy komplikovaně, aby byly dostatečně robustní, a pro aplikační vývojáře jsou to obvykle jen jakési černé skříňky, nebo spíše obrovské černé skříně. Proto si pod stejnými pojmy představí tyto černé skříně i v embedded světě, nechápou, jak je možné navrhnout řešení, které dostane certifikát bezpečnosti, který by Linux nikdy nedostal, přestože daná platforma nemá žádnou MMU apod. Je to dané vysokou specifičností a úzkým záběrem, který dané řešení musí obsáhnout. Díky tomu v embedded řešení spousta kódu vůbec nemusí být přítomna a kód, který neexistuje, nemůže být zabuggovaný, ani ho nelze exploitovat. V tom to celé vězí. V jednoduchosti je krása.

  • 6. 10. 2017 13:37

    Sten (neregistrovaný)

    I v kooperativním multitaskingu musím mít plánovač, který naplánuje další proces k běhu, i zde musím mít rutinu pro přepnutí kontextu. Z hlediska jádra OS je rozdíl minimální, z hlediska aplikačního vývojáře je kooperativní multitasking náročnější.

    Ten plánovač v kooperativním multitaskingu je mnohem jednodušší, protože nemusí řešit čekání na podmíněné proměnné či čekající mutexy.

    Sice se zjednoduší kritické sekce, ale za to se zkomplikuje vše ostatní, protože aby to celé fungovalo, musí kooperovat každý z procesů, nejen ty, co vzájemně sdílejí nějaké prostředky, jako tomu je u preemptivního multitaskingu.

    Jenže správné zamykání je hodně komplikované a náchylné na vážné a velmi obtížně laditelné chyby (třeba různé Heisenbugy), zatímco kooperaci lze velmi snadno vyřešit třeba smyčkou zpráv, a protože je synchronní, nemění debugging její chování. Navíc bez MMU stejně musí procesy kooperovat a, jak sám uvádíte, „mikrořadičem něco řídím a nefunguje-li nějaká část jeho programového vybavení, tak to zařízení zkrátka nebude fungovat správně“.

    Kooperativní multitasking má IMHO smysl jedině na platformách, kde chybí systém přerušení nebo časovač, což jsou v dnešní době prakticky jen případy různých zákaznických chipů obsahujících procesorová jádra v nějaké minimalističtější variantě.

    Přerušení a časovač má i Arduino, přesto se tam preemptivní multitasking nepoužívá.

  • 6. 10. 2017 14:18

    Kiwi (neregistrovaný)

    > Ten plánovač v kooperativním multitaskingu je mnohem jednodušší, protože nemusí řešit čekání na podmíněné proměnné či čekající mutexy.

    Plánovač se zabývá jen procesy připravenými k běhu a procesem běžícím. Ostatní pro něj neexistují, takže ve skutečnosti vůbec žádný rozdíl mezi kooperativním a preemptivním plánovačem není.

    > Jenže správné zamykání je hodně komplikované a náchylné na vážné a velmi obtížně laditelné chyby

    Je-li třeba v prostředí kooperativního multitaskingu dodržovat nějaké časování, např. kvůli síťové komunikaci nebo periodickému měření, stejně se kritickým sekcím obvykle nelze vyhnout - yield se prostě zavolat musí, nikoli protože už to s klidným srdcem lze, ale protože čas si hlídám sám místo chybějícího přerušení časovače. Dalším případem je reentrance - typicky u různých event handlerů, operujících nad statickými objekty.

    > Přerušení a časovač má i Arduino, přesto se tam preemptivní multitasking nepoužívá.

    Arduino je především amatérský hobbybastl, dětská hračka. Stejně tak odrážedlo pro děti taky nemá převodovku.

  • 6. 10. 2017 8:07

    Petr M (neregistrovaný)

    No kdysi jsem si zkoušel napsat OS core na MSP430. Vyšlo to na cca 350B FLASH + 20+(počet vláken * 4)B RAM + kousek zásobníku + 1x timer. Fakt primtivní. I bez MPU a podobných kravin.

    Překrývání paměti nepotřebuješ. Co chceš překrývat ve třeba 64kB RAM? Dynalická alokace se taky omezuje na nějaký pool (třeba widgety a texty na displeji, který s novou obrazovkou mažeš). A na vytvoření dál statických struktur jako thread, fronta, mutex,... při initu a ani to není povinný.

    Předávání dat je taky easy. Pokud je to jednosměrně z jednoho vlákna, tak stačí volatile proměnná. Když se má vlákno probudit a nebloudit ve smyčce, tak event (jako signalizace pro scheduler, že má zařadit vlákno na seznam) nebo semafor nebo mailbox. Když má vlákno nižší prioritu a může tam být těch dat víc, tak fronta.

    Pokud něco sdílím (třeba společnou EEPROM s konfigurací několika modulů a současný přístup by vedl k problémům, zamknu si to mutexem<a>.

    No a pokud mám vláknu říct, že má pokračovat (třeba se čeká na přerušení od DMA), zařídí to semafor.

    No a víc vlastně většinou ani člověk nevyužije...