2011-12-27

Mikrokontrolerių programavimas


Mikrokontroleriai – viena iš plačiausiai ir labiausiai tobulinamų „advanced“ elektronikos sričių. Kai diskretiniais elementais sudėtinga realizuoti reikiamas sistemos funkcijas – pereinama būtent prie jų. Kam konstruoti dvejetainį skaičių sumatorių, daugybos įrenginį ar pan. iš šimtų diskretinių komponentų (tokių entuziastų vis dar atsiranda), jei tą patį gali atlikti nesudėtingas 8/16/32-bitų mini procesorius, be to sutaupoma vietos? Ne paslaptis, kad lanksčios, didelės ar sudėtingos sistemos nebeapsieina be šių mini procesorių. Pažvelkime į juos iš arčiau bei susipažinkime su vienos iš geriausiai žinomos kompanijos "Atmel" ATmega mikrokontrolerio programavimu.


Mikrokontroleris – tai ne kas kita, kaip plačios(arba specializuotos) paskirties programuojama ir mikroprocesorių turinti schema, telpanti į vieną DIP (angl. dual inline package), TQFP (angl. thin quad flat pack)  ar kitokį įpakavimą. Pats mikrokontroleris gali būti programuojamas (t.y. įrašoma sukompiliuota programa) vieną arba tūkstančius kartų. Įrašyta programa yra komandų rinkinys, kurios po vieną yra vykdomos mikroprocesoriaus kiekvieno darbo takto (angl. clock) metu. Svarbiausios charakteristikos, kuriomis pasižymi šie įrenginiai  – tam tikras Flash (max programos dydžio), EEPROM ir SRAM atminties kiekis, toliau - fiksuotas operuojamų žodžio bitų skaičius 8-32bit, maitinimo įtampa (pvz. 3.3-5V), veikimo dažnis kHz ar MHz, I/O išvadų(angl. pins) skaičius ir kt. Taip pat reikia pastebėti, jog 8 bitų mikroprocesorius gali apdoroti 32 bitų instrukcijas/operacijas, tačiau tam prireiks apie 4 kartus daugiau laiko nei specializuotam. Tokia pilna CPU sistema dar kitaip vadinama SoC (angl. system on chip) arba tiesiog vienluste sistema (angl. embedded system). Liaudiškai mikrokontrolerį priimta trumpinti tiesiog kaip „uc“, čia u – kaip graikų „mikro“ raidė. Nors tokių lustų gamybos kaštai labai dideli (kadangi reikia ypač tikslių mikro/nanometrinių technologijų), tačiau dėl didelių gamybos ir pardavimo (jų gaminama milijonais vienetų) mastų, patys paprasčiausi ir dažniausiai naudojami mikrokontroleriai yra pakankamai pigūs 5-25Lt (priklausomai nuo pateikiamų funkcijų ir atminties kiekio). Geriausiai žinomi ir plačiausiai naudojami mėgėjiški norvegų/amerikiečių kompanijos "Atmel" ATmega ir JAV kompanijos Microchip Technology PIC mikrokontroleriai. Abiejų kompanijų gaminių nesunku įsigyti artimiausioje elektronikos prekių parduotuvėje (www.lemona.lt, www.rcl.lt, www.evita.lt ir pan.). Šiam straipsniui pasirinkau Atmel ATmega16 mikrokontrolerį dėl savo populiarumo ir daugelio pavyzdžių kiekio.
Norint pradėti programuoti mikrokontrolerius reikia pirmiausia bent šiek tiek būti susipažinus su diskretine elektronika (varžos, kondesatoriai ir pan.) bei C/C++ programavimo kalba, todėl tikiuosi, jog skaitytojui su ja jau teko susipažinti ir pasipraktikuoti. Patys mikrokontroleriai yra programuojami tiesiogiai jungiant juos prie kompiuterio per COM9/LPT/USB portus ir per specialų programatorių (kitais atvejais netgi ir tiesiogiai). Tokiu būdu šiandien lengviausia programuoti šiuos lustus - programa parašoma kompiuteriu kokia nors programavimo kalba, toliau sutransliuojama į mikroprocesoriaus suprantamų komandų seką (t.y. dvejetainio/šešioliktainio(hex) kodo pavidalą) ir įrašoma į Flash atmintį.
Programatorius – tai prietaisas, paverčiantis iš/į PC ateinančius signalus į mikrokontroleriui suprantamus signalus. Taip pat jis tiesiog gali suvienodinti skirtingų jungčių įtampų lygius su mikrokontrolerio naudojama įtampa (pvz. COM9 -15...+15V su TTL 0-5V įtampa). Programatorius nėra privalomas ir būtinas siekiant programuoti mikrokontrolerius, kai kurie lustai palaiko ir tiesioginį programavimą per LPT ir pan. Interneto platybėse yra nemažai pavyzdžių, kaip tai padaryti, jeigu po ranka kompiuteris su tokia jungtimi. Tačiau mano atveju pasitaikė tik COM portas kompiuteryje, tad teko pasipraktikuoti šiek tiek daugiau. Norintiems šiuolaikiškesnio - USB pavyzdžio, taip pat yra ir USBasp programatorius Atmega programavimui. Kad ir kokį variantą benaudotumėte, principai išlieka tie patys.
Taigi, naudojant COM, reikės pirmiausia susikonstruoti programatorių, kad galėtume užprogramuoti patį mikrokontrolerį. Internete galima rasti labai daug įvairių programatorių, vienas iš paprastesnių ir dirbančių per COM atrodo taip:

