EMTOS11: REAL-TIME MULTITASKING OPERACIJSKI SISTEM ZA MIKROKRMILNIK MC68HC11

Danes že skoraj ni več področja v elektrotehniki na katerem se ne bi uporabljal mikrokrmilnik kot visoko integriran del sistema, zato je poznavanje mikrokrmilniških sistemov prav gotovo že nujnost. Posledica tega je visoka stopnja razvoja v tej smeri in sledenje današnjim trendom je zaradi pomankanja dobre razvojne opreme in seveda tudi visoke cene vsaj otežkočeno, če že ne onemogočeno. Novih procesorjev oziroma, če smo bolj natančni, mikrokrmilnikov je vse več in uporabnik se že skoraj ne more odločiti, kateri mikrokrmilnik bo uporabil. Po skrbnem premisleku pa se večina uporabnikov še vedno odloči za stare družine, saj je razvojno orodje najbolj dosegljivo in tudi poznajo jih. Vendar tudi tu ni vse v rožicah. Razvojno okolje se še najde, »platica« tudi, pri operacijskem sistemu (OS) pa se že zatakne. Namreč ti zadnji, ne ležijo kar vsepovsod. Obstajajo komercialne izvedenke, ki pa niso ravno poceni, pa tudi najboljšega vpogleda ne nudijo. Govoriva seveda o operacijskih sistemih za mikrokrmilnike 8051, 68HC11, itd. in ne o Windows-ih.

Pri laboratorijskem delu pri predmetu Mikrokrmilniški sistemi, sva se srečala z družino mikrokrmilnikov 68HC11, ki izhaja iz Motoroline družine 6800. 68HC11 ima predvsem izboljšan set inštrukcij in več periferije na čipu samem. Več o razvojnem okolju in zgradbi mikrokrmilnika se da prebrati v skripti z naslovom Mikrokrmilniški sistemi. V skripti je opisan tudi majhen razvrščevalnik, operacijski sistem, ki deluje po principu časovnega rezinjenja in v točno določenih časovnih intervalih dodeljuje opravilom procesorski čas. Za majhne aplikacije je ta pristop več kot dovolj, pri srednjih se še da dihati, za večje pa pristop popolnoma odpove. Razvrševalnik namreč ne dovoljuje prekinitev razen časovnikove, ki skrbi za rezinjenje. Poleg tega se morajo vsa opravila končati še pred iztekom časovne rezine. S sistemskega stališča so to velike omejitve, zato sva se s kolegom odločila za razvoj novega operacijskega sistema, ki bi bil prijaznejši do uporabnika in prikladnejši za razvoj večjih aplikacij, kjer lahko sodeluje tudi več programerjev. Poudariti pa je potrebno, da je bil operacijski sistem razvit v eksperimentalne namene in kot tak ni popolnoma preizkušen.

Najprej se pojavi vprašanje, kako se sploh lotiti razvoja operacijskega sistema? Kje začeti? Kakšne funkcije naj vsebuje? Najpombnejši je način preklapljanja med opravili (angl. tasks) in stanja v katerih se opravila lahko nahajajo. Opravilo, ki je trenutno v obdelavi oziroma mu je namenjen ves procesorski čas, je npr. v delujočem stanju (angl. running), opravila, ki čakajo na svoj procesorski čas, pa so v stanju pripravljenosti (angl. ready). Kaj pa če nočemo, da bi opravilo čakalo v ready vrsti? Logično je, da potrebujemo spet eno stanje, na primer speče stanje (angl. asleep). Kaj pa če je opravil veliko, npr. 200? Procesor ne more kontrolirati vseh, saj bi tako porabil preveč pomnilnika, zato potrebujemo še eno stanje, ki prevzame vsa neaktivna opravila, to je stanje neaktivnosti (angl. suspended). Zdaj, ko so stanja določena, je potrebno določiti še način preklapljanja med opravili. V osnovi gre za dva načina preklapljanja. V tuji literaturi se imenujeta cooperative multitasking in preemptive multitasking. Tudi midva bova raje uporabljala že ustaljene angleške izraze, saj ni dobrih slovenskih izrazov. Pri cooperative multitaskingu gre za preklapljanje odvisno od programerja. Opravila se izvajajo tako dolgo, kot je potrebno, nato pa svojevoljno prepustijo procesor drugemu opravilu. Programer mora zato dobro poznati problematiko sistema, ki ga programira in ne sme pisati opravil, ki bi predolgo zasedala procesor, saj bi se lahko izvajanje programa upočasnilo ali pa celo podrlo. Pri preemptive multitaskingu pa lahko opravilo z večjo prioriteto »nasilno« prekine trenutno delujoče opravilo in prevzame procesor. Sistem mora nato, ko se višje prioritetno opravilo konča, poskrbeti, da se prekinjeno opravilo brez napak nadaljuje na mestu prekinitve. V praksi se uporabljata oba načina v konjukciji in je tako nešteto možnosti realizacije operacijskih sistemov.

