FreeRTOS ir ARM Cortex-M3

Posted: 2016-02-11 in Darbeliai
Žymos:, , ,

FreeRTOS logotipas | Darau, blėPradėsiu turbūt šiokį tokį dalinimąsi mintimis ir patirtimis apie FreeRTOS. Trumpai tariant tai kažkas panašaus į operacinę sistemą mikrovaldikliams. Galima naudoti net ir AVR procesoriukuose, kurie, palyginus su ARM, yra kiek labiau riboti resursų prasme. Na, bet ir kai kurie dalykai juose vykdomi paprasčiau ir taupiau. Apskritai, jeigu jumi kyla klausimas, kam gi OS reikalinga mikrovaldiklyje, tai atsakymas yra toks: nekūrėte bent kiek didesnio projekto, žongliruojančio davikliais, besikeičiančio informacija ir kitokiais periferiniais smagumais.

Kadangi čia pirmas įrašas, tai daug kodinės gyvasties nesitikėkite, bus daugiau lyrinių ir filosofinių blevyzgų. Bo aš pats tą FreeRTOS tik biškutuką čiupyt pradėjau. Tiesa, ten nieko tokio nėra, kas man, maršrutizatorių programuotojui, būtų kažkaip baisiai nauja ar keista. Visus OS pagrindus apskritai išdėsto atitinkamuose universiteto ar kitų mokslų kursuose, jei tik noro yra.

Taigi kam mažučiukui mikrovaldikliui kažkokia OS? Juk geras programuotojas gali sukurti mikrovaldiklio kodą taip, kad visa esme diriguos pertraukimai, o pagrindinis ciklas bus arba tuščias, arba jame bus daroma kažkas pagal kažkokius semaforus.

Va čia ir prasideda šiokios tokios problemėlės bei programuotojo išradingumo ribų tampymas. Kadangi aš kūriau šilumos valdiklį nuo nulio, tai visas tas problemas perėjau. Tiesa, ten jokios RTOS (realaus laiko operacinės sistemos) nenaudojau. Šiaip be reikalo, nes tikrai būtų viskas gavęsi švariau ir tvarkingiau. Sakykim, tame valdiklyje sukosi keli atskiri procesai:

  • Daviklių nuskaitymas
  • Ekrano atnaujinimas
  • Mygtukų nuskaitymas
  • Relių junginėjimas pagal logiką
  • Reakcija į web sąsajos veiksmus, informacijos vaizdavimas

Visko aš ten sukrauti į pertraukimų kodą negalėjau. Sakykim, Dallas DS18B20 daviklių nuskaitymas yra sąlyginai ilgas darbelis. Jei jis vykdomas pertraukime, tai užkišama visa AVR procesoriuko pertraukimų magistralė — jame gi nėra prioritetų, kur aukštesnio prioriteto pertraukimas gali pertraukti žemesnio prioriteto pertraukimą. Jei nuskaitymas vykdomas pagrindiniame cikle, tai kiti to ciklo „gyventojai“ irgi nieko negali daryti — ekranas neatnaujinamas, web sąsaja nereaguoja ir taip toliau. Turbūt toliau tęsti nebereikia. Be abejo, čia yra kraštutiniai atvejai, valdiklis didžiąją gyvenimo dalį praleidžia tyliai sandėliuky ir niekas jo mygtukų anei Web sąsajos nemaigo, todėl laiko viskam pakanka. Bet, sakykim, jeigu tas valdiklis dar turėtų priiminėt kažkokius duomenis ganėtinai sparčiai ir pateikinėti atsaką, tokie „užstabdymai“ gali sukelti ir problemų.

Na ir va, priartėjome prie to, kad mums visai praverstų keleto užduočių lygiagretinimas. Geriausia būtų priverstinis, o ne kooperacinis, nes su kooperaciniu, patikėkite, pisnios yra nemažai. Tai jokiu būdu nėra blogas dalykas, anaiptol. Bet kartais jis tiesiog nėra toks efektyvus, kaip norėtųsi. Sakykim, aš su Lua gan intensyviai naudoju korutinas (coroutine), tokias gudrias funkcijas, iš kurių galima grįžti daug kartų ir paskui jas vėl „atgaivinti“ ir vykdyti toliau. Nesgi maršrutizatoriaus aplinkoj, kurioj dirbu, gijų palaikymo nėra, o kai kurie demonai turi būti pakankamai reaktyvūs. Deja, su viena gija reaktyvumas yra dalykas ne toks jau paprastas. Tam aš ir uloop su laikmatukais naudoju dispečerio vietoj, ir korutinas didelių užduočių skaldymui (sakykim, santykinai didelių failų parsiuntimui). Taigi yra įmanomas ir hibridinis užduočių valdymo metodas: tiek priverstinis, tiek kooperacinis.

Gerai, apie ką aš čia… taigi FreeRTOS (arba kokia kita iš panašių) suteikia programuotojui galimybę mikrovaldiklyje turėti daugiaužduotinę sistemą (multitasking). Aš apskritai kiekvienam valdiklių programuotojui, kuris kažką daro ne savo naminiam pažaidimui, o rimtam klientui su rimtais reikalavimais, patarčiau iš karto pradėti nuo kažkokios RTOS įsisavinimo. Nes užduočių lygiagretinimo vis tiek prireiks, pertraukimų ir vietinių flagelių neužteks. Arba jei užteks, tai klientas gali bet kada pasakyti: „o žinai, aš čia pagalvojau… davai dar tą ir aną įdėk“. Nu ir tada belieka, pardon my french, užsipisti grabais. Patikėkit programuotoju pačiam jėgų žydėjime — taip būna nuolat. Na, kam aš čia aiškinu, vis tiek šitas įrašas daugiausiai visokiems koderiams, jie ir taip supranta 🙂