Vienas iš paprastesnių COM programatorių
Diagramoje raudona spalva apibrėžta universalaus programatoriaus schema (tinkanti programuoti ir kitas Atmega versijas), o pilka spalva parodyta Atmel ATmega mikroschema ir jos prijungimas prie programatoriaus. Prie skirtingų elementų parašiau tokius parametrus, kuriuos man pavyko gauti ar rasti, tačiau galima naudoti ir kitus panašaus nominalo komponentus. Pats programatorius turi COM jungtį jungiamą į PC ir prie jos prijungtą MAX232 mikroschemą, reikalingą suvienodinti COM įtampų lygius su Atmega TTL 0-5V lygiais. Taip pat tranzistorių (reset signalo inversijai) ir porą keraminių bei vieną elektrolitinį(+) kondensatorių įtampai, paduodamai į Atmega, išlyginti/stabilizuoti. Elektrolitinis ar keramikinis kondensatorius - nėra skirtumo. Taip pat siūlau atkreipti dėmesį į tai, jog programatoriui, kaip ir daugliui kitų, reikalingas atskiras 5V maitinimas! 
Nusprendžiau atskirai sulituoti programatorių, kad būtų galima lengvai vėliau dar kartą jį panaudoti, o bandomą ATmega mikrokontrolerį tiesiog lituoti kitoje plokštėje. Jei nenorite atskirai daryti programatoriaus, tai skaitykite toliau, nes pateikiama ir schema, kurioje parodytas pilnas programatoriaus ir Atmega prijungimas.
Programuojant mikrokontrolerius iš pradžių gali būti tikrai daug kas neaišku, todėl dar siūlau atsisiųsti oficialius max232 ir atmega16 aprašus (datasheets), kuriuose be visų funkcijų aprašymų dar pateikta ir tiksli išvadų schema, registrų reikšmės ir t.t. Sėkmingai pasigaminus programatorių toliau galėsime atlikti jo prijungimą prie Atmegos. Pilna jungimo schema su išvadų numeriais pavaizduota žemiau.
COM programatoriaus prijugimas prie ATmega16