S kolegom sva se prav tako odločila za mešanico obeh načinov. Operacijski sistem, ki sva ga razvila, bazira na prioritetah opravil in ne na krožnem sistemu. Opravilo dobi procesorski čas le, če ima večjo prioriteto od ostalih opravil v ready vrsti ne glede na to katero mesto v vrsti zaseda. Če se v vrsti pojavi opravilo z večjo prioriteto, potisne delujoče opravilo v ready vrsto in samo prevzame procesor. Opravilo, ki samo prepusti procesor, pa se vljudno umakne v speče stanje. Speča opravila se zbudijo le ob točno določenih dogodkih (angl. events), ki bodo opisani kasneje. Vse skupaj dobro prikazuje slika 1.

SLIKA1: PREHAJANJE OPRAVIL MED STANJI V SISTEMU EMTOS11

Osnovni parametri operacijskega sistema so določeni in sedaj je potrebno določiti še sinhronizacijo med opravili ter med opravili in OS. Glede na izkušnje je to težji del naloge. Vse skupaj je zajeto v sistemskih klicih (angl. system calls). Sistemski klici skupaj z jedrom sestavljajo operacijski sistem in služijo kot API funkcije (angl. application programming interface). Programer uporablja te funkcije za stik z sistemom.

Zdaj, ko je okvir naloge določen, je čas za razvoj jedra in nato še sistemskih klicev. Jedro skrbi za nemoteno preklapljanje in shranjevanje delovnih registrov procesorja, kadar gre za preemptive multitasking. Skrbi tudi za tabele posameznih opravil, v katerih je zapisano trenutno stanje vsakega opravila. V tabeli ready je zapisan status aktivnih opravil. Opravila, ki so v spanju, imajo status $FF, ostala opravila pa imajo zapisano prioriteto. Manjše kot je število, večja je prioriteta. V tabeli priority so zapisane prioritete vseh aktivnih taskov. Prioritet je lahko 255. Vseh opravil je lahko 256 in vsako ima svojo identifikacijsko številko. Aktivnih opravil pa je lahko le 8. Zato potrebujemo še tabelo catalog, ki vsebuje identifikacijske številke aktivnih opravil. Seveda ni nujno, da je število aktivnih taskov vedno 8, lahko je tudi manjše. Najpomembnejša tabela, je tabela za shranjevanje skladovnih kazalcev posameznih opravil. Vsako aktivno opravilo ima določen svoj sklad brez katerega ne bi bilo multitaskinga. Opravila se preklapljajo med seboj tako, da se preprosto zamenja skladovni kazalec trenutnega opravila s skladovnim kazalcem naslednjega opravila. Vendar je tu potrebno zelo paziti, saj lahko program podivja, oziroma se sistem zruši, če se napačno naloži skladovni kazalec. Funkcije jedra so sinhronizirane z notranju uro (angl. system tick), ki ob natančnih časovnih intervalih kontrolira tabele in preklaplja opravila. Skoraj vsak mikrokrmilnik vsebuje enega ali več časovnikov, ki se lahko uporabijo za funkcijo sistemske ure. Ob prekinitvi časovnika se prekontrolirajo tabele in se po potrebi izvede preemptive switch. Tu sta prisotni tudi tabela time in interval. Time tabela vsebuje število sistemskih taktov do zbujanja opravila, interval tabela pa vsebuje vrednost za ponovni vnos v tabelo time. Obe funkciji sta povezani s sistemskimi klici, ki bodo natančneje opisani kasneje. Važna je tudi informacija o tem, katero opravilo je trenutno delujoče. Številka delujočega opravila je zapisana v spremenljivki running.