Kaip pasikeičia mūsų, programuotojų, gyvenimas, kai valdiklio firmwarę pradedam gaminti su kokia nors RTOS? Ogi labai šauniai. Visų pirma, kiekvieną valdiklio procesą galime kurti atskirai, nepriklausomai nuo kitų, tarsi mažytę programėlę — arba, RTOS terminais, užduotį (task). Kiekviena užduotis turi savo asmeninį amžiną ciklą! Įsivaizduojate, kaip patogu? Lieka tik pasirūpinti, kad tas ciklas nesisuktų nuolat, paplanuoti, kaip dažnai ir kaip ilgam užduotis gali eiti pamiegoti. Nors jei tai bus mygtukų nuskaitymo (tokia va paprasta, be jokių ten pertraukimų) užduotis, tai ji gali suktis ir visą laiką be pauzių.

Tai va taip ir daroma su RTOS. Prisikuriam „programėlių“ su amžinais ciklais, sudedam jas dispečeriui į krūvelę ir jį paleidžiam. Dispečeris tada rūpinasi tų „programėlių“ perjunginėjimu. Kiekviena užduotis gyvuoja pati sau, kaip kompiuterio programa, jos vidiniu požiūriu ji veikia be perstojo. Nors dispečeris visoms užduotims dalina laiko kvantus, kada jos realiai vykdomos. Sakykim, FreeRTOS dispečerio standartinis laiko kvantas yra 1 ms. Sakykim, jei sukursime dešimt užduočių vienodu prioritetu, tai kiekviena iš jų paeiliui gaus po gabaliuką iš tos milisekundės ir bus vykdoma kas dešimtą milisekundę. Dispečeris pasirūpina, kad užduoties būsena, stekas ir kontekstas būtų išsaugoti ir atstatyti kito jai priklausančio kvanto metu. Na, grynų gryniausias multitaskingas. Aišku, jei dispečeris bus priverstinis. Jeigu jis bus kooperacinis, tai užduotys kažką padarę turi užleisti laiką kitai užduočiai. T.y. piktybinė užduotis gali užsigrobti per daug laiko. O ir galvoti reikia daugiau. Tad jei nėra specifinio poreikio, paprasčiau naudoti priverstinį dispečerį. Tiesa, jis yra „brangesnis“ procesoriaus ciklų požiūriu, procesorius gali būti išnaudojamas ne iki galo efektyviai, bet programuotojui tikrai paprasčiau. Tačiau jei yra kažkas kritiško, kažkas tokio, ko negalima skaidyti kvantais, arba tų kvantų reikia tikrai tikrai daugiau, būtina pagalvoti apie kooperacinį arba hibridinį dispečerio režimą.

Na, jeigu taip viskas gražu ir blizgu, tai kokios problemos? Pirma mintis, kuri ateina į galvą, FreeRTOS ir jos branduolys užima tiek flash atmintį, tiek RAM. Na, bet čia nežymiai. FreeRTOS dispečeris ARM procesoriukuose suvalgo vos 236 baitus. Ne karvė, tikrai. Užduotys irgi atitinkamai, tiek kiek pačios paprogramės, plius 64 baitai dėl dispečerio poreikių.

Deja, visgi yra vienas dalykas, kuris su FreeRTOS (ar kita RTOS) ateina ir jo neišvengsi. Reikia planuoti kiekvienos užduoties steko atmintį. Taip taip, kiekvienai užduočiai išskiriamas atskiras stekas, kuriuo ji ir naudojasi. Na, kaip kompiuterio programa, jo. Tik iš pradžių gali pasirodyti baisoka visokie stack overflow ir panašūs baubai. Juos išdebuginti tikrai nelengva, žinau, ką tai reiškia. Na, bet FreeRTOS turi pakankamai gerą krūvelę įvairių debuginimo priemonių, tad galima užmatyti perpildymus, ir artėjimą prie steko ribos. Taip netgi galima ir planuotis: išskirti užduotėlei steko plius-minus, paskui paleisti, pažiūrėti, ką ji naudoja ir steką sumažinti iki tiek, kiek reikia. Iteracinis procesas čia, nieko tokio baisaus 🙂

Kas čia dabar dar. Aha, apsikeitimas informacija tarp užduočių ir dar visokių ten pertraukimų. Viskas paprasta, tokiems dalykams naudojamos eilės (queue). Atėjo žinutė per kokią nors magistralę, ją padėjo į eilę. Atsibudo kokia užduotėlė, o, eilėj yra žinutė, jamam ir dorojam. Apskritai labai naudinga išskirstyti užduotis pagal… na, jų užduotis 🙂 Sakykim, jei paskirsime kokią nors užduotį informacijos vaizdavimui ekrane, tai ji neturi iš niekur jokių duomenų pasiiminėti, kaip tik iš eilės. O visos kitos užduotys turi rūpintis tik eilės pildymu, bet jokiu būdu nelįsti prie paties ekrano. Taigi reikia viską protingai išsluoksniuoti, išskaidyti ir tada RTOS grožis ir gėris pats atsiskleis jusyse.

Šiandienai, kaip pradžiai, „Hello, world“ su FreeRTOS ir STMF103 procesoriuku. Nieko ypatingo nedarysiu, tik užkursiu tris užduotėles, kurių kiekviena šiek tiek skirtingu intervalu žibins mirksiukus. Nieko blatno, viskas paprasta ir banalu, bet reikia pavaizduoti koncepciją.

