Kasutaja tarvikud

Lehe tööriistad


juhendid:avr

AVR mikrokontroller

AVR on Atmeli poolt toodetav 8 bitiste CMOS tehnoloogias valmistatud RISC mikrokontrollerite seeria. Harvardi arhitektuuri kohaselt on AVR-il eraldi programmi- ja andmemälu. Programmi jaoks on süsteemisiseselt ümberkirjutatav Flash mälu, andmete jaoks SRAM ja EEPROM. AVR teostab ühe instruktsiooni takti jooksul, kuid on ka mitmetaktilisi instruktsioone. Sellegipoolest nimetab Atmel tema jõudluseks 1 MIPS megahertzise takti kohta. Taktsagedus on kuni 16MHz (erandlikud kiibid kuni 20MHz).

AVR mikrokontrollerite tootmist alustati 1997 aastal ja praeguseks on see vabakutseliste elektroonikute seas üks levinumaid. Esialgse edu tagasid odavad arendusvahendid, mitmekesine perifeeria ühes korpuses ja madal voolutarve. Nüüdseks võib eeliseks lugeda suure infomaterjali ja õpetuste pagasi mis aastate jooksul tekkinud on. Paratamatult on AVR tehnoloogia vananev kuid konkurentsis püsimiseks teeb Atmel ka kaasaegse perifeeria ning 16 ja 32 bitiste siinidega AVR mikrokontrollereid, millest esimesed on 8-bitistega ühilduvast XMega ja teised täiesti uuest AVR32 seeriast.

ATmega8

Vastavalt rakenduste tüübile on ka AVR mikrokontrollereid olemas erineva konfiguratsiooniga. Suurema osa AVR-e moodustab megaAVR seeria mis on suure programmimälu mahuga. Vastupidiselt megaAVR-ile on olemas tinyAVR seeria väiksemate korpuste ja kärbitud võimalustega. Lisaks on veel AVR-e USB, CAN, LCD, Zigbee, automaatika, valgustuse juhtimise ja akutoitega seadmete jaoks.

Järgnevalt on kirjeldatud peamisi megaAVR seeria mikrokontrollerite võimalusi selle seeria ühe lihtsaima kontrolleri - ATmega81) näitel. Üldiselt on kõigil AVR seeria mikrokontrolleritel registrite nimed, tähendused ja kasutamise kord reglementeeritud, nii et näiteid saab väikeste muudatustega ka teiste kontrollerite puhul kasutada. Erinevusi esineb keerukama perifeeria juures. Koodinäited on toodud Assembleris ja C-keeles AVR Libc abil.

Füüsiline kest

Nagu kõik teisedki kiibid on ka AVR mõne kesta sisse integreeritud. Traditsiooniline kest on DIP (nimetatakse ka DIL). DIP on niiöelda jalgadega kest - kõik kiibi viigud on mõnemillimeetriste jalgadena välja toodud. DIP kest on mõistlik valik hobirakendustes ja prototüüpide puhul, sest selle jaoks on saada odavad pesad kust mikrokontrolleri saab läbipõlemise korral lihtsalt kätte ja uuega asendada.

Samas on jalad ka DIP kesta miinuseks, sest nende jaoks on vaja trükkplaadile auke puurida. Palju kompaktsemad on pindliides, ehk SMT kestad, sest neil on jalad mõeldud mitte plaadi läbistamiseks vaid otse rajale kinni jootmiseks. SMT kestade kasutamisel on aga vaja täpsemat kätt ja paremaid töövahendeid.

AVR-e on saada mitmesugustes kestades. Viikusid on püütud loogiliselt ning elektriliselt ühtlaselt paigutada. Näiteks on maa ja toiteviigud suurematel kiipidel toodud mitmesse kiibi külge, välise kvartsi viigud on maa viigu lähedal, siinide viigud on numbrilises järjekorras, andmesideliideste viigud on kõrvuti jne. Kuna AVR on CMOS tehnoloogias valmistatud siis toimivad kõik selle digitaalsed, teisisõnu ka loogilised, viigud 0-5V vahemikus. 0V on loogiline null (0, madal, maa, ground, GND), 5V on loogiline üks (1, kõrge, high) 2). Tehnoloogiast tingituna on ka analoogpinge väärtused lubatud sarnases 0-5.5V vahemikus.

ATmega8 kest

ATmega8 kesta viigud.

Et järgnevatest näidetest ATmega8 kohta paremini aru saada, on välja toodud ATmega8 DIP kesta viikude skeem. Viikude juures on selle number, primaarne funktsioon ja sulgudes alternatiivne funktsioon. Toiteotsad on GND ja VCC. AVCC ja AREF on vastavalt analoog-digitaalmuunduri toite ja võrdluspinge viigud. XTAL1 ja XTAL2 on välise kvartsostsillaatori, resonaatori või taktigeneraatori jaoks, kuigi taktsignaalina allikana saab kasutada ka ATmega8 sisemist RC ostsillaatorit. Viigud PB0 kuni PD7 tähistavad IO siinide bitte. Kõigist viikude funktsioonidest tuleb juttu allpool.

Joonis 1. ATmega8 kesta viigud.

Arhitektuur

ATmega8 sisu üldskeem

Joonis 2. ATmega8 sisu üldskeem.

AVR-il on sisemine 8-bitine andmesiin kustkaudu liiguvad andmed arvutusüksuse (ALU), staatuse registri (SREG), programmiloenduri (PC), muutmälu (SRAM) ja perifeeria vahel. ALU-s täitmisele minev programm, ehk instruktsioonide jada, tuleb Flash mälu aadressilt mille määrab programmiloendur. ALU juurde kuuluvad 32 üldkasutatavat 8-bitist registrit, mida kasutatakse paljude instruktsioonide täitmisel operandidena.

Üldkasutatavad registrid