Naujoje schemoje programatorius sujungtas tiesiai su Atmega mikrokontroleriu, tokia schema manau yra žymiai aiškesnė, tačiau ne tokia universali, jei realizuojame viską vienoje spausdintinėje plokštėje (angl. PCB). Prie mikrokontrolerio testavimui ir vaizdumui taip pat prijungti 8 LED diodai, pagal kuriuos bus pradžioje lengva matyti visus kontroliuojamo registro 8 bitus.
Mikrokontrolerio širdis – taktinis generatorius (t.y. kvarcinis rezonatorius), su Atmega16 galima naudoti 4-16MHz dažnio kvarcą. Aš panaudojau turėtą 12MHz, tačiau šį komponentą su šalia esančiais kondensatoriais pradžioje galima ir praleisti, nes jis nebūtinas. Atmega jau tiesiai iš gamyklos turi sukonfigūruotą vidinį 1 MHz taktinį generatorių, kurio pirmiems bandymams gali visiškai pakakti. Norint prijungti didesnio dažnio generatorių, reikia teisingai nustatyti "fuse" konfigūracinius bitus, esančius Atmega mikroprocesoriuje. Tai daroma atskirai Fuse bitų programavimo metu ir apie tai šiame straipsnyje nešnekėsiu.
Straipsnio pabaigoje pateikiu ir PCB plokščių takelių vaizdus bei Sprint Layout programa sukurtą .lay schemą, jei susiruošite pasigaminti ėsdintą spausdintinę plokštę. ĮSPĖJIMAS: pcb takelių schemos detaliai netikrintos ir neišbandytos, nors daryta pagal tą pačią schemą - naudokite atsargiai.

Kaip man pavyko pagaminti schemas – toliau nuotraukose. Visiškai išvengti orinio montažo nepavyko, tačiau svarbiausia, jog schema veiktų. Montažinės plokštės tinka greitam paeksperimentavimui, tačiau turi ir trūkumų - ilgai kaitinant gali atsilupti varis, lengvai įmanoma užtrumpinti gretimus elementus, nėra kompaktiškos.  Baigus visus litavimo darbus lieka prijungti viską prie PC, įjungti išorinį maitinimą ir pabandyti suprogramuoti demo programą.




Mikrokontroleriai dažniausiai programuojami C kalba (galima ir asembleriu pagal pageidavimą), toliau kodas transliuojamas ir kompiliuojamas į mašininį kodą (.hex failai), gautą hex failą pateikiame PonyProg ar AVR Burn-O-Mat programai, kuri persiunčia (įrašo) tą dvejetainį kodą į mikrokontrolerio Flash atmintį. Naudojant C kalbą gauname pilną prietaiso valdymą, tiesioginį priėjimą prie atminties ir aukštesnį abstrakcijos lygį nei programuojant asembleriu. Tačiau čia irgi lengva padaryti klaidų. Kartais programuojama asembleriu dėl kodo spartos/efektyvumo ir programos dydžio kompaktiškumo sukompiliavus (mikrokontroleryje Flash atmintis visgi ne begalinė!), tačiau tai vertėtų daryti tik įvertinus patirties, visus efektyvumo ir laiko kaštus. Šiais laikais kompiliatoriai labai gerai optimizuoja kodą, tad be didelių pastangų gauname efektyvų mašininį kodą.

Programavimui patariu naudoti Atmel kompanijos siūlomą IDE programavimo aplinką AvrStudio, o hex kodo įrašymui į mikrokontrolerį jau minėtas – AVR Burn-O-Mat su AVRDUDE arba PonyProg programas. Atsidarius AVR Studio pirmiausia sukuriame naują projektą ir sukonfigūruojame, jog galėtume kompiliuoti C kodą ATmega16 mikrokontroleriui. Tam pasirenkame AVR GCC kompiliatorių ir įrašome projekto vardą, C kodo failo pavadinimą ir judame toliau.

Naujo projekto kūrimas AVR Studio 
Atmega modelio pasirinkimas - jei programą reikės derinti pradžioje, tada geriausia pasirinkti "AVR Simulator". Būtina teisingai nustatyti, jog kompiliuojame kodą būtent pasirinktam Atmega16 mikrokontroleriui, to nepadarius – programa gali nekorektiškai veikti arba iš viso neveikti! Skirtingi Atmega modeliai turi skirtingas instrukcijas, registrus ir pan., todėl to pamiršti nevalia!

Teisingas mikrokontrolerio modelio pasirinkimas