V uvodu sva že omenila, da je možno uporabiti tudi ostale prekinitve razen časovnikove in prekinitve, ki jih ni možno maskirati. Vendar so prekinitve zelo nevarna stvar pri multitaskingu in je potrebno močno paziti pri njihovi integraciji v sistem. Nekateri načrtovalci jih preprosto ne dovolijo, ker lahko zrušijo sistem ali pa podaljšujejo čas med dvema sistemskima taktoma. Midva sva jih vključila v sistem tako, da programer ne more pisati svojih prekintvenih rutin (angl. interrupt handlers) , ampak napiše opravilo (angl. interrupt task routine), ki je enako vsem ostalim in nato s sistemskim klicem pove, da je to opravilo namenjeno obdelavi prekinitve. Prekinitvene rutine (v nekateri lit. tudi angl. interrupt service routine) pa so trdno integrirane v jedro operacijskega sistema. Vendar tako kot ponavadi spet ni vse v rožicah. Opisani pristop namreč močno podaljša čas med postavljeno zahtevo po prekinitvi in samo obdelavo prekinitve. Je pa to še vedno boljše, kot pa ne imeti prekinitev nasploh. Vključila sva samo dva vira prekinitev, ostale lahko uporabnik vključi na enak način.

Po jedru pride na vrsto opis vseh sedemnajstih sistemskih klicev. Sistemska klica za inicializacijo in za zagon operacijskega sistema sta nujna za pravilno delovanje in se izvedeta ponavadi samo enkrat, na začetku aplikacijskega programa. Ustvarjanje in brisanje aktivnih opravil je izvedeno preko sistemskih klicev _CREAT in _DELET. Tako je omogočena uporaba čim manj aktivnih opravil, ko pa se potrebuje katero drugo opravilo, se le to  preprosto ustvari in nato po uporabi briše. S sistemskim klicem lahko tudi spremenimo prioriteto poljubnega aktivnega opravila, ali pa poiščemo številko aktivnega taska, preko identifikacijske številke v catalog tabeli. Za uspavanje opravil je na voljo cel kup klicev. Naprimer, opravilo lahko gre v spanje za določen čas, lahko čaka na interval ali pa semafor, možno tudi na sporočilo. En sistemski klic je celo rezerviran za čakanje na prekinitev. Prej sva omenila semafor. Semafor je struktura, ki sinhronizira spanje nekega opravila z ostalimi opravili. Na primer, eno opravilo postavi svoj semafor na vrednost 3 in se postavi v spanje. Drugo opravilo izvede operacijo povezano s prejšnjim opravilom in zmanjša semafor za 1. Spet tretje opravilo opravi neko delo in zmanjša semafor za 2, na vrednost 0 in s tem zbudi prvo opravilo, ki nato rezultate drugega in tretjega opravila obdela. Torej je potreben sistemski klic za zmanjšanje semaforja, prav tako tudi za povečevanje in mogoče še za branje semaforja. Podobno dela tudi struktura sporočil. Opravilo čaka v spanju, na sporočilo nekega drugega opravila. Včasih si želimo tudi, da bi se opravilo periodično izvajalo in temu primerno je bil razvit sistemski klic, ki avtomatsko po preteku nekega intervala prebudi opravilo. 


SISTEMSKA ORODJA JEDRA EMTOS11

ČASOVNA PAVZA (timeout)