Üldkasutatavaid registrid R0-R31 on justkui vahepuhvrid mälu ja perifeeria andmete hoidmiseks ning nendega toimetamiseks. Üldkasutatavad registrid lihtsustavad protessori arhitektuuri, kuna ALU jaoks on need kiirelt kättesaadavad ja igal arvutusel ei pea operandide lugemiseks mälust andmesiini kasutama hakkama. Üldkasutatavaid registreid kasutatakse kõikide andmetega seotud aritmeetiliste ja loogiliste operatsioonide tegemiseks.

Assembler keeles programmeerides võib kiiret töötlust vajavad andmeid üldkasutatavas registrites hoida. Kui programmeeritakse C keeles ja soov on kindlasti üldkasutatavat registrit muutuja hoidmiseks kasutada defineeritakse muutuja täiendavalt „register“ tüüpi. Näiteks:

register char x;

Käsustik

AVR-i käsustik3) koosneb 90-133 erinevast instruktsioonist. ATmega8-l on 130 instruktsiooni. Instruktsioonid on kas ühe, kahe või üldse ilma operandideta. Enamus instruktsioone töötab ühe takti jooksul, kuid keerukamad kulutavad kuni 5. AVR-i järeltulija XMega puhul on mitmeid instruktsioone täiendatud, nii et need kulutavad vähem takte. Suurem osa AVR instruktsioonidest on andmete liigutamiseks, võrdlusteks, loogilisteks- ning aritmeetilisteks teheteks. Tehete ja võrdluste puhul on kasutusel olekuregister kuhu märgitakse vastavate bittidena ära kui tehte tulemus ALU-s oli negatiivne või positiivne, oli null, ületas maksimaalse võimaliku väärtuse (8-bitti), vajab biti ülekandmist järgmisesse tehtesse ja paaril keerukamal juhul veel.

Näide

Toodud on assembleris, ehk puhtalt instruktsioonidena kirjutatud kood mis liidab muutmälus aadressil $1000 (detsimaalarvuna 4096) asuvale baidile juurde arvu 5. Kasutatud käsud on olemas kõigil AVR-idel.

ldi  r1, 5       ; Konstandi 5 laadimine üldkasutatavasse registrisse r1
lds  r2, $1000   ; Baidi laadimine muutmälust registrisse r2
add  r2, r1      ; Registrile r2 registri r1 väärtuse liitmine
sts  $1000, r2   ; Registri r2 väärtuse kirjutamine muutmällu vanale kohale tagasi

Programmi pinumälu

Pinumälu (Stack) on andmete ülesehitus kus viimasena mällu kantud andmed loetakse esimesena välja. AVR-is saab pinumälu kasutada alamfunktsioonide, katkestuste ja ajutiste andmete juures. Alamfunktsioonide ja katkestuste täitmisel lisatakse eelnevalt pinumällu programmiloenduri aadress mille pealt programm katkes. Kui alamfunktsioon või katkestus on töö lõpetanud loetakse pinumälust aadress kust programmi tööd jätkata. Ajutisi andmeid lisatakse pinumällu tavaliselt lühemate programmilõikude juures mis ei vaja mälu reserveerimist kogu programmi ajaks.

MegaAVR seeria mikrokontrolleritel on pinumälu füüsiline asukoht muutmälus, kuid mõnel tinyAVR seerial muutmälu üldse puudub ja pinumälu tarbeks on spetsiaalne üsna piiratud mahuga mälu. Sellistele, ilma muutmäluta mikrokontrolleritele kompilaatoreid üldjuhul pole, või nad lihtsalt ei suuda piiratud mahuga hakkama saada.

Kõrgtaseme keeles (Pascal, C, C++) programmeerides ei pea otseselt mikrokontrolleri siseeluga kursis olema, sest kompilaator valib ise oma äranägemise järgi üldkasutavaid registreid ja instruktsioone, kuid see teadmine tuleb kasuks. Oluline on mikrokontrolleri instruktsioone tunda ajakriitilistes rakendustes kus protseduurid peavad toimuma loetud protsessori taktide jooksul.

Töötakt

Nii nagu enamus digitaalelektroonikatki, töötab AVR kindlal taktsagedusel. Kindel taktsagedus tagab kogu kiibi ulatuses andmevahetuse töökindluse. Taktsignaali, ehk töötakti genereerimiseks on AVR-i puhul mitu võimalust:

  • Sisemine RC ostsillaator
  • Väline RC ostsillaator
  • Väline kvarts
  • Väline resonaator
  • Väline taktsignaal

IO siinid

Kõik AVR siinid on loetavad ja kirjutatavad kui neid kasutada tavalises loogilises sisend-väljund (IO) režiimis. AVR siinid on nimetatud suurte ladina-tähestiku algustähtedega A, B, C, jne. Mõnel AVR-il võib aga siin A puududa, kuigi B on olemas. Iga siin on 8 bitine ja iga biti jaoks on enamasti kontrolleri kestast välja toodud eraldi viik. Viikusid loendatakse arvudega alates nullist. Siini mõlema kasutussuuna jaoks on olemas kaks eraldi registrit. Lisaks on olemas iga siini kohta register siini reaalse toimimissuuna määramiseks milles biti väärtus 1 näitab viigu kasutamist väljundina ja 0 sisendina. Kokku on iga siini kohta kolm registrit:

  • PORT - Siini füüsilise väljundoleku määramiseks.
  • PIN - Siini füüsilise sisendoleku lugemiseks.
  • DDR - Siini füüsilise suuna määramiseks.

Näide

Vaja on siini B viigud 0-3 teha sisenditeks, viigud 4-7 väljunditeks, seada 5-s viik kõrgeks ja lugeda 0-3 viigu väärtus muutujasse. C keele programmi kood on järgnev:

// Siini B suunaregistri määramine
DDRB = 0xF0;
 
