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.
Č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 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.
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.
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.
_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.
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:
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)