Vsako opravilo lahko samo sebe postavi v spanje za določeno časovno obdobje. Po preteku tega časa se opravilo zopet zbudi in nadaljuje na mestu, kjer je prej končalo. Vrednost (timeout), ki jo podamo kot parameter pri časovni pavzi, se shrani na ustrezno mesto v strukturi TIME, ki pripada prekinjenemu opravilu. Trajanje pavze je določeno kot produkt: timeout*ST(sistemski takt), kjer je sistemski takt, čas med dvema zaporednima prekinitvama časovnika. V našem sistemu je nastavljen približno na dve milisekundi. Če za parameter timeout podamo vrednost FF, časovna pavza ne deluje, FF pomeni neskončno dolgo pavzo.  

INTERVAL (interval)

Interval se uporablja za kontinuirano bujenje opravil, ki interval uporabljajo. Ko nekemu opravilu poteče interval, se le to zbudi iz spanja in se postavi v ready stanje. Takoj po poteku intervala, začne teči nov interval, po izteku katerega se bo zopet zbudilo opravilo. S tem lahko dosežemo, da sistem deluje podobno kot razvrščevalnik. Interval določenega opravila izključimo tako, da na ustrezno mesto, ki pripada opravilu, v strukturi INTERV, ki vsebuje intervalne vrednosti vseh aktivnih opravil, vpišemo vrednost FF.

 

SEMAFOR (semaphore)

Vsako aktivno opravilo ima svoj semafor. Vsako opravilo lahko prišteva ali odšteva vrednosti k semaforju katerega koli drugega opravila. Posamezno opravilo se lahko postavi v spanje, dokler pripadajoč semafor ne doseže vrednosti 0. Vrednosti semaforjev posameznih opravil se nahajajo v strukturi SEMAPHR. MSB bit posameznega semaforja indicira, da opravilo, ki mu semafor pripada, čaka da semafor doseže vrednost 0. Najvišja vrednost, ki jo ima lahko semafor je tako 7F.

 

SPOROČILO (message)

Posamezna sporočilna struktura potrebuje 1 bajt, t.i. statusni bajt (MBXSTAT) in dva dodatna bajta za shranjevanje naslova, kjer se sporočilo nahaja. Naslovi sporočil se tako zapisujejo v stukturo MSG (messages), ki torej v bistvu vsebuje kazalce na sporočila. Sporočila nimajo opredeljenih opravil, katerim bi pripadala ampak lahko vsako opravilo uporablja katero koli sporočilno mesto, seveda le če je to mesto prosto. Status sporočilnega mesta podaja MBXSTAT. Če je MBXSTAT[i]=0, je sporočilno mesto i prosto, če je postavljen MSB bit v MBXSTAT[i], je na tem sporočilnem mestu že neko sporočilo, sicer je v MBXSTAT[i] številka opravila, ki na tem sporočilnem mestu pričakuje sporočilo. Če neko opravilo pričakuje sporočilo na določenem sporočilnem mestu, v katerega sedaj prispe sporočilo, le to postavi čakajoče opravilo v ready stanje.

 


SISTEMSKI KLICI JEDRA EMTOS11  

_INITK()

Sistemski klic za inicializacijo operacijskega sistema _INITK inicializira vse sistemske spremenljivke, tabelo za status sporočil in tabelo za shranjevanje skladovnih kazalcev. Sklad razdeli na posamezne segmente in vsakemu opravilu priredi svoj segment. Sklad je že na začetku razdeljen na toliko segmentov, kot je definirano maksimalno število aktivnih opravil. Torej v sistemu ni uporabljeno dinamično segmentiranje sklada.

 

_STRTK()

Ta sistemski klic inicializira časovnik, ki skrbi za sistemski takt. Sistemski takt postavi na 2.0345ms in zažene časovnik ter prvo pravilo. Programer mora pred tem sistemskim klicem izvršiti sistemski klic _INITK() in ustvariti vsaj eno opravilo s sitemskim klicem _CREAT, drugače sistem ne bo deloval.

 

_CREAT(prioriteta, identifikacijska številka opravila, naslov opravila)