// Viienda viigu kõrgeks seadmine
PORTB |= 0x20;
 
// Sisendviikude väärtuse lugemine muutujasse x
x = PINB & 0x0F;
  • Esimesena määratakse siini B viigud 0-3 sisendiks ja 4-7 väljundiks.
  • Teise etapina toimub siini väljundregistri väärtuse loogiline liitmine arvuga mille tulemusena muutub siini B viies bitt (viik) üheks.
  • Kolmandana toimub muutujale x siini B bittide 0-3 väärtuse omistamine.

Toodud näites on sisendeid kasutatud Hi-Z, ehk kõrge impendatsiga režiimis (sisend mis ei koorma signaaliallikat). Seda režiimi võib vaja minna kui viiku kasutatakse andmesiinina. Kui viik on kasutusel nupuga, lülilitiga või muus maad ning sisendit kokku ühendavas lahenduses, siis tasub sisendis kasutada pull-up takistit. Selleks tuleb inimlikke loogikareegleid eirates sisendrešiimis seada kõrgeks vastava viigu väljundbitt - tulemusena lülitub toitepinge ja sisendi vahele takisti mis hoiab sisendi pingenivood kõrgel kui miski seda just alla ei tõmba. Pull-up takisti eesmärk on ära hoida sisendi „ujumine“ staatilise elektri ja muude häirete tõttu.

Enamasti on IO siinil olevaid viike peale loogiliste ühenduste kasutatud ka muu perifeeria tarbeks. Näiteks analoog-digitaal muunduri sisenditena, sideliinide viikudena jne. Viigu muid funktsioone nimetatakse alternatiivseteks. Kui on kasutusel viigu alternatiivfunktsioon, tuleb IO siinil asuv viik sisendiks defineerida - siis ei hoia siin viiku ühelgi väärtusel kinni. Kuna programmi käivitumisel on kõik IO siinid vaikimisi sisend-režiimis siis pole alternatiivfunktsioone kasutades vaja viike uuesti sisendiks defineerima hakata. Seda muidugi juhul kui vahepeal neid väljundiks pole defineeritud.

Katkestused

Katkestuse (Interrupt) saavad AVR-il tekitada loendurid, andmesideliidesed, analoog-digitaal muundur, komparaator, spetsiaalsed IO viigud ja paljud muud asjad, olenevalt kontrollerist. Igat katkestust saab lubada või keelata seda genereerivas üksuses. Olenemata katkestuse lubamisest või mitte, on iga katkestuse jaoks vastavas kontrolleri üksuses 1-bitine andmeväli (lipuke, Interrupt flag) mis märgitakse katkestust põhjustava sündmuse toimumisel tõeseks. Kui toimub nimetatud andmevälja muutmine ja katkestus on lubatud, hakkab kontroller täitma koodi mis oli katkestuse toimumisel ette nähtud.

Näide

Katkestuste kasutamiseks AVR Libc teegiga tuleb kasutusele võtta interrupt.h fail. Katkestusel täitmisele minev programmikood lisatakse peale ISR nimelist võtmesõna. ISR järele sulgudesse kirjutatakse katkestuse nimi. C keele koodinäide:

#include <avr/interrupt.h>
 
ISR(XXX_vect)
{
  // Tee midagi
}

Kõigi katkestuste toimumise lubamine määratakse ära SREG registris. Võimaluse kõiki katkestusi keelata või lubada tingib andmete kaitse vajadus. Kuna katkestused katkestavad käimasoleva programmi täitmise, võivad nad segada või rikkuda andmeid mida põhiprogramm katkestamise hetkel kasutas. Sellist olukorda saab vältida kui enne tundlikke andmetega tegelemist keelata kõik katkestused. Globaalne katkestuste keelamine on lihtne kui seda saab ühe registri (SREG) muutmisega teha. Peale kriitilise programmiosa lõppu saab katkestused uuesti lubada ja kõik katkestused mille lipuke vahepeal ära märgiti lähevad täitmisele.

Näide

Oletame, et programmis on kasutusel 16-bitine muutuja mille väärtust muudab nii põhiprogramm kui katkestuse programmiosa ja selle muutuja väärtus omistatakse hiljem teisele muutujale:

#include <avr/interrupt.h>
 
// Globaalsed 16-bitised muutujad x ja y
uint16_t x, y;
 
// Suvaline katkestus mis muudab x väärtuse
ISR(XXX_vect)
{
  x = 0x3333;
}
 
int main()
{
  // Muutujale x väärtuse omistamine
  x = 0x1111;
 
  // Mingi katkestuse tööle seadistamine
  ...
 
  // Globaalne katkestuste lubamine - võrdub SREG registris I biti märkimisega
  sei();
 
  // x väärtuse muutujasse y laadimine
  y = x;
 
  // Programmi lõpp
}

Programm on väga lihtne - algul omistatakse muutujale x väärtus 0x1111 ja hiljem selle väärtus omakorda muutujale y. Kui vahepeal tekib katkestus, saab x-i väärtuseks 0x3333. Loogikareeglite järgi saab muutujal y programmi lõpus olla kaks võimalikku väärtust, kuid 8-bitise AVR-i peal on ka kolmas võimalus. Nimelt, 8-bitise arhitektuuriga toimub 16-bitiste andmete liigutamine 2 takti jooksul ja vahepeal tekkiv katkestus võib andmete ühtsust rikkuda. Niisiis, peale 0x1111 ja 0x3333 võib tekkida ka väärtus 0x3311. Et sellist asja ei juhtuks, tuleks enne operatsioone, mis toimuvad enam kui 1 takti jooksul, katkestused globaalselt või lokaalselt ajutiselt keelata.

Toodud näites muutujale y muutuja x väärtuse omistamine ohutul meetodil:

  // Globaalne katkestuste keelamine
  cli();
 
  // Laadimine
  y = x;
 
  // Globaalne katkestuste lubamine
  sei();