Dar prieš rašant kodą šiek tiek teorijos. Jei pažvelgsime į oficialią Atmega16 išvadų schemą, pamatysime išvadus pažymėtus PAx, PBx, PCx, PDx su skirtingais indeksais ir pan. Jie skirti duomenų įvedimui arba išvedimui. Kai kurie išvadai yra naudojami specialiai valdymui/komunikacijai MISO, MOSI arba atlieka antrą funkciją ir pan. Išvadų kryptis IN/OUT gali būti dinamiškai keičiama – taigi Atmega, gali per tą patį išvadą siųsti informaciją, o kitu (ne tuo pačiu) momentu per tą patį ir gauti. Informacijos kryptis gali būti keičiama programiškai. Tai nustatoma keičiant reikšmes specialiuose registruose DDRA, DDRB, DDRC, DDRD. Atitinkamai kiekvienam išvesčių tipui A-D. Informacijos nuskaitymas/išvedimas atliekamas skaitant/įrašant atitinkamo registro PORTA, PORTB, PORTC ar PORTD reikšmę programos kode. Visi šie registrai yra 8 bitų ilgio ir kode jau yra apibrėžti per specialias pridedamas AVR bibliotekas. Telieka jomis teisingai pasinaudoti. Neatsitiktinai tuos aštuonis LED diodus prijungėme būtent prie PD0-PD7 išvadų, nes kaip tik toliau pavyzdžiuose valdysime DDRD ir PORTD registrus. PD0-PD7 išvadai kaip tik ir sudaro vieną baitą susidedantį iš 8 bitų - t.y. visą registrą.



Primenu, jog vienas bitas yra loginė reikšmė, kuri yra 0 (išjungta) arba 1 (įjungta), to atitikmuo atmega išvaduose yra atitinkama įtampa. Jei bitas = 0, tai išvade turime 0V, jei bitas = 1, tai išvade bus +5V, manau pakankamai paprasta. Taigi, pasiaiškinę principus, atsidariusiame AVR Studio projekto kodo lange rašome pirmąjį kodą. Paprasčiausią vienos lemputės įžiebimą. Kad lemputė užsidegtų, turime jai paduoti įtampą, šiuo atveju +5V. Tai atitinka vieno bito reikšmę programiškai pakeisti iš 0 į 1 valdomame registre. 

Pirmosios programos kodas AVR Studio
Pirmos programos kodas:


Kodo pradžioje visada prisijungiame standartines AVR bibliotekas (angl. headers), jose apibrėžti registrai ir kitos reikšmės. Visas kontrolerio kodas rašomas main() metode. Įprasta, jei teko susidurti su konsolinių programų rašymu-programuoti. Pradžioje DDRD nustatome 255 arba 0xFF reikšmę, tai atitinka visus 8 bitus nustatytus į 1. Tokių būdu nurodome, jog visi PD0-PD7 išvadai bus naudojami duomenų išvedimui. Norint juos nustatyti įvedimui, turėtume reikšmę pakeisti į 0 arba 0x00. Su PORTD = 0x01 įjungiame pirmą LED lemputę (t.y. pirmas 8 bitų registro bitas nustatomas į 1, kiti bitai lieka 0). Taip į PD0 išvadą paduodame +5V įtampą. Toliau begaliniame while cikle leidžiame mikrokontroleriui nieko nedaryti, bet neuždaryti/neužbaigti vykdomos programos. Čia cikle vykdoma asemblerio NOP (angl. no operation) komanda, kuri leidžia procesoriui praleisti taktą nieko nedarant. Galėtume šioje vietoje nerašyti asemblerio komandos, tačiau tai apsaugo nuo pernelyg gero kompiliatoriaus optimizavimo. Kompiliatoriai labai gerai moka optimizuoti kodą, jei kodas nieko nedaro naudingo (kaip mūsų atveju - suka begalinį tuščią ciklą), net gali jį išmesti iš viso. NOP komanda yra vienas iš būdų kaip priversti procesorių neužbaigti programos vykdymo.