Sistemski klic za dinamično ustvarjanje opravil. Prioriteta opravila se poda v registru A, identifikacijska številka opravila se poda v registru B in naslov opravila se poda v registru Y. Opravilo se ustvari na koncu vrste in se takoj postavi v ready tabelo, vendar ne pride do preklopa opravil. Identifikacijska številka se vpiše v tabelo catalog, na sklad pa se vpiše naslov opravila. Ostale tabele se inicializirajo na začetno vrednost. Na koncu se spremeni še število aktivnih opravil. Če se zaradi kakršnega koli vzroka opravilo ne ustvari, se vrne v registru A vrednost $FF, drugače pa številka opravila. Sistemski klic ne sme biti prekinjen s klicem samega sebe ali s klicem sistemskega klica _DELET. Zato je priporočeno, da programer pred klicem _CREAT funkcije izvrši sistemski klic _SETPR in spremeni prioriteto delujočega opravila na največjo vrednost (torej na 0). Po končanem ustvarjanju opravila pa naj prioriteto spremeni nazaj na staro vrednost.

 

_DELET(identifikacijska številka opravila)

Ta funkcija »izbriše« opravilo z identifikacijsko številko, ki se poda v registru B. Opravilo se ne izbriše fizično, ampak se izbriše samo iz tabel operacijskega sistema. Deli tabel za izbrisanim opravilom se nato pomaknejo naprej in zapolnijo mesto izbrisanega opravila. Tako si opravila v tabelah vedno sledijo, prazna mesta pa so na koncu tabel, kjer se lahko ustvarjajo nova opravila. V primeru, da je izbrisano opravilo ravno tudi delujoče, se zažene subrutina za zagon naslednjega opravila v ready vrsti. Podobno kot prej, tudi ta sistemski klic ne sme biti prekinjen sam s sabo ali pa s klicem _CREAT. Sistemski klic ne vrne nobene vrednosti.

 

_SETPR(nova prioriteta, identifikacijska številka opravila)

To je sistemski klic za spreminjanje prioritete nekega opravila. Prioriteta je podana v registru A in identifikacijska številka v registru B. Sistemski klic ne vrne ničesar.

 

_WAITX(številka prekinitve, timeout vrednost)

S tem sistemskim klicem programer pove operacijskemu sistemu, da je opravilo namenjeno servisiranju prekinitve. Operacijski sistem nato postavi opravilo v spanje in čaka na prekinitev. Številka prekinitve pove, na katero prekinitev opravilo čaka, timeout vrednost pa določi, kako dolgo naj opravilo čaka preden se nadaljuje. Vrednost $FF v timeout parametru onemogoči timeout. Številka prekinitve se poda v registru A, timeout vrednost pa v registru B. Sistemski klic vrne v registru A vrednost $01, če se je dogodek že izvršil, $02, če drugo opravilo že čaka na isto prekinitev, $FF v primeru napačne številke prekinitve in $00, če je vse v redu. Do preklopa opravil pride samo v primeru, da nobeno drugo opravilo ne čaka na isto prekinitev, ali pa da se dogodek še ni zgodil. Programer zato lahko preverja vrnjeni status in ustrezno ukrepa.

 

_FINDT

Parametri: B-task id

Na podlagi parametra task id, nam vrne številko opravila, ki mu parameter task id pripada. Vsi task id-ji aktivnih opravil so zapisani v strukturi CATALOG. Številka taska je vrnjena v B, medtem ko ostane A tak kot je bil pred sistemskim klicem.

_WAITT

Parametri: A-timeout

Postavi tekoče opravilo v spanje za časovno obdobje timeout*ST, ST=2ms. Po preteku tega obdobja opravilo nadaljuje, kjer je prej končalo. Če kot parameter timeout podamo FF, s tem določimo neskončno časovno obdobje.

_INTVL

Parametri: A-intv, B-task id

Opravilu določenem z task id postavi interval z vrednostjo podano kot parameter intv. Če podamo za intv vrednost FF, s tem izključimo interval pripadajočega opravila.

_WAITI

Postavi tekoče opravilo v spanje, dokler ne nastopi intervalna prekinitev in se opravilo zopet zbudi.

_WAITS

Parametri: A-timeout