Välised katkestused

Välised katkestused (External Interrupt) on ühed lihtsaimad perifeeria funktsioonid. AVR-idel on tavaliselt 1 kuni 8 spetsiaalset viiku mille loogilise väärtuse muutumisel või kindlal olekul tekitatakse programmis katkestus. Kuna enamasti kasutatakse seda funktsiooni kontrolleriväliste loogikasignaalide jälgimiseks siis nimetataksegi vastavaid viike välise katkestuse viikudeks.

Välise katkestuse kasutamiseks tuleb viiku kasutada tavalises IO sisend-režiimis (võib ka väljund-režiimis kasutada aga siis saab katkestust tekitada vaid kontroller ise). Välise katkestuse seadistusregistrites tuleb ära märkida kas lubada katkestuste tekitamine ja mille peale seda teha. Võimalikke tekitajaid on neli:

  • Loogiline null (pinge on 0V)
  • Loogilise väärtuse muutus
  • Langev front - loogiline muutus ühest nulli.
  • Tõusev front - loogiline muutus nullist ühte.

Loogilise nulli valimisel katkestuse tekitamiseks, tekitatakse katkestust järjest senikaua kuni viigu väärtus on null ja samal ajal põhiprogrammil töödata ei lasta.

Väliseid katkestusi on tööpõhimõttelt kahte liiki: kontrolleri taktiga sünkroniseeritud ja asünkroonsed. Sünkroniseeritud katkestused toimivad sisendite väärtuse meelespidamise teel - see tähendab, et loogilised muutused leitakse kahel erineval taktil saadud väärtuste võrdlemise teel. Kui välise signaali loogilised muutused toimuvad kiiremini kui käib töötakt, siis katkestused ei teki õigesti või ei teki üldse. Asünkroonsed katkestused ei sõltu kontrolleri taktist ja võimaldavad natuke kiiremini muutuvat välist signaali - loogilist nivood peab signaal hoidma vähemalt 50ns. ATmega8-l on ainult 2 sünkroniseeritud välist katkestust.

Näide

Vaja on panna ATmega8 kesta viik number 4, ehk siini D viik 2 tekitama katkestust kui selle väärtus muutub.

#include <avr/interrupt.h>
 
// Väline katkestus 0
ISR(INT0_vect)
{
  // Tee midagi
}
 
int main()
{
  // Siini D viigu 2 muutmine sisendiks biti 4 nullimise teel
  DDRD &= ~_BV(DDRD2);
 
  // Siini D viigule 2 pull-up takisti määramine sisendi kõikumise vastu
  PORTD |= _BV(PORTD2);
 
  // Väliste katkestuste seaderegistris katkestuse 0
  // tekitajaks loogilise muutuse määramine
  MCUCR = _BV(ISC10);
 
  // Välise katkestuse 0 lubamine
  GICR |= _BV(INT0);
 
  // Globaalne katkestuste lubamine
  sei();
 
  // Lõputu programmitsükkel
  while (1) continue;
}

Lisaks üksikute viikude tekitatavatele katkestustele on suurematel AVR-idel ka tervete gruppide viikude loogiliste väärtuste muutuste katkestusi võimalik kasutada. Neid katkestusi nimetatakse lihtsalt viigu-muutus katkestusteks (Pin Change Interrupt). Rakenduvad nad siis, kui vähemalt ühe viigu väärtus grupis muutub.

Loendurid

Loendurid (Counter), teatud mõttes taimerid (tuleneb inglise keelsest sõnast timer), on mikrokontrollerite ühed vajalikuimad funktsioonid. Nende abil saab protsesse täpselt ajastada, signaale genereerida ja sündmusi loendada. Loenduri tööpõhimõte seisneb sisendtaktide arvu trigerite ahela abil binaarväärtuseks teisendamises. Ahela pikkusest oleneb maksimaalne loendatavate taktide arv mida tähistatakse kahendkoodi pikkusega. AVR mikrokontrolleril on loendurid 8 ja 16-bitised. Kui loendur omandab maksimaalse väärtuse, täitub loendur järgmise taktiga ja omandab väärtuse 0 ning loendamine algab uuesti nullist. Loenduri taktsignaal saab tulla mikrokontrolleri töötaktist ja sel juhul on võimalik selle sagedus sagedusjaguriga (Prescaler) ka vähendada. Mõnel AVR-il on ka sisemine eraldiseisev taktsignaali generaator mille sagedust saab sageduskordistiga tõsta. Sageduskordistiga on näiteks USB liidesega AVR-id. Loendurid erinevat ka rakendusvõimaluste ja töörežiimide poolest.

Tüüpiline loendur

Loendurit, mille taktsignaal tuleb töötaktist, võib nimetada tüüpiliseks loenduriks. Sel loenduril on lisaks hetkeväärtuse lugemisele vaid üks lisavõimalus - katkestuse tekitamine loenduri ületäitumisel. Tüüpilise loenduri signaaltakti sagedust on võimalik vähendada kuna see tuleb kontrolleri töötaktist. Tüüpilist loendurit või loendurit tüüpilises režiimis kasutatakse tavaliselt kindlate ajaintervallide järel programmi täitmiseks.

Näide

Vaja on 8MHz taktsagedusel töötav ATmega8 10ms (ehk 100Hz) ajavahemiku järel katkestust tekitama panna. Ülesandeks sobib 8-bitine loendur 0.

#include <avr/interrupt.h>
 
// Loenduri 0 ületäitumine
ISR(TIMER0_OVF_vect)
{
  // Loendurile sellise väärtuse omistamine, et järgmine täitumine saabuks 10ms pärast.
  // Valem: 256 - 8MHz / 1024 / 100Hz = 177,785 = ~178
  TCNT0 = 178;
 
  // Tee midagi, aga alles pärast loendurile uue väärtuse omistamist
}
 