Ai, kodėl FreeRTOS? Nagi nemokama, kompaktiška, populiari, gerai (nors ir nuobodokai) dokumentuota ir palaiko velniškai daug architektūrų. Regis, šituo atžvilgiu ji tikrai yra pirmoje vietoje. Be to, norint kažką komercializuoti su jos licencija yra paprasta, priešingai, nei su kokios nors ChibiOS. ChibiOS šiaip turi privalumų su visokiais paruoštukais periferijai, bet dėl šito galima baisiai nepergyventi. Na, dar aš su ja esu šiek tiek pažįstamas iš kitų darbelių, tai mielai imu kažką, kas bent truputuką girdėta/matyta. O dėl dispečerių ir kitų dalykų tai visos RTOS panašios, nėra dar kažkokių labai naujų ir labai gudrių stebuklų išrasta.

Pradžiai parsisiunčiam FreeRTOS kodą. Pareis toks nemažas, apie 45 MB zipukas. Didžiąją jo dalį sudaro pavyzdžiai, daug tuzinų jų. Vien dėl tų pavyzdžių šita OS gera, ten tik skaityk, nagrinėk, mokykis ir nerdgazminkis.

Išsipakuojam zipuką. Tuzinai pavyzdžių užima apie 280 MB, OS kodas su viskuo — apie 7 MB, o jau kai išsikrapštom, ko mums reikia, tai ir megabaito kodo nelieka. Taigi einame į katalogą FreeRTOSV<versija>/FreeRTOS/Source ir imam iš ten „include“ katalogą bei visus „c“ failiukus. Paskui lendam į „portable“ ir ieškom ten savo toolchain’ą beigi architektūrą atitinkantį portą. Kadangi aš naudoju GCC po Eclipse pakavotą, tai einu iš karto į GCC katalogą, o ten randu ARM_M3. Iš ten pasiimu port.c ir portmmacro.h beigi padedu atitinkamai prie „c“ ir prie „h“ failiukų.

Vienas niuansas. Aš naudoju Eclipse ir nemėgstu dubliuoti projektuose kodo. Todėl FreeRTOS susikūriau atskirą projektuką, kurį paskui kituose projektuose linkinsiu:

FreeRTOS Eclipse projektas | Darau, blė

Dar vienas dalykas, reikia nusikopijuoti iš kažkur atminties valdymo modulius. Jie guli FreeRTOS/Source/portable/MemMang kataloge, vadinasi heap_1.c iki heap_5.c. Jie skirti įvairiam atminties valdymui. Jamam ir juos susidėliojam į atskirus kodo katalogėlius kiekvieną atskirai. Nes jie kiekvienas įgyvendina bent jau iš dalies tas pačias funkcijas ir tarpusavy pjaunasi — naudoti galima tik vieną kurį nors. Mano FreeRTOS atskiro projekto struktūra va tokia gavosi:

FreeRTOS galutinis projektas Eclipse | Darau, blė

Trumpai apie įvairius heap’us:

  • heap_1 — pats paprasčiausias atminties valdiklis. Juo išskirta atmintis nebegali būti išlaisvinta. Šiaip nėra labai blogas dalykas paprastesniems projektams su nekintamu užduočių skaičiumi. Atmintis užduotims, eilėms, semaforams ir kitam gėriui gali būti išskiriama ir viskas. Jeigu nėra poreikio kurti užduočių (na, atseit, „paleidinėti programų“ OS darbo metu), tai šitas metodas visai liuks. Tačiau jeigu kursite kokį nors mandrą projektą su užduotimis priklausomai nuo konfigūracijos, tai šitas reikalas netiks. Pavyzdukas gali būti temperatūros nuskaitymas. Sakykim, sukonfigūruojate, kad temperatūrą ant kažkokio elektrodo rodys termistorius. Tai tada OS gali paleisti termistoriaus nuskaitymo užduotį. Šast, persigalvojote, išlupote termistorių, prijungėte Dallas’ą, perkonfigūravote, kad tas inputas bus Dallasas. Aha, reikia keisti užduotį. Tai va, heap_1 šitam reikalui jau netiks.
  • heap_2 — gali atlaisvinti atmintį, bet nesugeba sulieti gretimų laisvų blokų, gaunasi fragmentuota atmintis. Jei po atlaisvinimų lieka daug mažų fragmentukų, jų neįmanoma sujungti. Pagal poreikį nauja užduotis su savo nurodytu steko dydžiu bus kišama į tinkamiausią „skylutę“.
  • heap_3 — klasikinis malloc() ir free() pavyzdukas. Dar biškį priklijuota saugiklių, kad ta pati atmintis nebūtų išskiriama/atlaisvinama iš skirtingų užduočių, gijų saugumas, atseit.
  • heap_4 — mandras metodas atminties išlaisvinimui ir defragmentacijai. Gali sujunginėti mažesnius šalia esančius laisvus atminties blokus. Na, be abejo, tas kainuoja, daugiausiai — dispečerio laiko. Čia jau reikalingas daiktas ypač dinamiškoms sistemoms, su visokiais Plug&Play, gyvais perkonfigūravimais ir 99,9% laiko neišjungiamomis sistemomis. Na, jau rimtam darbui su šimtais ar net tūkstančiais dinamiškų užduočių. Tokių, kurios gali pasileisti, kažką padaryti ir susinaikinti. Kažkas jau labai arti tikro kompiuterio.
  • heap_5 — nagi, o čia naujausias ir šviežiausias FreeRTOS heapo valdiklis. Manrods, prieš kokius porą metų jo dar nebuvo. Taigi, panašu į heap_4, bet dar panaglintas, gali heapą išbarstyti per fragmentuotus atminties blokus, net ir nesančius vienas šalia kito. Dar reiklesnis laikui ir atminties resursams, bet jau visai panašu į kompą. Tiksliau, gali tikrai būti naudojamas kokiems tai mikrokumpiuteriukams su galybe periferijos, GUI ir dar gal kitokiom sąsajom.