Programa sukompiliuota sėkmingai
Toliau sukompiliuojame kodą (F7), įsitikiname, jog AVR studio nepraneša jokių kritinių klaidų. Jei viskas tvarkoj – gausime sukompiliuotą hex kodo failą (projekto direktorijoje), jo toliau reiks įrašyti programą į mikrokontrolerio atmintį.  Pasileidžiame AVR Burn-O-Mat ir sukonfigūruojame: nurodome kelius, pasirenkame COM prievadą ir programatoriaus tipą į „siprog“. Jei tokio programatoriaus nėra, tai atsidarome avrdude.conf failą (jį galima rasti ten, kur instaliuotas AVRDude) ir pridedame žemiau esančias eilutes bei paleidžiame iš naujo AVR Burn-O-Mat.

programmer
  id    = "siprog";
  desc  = "Lancos SI-Prog";
  type  = serbb;
  reset = ~3;
  sck   = 7;
  mosi  = 4;
  miso  = 8;
;




Toliau pasirinkę .hex failą įrašome jį į Flash atmintį spausdami Write.


Jei viskas sėkmingai ir programatorius randamas, tai programa įrašoma į mikrokontrolerio Flash atmintį.

Sėkmingas įrašymas
Jei rodoma klaida, kad programatorius nerastas arba nėra ryšio su juo, tai teks patikrinti, ar viskas teisingai sukonfigūruota, nustatytas reikiamas COM portas, sulituota ir nepadaryta klaidų montuojant komponentus. Šiame etape viena iš dažniausių problemų būna ryšys su programatoriumi. Na, o jei viskas gerai, tai pirmoji LED lemputė turėtų įsižiebti ir degti iškart vos tik programa sėkmingai įrašoma.


Dažniausia klaida - nėra ryšio su programatoriumi ar ATmega
Dabar pabandysime parašyti programą, su mirksinčia lempute. Principas nesudėtingas – įjungti lemputę, palaukti trumpą laiką, po to išjungti lemputę ir vėl palaukti. Visa tai kartoti be galo daug kartų. Tai galima realizuoti while cikle, tik reikia papildomai parašyti laukimo funkciją, kad būtų įmanoma laukti reikiamą laiko tarpą.


Tokiose realaus laiko sistemose laiką skaičiuoti tiksliai yra gana sudėtinga, kadangi taktinio generatoriaus dažnis nėra visiškai stabilus ir idealus - jis svyruoja šiek tiek dėl temperatūros kaitos ir pan. Nors kvarciniai rezonatoriai pasižymi ypač tiksliu dažnio išlaikymu, neretai priimama, kad mikroprocesorius veikia būtent tuo dažniu, kuris rašomas ant kvarco ir į svyravimus nekreipiama dėmesio. Kitaip tariant, priimama sąlyga, kad jei taktinis generatorius dirba 1 MHz dažniu, tai jis ir atlieka 1000000 operacijų per sekundę. Kadangi paprastai operacijai/instrukcijai atlikti reikia tik vieno takto (kaip Atmega‘oje), tai vienai tokiai operacijai atlikti reikės 1/1000000 = 1 us laiko. Pasikeitus dažniui galima atitinkamai perskaičiuoti reikšmę. Iš to jau galime pakankamai gerai paskaičiuoti laiką - tarkime, norėdami palaukti 1ms, turėtume atlikti 1000 elementarių operacijų. Tokia matematinė operacija kaip skaitliuko padidinimas/sumažinimas (sudėtis/atimtis) gali būti traktuojamas kaip viena paprasta operacija. Dabar, jeigu šią operaciją pakartosime 1000 kartų, tai CPU bus užimtas 1000 taktų ir tai apytiksliai užtruks apie 1 milisekundę. Taigi manau dabar viskas pakankamai aišku.
Šiuose mano pateikiamuose pavyzdžiuose priimta, jog mikrokontroleris naudoja vidinį Atmega 1 MHz taktinio dažnio generatorių. Jei naudosite išorinį kvarcinį rezonatorių ar kitą generatorių, tai atitinkamai programos vykdymas pagreitės apie 4x – jei 4MHz, 12x – jei naudojamas 12 MHz kvarcas ir pan., tada užlaikymo funkciją teks pakoreguoti. Kaip ir minėjau anksčiau, naudojant išorinį kvarcą ar generatorių teks pakeisti atitinkamus Atmega „fuse“ bitus. Jeigu naudojamas vidinis Atmega generatorius, tai nieko papildomai keisti nereikia.
Naujoje programoje viską darome panašiai kaip ir prieš tai, tik dabar vykdymas perkeliamas į „amžiną“ while ciklą ir išvedamos reikšmės į išvadus keičiamos dinamiškai laike. Laukimo funkcijoje priimta, kad skaitliuko pamažinimas vienetu + NOP yra ta elementari operacija atliekama apytiksliai per 1 us, todėl galima pakankamai gerai skaičiuoti laiką, tą patį patvirtina ir praktika. Įrašius programą į mikrokontrolerio atmintį, gausime kas sekundę mirksintį LED.