int main()
{
  // Sagedusjaguri teguriks 1024
  TCCR0 = 0x05;
 
  // Loenduri täitumise katkestuse lubamine
  TIMSK |= _BV(TOIE0);
 
  // Globaalne katkestuste lubamine
  sei();
 
  // Lõputu programmitsükkel
  while (1) continue;
}

Näites toodud loendurile omistatava väärtusega siiski täpselt 10ms järel katkestust ei tekitata, sest väärtuse arvutamisel toodud valemiga tekib komakohaga arv. Et täpset katkestuse intervalli saada tuleb nii sagedusjaguri tegur kui loendurile täitumisel omistatav väärtus valida sedasi, et tekiks täisarv. Paraku pole see alati võimalik ja eriti just 8-bitise loenduri puhul, sest selle skaala on üsna väike. Näiteks pole 8MHz taktsagedusel töötava ATmega8 loenduriga 0 võimalik tekitada katkestust ka tihedamalt kui ~32ms järel. Täpsema ja suurema intervalli tekitamiseks saab kasutada 16-bitist loendurit.

Kõik muud loendurid täiendavad tüüpilist loendurid. Seega kõigil loenduritel on tüüpilise loenduri tunnusjoon - ületäitumise katkestus, kuid olenevalt režiimist võib see katkestus erinevatel juhtudel tekkida.

Välise taktika loendur

Loenduri taktsignaalina saab kasutada ka mikorkontrolleri-välist signaali (External clock source). Selleks on AVR mikrokontrolleril Tx viik, kus x tähistab loenduri numbrit. Välist taktsignaali ja polaarsust saab valida sagedusjaguri registriga. Välise taktika loenduril on muus osas samasugused omadused kui tüüpilisel loenduril.

Sündmuste mõõtmine

Kuna loendurid võimaldavad mõõta aega, on keerukamatel AVR mikrokontrolleritel võimalus ka riistvaraliselt mõõta aega mil toimus mingi sündmus. Seda loenduri osa nimetatakse sündmuse püüdjaks (Input Capture Unit). AVR-is on valida kahe sündmuse vahel: spetsiaalse sisendviigu või analoog-komparaatori võrdlustulemuse loogilise väärtuse muutus. Kui toimub valitud sündmus, kirjutatakse loenduri väärtus spetsiaalsesse registrisse kust selle võib soovitud ajal välja lugeda. 16-bitise loenduri puhul toimub tegelikult kahe 8-bitise väärtuse kirjutamine/lugemine, kuid kõrgtaseme keelt kasutades paistab see siiski ühena. Kui sündmuse toimumise aeg on pikem kui loenduri ületäitumise aeg, tuleb tarkvaraliselt lugeda ka loenduri ületäitumisi (näiteks ületäitumise katkestusega) ja need lõpptulemusse arvestada.

Näide

Vaja on 8MHz taktsagedusel töötava ATmega8-ga mõõta välise 200Hz - 200kHz loogilise nelinurksignaali sagedust 1Hz täpsusega. Programm on tehtud loendur 1 sündmuste püüdjaga.

#include <avr/interrupt.h>
 
uint32_t frequency;
 
// Sündmuse toimumise katkestus
ISR(TIMER1_CAPT_vect)
{
  // Tulemus on ainult siis arvestatav kui loendur pole ületäitunud
  if (TIFR & _BV(TOV1) == 0)
  {
    frequency = (uint32_t)8000000 / (uint32_t)ICR1;
  }
 
  // Loenduri nullimine - aga mitte nulliga
  TCNT1 = 10;
 
  // Loenduri ületäitumise lipukese nullimine
  TIFR &= ~_BV(TOV1);
}
 
int main()
{
  // Tõusva frondi registreerimine, sagedusjaguri tegur 1
  TCCR1B |= _BV(ICES1) | _BV(CS10);
 
  // Sündmuse toimumise katkestuse lubamine
  TIMSK |= _BV(TICIE1);
 
  // Globaalne katkestuste lubamine
  sei();
 
  // Lõputu programmitsükkel
  while (1) continue;
}

Programmis tekib välise signaali tõusva frondi ajal sündmuse katkestus. Katkestuse jooksul kontrollitakse ega loenduri ületäitumine pole toimunud - see saab juhtuda kui signaali sagedus on alla 123Hz (8MHz / 16bit) ja sel juhul ei kajasta loenduri väärtus reaalset perioodi. Sagedus arvutatakse 32 bitiste arvudega pöördväärtusena perioodist. Katkestuse lõpus nullitakse loendur ja ületäitumise lipuke.

Tegelikult pole siinne tulemus veel päris täpne. Kuna arvutustehted katkestuse jooksul võtavad aega mitmeid protsessori takte siis loenduri nullimine ei toimu signaali tõusval frondil vaid täpselt niipalju hiljem kui kulub aega arvutusteheteks. Selle kompenseerimiseks ei nullita loendurit mitte arvuga 0 vaid ligikaudse kaotatud taktide arvuga. Katkestuse programmiosa tööaeg seab piirangud ka maksimaalsele mõõdetavale sagedusele.

Sündmuste püüdmist ning nende aja registreerimist saab teha ka tarkvaraliselt. Saab kasutada väliseid või muid katkestusi ja nende tekkimise ajal lugeda loenduri väärtuse. Kuid riistvaraline sündmuste püüdmine on mõeldud eeskätt siiski programmist sõltumatuks töötamiseks ja suhteliselt lühikeste sündmuste mõõtmiseks.

Signaali genereerimine