Jei buvote atidūs, tai includų kataloge turėjot užmatyti tokį stdint.readme failiuką. Jį reikia tiesiog pervadinti į stdint.h ir nesukt galvos per daug.

Nu va. Dabar, jeigu skaitėme (t.y. aš skaičiau) readme ir kitus dokumenčiukus, tai dar reikia pasigaminti savo FreeRTOS konfigūracijos failą, FreeRTOSConfig.h. Jį aš jau rekomenduoju dėti į tą projektą, kuris bus skirtas kažkokiai specifinei užduočiai, o ne prie bendrų FreeRTOS failų. Šis failas yra visa OS konfigūracija, čia ir laiko kvantai nurodomi, ir dispečerio darbo tipas, ir visokie problemų gaudymo hookai, nu ir dar visokia gudri bei reikalinga velniava. Kadangi prie FreeRTOS pridėta melejonai pavyzdukų, tai einam vogt kokio nors apytikriai tinkamo failiuko ir kopijuoti jį į projektą.

Atsidarom dabar savo būsimą OS konfigūraciją jau būsimame projekte (ar kaip ten bevaldytumėte savo darbelius) ir einam nagrinėtis. Šiaip nėra ten didelio kostmoso, bet perskaityti ir apmąstyti reikia. Pradedam, aš čia paeiliui šiek tiek surašysiu.

#define configUSE_PREEMPTION 1

Na, čia aišku. Jeigu vienetukas, tai dispečeris priverstinis, jei 0 — kooperacinis. Kooperacinės užduotys turi naudoti taskYIELD() paprogramę, kad perduotų valdymą dispečeriui. Kitaip užsigrobs viską ir jokio daugiaužduotiškumo nebus. Aš palieku prievartinį dispečerį, vien dėl to smagumo, kad pačiam rūpintis nereikės 🙂

#define configUSE_IDLE_HOOK 0

FreeRTOS dispečeris „pamato“, kada procesorius yra laisvas, t.y. jokiai užduočiai jo nereikia. Jeigu perėjimui į IDLE režimą mes norime kažką padaryti, tai čia reikia uždėti vienetuką ir paskui sukurti hook’ą, atitinkamą paprogramę. Sakykim, perėjimo į IDLE metu galime išjungti kokią nors periferiją ir taupyti energiją. Ypač svarbu visokioms batareikinėms sistemoms.

#define configUSE_TICK_HOOK 0

Čia panašu, bet šitas hook’as iškviečiamas kiekvieno laiko kvanto metu, kai valdymą gauna dispečeris. Na, galbūt kažko tokio reikia… nors aš nelabai įsivaizduoju 🙂

#define configCPU_CLOCK_HZ ( ( unsigned long ) 72000000 )

Čia viskas aišku, nurodomas procesoriuko darbo dažnis hercais. 72 MHz yra mano mylimasis C8T6, taigi taip ir paliekam, juk iš gyvo pavyzdžio konfigūracija 🙂 Tiesa, kadangi turime STM32, tai geriau parašyti taip:

#define configCPU_CLOCK_HZ ( SystemCoreClock )

Tada nebus problemų, jei keisite procesoriaus dažnį kitomis priemonėmis.

#define configTICK_RATE_HZ ( ( TickType_t ) 1000 )

Kaip dažnai (kas kiek hercų) persijunginėti dispečeriui. Standartas — viena milisekundė. Šį dažnį galima pakoreguoti pagal projekto poreikius, bet reikia suvokti, kas daroma. Esmė tokia, kad kas kiekvieną kaptelėjimą suveikia dispečeris ir suvalgo kažkiek procesoriaus laiko. Jei su procesoriaus laiku žiauriai striuka, galbūt kapsėjimą galima padaryti retesnį, dispečeris suvalgys laiko mažiau. Bet tada atitinkamai sulėtės užduočių perjunginėjimas. Na, o pernelyg užturbinus kapsėjimą dispečeris gali pradėti naudoti daugiau laiko, nei pačios užduotys per kvanto likutį. Tai aš palieku milisekundę, tai ganėtinai geras laiko tarpas tiek sistemos reaktyvumui, tiek dispečerio makalavimuisi. Beje, retinti dispečerio kapsėjimą galima su heap_4 ir heap_5, kur sudėtingas atminties valdymas ir galbūt sistemoje labai daug užduočių. Tuo tarpu su statiškuoju heap_1 nėra problemų ir su dideliais kapsėjimo dažniais.

#define configMAX_PRIORITIES ( 5 )

Nagi kiek užduotys turės prioritetų. Standartinis skaičiukas 5 yra per akis, mano galva. Dar ir šituos reikia sugebėti išnaudoti.

#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 120 )

Mažiausias steko dydis kokiai nors užduotėlei. Šito neliečiu, tegu stovi, koks yr. Beje, pastabėlė, jis skaičiuojamas word’ais, nes procesorius 32 bitų. Kitaip sakant, steko dydis nurodomas 480 baitų!