Reziumė

Panagrinėjome paprasčiausius programų pavyzdžius – palieku skaitytojui pabandyti kitus scenarijus su užuominom: įjungti/išjungti keletą LED lempučių vienu metu (t.y. įrašyti kitą reikšmę į PORTD), padaryti bėgančią grandinėlę (įjungti lemputes paeiliui – panaudojus numerio skaitliuką ir bitų operacijas), palaipsniui (pulsuojančiai) valdyti lemputes (panaudoti PWM – pulse width modulation), t.y. kai uždegimo/užgesinimo trukmės ilgis keičiasi dinamiškai laike.

Visus projektų dokumentus ir sukompiliuotas programas galite rasti čia.

Jei viską pavyko atkartoti, tai galime pasidžiaugti - įvykdėme tą nelengvą pradžią į mikrokontrolerių programavimą. Žinau, jog nėra ji tokia lengva ir aiški, tačiau pasigaminus veikiantį programatorių galima toliau praktikuotis, pradėti taikyti išmoktas žinias vis rimtesniuose projektuose, valdyti kitus prietaisus, rinkti sensorinę informaciją, pasigaminti mažai įtampos naudojančius prietaisus (atmega „L“ pažymėti modeliai) ir t.t. Su mikrokontroleriais atsiveria plačiausios elektronikos durys. Išmokus dirbti su vienu Atmega modeliu, nesunku bus panaudoti ir kitą.  Įgudus siūlau netgi išbandyti kitus, pvz. PIC mikrokontrolerius, 8-32bitų ARM procesorius ar rimtesnes Xilinx bei Altera FPGA matricų sistemas, kuriose programuojama VHDL ar Verilog kalbomis ir patys galite susikurti savo mikroprocesorių. Sėkmės!



3 komentarai:

  1. Sveikas,

    norečiau truputėlio pagalbos su Atmega 8. Pakeitus fuse bitus atmegos į lfuse:D7, o hfuse:D9, nebeišeina nuskaityti atmegos. Papildomai mėginau pajungti 4 ir 12 mhz kvarcinius rezonatorius, tačiau tai nedavė jokios naudos, vis tiek neina nuskaityti mikrokontrolerio. Ar tai gali būti del fuse bitų, o gal tiesiog sugebėjau sugadinti atmegą?

    AtsakytiPanaikinti
    Atsakymai
    1. Sveikas,

      Greičiausiai, kad dėl fuse bitų. Reikšmės lfuse:D7, o hfuse:D9 duoda clock select bitus tokius: CKSEL3..0=0111 (galima pasitikrinti su AVR Burn-O-Mat programa). Oficialiame atmega8 datasheet "Clock Sources" skyriuje Table 2. parašyta, jog 0111 reikšmė papuola į "External RC Oscillator" diapazoną. Taigi kvarcinis rezonatorius šiuo atveju netiks - atmegą atgaivinti galima pajungus prie XTAL1 ir GND pin'ų 3-8MHz RC osciliatorių - t.y. varžą ir kondensatorių. Schemą ir detalesnę informaciją patarčiau žiūrėt minėtame datasheet dokumente - "External RC Oscillator" skyriuje viskas paaiškinta bei kokių nominalų komponentų reikia.

      Panaikinti
  2. Kaip ir sakei, pajungus atitinkamą varžą bei kondensatorių atmega atgijo. Ačiū už greitą atsakymą

    AtsakytiPanaikinti