Peale signaali pikkuse mõõtmise saab keerukamate loenduritega ka signaali tekitada. Selleks on loenduril väärtuse võrdlemise (Output Compare Unit) ja võrdlustulemuse väljastusüksus (Compare Match Output Unit). Võrldusüksusesse kuuluvad registrid sama bitilaiusega kui loendur ise ja mille väärtusi võrreldakse loenduri väärtusega selle töö ajal. Hetkel mil loenduri väärtus saab võrdseks võrdlusüksuse registri väärtusega saab tekitada katkestuse ja spetsiaalsete väljundviikude oleku muutuse. Väljundviigu oleku muutused tekitavadki elektrisignaali.

Väljastusüksuses on võimalik seadistada väljundviikude käitumist võrdusmomendil. Valida on viigu kõrgeks muutmise, madalaks muutmise ja ümbermuutmise vahel. Nende täpsem toime aga sõltub sellest millist signaali genereerimise režiimi kasutada. Kui kasutusel on PWM signaali (täpsemalt allpool) tekitamise režiim siis viik muutub loenduri täitumisel kas madalaks või kõrgeks, vastupidiselt sellele mida viik teeb võrdusmomendil.

Mõnedel signaali genereerimise režiimidel on määratav ka loenduri suurim väärtus - loenduri füüsiline suurus jääb küll samaks, kuid mängus on võrdlusregister mille väärtust ületades loendur nullitakse. Seda võimalust kasutades saab eelpool toodud ülesandeid täpse ajalise katkestuse tekitamise kohta lahendada, kuid mõeldud on see pigem signaali perioodi muutmiseks. Vähe sellest - mõnes režiimis toimib loendur juurde ja maha lugedes.

Loendurid ja eriti just nende signaali genereerimise režiimid on ühed keerulisemad üksused AVR-il. Kõigist neist kirjutamine läheks pikaks ja enamasti pole nende juures vaja ka kõike teada. Seetõttu on järgnevalt kirjeldatud vaid üht levinuimat PWM signaali robootikas. Ülejäänut saab juba AVR-i dokumentatsioonist järgi uurida.

Pulsilaius-modulatsioon

Pulsilaius-modulatsioon (PWM) on signaali tüüp mille sagedus ja ühtlasi ka perioodi pikkus on konstantne (enamasti) kuid mõlema poolperioodi pikkus on muutuv. PWM signaale kasutatakse elektromehaaniliste, optiliste, jms. seadmete juhtimiseks. Näiteks mudelismist tuntud servomootorite PWM signaal on 50Hz sagedusega ja 1ms kuni 2ms pikkuse kõrge poolperioodiga.

Näide

Vaja on 8MHz taktsagedusel töötava ATmega8-ga genereerida kaks kiirusreguleeritavate servomootorite signaali ja luua funktsioon servomootorite pöörlemiskiiruse määramiseks -100 kuni 100% piires.

#include <avr/interrupt.h>
 
/*
 * PWM signaali genereerimiseks on kasutusel 16-bitine loendur 1
 * Signaaliallikaks on PB1 (OC1A) ja PB2 (OC1B) viigud
 */
void servo_init(void)
{
  // Viigud IO režiimis väljundiks
  DDRB |= _BV(DDB1) | _BV(DDB2);
 
  // Väljundid A ja B võrdusmomendil madalaks, "Fast PWM" režiim, sagedusjagur = 8
  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM11);
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11);
 
  // Suurim loenduri väärtus. Valem:
  // TOP = 8MHz / 8 / 50Hz
  ICR1 = 20000;
 
  // Võrdlusväärtused nulli algul
  OCR1A = 0;
  OCR1B = 0;
}
 
/*
 * Soovitud servomootori pöörlemissuuna määramine
 * vahemikus -100% kuni 100%
 */
void servo_dir(uint8_t index, int8_t dir)
{
  switch (index)
  {
    case 0:
      OCR1A = 1500 + dir * 5;
      break;
 
    case 1:
      OCR1B = 1500 + dir * 5;
      break;
  }
}
 
int main()
{
  // Servomootorite signaaligeneraatori seadistamine
  servo_init();
 
  // Globaalne katkestuste lubamine
  sei();
 
  // Esimise servomootori edaspidi pöörlemiskiiruseks 50%
  servo_pos(0, 50);
 
  // Lõputu programmitsükkel
  while (1) continue;
}

Analoog-digitaal muundur

Analoog-digitaal muundur (ADC) muundab analoogpinge väärtuse digitaalseks väärtuseks. AVR-i ADC analoogpinge sisend on lubatud 0-5.5V piires. Digitaalne väärtus on 10-bitine, kuid selle täpsus on ±2 ühikut. Viga võib veelgi kasvada kui kiibi toitepinget häirete eest ei kaitsta. ADC jaoks on AVR-il eraldi toite ja võrdluspinge viik. Eraldi toide on mürakindluse pärast ja see ei tohi kiibi toitepingest (üle 0.3V) erineda. Võrdluspinge määrab maksimaalse digitaalse väärtuse. Ehk kui võrdluspinge on 3V siis sama pingega sisend annab väärtuseks 2 astmes 10 miinus 1 ehk 1023.

AVR-i ADC töötab võrdlusmeetodil (Successive Approximation ADC). Lühidalt öeldes toimub mõõdetava pinge võrdlemine kindlate nivoopingetega ja tulemuste esitamine tõeväärtuste-, ehk bitijadana. See meetod võtab aga aega - iga biti leidmine lõppväärtuses toimub eraldi. AVR-il kulub töö ajal 13 takti ühe mõõtmise tegemiseks ja 25 takti kõige esimesel mõõtmisel (käivitusel). Need taktid pole aga kontrolleri töötaktid vaid spetsiaalselt ADC üksuse jaoks sagedusjaguriga saadud. Maksimaalse täpsuse saamiseks peaks ADC takt olema 50-200 kHz. Kõrgemal taktil kaob täpsus, kuid vahel on ka mõõtmiste suur arv olulisem kui täpsus. Ühele mõõtmisele kuluvaks ajaks on AVR-i dokumentatsioonis antud 13-260 µs.