Postavi tekoče opravilo v spanje, dokler pripadajoči semafor ne doseže vrednosti 0, oz. do preteka časovnega obdobja določenega s parametrom timeout. Če je ustrezen semafor 0 že v trenutku klica _WAITS, nam le ta vrne vrednost 01 v A in nadaljuje s tekočim opravilom.

_ADDSU

Parametri: A-units, B-task id

Prišteje število enot podano kot parameter units, ustreznemu semaforju, ki je določen s parametrom task id. Če število enot v semaforju želi preseči 7F, obdržimo število 7F.

_SUBSU

Parametri: A-units, B-task id

Odšteje število enot, podano kot parameter units, ustreznemu semaforju, ki je določen s parametrom task id. Če se število enot v semaforju zmanjša do  0 oz. želi iti pod 0, potem obdržimo vrednost 0, ustrezno čakajoče opravilo pa postavimo v stanje ready.

_CHEKS

Parametri: B-task id

Prebere število enot v semaforju, ki je podan z task id.

_WAITM

Parametri: A-timeout, B-mbx

Postavi tekoče opravilo v spanje, dokler ne prispe sporočilo na sporočilno mesto podano s parametrom mbx, oz. do izteka časovne pavze, podane s parametrom timeout. Če na sporočilnem mestu čaka na sporočilo že neko drugo opravilo, potem tekoče opravilo ne gre v spanje, _WAITM pa nam to sporoči z vrnjeno vrednostjo FF v A. Sicer dobimo v X vrnjen naslov sporočila. Če je v času sistemskega klica sporočilo že na mestu, _WAITM ne preklaplja opravil ampak nam takoj v A vrne vrednost 01. V primeru, da je potrebno na sporočilo počakati, _WAITM zamenja opravila, ko prispe sporočilo pa nam v A vrne vrednost 00.

_SENDM

Parametri: B-mbx, X-naslov sporočila

Shrani naslov sporočila iz X, na sporočilno mesto podano z mbx. Če na tem sporočilnem mestu neko opravilo že pričakuje sporočilo, _SENDM obriše statusni bajt MBXSTAT, čakajoče opravilo postavi v ready stanje, v A pa vrne vrednst 01. Če na sporočilnem mestu ni čakajočega opravila, po predhodni shranitvi naslova sporočila, _SENDM postavi MSB v MBXSTAT bajtu. _SENDM ne preklaplja opravil.

_CHEKM

Parametri: B-mbx

Vrne status sporočilnega mesta podanega s parametrom mbx. Status pomeni vrednost vrnjena v A. Vrednost 00, pomeni prosto sporočilno mesto, 01 čakajoče sporočilo, 02 pa čakajoče opravilo, ki čaka na sporočilo na tem sporočilnem mestu.


ZGLED UPORABE JEDRA EMTOS11 NA PREPROSTEM PRIMERU 

Delovanje sistema ponazorimo z demo programom, ki bo krmilil vhodno/izhodno enoto s tipkovnico in 4-mestnim LED prikazovalnikom, ki se uporablja v povezavi z razvojno ploščo za 68HC11, pri vajah predmeta Mikroprocesorji v elektroniki, na Fakulteti za elektrotehniko v Ljubljani. Uporabljena strojna oprema je opisana v literaturi Mikrokrmilniški sistemi, avtorja Tadeja Tume. Zgradimo preprost program, ki nam ob pritisku numeričnega znaka na tipkovnici, le tega izpiše na zadnjem mestu 4-mestnega LED prikazovalnika, medtem ko ostale znake na prikazovalniku pomakne za eno mesto, zadnji znak pa seveda izpade.

Takoj lahko ugotovimo, da za naš program potrebujemo tri različna opravila. Prvo naj skrbi za kontinuirano osveževanje led prikazovalnika, drugo naj poskrbi za redno pregledovanje tipkovnice, tretje pa naj ob zaznanem pritisku le tega pretvori v ustrezno obliko za prikaz ter ga shrani na primerno mesto od koder, ga bo v naslednjem osveževalnem ciklu prvo opravilo prebralo ter prikazalo ustrezen znak na prikazovalniku.