#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 18 * 1024 ) )

O va čia kiek iš viso procesoriuko RAM’o išskirsime OS su visom užduotim. Čia nurodyta 18 kB, o aš galvoju, kad gal pakeisiu į 17. Tegu lieka 3 kB man, nuo OS nepriklausomiems darbeliams, gal ten su pertraukimais kažkam ir pan. Jo jo, steko dydis nurodomas wordais, o va jau heapas nurodomas baitais…

#define configMAX_TASK_NAME_LEN ( 16 )

Va, kiekviena užduotis gali gauti tokį tai „human readable“ vardą. Kad būtų aiškiau, kai debuginiesi. Na, bet šitie 16 baitų prisideda prie kiekvienos užduotėlės steko. Man užteks ir 4 baitų, o jei prikursiu kokiam projekte daug užduočių, tai gal ir ilginsiu iki 8.

#define configUSE_TRACE_FACILITY 1

Nagi klausimėlis, ar naudosim FreeRTOS debuginimo galimybes ir pan. Pradžiai nenaudosiu, tai nustatau į nuliuką. O kai prireiks rimtam darbui, tai ir įsijungsiu.

#define configUSE_16_BIT_TICKS 0

Čia toks reikalas įdomus. Sakykim, jei koks nors laikmatis, ant kurio kabinasi dispečeris, yra 16 bitų, tai reik nustatyti 1. STM32F103 paprastai dispečeriui naudojamas SysTick, kuris yra 24 bitų, taigi logiška, kad čia nuliukas.

#define configIDLE_SHOULD_YIELD 1

Šitas parametras toks įdomus. Maždaug taip. Kai kuriam užduotį, nurodom jai prioritetą. Jei tas prioritetas yra IDLE (t.y. pats žemiausias), tai tokia užduotis bus vykdoma tik tada, kai visos kitos nieko neveikia. Bet: jei čia stovi vienetukas, tai dispečeris laiką IDLE prioriteto užduotims duos tik tada, jei jos bus pasiruošę darbui (baigsis ten kokia pauzė ar pan.) ir perims valdymą atgal, jei užduotis taip norės — net ir nesibaigus jos laiko kvantui. Na, o jei čia nuliukas, tai dispečeris laiko kvantus dalins lygiai visoms IDLE užduotims. Taigi tokios žemo prioriteto užduotys su vienetuku gali kiek efektyviau išnaudoti laisvą procesoriaus laiką, kas yra gerai, kai reikia taupyti energiją. Reikia kąstelėti dokumentacijos, kad būtų aiškiau, jei neaiškiai paaiškinau.

Manau, aišku, kad šita nuostata apskritai turi įtaką tik tada, kai naudojamas prievartinis dispečeris ir yra užduočių su IDLE prioritetu.

#define configUSE_CO_ROUTINES 0
#define configMAX_CO_ROUTINE_PRIORITIES ( 2 )

Dvi nuostatos susiję su korutinomis. Jei jų nenaudojam, tai nesukam galvos. Aš nenaudosiu, nebent kada nors prireiks.

#define configUSE_MUTEXES 1
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_RECURSIVE_MUTEXES 1

Visokie nustatymukai su muteksais. Galima pagalvoti, ko reikės, ko nereikės ir atjungti. Bo atmintį ir laiko resursus tai naudoja, bjaurybės. Pradžiai palieku, užteks tų resursų.

Dar keli labai svarbūs pertraukimų prioritetų nustatymai:

#define configKERNEL_INTERRUPT_PRIORITY 255
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191
#define configLIBRARY_KERNEL_INTERRUPT_PRIORITY 15

Aš nepasakojau apie STM32 NVIC pertraukimų prioritetus. Trumpai: jų yra šešiolika, nuo 0 iki 15. Nu tai paskutinis nustatymas tą ir rodo. Dabar toliau, NVIC prioritetai dvejetainiu būdu koduojami biškį durnai, naudojami keturi vyriausi bitai. Tai sakykim, 191 yra 0xB0, o tai yra 11 prioriteto lygis. Toliau. Branduolio prioritetas žemiausias, 255. Čia viskas ok. Ką gi reiškia tas 191? O tai reiškia, kad tik pertraukimai su prioritetu nuo 11 iki 15 gali naudoti FreeRTOS API. Grubiai taip tariant, sakykim, naudojam eiles (queue). Į eiles duomenis gali rašinėti pertraukimų paprogramės, kad ir kokie nors per USART ar USB ateinantys duomenukai gali būti suguldomi eilėsna. O branduolys turi rūpintis tų eilių tvarkymu: perdavimu užduotims, eilių perstūmimu ir pan. Jei tuo metu, kai branduolys tvarko eilę, ateis aukštesnio prioriteto pertraukimas ir kažką į tą eilę įdės, tai gausis sugadinti duomenys. Todėl branduolio pertraukimo negalima pertraukti, taigi visokios FreeRTOS funkcijos su pavadinimu <kažkas>FromISR gali būti naudojamos tik žemesnio prioriteto pertraukimuose. Šiuo atveju, žemesnio ir lygaus 11.

Paskutinis dalykas, kurį reikia įrašyti į šį konfigūracijos failą — tai dispečeriui reikalingų pertraukimų paprogramių pavadinimai:

#define xPortSysTickHandler SysTick_Handler
#define xPortPendSVHandler PendSV_Handler
#define vPortSVCHandler SVC_Handler