Mõõtetulemust saab kasutaja lugeda 8- ja 10- bitisena. Kuna AVR on 8-bitine, siis ADC mõõteväärtuste jaoks on sel kaks 8-bitist registrit. Seadistustes saab määrata kas 10 bitisest väärtusest 2 esimest või 2 viimast bitti lähevad eraldi registrisse. Kui eraldatakse 2 noorimat, ehk tulemust vähem iseloomustavat bitti, saab mõõtetulemuse 8-bitisena lugeda - sellist kombinatsiooni nimetatakse vasak-asetusega mõõtetulemuseks (Left align). Teistpidist kombinatsiooni, kus kaht tulemusregistrit lugedes tekib 10-bitine arv, nimetatakse parem-asetusega mõõtetulemuseks (Right align).

Mõõdetavaid analoogpinge sisendeid on AVR-idel tavaliselt 8, ATtiny seerial üksikud, mõnel ATmega seeria kiibil 16, kuid muundureid on siiski üks. Erinevate sisendite kasutamiseks on kiibis multiplekser. Multiplekseri sisend on spetsiaalse registriga määratav. ADC üksusel on veel mõned omadused: muundamine protsessori magamisrežiimis müra vähendamiseks ja sisemise fikseeritud võrdluspinge (2.56V, mõnel ka 1V) kasutamise võimalus.

Näide

Vaja on mõõta ATmega8 ADC kanali 3 pinget vahemikus 0-5V 8-biti täpsusega.

#include <avr/interrupt.h>
 
/*
 * Soovitud ADC kanali väärtuse lugemine
 */
uint8_t get_sample(uint8_t channel)
{
  // Multiplekseriga kanali 3 valimine
  ADMUX = (ADMUX & 0xF0) | (channel & 0x0F);
 
  // Mõõtmise alustamine
  ADCSRA = _BV(ADSC);
 
  // Mõõtmise lõpetamise ootamine
  while (ADCSRA & _BV(ADSC)) continue;
 
  // 8-bitise tulemuse tagastamine
  return ADCH;
}
 
int main()
{
  uint8_t result;
 
  // Võrdluspingeks AREF viigu valimine - eeldatavasti on see ühendatud +5V toiteahelasse.
  // Tulemus on vasak-asetusega.
  ADMUX = _BV(REFS0) | _BV(ADLAR);
 
  // ADC üksuse käivitamine, teisendustakt 16 korda aeglasemaks töötaktist.
  // Samal
  ADCSRA = _BV(ADEN) | _BV(ADPS2) | _BV(ADSC);
 
  // ADC esimese "käivitus" mõõtmise lõpu ootamine.
  while (ADCSRA & _BV(ADSC)) continue;
 
  // Kanali 3 pinge mõõtmine
  result = get_sample(3);
}

Näites on tehtud ka üks „käivitusmõõtmine“. Tegu on AVR-i omapäraga - nimelt on mõõtmistulemuse õigsuse garanteerimiseks vaja ADC-l lasta pärast käivitamist ühe korra tühimõõtmine teha. On ka väidetud, et käivitusmõõtmist tuleks teha peale kanali vahetamist.

Analoog komparaator

AVR-i komparaator võrdleb kahe analoogpinge väärtust 0-5.5 voldi ulatuses. Tulemusena saadakse tõeväärtus selle kohta, kas esimene pinge on teisest kõrgem või mitte. Üheks võrdlusnivooks on olenevalt kontrollerist võimalik võtta ka kontrolleri-siseselt fikseeritud pinge. Komparaatori saab panna katkestust tekitama kas võrdluse tulemusena saadud tõeväärtuse suvalise muutumise, tõeseks muutumise või vääraks muutumise peale.

Kui kasutada AVR-i millel on analoog-digitaal muundur saab tõenäoliselt kasutada ka võimalust ühe võrdluspinge valimist mõnest muunduri sisendist.

UART

(UART) on lahtitõlgituna „universaalne asünkroonne vastuvõtja/saatja“. USART on peaaegu sama asi kui UART, kuid selle erinevusega, et peale andmete edastakse ka taktsignaal. UART-i võib jämedalt nimetada ka jadaliideseks. Jadaliides on andmete ülekandmise mehhanism kus iga bitt edastakse ükshaaval. Näiteks selleks, et edastada 1 bait, edastakse kindla ajavahemiku tagant 8 bitti. Ehk siis füüsiliselt toimub jadaliidese liinil, mis on 1 mikrokontrolleri viik, kindla ajavahemiku järel selle viigu pingeväärtuse muutus kõrgeks või madalaks. Jadaliidesega on üldjuhul ühendatud 2 seadet, millest üks edastab infot (viigu väärtust muutes) ja teine võtab seda vastu (viigu väärtust registreerides). Info liigub ühel liinil alati ühes suunas. Andmete teistpidi saatmiseks kasutatakse teist liini. Andmed võivad kahel liinil sama-aegselt liikuda, ehk tegu on täisdupleks siiniga.

Andmete edastamine toimub UART liideses üldjuhul 1 baidi kaupa, kuid võimalus on valida 5-9 bitine sõna (sõnaks nimetatakse ühik-andmehulga, 8 biti puhul sõna võrdub baidiga). Peale andmebittide edastakse lisabitte mille abil toimub andmete saabumise ja lõppemise hetke äratundmine vastuvõtja poolel. Esimest nimetatakse start-bitiks mis on alati 0, teist nimetatakse stopp-bitiks (või bittideks) mis on alati 1. Enne stopp-bitti võib tulla ka paarsuse bitt mida kasutakse andmete korrektsuse kontrolliks. Paarsuse bit näitab kas andmebittide hulgas on paaris või paaritu arv ühtesid. See kumba näitu see omab sõltub UART liidese häälestusest. Paaruse bitti tänapäeval enam üldjuhul ei kasutata ja selle saab häälestuses ka ära keelata. Nii nagu saab paarsuse bitti seadistada, saab ka andmebittide ja stopp-bittide arvu.