Opravilo za osveževanje prikazovalnika uporablja 4 spremenljivke, v katerih so zapisani znaki, ki naj se trenutno izpisujejo na prikazovalniku. Zaradi same narave osveževanja, takoj ugotovimo, da bo najprimernejše orodje za to  opravilo interval. Koda opravila je naslednja:

 

    DISP                 ldaa          #$F7
                               staa         _DPRA
                               ldaa          #2
                               ldab          #1
                               jsr            _INTVL
    DISLOOP        ldaa          _DPRA
                               oraa         #$F0
                               asra
                               bcs           CHGD
                               ldaa          #$F7
    CHGD               staa          _DPRA
                               ldx           #LED-1
    INCD                 inx
                               asra
                               bcs           INCD
                               ldaa          0,X
                               staa         _DPRB
                               jsr           _WAITI
                               bra          DISLOOP

Vidimo, da se prvih pet ukazov opravila izvede le enkrat, tu inicializiramo _DPRA ter nastavimo interval za osveževanje. Interval nastavimo na dva sistemska takta, kar v praksi pomeni 4ms, celoten prikazovalnik se osveži vsakih 16ms. Ostali del opravila se izvaja v neskončni zanki, kar je lastnost praktično vseh uporabniških opravil. Ob vsakem prehodu skozi zanko opravilo na ustreznem mestu prikazovalnika prikaže ustrezen znak, nato pa odide v spanje za čas, določen z intervalom, in sicer ob izvedbi sistemskega klica _WAITI. Po poteku intervala se program vrne v opravilo in izvede ukaz bra DISLOOP.

Opravilo za branje tipkovnice periodično pregleduje stanja tipk. Koda je naslednja:

 

    KBDSCAN       clr         KBDKEY

                           ldaa       #5

                           ldab       #2

                           jsr         _INTVL

                           ldaa       #1

                           ldab       #2

                           jsr        _ADDSU

    KBD1             ldaa       _DPRB

                           psha

                           ldab       #4

                           ldaa       #%11110111

                           staa       _DPRB

    LOOP             ldaa       _DPRA

                           anda       #$F0

                           cmpa      #$F0

                           bne        KEYPRS

                           asr        _DPRB

                           decb

                           bne        LOOP

                           clr         KBDKEY

                           bra        RETURN

    KEYPRS         ldab       KBDKEY

                           bne        RETURN

                           inc         KBDKEY

                           eora       _DPRB

                           eora       #%00001111

                           staa       KBDVAL

                           ldaa       #1

                           ldab       #3    

                           jsr         _SUBSU

                           ldaa       #1

                           ldab       #3

                           jsr         _ADDSU

    RETURN         pula

                           staa       _DPRB

                           jsr         _WAITI

                           bra        KBD1

Tudi tu imamo del ki se izvede le enkrat, to je del do labele KBD1. V tem odseku nastavimo interval pregledovanja tipkovnice na 5 sistemskih taktov, kar je 10ms. Poleg intervala tu postavimo še semafor opravila, ki bo obdelalo morebiten pritisk, na 1. S tem omogočimo, da bo omenjeno opravilo ob prvem izvajanju, ko še ne bo aktivnega pritiska, lahko prešlo v spanje in počakalo na prvi pritisk. Preostali del opravila za pregledovanje tipkovnice tudi tu teče v neskončni zanki. V tej zanki opravilo pregleda celotno tipkovnico, ob morebitni pritisnjeni tipki preveri KBDKEY (ta pove, da je pritisk že obdelan), če je pritisk že bil detektiran poprej, odide opravilo v spanje, sicer shrani informacijo o pritisku v KBDVAL ter izvede sistemski klic _SUBSU. S tem ko semafor doseže vrednost 0, sistem postavi opravilo za obdelavo pritiska v ready stanje. Takoj za _SUBSU se izvede se _ADDSU, ki semafor premakne z vrednosti 0. S tem opravilu za obdelavo pritiskov, ki se bo izvedlo takoj ko bo to mogoče, po obdelavi omogoči ponoven prehod v spanje. Po izvedbi omenjenih sistemskih klicev preide opravilo za pregledovanje tipkovnice ponovno v spanje do naslednjega intervala. 