Čia turėtų būti viskas aišku. Tik nepamirškit užkomentuoti šitų paprogramių stm32f10x_it.c faile, bo kompiliatorius keiksis 🙂

—–

Aš va panašiai viską susirankiojau, susidėliojau, pasirinkau pabandymui padaryti paprastą mirksiuką su heap_1. Na ir ką, neveikia. xTaskCreate grąžina pdFALSE konstantą. Nesukuria užduoties. Sėdžiu, galvą krapštau, žiūriu, kaip ten viskas… nu neturiu prie ko kabintis. Ok, bandau ant durniaus heap_2. Dar geriau, xTaskCreate pakimba. Ech…

Šiaip pastaruoju metu aš susikompiliuoju kokį kodą ir įkeliu per USARTą su stm32flash programėle. Bo kartais tiesiog tingiu OpenOCD pasileidinėt, projekte visus debugus setupintis. Daugeliui paprastų pasikrapštymų su keliom kodo eilutėm to per akis. Panašiai ir su FreeRTOS padariau. O va, neveikia. Išbaigęs mintis visgi susijungiau JTAG zondą, susitvarkiau setupą ir žiūriu. Nugi su heap_2 procesoriukas nušoka į HardFault handlerį. Blėėė, šitaip jau reikia pasistengt… Nagi bandau kitus heapus ant durniaus. Ne kažin kas. Pradedam iš pradžių.

Na ir ką, truputuką pasiterliojęs ir pastepinęs su debugeriu pamačiau, kad mano procesoriuke pvPortMalloc (FreeRTOS atminties išskyrimo fintas) neveikia. Ir ten radau vieną įdomybę. Yra toks skaitliukas, xNextFreeByte. Na, paprastam atminties išskyrimui. Atmintis heap_1 išskiriama labai paprastai. Sukuriamas kintamasis, baitų masyvas, užduoto heapo dydžio. Na ir kai reikia kažkam atminties, tai paima iš to masyvo ir pažiūri, kiek jame vietos likę. Na, skaitliukas lyg ir gražiai inicializuojamas, nieko blatno:

static size_t xNextFreeByte = ( size_t ) 0;

O įdomybybė tokia, kad kai kviečiamas pirmasis pvPortMalloc, šito kintamojo dydis yra kažkodėl, blė, 3358562027. Chuinia kažkokia. Be abejo, kad su tokiu skaitliuku pvPortMalloc mano, kad heape nebeliko vietos. Nu ką, pyzia, reikia eiti aiškintis, kur ir kodėl ir kaip šitas kintamukas gali būti sugadintas. Štai jumi ir paprastas C uždavinukas.

Aš taip iš patirties galiu pasakyti. Kai kas nors paprasto neveikia, tai pirmas reikalas yra debuginimas. Na, priklauso nuo kalbos ir priemonių, bet vis tiek. Paimi ir debugini. Debuginimas ypač padeda naudojant visokias svetimas bibliotekas. Neseniai va ir su mano mylimuoju uloop turėjau apsipistį visišką, pusę dienos gaudžiau, o išgaudžiau tik jų libuką su debugu persikompiliavęs. Bo OpenWRT dokumentacija š… verta dažnai, geriausia dokumentacija būna C kodas ir debugas.

Pradžiai nusprendžiau sumažinti FreeRTOS heap’ą. Šast, sumažinau iki 16 kB. Va,  xNextFreeByte reikšmę jau 172687074. Hmmm… tai gal atminties procesoriukui neužtenka? Bet kaip taip galėtų būti? Juk tada neveiktų dar kas nors, USART’as koks nors ar pan. Jaučiu, kad priežastis ne ten, bet mažinu iki 10 kB.

Nepadėjo, reikšmė vėl kažkokia ne į temą.

Atsidulkinau tikrai atsakančiai ir išsiaiškinau tiek, kad tikrai kažkur yra atminties sugadinimas. Nurodžius visai nedaug heap atminties FreeRTOS suveikė. Na, bet, atleiskite… OS’ui 2 kB?! Na, kad ir daugiau, bet 10 kB jau neveikė, tai nė pusės RAM negaunu? WTF?

Deja, ir antrą kartą nesuveikė. Kapitaliai nesuprantu, kas su mano procesoriuko atmintimi darosi. Na, ir įdomybė tokia, kodėl gadinasi tik static kintamieji. Vieną įsidėjau tiesiai į pagrindinį „c“ failą. Iš pradžių lyg ir buvo gerai, nuliukas. O paskui ties kažkuriuo sugadinimu ir tas nuėjo velniop. Stebuklai stačiai.

Trumpai tariant, kadangi tokie bajeriai su static kintamaisiais, tai viskas aišku: kažkokia bledstva yra su kompiliavimu. Na, manyti, kad kompiliatorius „blogas“, būtų mažų mažiausia naivu. Kad kas nors neinicializuojama — irgi. FreeRTOS pavyzdukai yra paprasti, kaip dvi kapeikos. Ir turi viskas būti paprasta, tam juk ir yra programuotojų gyvenimo palengvinimas.

Static kintamieji yra sudedami .bss sekcijoje. Ką gi, eilinį kartą pagalvojau, nes vis pasitaiko, kad su Eclipse komplektuojamame sections.ld faile bus kas nors privelta. Na ir akurat:

sections.ld failo korekcija, kad freertos veiktų | Darau, blė