Peale andmete kuju on veel üks tähtis parameeter - see on boodikiirus (baud rate) millega määratakse edastavate sümbolite arv ühes sekundis. Bood näitab nimelt sümbolite arvu. UART puhul on 1 bood aga 1 bit. Põhimõtteliselt võib andmete edastamiseks kasutada ükskõik millist boodikiirust, kuid on olemas hulk üldkasutavaid boodikiirusi mida on soovitav kasutada. Näiteks: 9600bps, 19200bps, 38400bps. Nagu näha siis iga järgnev boodikiirus on eelmisest 2 korda suurem. Kuna UART boodikiirusele vastav taktsignaal genereeritakse mikrokontrolleri töötaktist läbi binaarse sagedusjaguri, siis ei pruugi iga töötakti puhul saada täpselt õiget boodikiirust, vaid midagi ligikaudset. Madalamatel boodikiirustel ja lühemate sideliinide korral väike boodikiiruse erinevus saatja ja vastuvõtja vahel üldiselt probleeme ei tekita, kuid parem on kui seda erinevust pole.


Vahemärkus: Täpseid standardseid boodikiirusi saab tekitada näiteks 7.3728 MHz, 11.0592 MHz ja 14.7456 MHz kvartsidega. Samas ei jagu need sagedused hästi 10 astmetega, ehk nendega ei saa tekitada näiteks millisekundi täpsuseid katkestusi, mida nii mõneski rakenduses vaja on.


AVR mikrokontrolleritel on üldiselt 0-2 U(S)ART liidest. Liidese seadistamiseks on spetsiaalsed registrid. Andmete edastamine toimub sõna kaupa, ehk AVR teeb riistavara tasandil sõna bittideks ja edastab selle. Kasutaja teeb kogu töö seade-, oleku- ja andmeregistreid kirjutades ning lugedes.

Näide

Seadistada 8MHz taktsagedusel töötava ATmega8 UART liides boodikiirusel 9600bps edastama 8 bitiseid sõnasid, 1 stopp-bitiga ja ilma paarsuse bitita. Saata üle liidese sõnum „Tere“

#include <avr/io.h>
 
// Märgi saatmise funktsioon
void send_char(char c)
{
  // Ootame kuni andmepuhver on tühi, ehk eelmine sõna on saadetud
  while (!(UCSRA & _BV(UDRE))) {}
 
  // Märgi kirjutamine puhvrisse kust see ka teele saadetakse.
  UDR = c;
}
 
// Peafunktsioon
int main()
{
  // Boodi kiiruseks 9600bps seadmine
  // UBRR registri väärtus = taktsagedus / 16 / boodi kiirus - 1
  // UBRR = 8000000 / 16 / 9600 - 1 = ~51
  UBRRH = 0;
  UBRRL = 51;
 
  // Saatmise funktsiooni lubamine
  UCSRB = _BV(TXEN);
 
  // Asünkroonse režiimi seadistamine, andmesõna pikkuseks 8 bitti
  // 1 stop bitt, keelatud paarsuse bitt.
  // ATmega8 eripära tõttu tuleb UCSRC registrisse kirjutamiseks ka URSEL bitt kõrgeks seada.
  UCSRC = _BV(URSEL) | _BV(UCSZ1) | _BV(UCSZ0);
 
  // Teate saatmine
  // Selleks kasutame ühe märgi saatmise alamfunktsiooni
  send_char('T');
  send_char('e');
  send_char('r');
  send_char('e');
  send_char('\r'); // Reavahetuse märgid ASCII-s
  send_char('\n');
}

See näide sisaldab küll kõike hädavajalikku saatmiseks, kuid reaalses rakenduses on soovitatav boodi kiiruse seadistamise registri väärtuse arvutamiseks kasutada makrosid. Ning teksti on palju mugavam saata mitte tähthaaval vaid ühe korraga. Allpool on ka näide teksti saatmise kohta, kuid kuna see töötab viitadega (pointer), mille kohta võiks eraldi juhendi kirjutada, siis ei ole ka siin pikalt koodi lahti selgitatud.

// Teksti (märgijada) saatmise funktsioon
void send_text(char *text)
{
  // Iga märgi saatmine kuni tuleb 0 märk
  // mis näitab sisuliselt teksti lõppu.
  while (*text != 0)
  {
    send_char(*text++);
  }
}
 
// Teksti saatmise funktsiooni väljakutsumine
send_text("Tere\r\n");

RS-232

Tegu on standardiga mida leiab arvutite juureski. Kui eelnevalt sai öeldud, et UART-i võib nimetada jadaliideseks siis jadaliides on rohkem seotud RS-232-ga. UART ja RS-232 protokoll on peaaegu sama - mõlemas toimub andmete edastamine bitthaaval koos lisainformatsiooniga (start-, stopp-, paarsusbitt). Peamine erinevus on neil signaali pingenivoodes. Kui UART on mikrokontrolleri liides mis töötab 0V ja 5V nivoodega (AVR puhul) siis RS-232 töötab 3-15V posiitivse ning negatiivse pingenivoo peal. Mõlema liidese ühendamiseks on olemas spetsiaalsed nivoomuundamise kiibid. Seega kui nivoomuundur vahel on, võib UART-i kaudu andmeid edastada ilma ühegi muudatuseta programmis.

Teine erinevus, mis puudutab protokolli on see, et RS-232 liides sisaldab ka eraldi andmevoo juhtimise liine (aga neid ei pea kasutama). Need on liinid mille kaudu üks seade annab teisele teada kas ta on töös ja kas ta on valmis andmeid vastu võtma või saatma.

Autor

juhendid/avr.txt · Viimati muutnud: 2016/12/08 14:24 persoon raimond.vaba