Avtor: mag. Vladimir Mitrović
E-pošta: vmitrovic12@gmail.com
V članku bom predstavil enega svojih projektov, ki daje odgovor na postavljeno vprašanje.
Projekt je specifičen in verjetno nihče od bralcev Sveta elektronike ne bo imel potrebe, da bi ga replicira, ampak vas vseeno vabim, da preberete članek: v njemu bom pokazal, s kakšnimi vse hardverskimi in softverskimi izzivi sem se srečal in kako sem jih uspel rešiti. Morda vam nekaj od tega pripomore pri realizaciji enega od vaših neobičajnih projektov!
No, pa pričnimo od začetka…
Uvodno razmišljanje
Predpostavimo, da želimo realizirati logično funkcijo “ekskluzivni ALI” (XOR) s pomočjo mikrokontrolerja ATmega328P. Risba na sliki 1 prikazuje resničnostno tabelo te funkcije in priključke mikrokontrolerja, ki jih želimo uporabiti kot vhode in izhode.
Ta vsebina je samo za naročnike
Na sliki 2 je z diagramom poteka prikazana logika mikrokontrolerskega programa, ki izvršuje XOR funkcijo. Postopek je enostaven: preberemo stanja vhodnih priključkov A in B ter, glede na njihovo kombinacijo, postavimo izhod Y v ustrezno stanje. Stanja preverjamo zaporedno in tukaj sta možna dva pristopa:
ko smo ugotovili katera vhodna kombinacija je bila prebrana, postavljamo izhod in se takoj vračamo na novo branje (levi diagram);
ko smo ugotovili katera vhodna kombinacija je bila prebrana, postavljamo izhod in (nepotrebno) nadaljujemo preverjati preostale kombinacije, ko se vračamo na novo branje (desni diagram).
Prvi pristop daje v poprečju hitrejši odziv na spremembo stanja vhodnih priključkov, medtem ko je prednost drugega pristopa v tem, da postopek preverjanja traja enako dolgo, neodvisno od tega, katera vhodna kombinacija je bila prebrana – to je včasih bolj pomembno od hitrosti.
O kakšni hitrosti je tukaj govora? Predpostavili bomo, da mikrokontroler ATmega328P dela na 16 MHz, kakor je običajno v Arduino projektih, in prvi program bomo napisali prav v Arduino IDE.
Arduino rešitev
Najprej bomo konfigurirali vhodne priključke in izhodni priključek:
void setup() {
pinMode(2, INPUT_PULLUP);
pinMode(3, INPUT_PULLUP);
pinMode(7, OUTPUT);
pinMode(13, OUTPUT);
}
Kakor je na sliki 1 prikazano, priključka 2 in 3 služita kot vhoda A in B, medtem ko je priključek 7 izhod Y. Tukaj smo dodatno konfigurirali še en izhodni priključek, 13, ki nima zveze z realizacijo zadane funkcije, vendar nam bo pomagal, da izmerimo trajanje celotnega postopka. Nato vstopimo v glavno programsko zanko, v kateri periodično kličemo funkcijo exor() in komplementiramo stanje priključka 13:
void loop() {
exor();
digitalWrite (13,!digitalRead(13));
}
Funkcija exor() je napisana po diagramu poteka prikazanem na sliki 2 levo:
void exor() {
byte AB = PIND & B00001100;
if (AB == B00000000){
digitalWrite(7, LOW);
} else if (AB == B00000100){
digitalWrite(7, HIGH);
} else if (AB == B00001000){
digitalWrite(7, HIGH);
} else if (AB == B00001100){
digitalWrite(7, LOW);
}
}
Ko vpišemo program v mikrokontroler, ga lahko preverimo tako, da postavljamo stikali S1 in S2 v različni stanji in spremljamo, ali sveti LED-ica ali ne (dioda bo svetila, kadar je izhodni priključek Y v stanju logične enice).
Da bi izmerili trajanje funkcije exor(), bomo potrebovali osciloskop. Vezali ga bomo na priključek 13 in merili trajanje ene polperiode (stanja “0” ali stanja “1”) pravokotnega signala prisotnega na tem priključku. Prav toliko traja izvršitev glavne zanke void(), klic in izvršitev funkcije exor() in komplementiranje stanja “merilnega” priključka 13. Iz tega rezultata moramo odstraniti nepotrebne stavke, zaradi česar bomo napisali nov program, ki namesto funkcije exor() kliče “prazno” funkcijo nop():
void loop() {
nop();
digitalWrite (13,!digitalRead(13));
}
void nop() {
}
Če sedaj z osciloskopom izmerimo trajanje ene polperiode pravokotnega signala na priključku 13, bomo ugotovili čas trajanja teh «nepotrebnih» stavkov; z odštevanjem sedaj dobljenega rezultata od predhodnega bomo vedeli, koliko točno traja izvršitev funkcije XOR v Arduino programu! Odvisno od kombinacije stanja na vhodnih priključkih smo dobili rezultate med 6,63 in 7,25 µs. Merjenje je narejeno na programu, ki je bil napisan za Arduino IDE 2.3.4.
Bascom-AVR rešitev
Sedaj bomo preverili hitrost ekvivalentnega Bascom-AVR programa! Na začetku bomo konfigurirati vhodne priključke in izhodni priključek:
Config Pind.2 = Input
Portd.2 = 1
Config Pind.3 = Input
Portd.3 = 1
Config Pind.7 = Output
Portd.7 = 0
Bascom-AVR ima izvrstni simulator, v katerem lahko točno izračunamo trajanje posamezne funkcije, zato za merjenje ne bomo potrebovali osciloskopa, niti nam ni bilo potrebno konfigurirati “merilnega” priključka. Funkcijo XOR bomo realizirali direktno v glavni zanki z uporabo identične logike, kot v Arduino IDE programu:
Do
Ab = Pind And &B00001100
Select Case Ab
Case &B00000000
Portd.7 = 0
Case &B00000100
Portd.7 = 1
Case &B00001000
Portd.7 = 1
Case &B00001100
Portd.7 = 0
End Select
Loop
Odvisno od kombinacije stanja na vhodnih priključkih je simulator “izmeril” trajanje zanke v razponu od 20 do 32 ciklov, kar pri predpostavljeni frekvenci 16 MHz ustreza trajanjem od 1,25 do 2 µs. Merjenje je narejeno z verzijo programa 2.0.7.9 demo.
Poglejmo še, kaj lahko pričakujemo od asemblerja!
Asemblerska rešitev
Asemblerski program je integriran v Bascom-AVR na način, da je glavna zanka zamenjana z asemblerskim programom:
Do
in r24,pind
andi r24,&B00001100
cpi r24,&b00000000
brne +2
cbi portd,7
cpi r24,&b00000100
brne +2
sbi portd,7
cpi r24,&b00001000
brne +2
sbi portd,7
cpi r24,&b00001100
brne +2
cbi portd,7
Loop
Asemblerski program je napisan po diagramu poteka na sliki 2 desno, traja 18 ciklov (= 1,125 µs), neodvisno od kombinacije stanja na vhodnih priključkih. Če uporabimo algoritem s slike 2 levo, dosežemo trajanja v razponu od 8 do 17 ciklov (= 0,5 do 1,0625 µs).
Zgolj zaradi primerjave so v tabeli prikazani rezultati vseh merjenj: (tabela vsebuje tudi nekatere meritve, ki jih nismo opisali), glej tabelo.
Opazimo, da imajo vse programske rešitve znatno počasnejši odziv od ekvivalentnega integriranega vezja v CMOS, HC ali HCT izvedbi, pri čemer se izpostavi nerazumljivo počasno Arduino rešitev. Prav tako je interesantno opaziti, da v Arduino rešitvi “fiksni” algoritem, kodiran po sliki 2 desno, pravzaprav nima fiksnega trajanja, ampak je trajanje odvisno od vhodne kombinacije – očitno Arduino prevajalnik dela neko optimizacijo programa na neobičajen in ne ravno optimalen način.
S poizkusom smo dokazali, da lahko softversko uspešno realiziramo neko logično funkcijo, če nam je sprejemljiv počasnejši odziv na spremembe vhodnih stanj. Moramo pa tukaj še komentirati dve stvari:
Zaporedno preverjanje kombinacij vhodnih stanj izgubi smisel pri realizaciji zahtevnih logičnih funkcij z večjim številom vhodnih spremenljivk, če želimo sočasno realizirati nekaj funkcij. V takšnih primerih priporočam, da se vhodna kombinacija uporabi kot indeks v tabeli, v kateri smo definirali izhodna stanja vseh funkcij. Bascom-AVR rešitev bi izgledala tako:
Do
Ab = Pind And &B00001100
Y = Lookup(ab , Y_table)
…
in traja malo manj kot 2,94 µs, neodvisno od števila vhodnih spremenljivk (do 8), vhodnih kombinacij in številu funkcij, ki jih realiziramo (do 8). Na to moramo še dodati prenos posameznega stanja na izhodni priključek, kar bi v našem primeru lahko realizirali tako:
Portd.7 = Y.0
…
Loop
Trajanje tega ukaza je 0,75 µs, za vsak izhodni priključek, ki ga želimo postaviti. Lahko pričakujemo, da bi ekvivalentni asemblerski program bil nekaj hitrejši, kar tukaj ne bomo več raziskovali.
Druga stvar, ki jo moramo upoštevati je to, da mikrokontrolerja, kot je ATmega328P, in tudi katerega znatno manjših možnosti, sigurno ne bomo uporabili samo zato, da bi opravljal neko logično funkcijo: to bo samo ena od nalog, ki jih je treba narediti. Te “prave” naloge morajo – ali pa ne, biti odvisne od rezultata logičnega izraza. Je pa za pričakovati, da bo precej kompleksnejši od realizacije same logične funkcije, kar sočasno pomeni, da bo programska koda, s katero realiziramo funkcijo, redkeje prihajala na vrsto za izvršitev. Zato bo odziv na spremembe vhodnih stanj pogosto tudi znatno počasnejši od vrednosti, ki smo jih dobili z dosedanjo analizo.
Temu se bomo izognili, če bomo logično funkcijo implementirali v prekinitveno rutino, ki bo klicana, ko bo zaznana sprememba vhodnih stanj. Mikrokontroler ima za to ustrezne mehanizme, vendar bo uporaba prekinitvene rutine dodatno upočasnila odziv na spremembo vhodnega stanja, saj moramo poskrbeti za registre, ki jih prekinitvena rutina uporablja. Predvsem pri Bascom-AVR klic »prazne« prekinitvene rutine s čiščenjem in povrnitvijo stanja vseh registrov ter vrnitvijo v glavni program traja 7,25 µs, pri čemer je največji del poskrbljen za registre. Ta čas se lahko pomembno skrajša, če v prekinitveni rutini shranimo in vračamo samo registre, katere uporabljamo, vendar je v tem primeru spet potrebno programirati v asemblerju.
Spoznajmo se z nalogo!
V nadaljevanju bomo predstavili napravo, na kateri bomo v praksi preizkusili, ali lahko relativno zapleteno logično vezje nadomestimo s programsko opremo. Gre za nekoč zelo zmogljiv žepni kalkulator HP-41C, ki ga je Hewlett-Packard izdeloval v obdobju 1979-1990. leto (slika 3). Kalkulator je programabilen, ima alfanumerični zaslon in vrsto novosti, s katerimi je izstopal med takratno konkurenco (če je sploh obstajala prava konkurenca).
Še posebej zanimiva je možnost, da se skriva pod štirimi pokrovčki na zgornji strani kalkulatorja (slika 4). Z odstranitvijo pokrova se odpre dostop do notranjega vodila kalkulatorja, na katerega se lahko priključijo različni moduli in naprave, ki jih ponuja Hewlett-Packard in drugi neodvisni proizvajalci. Ti moduli ponujajo dodatne pomnilniške kapacitete, nabore programov z različnih področij, možnost sintetičnega in asemblerskega programiranja ter različne nadgradnje strojne opreme.
Logično je, da je takšna naprava pritegnila množico navdušencev, ki so proučevali njene zmožnosti in svoje ugotovitve objavljali interesnim skupinam v specializiranih revijah. Z današnje perspektive je to težko razumeti, a takrat še ni bilo spletnih forumov in socialnih omrežij, kjer bi se takšne informacije delile. Izdanih je bilo tudi več knjig, ki so nam predstavile skrite zmožnosti kalkulatorja HP-41C (slika 5).
Tudi sam sem bil očaran nad zmožnostmi kalkulatorja HP-41C in za denar, ki sem ga porabil zanj, razne module in literaturo, bi si dandanes lahko kupil spodoben osebni računalnik… A ni mi žal – raziskovalno navdušenje, ki mi ga je dal HP-41C, je vredno stokrat več!
Eden od hardverskih dodatkov za HP-41C, ki so ga entuziasti razvili, je MLDL (Machine Language Development Lab). Gre za vezje narejeno s približno 20 integriranih vezij v CMOS tehnologiji in nekaj pomnilnika, kar je kalkulator “videl” kot svojo nadgradnjo in ki je omogočala, poleg ostalega, tudi asemblersko programiranje (programiranje v “lastnem” programskem jeziku jim je omogočal sam kalkulator). Kot pravi zaljubljenec v HP-41C sem zbral vso potrebno dokumentacijo, narisal shemo (slika 6), projektiral tiskano vezje, nabavil vse komponente in tukaj obstal – takrat so me potegnili drugi novi izzivi.
Tako je moj projekt MLDL končal v predalu. Spomnil sem se ga pred dvema letoma in se vprašal, ali bi lahko nekaj realiziral s pomočjo mikrokontrolerja. Bil je to velik izziv s hardverskega in s softverskega pogleda, in kaj vse sem se naučil in ali sem uspel, bom delil z vami v naslednjem nadaljevanju!
https://svet-el.si