Pasididinkit paveiksliuką, pamatysit. Kairėje — originalaus sections.ld iškarpa, o dešinėje — mano pakoreguota. Labai tiksliai nežinau, kas ten buvo, bet .bss persidengdavo su kažkuria kita atminties dalimi (spėju, kad .data) ir taip susigadindavo kintamieji. Nes nuėmus static direktyvą susitvarkydavo. Tai greičiausiai persidengimas su .data. Nu tai fukju, viskas pavyko susitvarkyti. Dar padeda ir DEBUG išmesti iš kompiliatoriaus, nes Standard Peripherals su DEBUG prikiša ko reikia ir ko nereikia.

——

Konkretus pavyzdukas su FreeRTOS. Trys šviesos diodai, vienas užduoties kodo gabaliukas su skirtingais parametrais. Toliau sukuriamos trys užduotis su tais skirtingais parametrais ir viskas, trys šviesos diodai mirksi skirtingais intervalais, viskas šaunu ir paprasta:

#include "stm32f10x.h"

#include "FreeRTOS.h"
#include "task.h"

// Mano usart utėlės visokiam reikalų spausdinimui lauk.
#include "usart_utils.h"

// Trys mirksiukai lygiagrečių užduočių testavimui
#define LED_PORT      GPIOA
#define LED1       GPIO_Pin_0
#define LED2       GPIO_Pin_1
#define LED3       GPIO_Pin_2
#define LED_PERIPH   RCC_APB2Periph_GPIOA

char buf[100];

static uint16_t l1[2] = { LED1, 500 };
static uint16_t l2[2] = { LED2, 600 };
static uint16_t l3[2] = { LED3, 700 };

/**
 * Čia yra FreeRTOS užduoties „kūnas“ su amžinu ciklu, tarsi nepriklausoma programėlė.
 * Jai sukūrimo metu gali būti perduodami bet kokio tipo reikalingi parametrai.
 * Aš perduodu masyvuką su išvesties elektrodu ir pauze.
 */
void vTaskBlink(void *pvParameters)
{
	uint16_t *led = (uint16_t *) pvParameters;

	while(1) {
		LED_PORT->BSRR = led[0];
		vTaskDelay(led[1]);
		LED_PORT->BRR = led[0];
		vTaskDelay(led[1]);
	}
}

int main(void)
{
	enable_usart(USART1, 57600);

	print_usart(USART1, "-------------------------\r\n");
	print_usart(USART1, "Pradzia - trys taskai!\r\n");

	GPIO_InitTypeDef GPIO_InitStructure;

	/* GPIO Periph clock enable */
	RCC_APB2PeriphClockCmd(LED_PERIPH, ENABLE);

	/* Configure pin in output push/pull mode */
	GPIO_InitStructure.GPIO_Pin = LED1 | LED2 | LED3;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(LED_PORT, &GPIO_InitStructure);

	print_usart(USART1, "\tLedukai\r\n");

	/* Štai čia yra sukuriamos užduotys. Pirmiausiai perduodama funkcija, kuri ir yra užduotis, tada draugiškas debuginimui pavadinimas,
	steko apimtis, parametrai (arba NULL, jei jų nereikia), užduoties prioritetas ir nuoroda (handle) į sukurtą užduotį
	(arba NULL, jei nereikia). Pagal tai, ką grąžina sukūrimo funkcija, galime spręsti, ar pavyko, ar ne. */
	if (pdTRUE != xTaskCreate(vTaskBlink, ( const char * ) "L1", configMINIMAL_STACK_SIZE, (void *) l1, tskIDLE_PRIORITY+1, NULL)) {
		print_usart(USART1, "\tTasko 1 feilas, ble\r\n");
	} else {
		print_usart(USART1, "\tTaskas 1 sukurtas\r\n");
	}
	/* Kita užduotis su tuo pačiu kodu, bet kitokiais parametrais (skirtinu mirksėjimo dažniu ir išvestimi) */
	if (pdTRUE != xTaskCreate(vTaskBlink, ( const char * ) "L2", configMINIMAL_STACK_SIZE, (void *) l2, tskIDLE_PRIORITY+1, NULL)) {
		print_usart(USART1, "\tTasko 2 feilas, ble\r\n");
	} else {
		print_usart(USART1, "\tTaskas 2 sukurtas\r\n");
	}
	/* Ir trečioji užduotėlė */
	if (pdTRUE != xTaskCreate(vTaskBlink, ( const char * ) "L3", configMINIMAL_STACK_SIZE, (void *) l3, tskIDLE_PRIORITY+1, NULL)) {
		print_usart(USART1, "\tTasko 3 feilas, ble\r\n");
	} else {
		print_usart(USART1, "\tTaskas 3 sukurtas\r\n");
	}

	/* Viskas, užduotys sukurtos, startuojam dispečerį */
	vTaskStartScheduler();

	/* Programinis dugnas, į kurį niekada neturėtumėm patekti */
	print_usart(USART1, "\tSchedulerio feilas\r\n");

	while (1) {}

	return 0;
}

/**
 * Šitas hook'as iškviečiamas, jeigu pvPortMalloc dėl kažko nesuveikia. Naudinga turėti, kad žinotumėte,
 * kada nepavyksta išskirti atminties ar dar kažkokia bėda įvyksta.
 *
 * FreeRTOS fyčia. configUSE_MALLOC_FAILED_HOOK turi būti 1, kad šitą hooką iškvietinėtų.
 */
void vApplicationMallocFailedHook( void )
{
	print_usart(USART1, "Mallocas failino, ble\r\n");
	LED_PORT->BSRR = LED3;
}