Opravilo za obdelavo pritiskov je sledeče:

 

    KBDCON         ldaa          #$FF

                               jsr            _WAITS

                               ldaa          KBDVAL

                               anda         #$0F

                               ldab          #$FC

    RLOOP            addb         #4

                               asra

                               bcc           RLOOP

                               ldaa          KBDVAL

                               anda         #$F0

                               decb

    CLOOP            incb

                               asla

                               bcc           CLOOP

                               ldx            #B2LMAP

                               abx

                               ldaa          0,X

                               ldx            #LED

                               ldab          2,X

                               stab          3,X

                               ldab          1,X

                               stab          2,X

                               ldab          0,X

                               stab          1,X

                               staa          0,X

                               bra           KBDCON

 

Tu vidimo, da se celotno opravilo izvaja kot neskončna zanka, vzrok je ta, da tu pač ni potrebe po kakšni inicializaciji. Opravilo se na začetku postavi v spanje in čaka, dokler ga neko drugo opravilo ne postavi zopet v ready stanje, kar na že opisan način opravi opravilo za pregledovanje tipkovnice, ko zazna aktiven pritisk. Opravilo po prihodu iz spanja, preko spremenljivke KBDVAL določi vrednost od 0 do 15, iz katere s pomočjo B2LMAP(preslikava binarne vrednosti v LED) nato določi vrednost, ki jo zapiše na mesto, ki ustreza prvemu digitu prikazovalnika. Ostale znake pomakne za eno mesto, in zopet odide v spanje čakajoč na naslednji pritisk.

Za pravilno delovanje programa pa zapis samih opravil še ni dovolj. Najpomembnejši del je namreč ustvarjanje aktivnih opravil. To opravimo v posebni inicializacijski rutini, ki je sledeča:

 

    INIT            ldaa         #0

                        staa         _CRA

                        staa         _CRB

                        ldaa         #$0F

                        staa         _DPRA

                        ldaa         #$FF

                        staa         _DPRB

                        ldaa         #$04

                        staa         _CRA

                        staa         _CRB

                        ldaa         #%11110000

                        staa         _DPRA

                        ldaa         #%11111111

                        staa         _DPRB

 

                        jsr           _INITK

                        ldaa         #5

                        ldab         #1

                        ldy           #DISP

                        jsr           _CREAT

                        ldaa         #10

                        ldab         #2

                        ldy           #KBDSCAN

                        jsr           _CREAT

                        ldaa         #20

                        ldab         #3

                        ldy           #KBDCON

                        jsr           _CREAT

                        jsr           _STRTK

                        rts

Ta rutina je standarden del programa. V prvem delu je izvedena inicializacija vhodno/izhodne enote, ki pač potrebna v našem primeru. Nato sledi obvezen sistemski klic _INITK, ki nam inicializira sistemsko jedro. Temu klicu sledijo ustvarjanja opravil, s tem določimo začetna aktivna opravila. V našem primeru, bodo vsa tri opravila ustvarjena enkrat za vselej. Pomembno pri ustvarjanju, je primerna določitev prioritet posameznih taskov. V našem primeru , bomo največjo prioriteto (najnižje število) dodelili opravilu za osveževanje prikazovalnika. Jasno, je tudi, da mora imeti opravilo za obdelavo pritiskov manjšo prioriteto, kot pravilo za pregledovanje tipkovnice. Vsakemu opravilu moramo določiti tudi t.i. task id, s katerim nato v programu opravilo identificiramo. Za postopkom ustvarjanja opravil obvezno sledi,sistemski klic _STRTK, s katerim šele pravzaprav program dejansko poženemo. Kot prvo se izvede opravilo z najvišjo prioriteto in nato vsa nadaljna. Še enkrat naj opozorim, da je neprimerna izbira prioritet lahko vzrok za nepravilno delovanje uporabniškega programa.     

 

 

 

Jedro OS (izvorna koda) Demo program (prikaz znakov tipkovnice na LED prikazovalniku)