/**
 * Labai naudinga FreeRTOS fyčia, padedanti gaudyti per mažus stekus. Šiaip verta naudoti tiktai
 * developmento metu su configCHECK_FOR_STACK_OVERFLOW 2, nes valgo resursus. Užtat galima eksperimentuoti
 * mažinant steką užduotims ir žiūrėt, kada šitas hookas iškviečiamas.
 *
 * Yra dar ir kitų priemonių, šita lengviausia.
 */
void vApplicationStackOverflowHook( TaskHandle_t xTask, char *pcTaskName )
{
	print_usart(USART1, "Stack overflow, nachui, ble\r\n");
	LED_PORT->BSRR = LED3;
}

/**
 * HwFault handleris, mano paties, kad žinočiau, jog procesoriukas dėl kažko užsilenkė. Pravertė su
 * atminties gadinimo sekiojimu.
 */
void HwFault(void) {
	print_usart(USART1, "Shit happens, ble\r\n");
}

Va ir viskas. Dabar dar galite pažiūrėti filmuką, kaip tie trys diodukai mirguliuoja:

Įsivaizduokit, kaip tokią paprastą užduotėlę reikėtų įgyvendinti be FreeRTOS. Kokio nors delay naudoti negalima, nes vienas kitam trukdys. Užkurti tris laikmačius? Tai bus laikmačių švaistymas. Pakurti vieną laikmatį ir skaičiuoti jo kapsėjimą ir pagal tai junginėti? Va, taip jau būtų teisingiau, tik kodijimo truputuką daugiau. O čia — funkcija su amžinu ciklu ir specialia vTaskDelay() funkcija, kuri atiduoda valdymą dispečeriui. Grožis ir gėris bei paprastumas. Dėl to ir sakiau, kad visos tos užduotys — tarsi mažos programėlės, besisukančios mikrovaldiklyje.

Reklama
Komentarai
  1. Andrius parašė:

    aciu uz gera straipsni, kaip tik ieskojaus informacijos nuo ko pradeti su RTOS. lauksiu sekanciu straipsniu sia tema

  2. automateaqua parašė:

    Nelabai įsivaizduoju kas turėtų būti per užduotys mikrovaldikliui kad reikėtų naudoti papildomai kažkokią operacinę sistemą, jei nuskaitinėji daviklius, mygtukus, atnaujini informaciją ekranėlyje ir pan. Esu daręs panašių projektėlių su PIC mikrovaldikliu, tai jokio poreikio kažkokiai RTOS tikrai nebuvo. Beje PIC18 turi dviejų lygių pertraukimus, auštesnių serijų mikrovaldikliai turi dar daugiau. Negi ARMas neturi? Be to esant papildomai operacinei gali pasunkėti problemų išsiaiškinimas, jei kas nors neveiktų: ar kalta geležis, ar bibliotekos kodas o gal ir pati RTOS.

    • Darau, Blė parašė:

      Na, jei neįsivaizduojate, tai siūlau pabandyti kelis projektėlius su RTOS. Aš šiaip ir nesakau, kad RTOS reikia naudoti visur ir visada, ypač, kai projektukai maži. Bet jų būna ir didelių, o su dideliais darbais visi žinom pagrindinį principą: skaldyk ir valdyk. Su RTOS tas skaldymas yra tiek patogus, kad vienu metu prie projekto gali dirbti skirtingi programuotojai. Be to, su RTOS galima gerokai efektyviau išnaudoti resursus. Sakykim, kai kurias rečiau vykdomas užduotis iš viso pašalinti iš atminties, o reikalui esant — įkrauti. Tam ir yra skirtingi FreeRTOS heapų valdymai.

      Problemų išsiaiškinimas gali pasunkėti, dėl to nesiginčiju. Bet čia tas sunkumas būna dažniausiai tol, kol sistema nelabai pažįstama. Man va problema buvo ir su paprastu mirksiukų pakūrimu, kur pasirodė kalta ne sistema, o lievas ld failiukas. Na, bet va, po truputuką susirankioju jau pakankamai išlaižytą ir sutvarkytą ld šabloną ateities projektams 😀

      Tai va, aš nenoriu pasakyti, kad RTOS yra panacėja. Ir nuspręsti, kada tokią sistemą naudoti, taip pat nėra lengva. Bet mano patirtis rodo, kad projektui pasiekus tam tikrą lygį tokia sistema pasidaro būtina, vien dėl skaldymo, užduočių bei kodo izoliacijos ir plečiamumo. Gerai, kai projektas su mygtukais, ekranu ir davikliais užima kelis šimtus ar kelis tūkstančius eilučių. Bet kai jis užima keliasdešimt tūkstančių — nori nenori ateina laikas skaldyti. Ir su RTOS tai tiesiog paprasčiau ir pigiau.

  3. Ką Mykolas? parašė:

    gcc toolchain’as? O versija binutils’ų kuri? Pamenu analogišką problemytę apturėjęs su kreivais linkerio skriptais… būtent stock’inėje Ubuntu versijoje. Meh.

Parašykite komentarą

Įveskite savo duomenis žemiau arba prisijunkite per socialinį tinklą:

WordPress.com Logo

Jūs komentuojate naudodamiesi savo WordPress.com paskyra. Atsijungti / Keisti )

Twitter picture

Jūs komentuojate naudodamiesi savo Twitter paskyra. Atsijungti / Keisti )

Facebook photo

Jūs komentuojate naudodamiesi savo Facebook paskyra. Atsijungti / Keisti )

Google+ photo

Jūs komentuojate naudodamiesi savo Google+ paskyra. Atsijungti / Keisti )

Connecting to %s