Mérő- és vezérlőberendezés megvalósítása ARM alapú mikrovezérlővel és Linux-szal Fuszenecker Róbert 2007. augusztus 5.
Tartalomjegyzék 1. Köszönetnyilvánítás és felajánlás 2 2. Bevezetés 3 3. A hardver felépítése 4 3.1. A számítógép, mint mérésvezérlő................................... 4 3.2. Az ARM alapú mikrovezérlők.................................... 5 3.3. ATMEL vagy NXP Te kit választanál?.............................. 7 3.4. A processzor modul.......................................... 8 3.5. Érzékelő, végrehajtó és beavatkozó szervek............................. 11 3.5.1. 12 V-os izzó vezérlése digitális és analóg (PWM) jellel................... 11 3.5.2. Fényérzékelők, fotodiódák.................................. 11 4. Fejlesztői környezet 14 4.1. C fordító Linuxos PC-re....................................... 14 4.2. C fordító ARM processzorra..................................... 15 4.3. Az OpenOCD feltöltő és nyomkövető program.......................... 16 4.4. Adatátvitel megvalósítása soros porton keresztül........................... 18 4.5. A kommunikáció protokollja és annak megvalósítása C nyelven.................. 20 5. Felhasználási területek 30 5.1. Vezérlés................................................ 30 5.1.1. 12 V-os izzó be- és kikapcsolása............................... 30 5.1.2. 12 V-os izzó be- és kikapcsolása időzítő megszakítással................... 34 5.1.3. 12 V-os izzó fényerejének beállítása impulzusszélesség-modulációval........... 35 5.2. Mérésadatgyűjtés........................................... 37 5.3. Transzfer karakterisztika felvétele.................................. 42 6. Összefoglalás, végkövetkeztetés 46 7. Felhasznált irodalom 47 8. Felhasznált szoftverek 48 9. Mellékletek 49 9.1. JTAG csatlakozó kiosztása...................................... 50 9.2. ARM JTAG csatlakozó leírása.................................... 51 9.3. USB-JTAG kapcsolási rajza...................................... 52 9.4. JTAGKEY elvi rajza......................................... 53 9.5. OOCD-LINK kapcsolási rajza.................................... 54 1
1. fejezet Köszönetnyilvánítás és felajánlás Ezúton szeretném kifejezni köszönetemet konzulenseimnek,...... és Krüpl Zsolt okleveles villamosmérnöknek, akik áldozatos munkájukkal és nélkülözhetetlen szakmai tanácsaikkal segítettek a dolgozat megírásában. Szintén hálával tartozom a lektoroknak, Gagyi Endre okleveles villamosmérnöknek és Gnandt András okleveles villamosmérnöknek, akik erejükön felül teljesítve azon fáradoztak, hogy a dolgozat mindenfajta szakmai, helyesírási és logikai hibától mentesen kerülhessen az Olvasó elé. A szabad szoftver mozgalom által készített operációs rendszer (Linux), a szövegformázó rendszer (L A TEX2ε) és a többi segédprogram nélkül elképzelhetetlen lenne ezen mű megírása. Ezért felajánlom ezt a dolgozatot a közösség számára, hogy bárki belátása szerint használhassa mindenféle anyagi ellenszolgáltatás nélkül. Ez a dokumentum szabad szoftver, szabadon terjeszthető és/vagy módosítható a napokban megjelent GNU General Public License 3-ban leírtak szerint. Váljon ez a dokumentum minden élőlény javára! 2
2. fejezet Bevezetés Ezen dolgozat megírására azért vállalkoztam, hogy megmutassam az Olvasónak: egy általános célú irányítóberendezés (mérésadatgyűjtő és vezérlő) megépítése ma már nem okoz komoly kihívást egy elektronikában jártas szakembernek. A (mikorelektronikai) technológia lehetővé teszi, hogy mikrovezérlőink olyan számítási kapacitással rendelkezzenek, amivel az asztali és hordozható számítógépek rendelkeztek néhány évvel ezelőtt. Egy grafikus kijelzővel bíró, internetezésre és Java játékok futtatására alkalmas mobiltelefonban pontosan olyan processzor található, mint amilyet a mérőberendezésünk megépítéséhez választottam. Ez a processzor pedig az Acorn cég által útjára indított ARM 1 fejlesztői prodzsekt terméke: az ARM7TDMI. Az ipar és a szórakoztató elektronika széles körben használja ezt a processzort, ezek találhatók az Apple ipod-okban, a HP PalmTop-jaiban, az Intel PDA-iban, a Nintendo GameBoy-okban, a legtöbb Nokia mobiltelefonban, és a Sirius műholdvevőiben. Természetesen készülnek belőle személyi számítógépek is: az A7000+ Odyssey a Castle Techologies Co-tól, az Acorn PC-i, vagy a IYONIX PC. A lista szinte végtelen, hiszen egy nagyon jól megtervezett processzorról beszélünk. De milyen méréstechnikai feladatok megoldására használhatók ezek az eszközök? Az adatlapok áttanulmányozása után könnyen beláthatjuk, hogy jónéhány mérési problémára megoldást nyújtanak, hiszen nagy részük rendelkezik több száz ksample/s 2 ) sebességű analóg digitális és digitális analóg átalakítóval, viszonylag nagy adatmemóriával, kommunikációs interfésszel (RS-232, CAN 3, SPI 4, Ethernet, USBUniversal Serial Bus univerzális soros busz) és általános célú digitális ki- és bemenetekkel. A lista most sem teljes, de az itt felsorolt eszközök közül sem fogjuk mindegyiket felhasználni. A dolgozat első részében a megfelelő mikrovezérlő 5 kiválasztásáról, a mérő és vezérlő hardver kialakításáról olvashat az Olvasó, ezt követi a szoftver háttér (fejlesztői környezet) ismertetése. Ezután a mikrovezérlőprogramozás legszebb részével, az adatátvitellel foglalkozunk. Végül néhány, a gyakorlatban is megvalósított példán keresztül mutatom be a legérdekesebb felhasználási területeket. Sajnos a dolgozat terjedelme nem teszi lehetővé, hogy minden, általam hasznosnak tartott témáról beszéljek, így csak a mérésadatgyűjtéssel, a vezérléssel, és a a transzfer karakterisztika felvételével tudunk részletesen foglalkozni. A dolgozat elolvasása során az Olvasó beláthatja, hogy az említett mikrovezérlők összetettségük ellenére könnyen és hatékonyan felhasználhatók mindennapi munkánk során. Talán az olvasó is kedvet kap hasonló áramkörök készítéséhez, hogy az itt megszerzett tudását a gyakorlatba is átültethesse. Remélem, hogy ezen mű olvasása éppoly örömet okoz az Olvasónak, mint nekem jelentett a megírása. A szerző 1 Advanced RISC Machines fejlett, csökkentett utasításkészletű gépek 2 1000 mintavételezés/másodperc, az átalakítás sebességéről ad információt 3 Controller Area Network mikrovezérlők helyi hálózata 4 Serial Peripheral Interface soros periféria interfész, három vezetéket használ: órajel, adat és föld 5 ARM7TDMI-S processzor, memória és nagyszámú periféria áramkör 3
3. fejezet A hardver felépítése Ebben a részben néhány blokkvázlat segítségével megpróbáljuk megtervezni mérőműszerünk felépítését. Mivel egy összetett alkalmazásról van szó, ezért az áramkört máris két fő részre bonthatjuk: 1. a mikrovezérlőt és a kisegítő alkatrészeit (JTAG 1 ) tartalmazó blokkra, valamint 2. a végrehajtókat, beavatkozókat és érzékelőket tartalmazó blokkra. Erre azért van szükség, hogy a mikrovezérlőt tartalmazó (processzor modul) blokk kapcsolási rajza ne legyen annyira összetett, hogy ne lehessen hozzá szép nyomtatott áramkört tervezni, másrészt rendkívül elegáns megoldás az, amikor a megoldandó feladatnak megfelelő blokkot egy szalagkábellel kötjük össze a processzor modullal. A mérőberendezés blokkvázlata 3.1. A számítógép, mint mérésvezérlő Nézzük meg a különböző blokkok felépítését! Szerencsénkre a számítógéppel nincsen különösebb gondunk, hiszen az többnyire adott, nem kell rajta változtatásokat eszközölni. Annyit azonban elvárhatunk tőle, hogy rendelkezzen párhuzamos (nyomtató) csatlakozóval, mert ezen keresztül tudjuk az elkészült programot feltölteni a mikrovezérlőbe, és ezen keresztül követhetjük nyomon a program futását a mikrovezérlőben. Ez az IBM kompatibilis gépeken mindig egy 25 pólusú DSUB csatlakozó, pontosabban annak female (anya) változata. Ennek a portnak az az előnye, hogy nagyon könnyen programozható, és TTL 2 szintű (0 V 5 V) jelekkel dolgozik 3. Egy másik fontos követelmény a számítógéppel szemben a soros port megléte, mert ezen keresztül történik 1 A lefordított program feltöltésére és a mikrovezérlő működésének ellenőrzésére (nyomkövetésre) szolgáló általános célú interfész 2 Tranzisztor-tranzisztor logika 3 Az ARM alapú mikrovezérlők 3,3 V-os tápfeszültséggel működnek, így az általuk kiadott logikai magas szint 3,3 V, ami a TTL rendszerben már magas szintnek számít 4
3.2. AZ ARM ALAPÚ MIKROVEZÉRLŐK a parancsok és mérési adatok átvitele a mikrovezérlő és a számítógép között. Nem várunk el a soros porttól túl sokat: mi is (csak) az iparban leggyakrabban alkalmazott 9600 8n1 átviteli konfigurációt (9600 bit/sec átviteli sebesség, 8 adatbit, nincs hibaellenőrzés, 1 stop bit) fogjuk alkalmazni. Kialakítását tekintve ez egy 9 pólusú DSUB male (apa) csatlakozó. Hátránya ennek a rendszernek, hogy -15 V +15 V-os jelszintekkel dolgozik, de azt a problémát egy illesztő áramkörrel (a jól bevált MAX232 IC-vel) könnyen megoldhatjuk. Cserében viszont az áthidalható távolság több 10 m-re növekedhet, ellentétben a JTAG kábel 1 m-es maximális hosszával. A számítógép természetesen hasznavehetetlen egy megfelelő működtető program nélkül. Erről témáról a Fejlesztői környezet részben talál további információkat, példákat az Olvasó. 3.2. Az ARM alapú mikrovezérlők Az ARM nevet eredetileg mikroprocesszorokkal kapcsolatban használták, mert alig néhány éve még csak mikroprocesszorok készültek ARM maggal. Ezeket a mikroprocesszorokat aztán számítógépekbe ültették, így alakítva ki az Acorn, az Apple, vagy az Intel ARM alapú gépeit. Az áramköri lapka csak mint mikroprocesszor működött, és számos kiegészítő elemet (memória, megszakításvezérlő, I/O 4 kezelő, busz illesztő) kellett hozzá illeszteni. Az elmúlt néhány évben a technológia gyors fejlődése lehetővé tette, hogy egy szilícium lapkán kialakítható legyen egy teljes értékű számítógép processzorral, memóriával, adat be-/kiviteli eszközökkel, és egyéb perifériákkal. Ezeket mikrovezérlőknek nevezzük. Nem a kis méretük miatt mikro -k, hanem azért, mert nem általános célú számítógépek, mint az asztali és hordozható számítógépeink, hanem bizonyos feladatcsoport megoldására tervezett kis teljesítményű számítógépek. Azért ne becsüljük le ezeket a mikro eszközöket, hiszen így is jóval többre képesek, mint amit el tudnánk képzelni! Ezekben ugyanis egy teljes értékű 32 bites mikroprocesszor dolgozik, ami azt jelenti, hogy az egy lépésben feldolgozható adat hossza 32 bit. A természetes számok (integer, unsigned int) 0 és 4.294.967.295 (több, mint 4 milliárd!) vehetnek fel tetszőleges értéket. Aki ezt is kevésnek találja, használhat lebegőpontos (float) számokat, ezekkel még a csillagászati számokat is könnyedén tudjuk ábrázolni. A lapkán kialakított memória már korántsem ennyire szívderítő: a program tárolására használható memória (FLASH nem felejtő memória) 8 kbájt és 512 kbájt között változik, míg az adatok tárolására szolgáló gyors, de a tápfeszültség megszűnésekor felejtő (RAM) memória 2 kbájt és 128 kbájt között változik. Ezek az értékek típusonként eltérőek. Az általam javasolt mikrovezérlő (a legkisebb és egyben a legolcsóbb) 2 kbájt RAM-mal és 8 kbájt FLASH-sel rendelkezik. És ez elég-e? Igen, majd úgy írjuk a programot, hogy beleférjen! 4 Adat be-/kivitel Az ARM processzor blokkvázlata 5
3.2. AZ ARM ALAPÚ MIKROVEZÉRLŐK A főprocesszor (ATM7TDMI main processor logic) belső blokkvázlata Ez persze nem minden: egy mai modern (2007. július) mikrovezérlő a processzoron és a memórián kívül még számos kiegészítő eszközzel rendelkezik. Ezeket perifériáknak nevezzük, mert általában a blokkvázlat szélén (perifériáján) kapnak helyet. Álljon itt néhány példa: Analóg digitális átalakítók: folytonos értékkészletű jeleket (tipikusan feszültséget, áramot) alakítanak valamilyen számként (egész vagy fix-pontos) ábrázolt értékké; az ipari gyakorlatban 10 bites A/D átalakítókat használunk, ezek felbontása 0,1 % a teljes skálára vonatkoztatva 16 és 32 bites számlálók: ezekkel vagy külső eseményeket tudunk számolni (hányszor következtek be), vagy a mikrovezérlő órajeléből egy késleltetési (időzítési) értéket tudunk képezni. Pl. mintavételezés minden 100. ms-ban: ekkor az időzítő 100 ms-onként jelez a processzornak, hogy ideje mintavételezni! UART: univerzális aszinkron adó-vevő; ezt fogjuk a számítógéppel való kommunikációhoz használni I 2 C, vagyis Inter-IC kommunikáció: számos gyártó készít olyan eszközt (memóriák, PLL szintézerek 5, A/D átalakítók, DDS 6 -ek), melyek ezen busz segítségével programozhatók. 2-vezetékes (+föld) átvitelt tesz lehetővé SPI: Serial Peripheral Interface, azaz soros periféria interfész; hasonló az I 2 C-hez, de másként működik. Tipikusan MMC 7 és SD kártyákkal történő kommunikációhoz használjuk, de számos eszköz támogatja (digitális hőmérők, soros FLASH alapú memóriacsipek, mikrovezérlők). 5 Frekvenciaszorzó 6 Direkt digitális szintézer 7 Fényképezőgépek, MP3 lejátszók, PDA-k kiegészítő memóriája 6
3.3. ATMEL VAGY NXP TE KIT VÁLASZTANÁL? Megszakításvezérlő: külső események felfüggeszthetik az aktuális program futását, és arra kényszeríthetik a processzort, hogy egy másik, előre meghatározott programrészt hajtson végre. Annak lefutása után a processzor visszatér az eredeti feladatához, és ott folytatja, ahol abbahagyta. A megszakításvezérlő a külső események rangsorolását, és a processzornak való jelzést végzi és segíti Órajel-generátor: a mikroprocesszor szívverését szolgáltatja, minden esemény szinkron módon ehhez igazodik GPIO: általános célú (digitális) adatbemenetek és kimenetek; ezek segítségével tudjuk például egy kapcsoló állását vizsgálni, vagy ezzel tudunk egy lámpát bekapcsolni RTC: valós idejű óra (és kalendárium, szökőév-számítással csak az LPC21xx-ben) WDT: watchdog timer, olyan időzítő, ami alaphelyzetbe állítja (RESET) a mikrovezérlőt, ha a szoftvere lefagy (Ez azért lehet hasznos, mert például egy műholdat vagy egy Mars-járót nem lehet kézzel újraindítani, ehhez hardver támogatás kell) USB: univerzális soros busz, tipikusan a számítógéppel való kommunikációhoz Ethernet: hálózati kommunikációhoz; ezzel megvalósíthatjuk a teljes TCP/IP kommunikációt, így a mikrovezérlőnket a Föld bármely pontjáról utasíthatjuk CAN busz: controller area network; mikrovezérlők hálózatba kötésére használatos, valósidejű kommunikációt valósíthatunk meg vele PWM: időzítő/számláló egységekkel (impulzusszélesség-modulációt alkalmazva) a digitális jelet analóg jellé alakíthatjuk. Pontos, de lassú eljárás Ezek után biztosan megérti a kedves Olvasó, hogy miért szeretik a mobiltelefonok, PDA-k, digitális fényképezőgépek és hálózati elemek gyártói az ARM alapú mikrovezérlőket alkalmazni termékeikben: a kiváló teljesítményű processzor és a nagyszámú beépített elemkészlet igen jó választás beágyazott rendszereik számára. Mielőtt azonban az Olvasó ellátogatna az ARM cég honlapjára (www.arm.com), hogy mikrovezérlőt rendeljen, el kell mondanom, hogy az ARM cég nem gyárt mikrocsipeket. Ők csak processzorok tervezésével és licenszek (gyártási engedélyek) eladásával foglalkoznak. De akkor hol lehet ARM mikrovezérlőt beszerezni? kérdezheti bárki. A lista szinte végtelen: világszerte majdnem 100 cég foglalkozik ARM lapka gyártással, tőlük kell rendelni. Magyarországon több forrásból beszerezhetők 8, arra azonban figyelnünk kell, hogy a gyártók csak a processzor gyártási engedélyét veszik meg, a perifériákat utólag illesztik hozzá saját ízlésüknek és hagyományaiknak megfelelően! Két mikrovezérlő-gyártó világcég (ATMEL 9 és NXP 10 ) termékeit hasonlítom össze a következő fejezetben, majd objektíven eldöntjük, hogy az NXP LPC 2101 mikrovezérlője a jobb (tényleg az a jobb). A mérőberendezés megépítéséhez is ezt a csipet fogjuk alkalmazni. 3.3. ATMEL vagy NXP Te kit választanál? Ebben a részben azt fogjuk megállapítani, hogy céljaink megvalósításához melyik gyártó mikrovezérlője a legmegfelelőbb. Magyarországon viszonylag könnyű választani, mert csak két gyártó (ATMEL és NXP) termékei kaphatók. A következő táblázat a két gyártó egy-egy mikrovezérlőjének legfontosabb tulajdonságait tartalmazza, teret engedve a szubjektív tapasztalatnak is. Lássuk hát, hogy mit is tudnak azok a bizonyos mikrovezérlők: 8 ATMEL: MSC Budapest Kft. (950 Ft + ÁFA), NXP: FDH Kft. (966 Ft + ÁFA), RET Kft. (rendelésre) és Lomex Kft. (910 Ft + ÁFA) 9 www.atmel.com 10 www.nxp.com, Philips alapította 7
3.4. A PROCESSZOR MODUL Funkció ATMEL AT91SAM7S32 NXP LPC2101 Processzor ARM7TDMI ARM7TDMI-S gcc támogatás Igen Igen OpenOCD támogatás Igen Igen FLASH memória 32 kbájt 8 kbájt RAM 8 kbájt 2 kbájt A/D átalakító 10 bites 10 bites Számlálók, időzítők 3 x 16 bit (PWM kimenettel) 2 x 32 bit, 2 x 16 bit (PWM kimenettel) Valós idejű óra Igen (32 bites egész szám) Igen (teljes naptár funkcióval) UART 1 db 2 db I 2 C 1 db 2 db SPI 1 db 2 db Megszakításkezelő Igen Igen Órajel frekvenciája 55 MHz (PLL nélkül), 220 MHz (PLL-lel) 25 MHz (PLL nélkül), 70 MHz (PLL-lel) Tokozás LQFP-48 LQFP-48 Kiskereskedelmi ára 950 Ft + ÁFA 966 Ft + ÁFA Mikrovezérlő összehasonlító táblázat Látható, hogy az ATMEL mikrovezérlője több memóriával rendelkezik ugyan, de kevesebb perifériát (UART, naptár, I 2 C) épített bele a gyártó. Természetesen ez nem jelenti azt, hogy az NXP mikrovezérlője jobb attól, hogy több perifériát tartalmaz. Személyes benyomásom mégis az, hogy az NXP jobban dokumentálja a mikrovezérlőit, több mintaprogramot ad, a mikrovezérlők hardverét egyszerűbb programozni, a nyomkövetés is egyszerűbb, és talán ez a legfontosabb: kevésbé érzékeny a külső zavaró hatásokra (csavarhúzó beejtése, lábak kézzel való érintése, stb. Ha a mostani mikrovezérlőm mesélni tudna 11... Figyelembe véve az objektív tényeket és szubjektív tapasztalatokat, úgy tűnik, hogy az NXP cég LPC2101 típusú mikrovezérlője fogja a mérőberendezés középpontját alkotni. 3.4. A processzor modul Ez a fejezet szolgál arra, hogy megismerjük az LPC2101 mikrovezérlő köré építendő külső áramköröket. Ilyen áramkör például a tápfeszültség előállítására szolgáló stabilizátor, a mikrovezérlő újraindítását szolgáló RESET áramkör, vagy a program feltöltését és működésének elemzését lehetővé tevő JTAG interfész. Ezek szükséges és elégséges áramkörök, de saját igényeink szerint ki is egészíthetjük ezeket, például kijelzővel, gombokkal, potenciométerekkel (jelen esetben ettől eltekintünk, hiszen ez csak egy tesz áramkör, és nem a Paksi Atomerőműnek fejlesztünk). Követelmények az áramkörrel szemben: Biztosítania kell a mikrovezérlő működéséhez szükséges feltételeket, úgymint stabilizált, túlfeszültség ellen védett tápfeszültség, max. 15 V bemenő feszültségig (tipikusan ipari 12 V) nagypontosságú órajel a pontos időzítésekhez, aszinkron soros átvitelhez (ezt egy 12 MHz frekvenciájú kvarc biztosítja) a JTAG kivezetések, 10 pólusú szalagkábel csatlakozón keresztül a megfelelő üzemmódba lépést beállító kapocspár (nyomkövetés vagy normál üzem) hardver RESET kivezetés a mikrovezérlő esetleges kézi újraindításához Elvárhatjuk, hogy az alkalmazáshoz szükséges alapvető elemek is szerepeljenek a panelen: (Token Ring )RS-232 kommunikációt lehetővé tevő kivezetések és illesztő áramkörök (MAX 232) 11... akkor elmondaná, hogy ma majdnem elégett (ez szó szerint értendő), a gyanta már szenesedett alatta, mégis működik. Eddig ezt az eszközt kínoztam meg legjobban, és kiválóan bírja. 8
3.4. A PROCESSZOR MODUL 10 pólusú szalagkábel csatlakozóra kivezetett ki- és bemenetek: 2 analóg bemenet 2 analóg (pontosabban PWM 12 ) kimenet 4 vagy 5 digitális ki-/bemenet (szoftverből beállíthatók) Az fenti követelményrendszert az következő kapcsolás maradéktalanul kielégíti. Talán az RS-232 illesztő áramkört hiányolhatja az Olvasó: az nem ezen a rajzon kapott helyet, mert már régebben készítettem egy teljesen különálló, 4 pólusú tüskesoron keresztül csatlakoztatható áramkört, így feleslegesnek látom újra megépíteni. Az illesztő rajzát is megtalálhatjuk a processzor modul rajza alatt. A processzor modul kapcsolási rajza Lássuk az RS-232 illesztő áramkör kapcsolási rajzát is! TTL RS-232 illesztő Ezzel teljessé vált a processzor modul rajza. A következő lista azt tartalmazza, hogy a kapcsolási rajz egyes elemei milyen funkciókat látnak el: 12 Impulzusszélesség-moduláció 9
3.4. A PROCESSZOR MODUL LPC210x: maga a mikrovezérlő. Látható, hogy több tápfeszültség-bemenettel rendelkezik, az azonos nevűek a tokozáson belül össze vannak kötve, ezért azok közül elegendő egyet bekötni RESET: ez a jel a mikrovezérlőt alapállapotba állítja; a csatlakozó közvetlenül a mikrovezérlő RST bemenetére van kötve. A vezetéken megjelenő logikai alacsony szint újraindítja a mikrovezérlőt, ezért egy megfelelő értékű felhúzóellenállással gondoskodnunk kell arról, hogy normál üzemben logikai magas szinttel lássuk a mikrovezérlőt JTAG csatlakozón keresztül tölthetjük a mikrovezérlő programját az eszközbe, és ezen keresztül végezhetjük el a program tesztelését is. Még számos hasznos funkciója van, ezzel a későbbi fejezetekben ismerkedünk meg részletesen Q1, C1 és C2: a mikrovezérlő órajel-generátorának külső elemei, ezek határozzák meg az órajel frekvenciáját RS-232 csatlakozó: a soros port jelillesztő áramkörét csatlakoztathatjuk ide. A mikrovezérlő P0.0-ás lába a TXD 13, míg a P0.1-es láb az RXD 14. Erről a tüskesorról csak 3,3 V-ot tudunk az illesztő számára biztosítani, de a tapasztalat azt mutatja, hogy ez is elegendő a megfelelő működéshez R2-es ellenállás: nagyon fontos. Ha a P0.14-es lábon alacsony logikai szint van a mikrovezérlő újraindításakor, akkor mindenképpen a beépített BOOT 15 kód indul el, és a felhasználói program (a mi programunk) nem tud lefutni. Ezért ezt a lábat mindig logikai 1 szinten kell tartani, erre szolgál az ellenállás. Értéke 10 33 kω GPIO 16 csatlakozó: az analóg és digitális vezérlő kimenetek és bemenetek DEBUG csatlakozó: a nyomkövető üzemmód kiválasztására használatos kivezetések. Ha a mikrovezérlő újraindításakor a DBGSEL lábon logikai alacsony szint van, akkor a JTAG interfész használhatóvá válik. Magas szint esetén a mikrovezérlő összes portja használható, mint közönséges I/O port Stabilizátor áramkörök (jobb felső sarok): a mikrovezérlő működéséhez szükséges 3,3 V-os és 1,8 V-os tápfeszültséget szolgáltatják. Mindkét stabilizátor IC hőmérséklet-kompenzált, és túláramvédelemmel rendelkezik Végezetül álljon itt mindkét áramkör nyomtatott áramköri terve. Természetesen, ha az Olvasó utánépíti az áramkört, nem kell a két áramkört külön-külön elkészítenie, nyugodt szívvel egyesítheti a két kapcsolási rajzot és NYÁK 17 -tervet. 13 Adat kimenet 14 Adat bemenet 15 Gyári programozó szoftver 16 General Purpose Input-Output általános célú bemenet-kimenet 17 Nyomtatott áramkör Processzor modul NYÁK-terve 10
3.5. ÉRZÉKELŐ, VÉGREHAJTÓ ÉS BEAVATKOZÓ SZERVEK TTL RS-232 illesztő NYÁK-terve 3.5. Érzékelő, végrehajtó és beavatkozó szervek Önmagában a processzor modul értéktelen, mert ugyan ki tudja adni az utasítást, de semmi sincs, ami végrehajtaná azt. Az utasítás végrehajtásáért a végrehajtó szerv a felelős, ilymódon működtetve a beavatkozó szervet. Ebben a részben azzal ismerkedünk meg, hogy miképp lehet végrehajtó szervet illeszteni a mikrovezérlőhöz. Ugyancsak fontos kérdés, hogy milyen információ alapján adja ki a mikroprocesszor az utasítást a végrehajtó szervnek. Ha a külvilágból vett mérési eredmények, logikai értékek alapján, akkor szükségünk lesz valamire, ami érzékeli a külvilágban bekövetkezett változásokat. Erre valók az érzékelő szervek. Ezek illesztését is megismerjük a fejezet végére. 3.5.1. 12 V-os izzó vezérlése digitális és analóg (PWM) jellel Az egyik legegyszerűbb végrehajtó szerv az elektronikus kapcsoló. Bemenete fogadja a processzortól érkező jelet, és ettől függően megváltoztatja a terhelése áramát vagy feszültségét. Mivel a mikroprocesszor nem rendelkezik (a hagyományos értelemben vett) digitális analóg átalakítóval, ezért a mikrovezérlő PWM kimenetét használjuk a kapcsolandó áram értékének beállítására. Ez azt is jelenti, hogy az elektronikus kapcsoló mindig digitális jelet kap: néha folyamatosan be lesz kapcsolva, néha pedig csak 1-2 ms-ra (majd 1-1 ms-ra kikapcsoljuk, aztán megint be 1-2 ms-ra, aztán ki... ). A ki-be kapcsolgatás idejének arányával (vagyis az áramimpulzus szélességének megváltoztatásával = PULSE WIDTH MODULATION) állíthatjuk be a terhelésen átfolyó áram ÁTLAGértékét. Szerencsére a mikrovezérlő rendelkezik hardveres PWM támogatással, így igazából nekünk semmit sem kell kapcsolgatni, megteszi azt helyettünk egy megfelelően beállított számláló. Elektronikus kapcsolóként egy MOSFET-et fogunk használni. Ennek előnye, hogy alacsony 18 frekvencián nem igényel vezérlő teljesítményt, emellett akár 28 A-es áramot is képes károsodás nélkül kapcsolni. A választás az IRL 540-es típusra esett, mert ez már 3,3 V kapufeszültség hatására is kinyit. A típusjelzésben az L betű arra utal, hogy logikai feszültséggel is vezérelhető. A FET-en kívül már csak védőelemek és csatlakozók találhatók az áramkörben. Előbbiek feladata, hogy megvédjék a FET-et a mikrovezérlőtől és a mikrovezérlőt a FET-től. 3.5.2. Fényérzékelők, fotodiódák Ebben a fejezetben egy fényérzékelő elemet, vagy ha úgy jobban tetszik: egy egy képpontból álló szemet építünk a mikrovezérlő számára. Ez már egy kicsit bonyolultabb, mint a FET-es kapcsoló volt, hiszen ebben erősítő elemet kell elhelyeznünk a megfelelő érzékenység és linearitás elérése céljából. Láthatjuk, hogy semmi olyan elemet nem tartalmaz az áramkör, amit otthon, a ház körül ne találnánk meg. Remélem elnézi nekem az Olvasó, de az elektronikus kapcsoló olyan egyszerű már-már primitív, hogy nem szándékozom külön nyomtatott áramköri lapon elhelyezni a két áramkört, ezért most adom meg az egyesített elvi rajzot és NYÁK-tervet. Az egybeépítésnek megvan a maga előnye: a fényérzékelő fotodióda közvetlen kapcsolatban van a lámpával, így leegyszerűsíthet számos mérést (könnyen végezhetnénk tranziens állapotbeli vizsgálatot, transzfer karakterisztika felvételt; hozzáteszem, hogy ez utóbbi mérést egy célszerűen kialakított, passzív hálózattal fogjuk elvégezni a 18 néhány 100 khz 11
3.5. ÉRZÉKELŐ, VÉGREHAJTÓ ÉS BEAVATKOZÓ SZERVEK könnyebb ellenőrizhetőség kedvéért). A végrehajtó-érzékelő modul kapcsolási rajza A végrehajtó-érzékelő modul NYÁK-terve Láthatjuk, hogy az érzékelő a következő részekből épül fel: fényérzékelő dióda (D5): ennek feladata, hogy a szivárgóáramát (ami eredetileg haszontalan, sőt káros) a beérkező fotonok számával arányosan változtassa. A szivárgóáram értékéből következtetni tudunk a fényerősségére áram-feszültség átalakító: a fotodióda szivárgóáramát alakítja mérhető feszültséggé. Ez egy elektronikai alapkapcsolás, bármely szakkönyvben megtalálható. Érdekessége, hogy a kimeneti feszültségszintet (offszetet) egy potenciométerrel lehet beállítani (R2). Ez egyúttal a dióda előfeszítését is változtatja. Mivel a dióda áramgenerátoros üzemmódban dolgozik, ez mit sem befolyásol működésén. Az R1-es potenciométer teremt kapcsolatot a szivárgóáram és a kimeneti feszültség között a már jól ismert Ohm törvény szerint: U ki = I sz R 1 ahol I sz a szivárgóáram értéke, és R 1 a potenciométer ellenállása 12
3.5. ÉRZÉKELŐ, VÉGREHAJTÓ ÉS BEAVATKOZÓ SZERVEK ehhez persze hozzá kell számolni az offszet feszültség értékét, és azt, hogy a kimeneten egy 6,2 V-os Zénerdiódás szinteltoló is helyet kapott. Ez a szinteltoló és határoló az utolsó fokozat. Kimenete közvetlenül a mikrovezérlő bemenetére csatlakozik Ezzel elérkeztünk a hardver leírás végéhez, a következő fejezetben a szoftver háttérrel fogunk megismerkedni. 13
4. fejezet Fejlesztői környezet Ebben a fejezetben arról lesz szó, hogy milyen szoftverkomponensek szükségesek a mikrovezérlő programjának (továbbiakban: firmware) és a személyi számítógép felhasználói programjának (továbbiakban: szoftver) előállításához. Mivel a számítógép és a mikrovezérlő egyaránt 32 bites, vagyis az egy lépésben kezelhető adatok 32 bitesek, ezért a forráskódok ott, ahol ez lehetséges megegyeznek egymással (elsősorban header fájlok, kommunikációs rutinok). A XXI. században már nem vagyok hajlandó assembly nyelven programozni, ezért a programokat C fordítóprogram segítségével fogjuk elkészíteni. Szerencsére a GNU 1 prodzsekt GCC 2 -je fordít Intel ia32 és ARM architektúrára, így ugyanazt a fordítóprogramot használhatjuk mindkét esetben. A gcc szabad szoftver, szabadon beszerezhető a http://www.gnu.org/gcc oldaltól. Célszerű a forráskódját letölteni, és azt lefordítani a célgépre. Ha így teszünk, szükségünk lesz a binutils 3 programcsomagra, töltsük le azt is. Az elkészült programot valahogyan a mikrovezérlőbe kell juttatni. Erre szolgál az OpenOCD 4. Természetesen ez is letölthető az internetről. A bevezető után nézzük meg röviden a felhasznált programok telepítését és használatát! 4.1. C fordító Linuxos PC-re Ha az Olvasó is Linuxot használ, akkor egyszerű dolga van, hiszen minden Linux disztribúciónak 5 része a gcc 6. Ez a fordítóprogram a személyi számítógép számára készít futtatható állományokat, tehát a felhasználói programunk elkészítéséhez fog hozzájárulni. Telepítése rendkívül egyszerű, Debian alapú Linux disztribúciók esetén (Pl. Ubuntu) nem tartogat semmilyen meglepetést. Léteznek különböző grafikus telepítőprogramok, mi viszont a legegyszerűbb módszerrel installáljuk fordítóprogramunkat: $ sudo apt-get install gcc make Ezzel a paranccsal utasítást adtunk a számítógépünknek, hogy töltse le és telepítse a C fordítót. Ha minden rendben megy, akkor néhány másodperc múlva ki is próbálhatjuk: $ gcc gcc: no input files A második sor a gcc válasza, vagyis egy hibaüzenet: nem adtunk meg forrásfájlt, amit fordíthatna. Ha eddig minden rendben történt, akkor nem lesz problémánk a számítógép programjának elkészítésével. Fontos azt is tudni, hogy ezzel a C fordítóval fogjuk a gcc-t lefordítani úgy, hogy az új fordító (keresztfordító) az ARM processzor számára generáljon futtatható állományt, ezért ennek megfelelő működése alapvető fontosságú. 1 GNU s Not UNIX a GNU nem UNIX 2 GNU Compiler Collection GNU fordítógyűjtemény 3 Assembler, linker, objcopy, objdump és még sok hasznos program 4 Open On-Chip Debug 5 Terjesztéssel 6 C, C++, objektív C, java, fortran, ADA fordító 14
4.2. C FORDÍTÓ ARM PROCESSZORRA 4.2. C fordító ARM processzorra A már előzőleg letöltött binutils és gcc forrást le kell fordítani. Ez nem túl bonyolult: csomagoljuk ki a tömörített állományt: tar xjvf binutils-2.17.tar.bz2 lépjünk be a binutils-2.17 könyvtárba: cd binutils-2.17 végezzük el a konfigurálást:./configure --target=arm-elf --prefix=/usr/local/arm fordítsuk le a forrást: make (ez néhány percig tart) telepítsük fel: sudo make install csomagoljuk ki a másik tömörített állományt is: tar xjvf gcc-4.0.3.tar.bz2 lépjünk be a gcc-4.0.3 könyvtárba, hozzuk létre az armgcc könyvtárat, és lépjünk bele: cd gcc-4.0.3; mkdir armgcc; cd armgcc végezzük el a konfigurálást: export PATH+=/usr/local/arm/bin;../configure --prefix=/usr/local/arm \ --target=arm-elf --enable-languages=c fordítsuk le a forrást: make (ezután menjünk el teázni... ) telepítsük fel: sudo make install A fordítás és telepítés után a keresztfordítónk (ami már ARM architektúrára fordít) a /usr/local/arm/bin könyvtárban található, és arm-elf-gcc névre hallgat. Ha ezzel is megvagyunk, akkor már majdnem tudunk programot írni ARM mikrovezérlőre. Egyetlen dolog, ami még ettől elválaszt, az a mikrovezérlő regisztereinek nevét és helyét tartalmazó header fájl (mert memóriacímmel mégsem hivatkozhatunk a regiszterekre). A C fordítónak mindegy, de az emberek nem tudnak 32 bites számokat megjegyezni, ezért a 32 bites számokat (memóriacímeket) névvel látták el, amit már meg tudunk jegyezni. Ezek listáját tartalmazza a header fájl, nevezetesen az lpc2103.h Egy rövid idézet a fájlból: /* Fast General Purpose Input/Output (GPIO) */ #define FIODIR (*((volatile unsigned long *) 0x3FFFC000)) #define FIOMASK (*((volatile unsigned long *) 0x3FFFC010)) #define FIOPIN (*((volatile unsigned long *) 0x3FFFC014)) #define FIOSET (*((volatile unsigned long *) 0x3FFFC018)) #define FIOCLR (*((volatile unsigned long *) 0x3FFFC01C)) /* Memory Accelerator Module (MAM) */ #define MAMCR (*((volatile unsigned char *) 0xE01FC000)) #define MAMTIM (*((volatile unsigned char *) 0xE01FC004)) #define MEMMAP (*((volatile unsigned char *) 0xE01FC040)) /* Phase Locked Loop (PLL) */ #define PLLCON (*((volatile unsigned char *) 0xE01FC080)) #define PLLCFG (*((volatile unsigned char *) 0xE01FC084)) #define PLLSTAT (*((volatile unsigned short*) 0xE01FC088)) #define PLLFEED (*((volatile unsigned char *) 0xE01FC08C)) Természetesen a header fájl is letölthető az internetről, sőt le is töltendő, hiszen első dolgunk lesz minden programunkban, hogy beidézzük azt az első sorban: 15
4.3. AZ OPENOCD FELTÖLTŐ ÉS NYOMKÖVETŐ PROGRAM #include "lpc2103.h" Ha megszereztük a header fájlt is, akkor készen állunk, hogy megkezdjük a fejlesztést! Működő, teljes példákat a Felhasználási területek fejezetben talál az Olvasó. Ott részletesen leírok minden lépést, a forráskód megírásától a tesztelésig és a futtatásig. 4.3. Az OpenOCD feltöltő és nyomkövető program Az OpenOCD egy nyílt forráskódú program az ARM alapú mikrovezérlők programjának feltöltésére és a program futásának ellenőrzésére. Töltsük le az aktuális stabil vagy fejlesztői verziót, és fordítsuk le az alábbiak szerint: csomagoljuk ki a tömörített állományt: tar xjvf OpenOCD.tar.bz2 lépjünk be a trunk könyvtárba: cd trunk végezzük el a konfigurálást:./bootstrap;./configure \ --enable-parport vagy --enable-parport --enable-parport_ppdev Ez utóbbi (két) argument azért szükséges, mert alapértelmezés szerint nincs engedélyezve a párhuzamos (nyomtató) porton való kommunikáció. USB-s JTAG interfész esetén telepítsük fel a libusb és libftdi függvénykönyvtárakat is, majd a konfigurálásnál használjuk a --enable-ft2232_libftdi argumentumot (is)! fordítsuk le a forrást: make (ez néhány másodpercig tart) telepítsük fel: sudo make install A kész program a /usr/local/bin/openocd nevet viseli. Az OpenOCD-t úgy tervezte a programozója, hogy telnet 7 program segítségével lehessen irányítani. Ez azért hasznos, mert a világ bármely pontjáról elérjük mikrovezérlőnk programozó szoftverét. A másik érdekessége az OpenOCD-nek, hogy össze lehet kapcsolni a gdb-vel 8, így közvetlenül a C forrásunk segítségével ellenőrizhetjük a mikrovezérlőt és a lefordított feltöltött programot. Ez bizony nagyon nagy segítség bonyolultabb algoritmusok esetén. De nézzük először, hogy miként tudjuk a C fordítóval előállított programunkat áttölteni a mikrovezérlőbe. Ehhez el kell indítani az openocd-t. Az openocd egy szöveges konfigurációs fájlból veszi, hogy milyen paraméterekkel induljon. Egy lehetséges konfigurációs fájl így néz ki: telnet_port 4444 gdb_port 3333 daemon_startup reset interface parport parport_port 0x378 parport_cable hg8lhs jtag_speed 0 reset_config trst_and_srst jtag_device 4 0x1 0xf 0xe target arm7tdmi little run_and_halt 0 arm7tdmi-s_r4 working_area 0 0x40000000 0x2000 nobackup flash bank lpc2000 0x0 0x20000 0 0 lpc2000_v1 0 12000 calc_checksum 7 Távoli belépés 8 GNU debugger GNU nyomkövető (hibamentesítő) 16
4.3. AZ OPENOCD FELTÖLTŐ ÉS NYOMKÖVETŐ PROGRAM Az első két sor a telnet és a gdb hálózati TCP portját adja meg. Aztán beállítjuk, hogy az openocd indulásakor mi történjen: ebben az esetben alaphelyzetbe állítja a mikrovezérlőt. Ezt követi a programozó interfész megadása. Példánknál maradva ez a párhuzamos port, ami a 0x378-as I/O porton van. A kábel típusa hg8lhs lesz, vagyis egy általam definiált kiosztás. A párhuzamos portra csatlakozó JTAG 9 programozó sebessége lehet 0 (valójában nincs szó semmilyen programozóról, így a sebesség értéke nem befolyásol semmit, de szintaktikailag szükséges). Beállítjuk a RESET viselkedést is: mivel nekünk van TRST 10 és SRST 11 kivezetésünk is, ezért mindkettőre igényt tartunk. A következő sorban azt határozzuk meg, hogy a legalacsonyabb JTAG szinten milyen hosszú a parancs regiszter, és milyen parancs szolgál az eszköz azonosítására. A processzor megadása kötött formát követ. A FLASH memória definiálása sem hagy sok lehetőséget a változtatásra, egyedül a 12000 értéket tudjuk módosítani, az ugyanis a kvarckristály frekvenciája khz mértékegységben. Mentsük ezt egy config nevű fájlba, erre fogunk majd az openocd indításakor hivatkozni. Csatlakoztassuk a mikrovezérlőt 12, zárjuk rövidre a DBGSEL kapcsolót (anélkül nem lép DEBUG 13 üzemmódba), indítsuk újra a mikrovezérlőt a RESET átkötéssel, és adjuk ki a következő parancsot: $ sudo openocd -f config Info: openocd.c:86 main(): Open On-Chip Debugger (2007-05-30 17:45 CEST) Amennyiben üdvözlő szöveg után az OpenOCD nem tér vissza hibaüzenettel, akkor minden sikeres volt. Ha nem ezt látjuk, akkor egy üzenet tájékoztat minket a hiba okáról. Ennek oka többnyire a mikrovezérlő tápfeszültségének hiánya, a DBGSEL átkötés rövidre nem zárása, esetleg hibásan kivitelezett JTAG kábel. Ezeken kívül persze a hibák száma végtelen, de hely hiányában nem tudom az összes hibát (és azok megoldását) tételesen felsorolni. Innentől kezdve két dolgot kell tehetünk: a telnet program segítségével belépünk az openocd-be, majd elindítjuk a gdb-t. Ez utóbbi lépés nem feltétlenül szükséges, véleményem szerint az openocd több lehetőséget biztosít a hibafelderítésre, ezért a gdb használatát még röviden sem tárgyalom. Ezt a feladatot az Olvasóra bízom. $ telnet localhost 4444 Trying 127.0.0.1... Connected to localhost. Escape character is ^]. Open On-Chip Debugger > > flash banks #0: lpc2000 at 0x00000000, size 0x00020000, buswidth 0, chipwidth 0 > flash info 0 #1: lpc2000 at 0x00000000, size 0x00020000, buswidth 0, chipwidth 0 #0: 0x00000000 (0x2000kB) erase state unknown, protected #1: 0x00002000 (0x2000kB) erase state unknown, protected #2: 0x00004000 (0x2000kB) erase state unknown, protected #3: 0x00006000 (0x2000kB) erase state unknown, protected #4: 0x00008000 (0x2000kB) erase state unknown, protected #5: 0x0000a000 (0x2000kB) erase state unknown, protected #6: 0x0000c000 (0x2000kB) erase state unknown, protected #7: 0x0000e000 (0x2000kB) erase state unknown, protected #8: 0x00010000 (0x2000kB) erase state unknown, protected #9: 0x00012000 (0x2000kB) erase state unknown, protected 9 Joint Test Action Group általános célú feltöltő, ellenőrző és hibaelhárító hardver és a hozzá tartozó protokoll 10 Target RESET cél alaphelyzetbe állítás 11 System RESET rendszer alaphelyzetbe állítás, ez a teljes áramkört újraindítja 12 A JTAG kábel bekötése a kapcsolási rajz bal alsó sarkában található. Az OpenOCD párhuzamos porti kiosztása a trunk/src/jtag/parport.c fájlból deríthető ki, de mellékletként is megtalálható a dolgozathoz csatolva 13 Nyomkövetés 17
4.4. ADATÁTVITEL MEGVALÓSÍTÁSA SOROS PORTON KERESZTÜL #10: 0x00014000 (0x2000kB) erase state unknown, protected #11: 0x00016000 (0x2000kB) erase state unknown, protected #12: 0x00018000 (0x2000kB) erase state unknown, protected #13: 0x0001a000 (0x2000kB) erase state unknown, protected #14: 0x0001c000 (0x2000kB) erase state unknown, protected #15: 0x0001e000 (0x2000kB) erase state unknown, protected lpc2000 flash driver variant: 1, clk: 12000 > poll target state: halted target halted in ARM state due to debug request, current mode: System cpsr: 0x000000df pc: 0x00000474 > Az időnként felbukkanó prompthoz 14 a következő parancsokat gépelhetjük be: targets flash banks flash info 0 flash erase 0 0 7 flash write 0 program.bin 0 armv4_5 disassemble 0 30 reg poll halt resume cím step cím reset soft_reset_halt mdw, mdh, mdb mww, mwh, mwb dump_image data.bin 0x40000000 2048 listát ad az elérhető eszközökről. Ebből úgyis csak egy lesz, az arm7tdmi (little endian) információt ad az elérhető FLASH bankokról (memóriaterületekről) információt ad a 0. bankról törli a 0. bank első 8 szektorát (0 7-ig) a program.bin lefordított programot a 0. bankba írja 0 kezdőcímmel az első 30 utasítást visszafordítja binárisról assembly nyelvre listát készít a regiszterek aktuális állapotáról megmutatja a processzor állapotát (fut-e vagy áll valahol) leállítja a program futását folytatja a program futását (cím címtől) végrahajtja a következő utasítást (a cím címtől kezdve) alapállapotba állítja a processzort, és elindítja a program végrehajtását alapállapotba állítja a processzort, de felfüggeszti a program végrehajtását. A 0. memóriacímnél várakozik. memóriaterületeken található szavak (32 bites), félszavak (16 bites) és bájtos adatok értékének listázása memóriaterületeken található szavak (32 bites), félszavak (16 bites) és bájtos adatok értékének módosítása RAM tartalom (pl. mérési adatok) mentése a data.bin nevű fájlba A felsorolt parancsokkal azután könnyedén tölthetünk fel programot, futatthatjuk azt, vagy végrehajthatjuk lépésenként, a regiszterek és memóriatartalmak értékének ellenőrzése mellett. Használatuk némi gyakorlatot igényel, de néhány óra alatt jártasságot szerezhetünk benne. 4.4. Adatátvitel megvalósítása soros porton keresztül Ahhoz, hogy a számítógép, mint mérésvezérlő utasításokat adhasson a mikrovezérlőnek, szükséges, hogy valamiféle adatátvitel jöjjön létre a két eszköz között. Ennek egyik legegyszerűbb, és ezért legelterjedtebb módja az RS-232 aszinkron soros átvitel. Aszinkron, mert nem visszük át az órajelet, így azt mindkét fél maga állítja elő az adatfolyam alapján, másrészt soros, mert a bitek nem egyszerre, hanem egymás után kerülnek át egyik gépről a másikra. A hardvert leíró részben már láttuk, hogy rendkívül egyszerű összekábelezni a két berendezést: az egyik készülék TDX lábát a másik RXD lábára kell kötni, és viszont. Természetesen szükségünk lesz egy földvezetékre, így összesen 3 ér szükséges a kommunikációhoz. 14 Készenléti jel, > 18
4.4. ADATÁTVITEL MEGVALÓSÍTÁSA SOROS PORTON KERESZTÜL Az adatátvitel paramétereit (sebesség, adatbitek száma, hibaellenőrzés) szoftveres úton állíthatjuk be: az ipari gyakorlatnak megfelelően válasszuk a 9600 bit/sec sebességű kommunikációt 8 bit adathosszal, hibaellenőrzés nélkül és 1 stop bittel (9600 8N1). A következő programrészlet azt mutatja be, hogy egy Linux-os gépen mindezt hogyan tehetjük meg a legegyszerűbben: FILE *setup_usart0(char *tty_name) { FILE *tty; // Soros port fájl azonosítója struct termios term; // Beállítások tty = fopen(tty_name, "r+"); if (tty == NULL) { return NULL; // Port megnyitása // Hiba esetén üzenet a felhasználónak tcgetattr(fileno(tty), &term); // Beállítások lekérdezése cfsetispeed(&term, B9600); // Vétel sebessége: 9600 bit/sec cfsetospeed(&term, B9600); // Adás sebessége: 9600 bit/sec term.c_cflag &= ~(CSIZE PARENB); // Beállítások módosítása term.c_cflag = CS8; // 8 adatbit, nincs h. e. tcsetattr(fileno(tty), TCSANOW, &term); // Beállítások mentése return tty; // Fájl leíró visszaadása Mivel egy Linux-os gépen minden eszköz egy fájl, ezért a soros portot is úgy kezeljük, mint egy fájlt. Írhatunk bele, olvashatunk belőle... Amit írunk, az megjelenik a soros port kimenetén, és átkerül a másik eszköz vételi regiszterébe. Amit pedig olvasunk, az a soros porton keresztül érkezett egy másik eszköztől. Adat küldése Linux alatt: fwrite(packet, sizeof(struct Packet), 1, fflush(tty); tty); Adat fogadása: fread(packet, sizeof(struct Packet), 1, tty); Ez kevés a kommunikációhoz, hiszen még csak bájtokat vittünk át, tehát következő lépésként a bájtokból meghatározott formátumú csomagot kell készíteni, ami tartalmazza a forrás és a célállomás azonosítóját, a parancsot és az adatokat. Erről részletesen a következő fejezetben olvashatunk. Azt se felejtsük el, hogy az adatátvitel mindig legalább két fél között jön létre (hacsak nem adattárolásról van szó, mert akkor a két fél többnyire azonos, csak időben eltolva), tehát meg kell ismerkednünk a mikrovezérlő soros kommunikációs interfészének programozásával is. Ez sem sokkal összetettebb, hiszen pontosan ugyanazokat a beállításokat kell alkalmazni (különben nem jön létre a kapcsolat). Mivel a mikrovezérlőn nem fut operációs rendszer, ami fájlként emulálja a soros portot, ezért itt más szemléletet kell alkalmaznunk. Létezik egy adó regiszter, ha ebbe írunk, akkor azt a hardver automatikusan elküldi az ellenállomásnak, a vevő regiszterben pedig az ellenállomástól vett adatot találjuk. Lássunk példát a paraméterek beállítására! void setup_usart0(unsigned int divisor) { if (divisor == 0) divisor = 20; /* Alapértelmezés: 9600 bit/sec 12 MHz-nél */ PINSEL0 &= ~0xf; PINSEL0 = 0x5; /* TXD és RXD lábak az UART-hoz rendelve */ 19
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN U0FCR = 0x7; /* Átmeneti puffertár engedélyezése */ U0LCR = 0x83; /* 8 bites szóhossz engedélyezése, nincs hibaellenőrzés, 1 stop bit */ U0DLL = divisor; /* 20_dec ----> BITSEBESSÉG = 9373 bit/sec [hiba = 2.34%] */ U0DLM = 0; /* Bitsebesség: 9600 bit/sec */ U0LCR = 0x3; /* Adó-vevő engedélyezése */ Mint már említettem, bájtok küldése és fogadása egy-egy regiszter írásával, illetve olvasásával érhető el. Erre mutat példát az alábbi kódrészlet: Adat küldése mikrovezérlővel: while(!(u0lsr & (1 << 6))); U0THR = packet_ptr[c]; // Várakozás, amíg foglalt a vonal // Adat beírása az átmeneti tárba Adat fogadása mikrovezérlővel: while(!(u0lsr & 1)); packet_ptr[c] = U0RBR; // Várakozás a bejövő adatra // Adat olvasása az átmeneti tárból 4.5. A kommunikáció protokollja és annak megvalósítása C nyelven Most, hogy már tudunk 1 bájtot küldeni és fogadni, ideje, hogy a bájtokat csomagba szervezzük. Ez azért fontos, mert egy bájt nem elegendő minden információ célba juttatására, ezért a teljes adatmennyiséget több bájt segítségével visszük át. Minden bájt meghatározott pozícióban helyezkedik el, és mindegyik meghatározott feladatot lát el. Ahhoz, hogy pontosan megértsük a rendszer működését, meg kell értenünk, hogy hogyan néz ki egy TokenRing RS-232 hálózat. TokenRing RS-232 hálózat topológiája A gyűrű topológiában elhelyezett állomások egyenrangúak, kivéve egyet, ami egyenrangúbb a többinél: ez a mérésvezérlő. Az állomások azonosítóval rendelkeznek, a mérésvezérlő a 0-s, a többi állomás azonosítóját 1-tól kezdve a mérésvezérlő osztja ki a gyűrű körüljárási irányával megegyező irányban haladva. A gyűrű lényege, hogy aki megkapja a csomagot, tovább is küldi, legfeljebb apróbb módosításokat eszközöl benne. Ez úgy lehetséges, hogy az egyik eszköz kimenete (TXD) a következő eszköz bementére (RXD) van 20
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN kötve. Esetünkben sincs ez másként, de a hálózatunkban mindössze 2 állomás van, így nem is nevezhető igazán hálózatnak. Ettől függetlenül mindent úgy fogunk kezelni, mintha több állomás lenne a gyűrűben, így később több mérőberendezést is össze tudunk kötni. A kommunikáció során alkalmazott csomag a következő formátumú: a célállomás dinamikus azonosítója: ezt az azonosítót minden eszköz ellenőrzi: ha nem nulla, akkor nem neki szól a csomag, csökkenti az azonosító értékét, és továbbküldi a teljes csomagot ha nulla, akkor neki szól a csomag, végrehajtja az utasítást, az azonosító értékét -1-re (0xFF) módosítja (csökkenti, csakúgy, mint a nem nulla esetben), és az adat részben válaszüzenetet (nyugta, hibajelzés, mért adat, stb.) helyez el. Ezt követően továbbküldi a csomagot a feladó azonosítója: mérésvezérlő osztja ki a gyűrű felépítése során, a kommunikáció ideje alatt változhat (a mérésvezérlő mindig a 0-s azonosítójú) az utasítás 8 bites kódja ellenőrző összeg: értékét úgy kell kiszámolni, hogyha csomag bájtjait (8 db) összeadjuk, és elosztjuk 256-tal, akkor a maradék éppen 0-t adjon 4 darab adatbájt, ami kezelhető 1 darab 32 bites számként, vagy akár külön-külön is (mi az előbbi megoldást fogjuk alkalmazni) Lássuk, hogyan néz ki a csomagot leíró C struktúra! struct Packet { unsigned char dst_id; unsigned char src_id; unsigned char command; unsigned char chsum; unsigned int data; // Mivel az ia32 és az ARM is little-endian // (beállítástól függően), ezt megtehetem A teljesség kedvéért definiálnunk kell még a command mező lehetséges értékeit és paramétereit. Az első oszlop az utasítás számmal kifejezett értéke, a második a neve, a harmadik a paraméter neve vagy értéke, míg a negyedik az utasítás rövid leírása. Érték Parancs neve Paraméter Leírás 0 COMM_HELLO üres üzenet a gyűrű feltérképezéséhez 1 COMM_SET_ID azonosító állomás dinamikus azonosítójának beállítása 2 COMM_GET_IOMASK válasz: maszk annak lekérdezése, hogy melyik láb kimenet és melyik bemenet (1: kimenet, 0: bemenet, bit pozíció a mikrovezérlő eredeti kiosztását követi) 3 COMM_SET_IOMASK maszk annak beállítása, hogy melyik láb a kimenet, és melyik bemenet 4 COMM_GET_DIGIO válasz: státusz digitális bemenetek állapotának lekérdezése 5 COMM_SET_DIGIO érték digitális kimenetek állapotának beállítása 6 COMM_GET_ANALOG bemenet azonosító analóg bemenet állapotának lekérdezése válasz: analóg érték 7 COMM_SET_ANALOG MSB: kimenet azonosító analóg kimenet állapotának beállítása periódus «16 + érték érték: PWM magas (H) részének hossza A parancsok kódja Az előbbi táblázatnak megfelelő C fájl (command.h) tartalma: 21
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN #ifndef COMMMANDS_H #define COMMANDS_H #define COMM_HELLO 0 #define COMM_SET_ID 1 #define COMM_GET_IOMASK 2 #define COMM_SET_IOMASK 3 #define COMM_GET_DIGIO 4 #define COMM_SET_DIGIO 5 #define COMM_GET_ANALOG 6 #define COMM_SET_ANALOG 7 #endif A csomagorientált átvitelhez szükségünk lesz csomagot küldő és fogadó C függvényekre. Ezek alapvetően nem bonyolultak, viszont a személyi számítógép és a mikrovezérlő ezen a szinten már annyira különbözik egymástól, hogy célszerűnek látszott külön-külön algoritmusokat létrehozni. Nézzük először a személyi számítógép függvényeit! A soros port átviteli paramétereit beállító programrésszel már megismerkedtünk, sőt az adatküldés és fogadás is ismert előttünk. A következő lépés a teljes csomagot elküldeni képes kód megalkotása. A forrásprogramot igyekeztem megjegyzésekkel ellátni, ez segít a működés megértésében. int send_packet(file *tty, struct Packet *packet) { // 8 bájtnyi adat "írása" a soros portot kezelő fájlba fwrite(packet, sizeof(struct Packet), 1, tty); // Átmeneti tárak ürítése fflush(tty); // Egy kis üzenet a felhasználónak, könnyíti a hibakeresést printf("\n\nküldött ------------------------\n"); printf("cél:\t\t%02x\n", packet->dst_id); printf("forrás:\t\t%02x\n", packet->src_id); printf("parancs:\t%02x\n", packet->command); printf("összeg:\t\t%02x\n", packet->chsum); printf("adat:\t\t%08x", packet->data); fflush(stdout); return 0; A küldő függvény egyszerűen csak kiírja a teljes 8 bájtos csomagot a kimenetre. A felhasználónak kell gondoskodnia arról, hogy a csomag minden mezeje valós információt tartalmazzon! A csomag fogadását végző függvény sajnos már nem ennyire egyszerű, hiszen ennek a hibaellenőrzést is el kell végeznie. Feladata továbbá, hogy a nem nekünk szóló csomagokat továbbküldje a következő állomásnak (a cél-azonosító értékének csökkentése után), valamint dobja el a válaszként érkezett, tehát a gyűrűn teljesen körbeért csomagot. Az eldobás jelen esetben nem azt jelenti, hogy nem foglalkozunk vele, hiszen a válaszüzenet adatot, mérési eredményt tartalmazhat, tehát fel lehet dolgozni. int receive_packet(file *tty, struct Packet *packet) { int c; unsigned char *packet_ptr = (unsigned char *) packet; unsigned char sum; // Beolvassuk a következő 8 bájtos csomagot fread(packet, sizeof(struct Packet), 1, tty); 22
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN // Hibamentesítést segítő üzenetek a felhasználónak printf("\n\nfogadott -----------------------\n"); printf("cél:\t\t%02x\n", packet->dst_id); printf("forrás:\t\t%02x\n", packet->src_id); printf("parancs:\t%02x\n", packet->command); printf("összeg:\t\t%02x\n", packet->chsum); printf("adat:\t\t%08x", packet->data); fflush(stdout); // A 8 bájt összege 0 kell, hogy legyen for (c = 0, sum = 0; c < 8; c++) sum += packet_ptr[c]; // Ha tényleg 0 a bájtok összege... if (sum == 0) { printf("\n\nellenőrizve."); // Ha a cél-azonosító már 0-ra csökkent, akkor nekünk // szól a csomag, "jelezni" kell a feldolgozó rutinnak if (packet->dst_id == 0) { printf("\nparancs csomag érkezett."); return 1; // Ha a forrás-azonosító megegyezik a saját (dinamikus) // azonosítónkkal, akkor a csomagot mi küldtük, tehát ez // egy válasz csomag. Jelezzük ezt is, hátha fontos adatot // tartalmaz if (packet->src_id == 0) { printf("\nválasz csomag érkezett."); return 2; printf("\nismeretlen csomag! Továbbítva..."); // Ha nem mi küldtük, és nem nekünk jött, akkor // csökkentjük a cél-azonosító értékét, és továbbküldjük packet->dst_id--; packet->chsum++; send_packet(tty, packet); // De erről is jelentést adunk return 3; else { // Ha ide jutottunk, akkor az ellenőrző összeg hibás: // a csomag hibás, más protokoll került a gyűrűbe, // vagy kiestünk a szinkronból. Jobb, ha nem csinálunk // semmit, csak továbbküldjük, de jelezzük a feldolgozó // rutinnak printf("\n\nérvénytelen csomag!"); send_packet(tty, packet); return -1; return 0; 23
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN Ezeken kívül még egy rövid rutin kapott helyet a forráskódban. Feladata, hogy a csomag (szándékolt) megváltoztatása után kiszámítsa az ellenőrző összeg új értékét. Valójában ez csak egy rövid ciklus, de nagyon hasznos. void calc_checksum(struct Packet *packet) { unsigned char sum; unsigned char *packet_ptr = (unsigned char *) packet; int c; // Ez a mező ne "szóljon bele" a végeredménybe packet->chsum = 0; // Összeadjuk a 8 bájtot (valójában csak 7-et) for (c = 0, sum = 0; c < 8; c++) sum += packet_ptr[c]; // Ezek ellentettje az ellenőrző összeg packet->chsum = -sum; Az előbb bemutatott függvények a személyi számítógép szoftverének részét képezik. A mikrovezérlő kódja nagyon hasonlóan működik, hiszen pontosan meghatároztuk, hogy miként működik a hálózat. Kisebb eltérések mutatkozhatnak a forrásprogramok között, de ezek nem alapvetőek. Ennek szemléltetése céljából álljon itt a mikrovezérlő hálózatkezelő algoritmusainak forráskódja is! int send_packet(struct Packet *packet) { int c; unsigned char *packet_ptr = (unsigned char *) packet; // 8 bájtnyi adat "írása" a soros portot kezelő fájlba for (c = 0; c < 8; c++) { // Várakozunk, amíg az előző adat még az átmeneti tárban van while(!(u0lsr & (1 << 6))); // A megfelelő adatbájt küldése U0THR = packet_ptr[c]; return 0; A következő kódrészlet a fogadó algoritmust ismerteti. A megjegyzések hasonlósága nem a véletlen műve. int receive_packet(struct Packet *packet, unsigned char my_id) { int c; unsigned char *packet_ptr = (unsigned char *) packet; unsigned char sum; // Beolvassuk a következő 8 bájtos csomagot for (c = 0; c < 8; c++) { while(!(u0lsr & 1)); packet_ptr[c] = U0RBR; sum = 0; // A 8 bájt összege 0 kell, hogy legyen for (c = 0; c < 8; c++) 24
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN sum += packet_ptr[c]; // Ha tényleg 0 a bájtok összege... if (sum == 0) { // Ha a cél-azonosító már 0-ra csökkent, akkor nekünk // szól a csomag, "jelezni" kell a feldolgozó rutinnak if (packet->dst_id == 0) return 1; // Ha a forrás-azonosító megegyezik a saját (dinamikus) // azonosítónkkal, akkor a csomagot mi küldtük, tehát ez // egy válasz csomag. Jelezzük ezt is, hátha fontos adatot // tartalmaz if (packet->src_id == my_id) return 2; // Ha nem mi küldtük, és nem nekünk jött, akkor // csökkentjük a cél-azonosító értékét, és továbbküldjük packet->dst_id--; calc_checksum(packet); send_packet(packet); return 3; else { // Ha ide jutottunk, akkor az ellenőrző összeg hibás. // Vagy a csomag hibás, vagy más protokoll került a gyűrűbe, // vagy kiestünk a szinkronból. Jobb, ha nem csinálunk // semmit, csak továbbküldjük, de jelezzük a feldolgozó // rutinnak send_packet(packet); return -1; return 0; Végezetül lássunk egy lehetséges de nagyon egyszerű példát az előbb ismertetett függvények használatára. Ezek a programok működőképesek: a hálózatban részt vevő állomások számának megállapítására, és a dinamikus azonosítók kiosztására használhatók. Kezdjük a számítógép főprogramjával: #include <termios.h> #include <unistd.h> #include <stdio.h> // A már régebben leírt függvények prototípusa #include "usart.h" // Parancskódok #include "commands.h" // A hálózat elemeinek számát állapítja meg: // Hánnyal csökkent a cél-azonosító // A cél-azonosító kezdeti értékének nagynak kell lennie, nehogy // valamelyik állomás neki szóló parancscsomagnak értelmezze int get_chain_length(file *tty) { struct Packet packet = {0xff, 0, COMM_HELLO, 1, 0; 25
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN // Előkészített csomag küldése send_packet(tty, &packet); // Válaszcsomag vétele receive_packet(tty, &packet); // A csökkenés mértéke -> állomások száma return 0xff - packet.dst_id; /*************************************************************/ /* A főprogram */ /*************************************************************/ int main() { FILE *tty; struct Packet packet; int length, id; // Soros port "megnyitása", paraméterbeállítás if ((tty = setup_usart0("/dev/ttys0")) == NULL) { perror("controller:setup_usart"); return 1; // Üzenet a felhasználónak, állomások számának megállapítása printf("\n\ngyűrű hossza: %d", length = get_chain_length(tty)); // Dinamikus azonosítók kiosztása printf("\nazonosítók kiosztása..."); for (id = 1; id <= length; id++) { // Csomag összeállítása packet.dst_id = id - 1; packet.src_id = 0; packet.command = COMM_SET_ID packet.data = id; // Ellenőrző összeg kiszámítása calc_checksum(&packet); // Csomag küldése send_packet(tty, &packet); // Válasz vétele, igazából nem foglalkozunk vele receive_packet(tty, &packet); // Soros port "lezárása" fclose(tty); printf("\n\n"); return 0; A számítógép főprogramja által küldött csomagokat a mikrovezérlő főprogramja dolgozza fel: // Hardverfüggő header fájlok #include "lpc2103.h" #include "../LPC210x/library.h" 26
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN #include "commands.h" // A főprogram és a megszakításkezelő prototípusa PROTO_MAIN(); PROTO_IRQ(); // Adatszegmens kezdetének jelzése egy vicces hexadecimális számmal // 0xdeadbeef: DEAD BEEF (kimúlt szarvasmarha) unsigned int temp = 0xdeadbeef; // Csomag helyének feltöltése véletlen adatokkal (helyfoglalás) struct Packet packet = {0x04, 0x03, 0x02, 0x01, 0x55aaff00; // Legyen a dinamikus ID-m 1, aztán majd úgyis kapunk újat // a számítógéptől unsigned char my_id = 0x1; /**********************************************************/ /* A főprogram */ /**********************************************************/ int main() { // Vermek beállítása (makró) SETUP_STACKS(); // Minden port kimenet IODIR = 0xffffffff; // Jellegzetes bitminta a kimenetekre, működés ellenőrzésére IOPIN = 0xaaaaaaaa; // Soros port paramétereinek beállítása setup_usart0(0); // Dinamikus azonosító beállítása (az előbbi nem elég!) my_id = 0x1; while(1) { // Ha van csomag a vételi tárban, akkor feldolgozzuk // Ha nincs, akkor csinálhatunk mást is... if (detect_packet()) { // Csomag kiolvasása az átmeneti tárból // A függvény visszatérési értéke: // 0: Elvileg sosem fordul elő // -1: Ellenőrző összeg hibás // 1: Nekünk szóló parancs csomag // 2: Egy régebbi parancscsomagunkra érkezett válasz, // ami körbeért a gyűrűn; választ tartalmazhat! // 3: Továbbküldendő csomag érkezett (továbbküldte) // Ha nekünk szóló parancs csomag érkezett if (receive_packet(&packet, my_id) == 1) { // Válogatás a parancskódok alapján switch (packet.command) { // Gyűrű felderítése case COMM_HELLO: break; 27
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN // Dinamikus azonosító beállítása // Erre a csomag adatmezejét a vicces 0xDEADBEEF-re // módosítjuk (az ellenőrző összegre vigyázunk!) case COMM_SET_ID: my_id = (unsigned char) (packet.data & 0xff); packet.dst_id = 0xff; packet.data = 0xdeadbeef; calc_checksum(&packet); send_packet(&packet); break; // Megszakítás-kiszolgáló rutin, most nem használjuk void irq() { Ha az előbb idézett forráskódokat megfelelően lefordítjuk, összefűzzük és lefuttatjuk, akkor a számítógép szoftvere együttműködve a mikrovezérlővel a következő eredményt szolgáltatja: KÜLDÖTT ------------------------ Cél: FF Forrás: 00 Parancs: 00 Összeg: 01 Adat: 00000000 FOGADOTT ----------------------- Cél: FE Forrás: 00 Parancs: 00 Összeg: 02 Adat: 00000000 Ellenőrizve. Válasz csomag érkezett. Gyűrű hossza: 1 Azonosítók kiosztása... KÜLDÖTT ------------------------ Cél: 00 Forrás: 00 Parancs: 01 Összeg: FE Adat: 00000001 FOGADOTT ----------------------- Cél: FF Forrás: 00 Parancs: 01 Összeg: C8 Adat: DEADBEEF 28
4.5. A KOMMUNIKÁCIÓ PROTOKOLLJA ÉS ANNAK MEGVALÓSÍTÁSA C NYELVEN Ellenőrizve. Válasz csomag érkezett. Látható, hogy a program először küldött egy általános csomagot, ebből megállapította a gyűrű hosszát: eredményül 1-et kapott. Ezt követően elkezdte az eszközöknek (1 db) kiosztani az azonosítókat: sikerességéről a 0xDEADBEEF (elhullott szarvasmarha) üzenet tájékoztat. A fejezetben felépített vázra fogjuk felépíteni a mérőkészülékünk többi funkcióját, ezért feltétlen hasznosnak tartom a leírtak megfelelő megértését és elfogadását. Ha az Olvasó bizonytalannak érzi magát, vagy kétség gyötri, lapozzon vissza bátran, s frissítse fel ismereteit, hiszen a következő részekben hivatkozni fogok erre az anyagra. 29
5. fejezet Felhasználási területek Ebben a részben néhány példán keresztül megismerkedhetünk egy mikrovezérlőből, egy személyi számítógépből és néhány egyszerű áramkörből álló mérési összeállítás felhasználási területeivel. Megnézzük, hogy az előbb felsorolt elemekből hogyan építhető mérésadatgyűjtő és vezérlő készülék. A rendelkezésre álló kevés számú elem ellenére az alkalmazási területek széles skáláját igyekszem bemutatni, de sajnos számtalan olyan mérési-vezérlési elv létezik, amit időbeli és terjedelmi okokból nem áll módomban tárgyalni. Ha tisztelt Olvasó bizonyos szakterületben jobban el kíván mélyülni, akkor a rendelkezésre álló szakirodalomhoz kell folyamodnia. Az első részben részletesen megismerkedhetünk a legegyszerűbb vezérlési feladatokkal és megoldásukkal. Ez a fejezet inkább azért hasznos, mert segítségével könnyen áttekinthetjük a mikrovezérlő teljes szoftverhátterének működését. 5.1. Vezérlés Automatikából így tanultuk: A vezérlés olyan nyílt hatásláncú irányítástechnikai tevékenység, amely során a beavatkozás nem közvetlenül a szabályozott jellemző megváltozásának eredményeképpen jön létre, hanem bizonyos külső és egyéb jellemzők logikai kapcsolatának hatására. Döntően diszkrét jelekkel dolgozik, de használhat analóg és digitális jeleket is. Ez azt jelenti, hogy nem lesz szükségünk érzékelőre: a vezérléshez a mikrovezérlő egyik kimeneti portját a végrehajtó szerv (elektronikus kapcsolóját irányító) bemenetére kötjük. Vezérlés egyszerűsített rajza A következő három alfejezetben ezt a mérési összeállítást használjuk. A helyesen megépített végrehajtó érzékelő áramkörön nem szükséges semmilyen változtatást végrehajtani, mert az pontosan megfelel az előbbi ábrának. 5.1.1. 12 V-os izzó be- és kikapcsolása Ez a feladat több célt szolgál: egyrészt megismerkedünk a mikrovezérlő vezérlőként való alkalmazásával, másrészt lévén a működtető szoftver igen egyszerű gondosan áttanulmányozhatjuk a programozás lépéseit. 30
5.1. VEZÉRLÉS Kezdjünk is hozzá! Szükségünk lesz egy C fordítóra, ami ARM architektúrára fordít. Erről már a Fejlesztői környezet fejezetben volt szó, így ezzel nem kívánok foglalkozni. Ezen kívül el kell készítenünk a forrásprogramot, valamint egy olyan fájlt, ami megkönnyíti a fordítást (Makefile). Ez utóbbi arra szolgál, hogy forgatókönyvként szolgáljon a make 1 számára. A C forráskód (main.c) tartalma a következő: // A mikrovezérlő hardver regiszterei #include "lpc2103.h" // Hasznos rutinokat tartalmazó függvénykönyvtár (ld. melléklet) #include "../LPC210x/library.h" // A főprogram és a megszakításkezelő prototípusa. Tartalma: // #define PROTO_MAIN() int main(void) attribute ((noreturn)) \ // attribute ((naked)); // #define PROTO_IRQ() void irq(void) attribute \ // ((interrupt ("IRQ"))); PROTO_MAIN(); PROTO_IRQ(); // ********************************************************* // A főprogram // ********************************************************* int main() { // Helyi változó, egy számláló értéket tartalmazza int c; // ELSŐ LÉPÉS: mikrovezérlő vermének (visszatérési címeinek, és // helyi változóinak helye) beállítása. SETUP_STACKS(); // Minden I/O port legyen kimenet IODIR = 0xffffffff; // ciklus, 0-tól "végtelenig", "c" értéke egyesével növekedik for(c = 0;; c++) { // Ha az alulról számolva 16. bitje 1 értékű, akkor... if (c & 32768) //... a lámpa húnyjon ki! IOPIN = 0; else // De ha nem nulla, akkor világítson fényesen! IOPIN = 0xffffffff; // Megszakítás-kiszolgáló rutin, sosem fut le void irq() { A mikrovezérlő legelső teendőit egy assembly nyelvű fájl (crt.s 2 ) határozza meg, ami nem csak az első néhány utasítást tartalmazza, hanem elő is készíti a mikrovezérlőt a C nyelven írt kód futtatására. Ennek forrását láthatjuk a következő néhány sorban: 1 A fordítást több lépésben végző program 2 CRT C RunTime,.S (Source) forrás 31
5.1. VEZÉRLÉS # A KÓD FELHASZNÁLÁSÁHOZ EL KELL TÁVOLÍTANI A #-VAL KEZDŐDŐ SOROKAT, MERT # A FORDÍTÓPROGRAM NEM TUDJA ÉRTELMEZNI AZOKAT..text.align 4.global _init.type _init, %function.global _start.type _start, %function # A 0-s címen kezdődjön a kód.org 0 # Kötelező címkék, a program belépési pontja _init: _start: # RESET után kezdje futtatni a "main()" függvényt (főprogram) b main # Minden más esetben (hibák, kivételek) ugorjon egy végtelen ciklusra b _stop b _stop b _stop b _stop # Ide az OpenOCD egy ellenőrző összeget ír, innen tudja a mikrovezérlő # BOOT kódja, hogy a memóriában valós felhasználói program van. nop # Megszakításkezelő címének olvasása a megszakítás-kezelőtől, és ugrás a # kapott címre. Ezt a címet mi állíthatjuk be a "main()" függvényben. ldr pc, [pc, #-0xFF0] # FIQ-t nem használunk, ekkor is ugorjon a cégtelen ciklusra b _stop # Végtelen ciklus, nem módosítja a regisztereket, könnyebb a hibakeresés, # ha "áll" a processzor _stop: b _stop A fordításhoz ne felejtsük el elkészíteni a Makefile-t! # Az általunk elkészített C fordító (keresztfordító ARM-ra) helye PREFIX = /usr/local/arm/bin # A fordító neve CC = $(PREFIX)/arm-elf-gcc # A fordító paraméterei CFLAGS = -Wall -g -O3 -mlittle-endian -marm -mcpu=arm7tdmi-s # A linker, ami összeállítja a részenként lefordított kódot: # crt.s main.c ---> crt.o main.o ---> program.elf ---> program.bin LD = $(PREFIX)/arm-elf-ld # A linker paraméterei: a futtatható kód a 0 kezdőcímre kerüljön # Az adat pedig 0x40000000-re, vagyis 1 GBájtra # Fűzze még hozzá a library.a-ból a szükséges függvényeket # (most nincs ilyen, de majd lesz) 32
5.1. VEZÉRLÉS LDFLAGS = -Ttext 0 -Tdata 0x40000000../LPC210x/library.a # Segédprogramok (és paramétereik) az adatkonverzióhoz OBJCOPY = /usr/local/arm/bin/arm-elf-objcopy OCFLAGS = -j.text -O OBJDUMP = /usr/local/arm/bin/arm-elf-objdump ODFLAGS = -j.text -j.data -ds # A programrészek (C forrásfájlok) felsorolása OBJS = main.o # Mit kell tennie a make-nak fordításkor: takarítás, # CRT fordítása, programrészek fordítása all: clean crt.o $(OBJS) # Majd ezek összefűzése egy fájllá (speciális formátum) $(LD) -o program.elf crt.o $(OBJS) $(LDFLAGS) # Konvertálás bináris formába (az OpenOCD ezt fogadja el) $(OBJCOPY) $(OCFLAGS) binary program.elf program.bin # Listing fájl készítése: a C forrásfájl sorait a nekik megfelelő # assembly kóddal együtt listázza, memóriacímekkel, adatokkal... # Nagyon hasznos! $(OBJDUMP) $(ODFLAGS) program.elf > program.list # Takarításkor töröljön minden core,.o,... végű fájlt! clean: rm -f *core *.o *.hex *.bin *.list *.img *.elf Ha minden fájlt (Makefile, crt.s, main.c, lpc2103.h, library.h 3 és library.a) hiba nélkül előkészítettünk, akkor a fordítást a következőképpen végezhetjük el: $ make rm -f *core *.o *.hex *.bin *.list *.img *.elf /usr/local/arm/bin/arm-elf-gcc -c -o crt.o crt.s /usr/local/arm/bin/arm-elf-gcc -Wall -g -O3 -mlittle-endian -marm -mcpu=arm7tdmi-s -c -o main.o main.c /usr/local/arm/bin/arm-elf-ld -o program.elf crt.o main.o -Ttext 0 -Tdata 0x40000000../LPC210x/library.a /usr/local/arm/bin/arm-elf-objcopy -j.text -O binary program.elf program.bin /usr/local/arm/bin/arm-elf-objdump -j.text -j.data -ds program.elf > program.list Az OpenOCD-t úgy írták meg, hogy bináris fájlokkal dolgozzon, ezért nekünk a program.bin-re lesz szükségünk. Adjunk tápfeszültséget a mikrovezérlőnek, csatlakoztassuk a JTAG kábelt, zárjuk rövidre a DBGSEL átkötést, végezzünk hardver RESET-et (ez kell a DEBUG üzemmódba lépéshez), indítsuk el az openocd-t a már megismert módon, telnet-eljünk bele, végül adjuk ki a memóriatörlési és írási parancsot: hg8lhs@buddhayana:~$ telnet localhost 4444 Trying 127.0.0.1... Connected to localhost. Escape character is ^]. Open On-Chip Debugger > flash erase 0 0 7 erased sectors 0 through 7 on flash bank 0 in 0s 514536us > flash write 0 program.bin 0 3 Ezek a fájlok most nem mind vesznek részt a fordításban, de a későbbiekben kiemelt jelentősséggel fognak bírni. A szükséges módosításokat a megfelelő időben közölni fogom. 33
5.1. VEZÉRLÉS wrote 148 byte from file program.bin to flash bank 0 at offset 0x00000000 > resume 0 Target 0 resumed > A legutolsó utasítás (resume 0) el is indítja a program végrehajtását a 0 címtől (a memória legelejétől) kezdve. Ha minden sikeres volt (ennek esélye a nullával egyenlő, de ha mégis működik, akkor valami nincs rendben szokták mondani a szakemberek), akkor a lámpa kb. 60-szor... 120-szor villan fel percenként. 5.1.2. 12 V-os izzó be- és kikapcsolása időzítő megszakítással Ha végiggondoljuk, az előbbi program egész idejében csak számol, számol, és nagyon ritkán kapcsolja ki vagy be a lámpát. Idejének nagy részében olyan dolgot végez, aminek számunkra nem sok haszna van (ha nem számolna, akkor nem is villogna a lámpa, de maga a számolás nem hasznos művelet). Mi lenne, ha számolásra inkább egy céleszközt, egy számlálót használnánk? Akkor a processzornak lenne ideje mással is foglalkozni. A megoldás nem ennyire egyszerű, de azért nem is bonyolult. Ebben a fejezetben olyan mintaprogramot készítünk, amely egy számlálót használ: ha a számláló elér egy előre beállított értéket, akkor megszakítást generál. A megszakítás hatására a processzor felfüggeszti aktuális tevékenységét, megjegyzi, hogy hol hagyta abba, lefuttatja a megszakítás-kiszolgáló rutint (ami kikapcsolja vagy bekapcsolja a lámpát annak aktuális állapotától függően), majd visszatér eredeti feladatához, mintha semmi sem történt volna. Szerencsére csak a C forrásállományban kell változtatást eszközölnünk, mert a Makefile és a crt.s állomány elég általános ahhoz, hogy később is bármikor felhasználhassuk. #include "lpc2103.h" #include "../LPC210x/library.h" PROTO_MAIN(); PROTO_IRQ(); // Globális változó, amit megtartja értékét a megszakítási // rutin lefutása után is. Értéket kell neki adni, hogy // a C fordító helyes kódot generáljon! Ez nem a fordító // hibája, hanem a bináris formátumú fájl miatt kell ezt a // trükköt alkalmazni. Másik megoldás az 1GBájt méretű // bináris fájl lenne, de azt azért mégsem kellene.. unsigned int r0 = 0x55; int main() { SETUP_STACKS(); IODIR = 0xffffffff; // Megszakításvezérlő beállítása // 1. időzítő_0 megszakításának engedélyezése // 2. megszakításkezelő rutin címének megadása // 3. megszakítások prioritásának (elsőbbségének) // megadása. Egy darab megszakításunk van, tehát // nem számít VICIntEnable = 0x10; VICVectAddr0 = (unsigned long) irq; VICVectCntl0 = 0x20 4; // Időzítő_0 beállítása T0CTCR = 0; T0PR = 0; // Mely érték elérésekor keletkezzen megszakítás 34
5.1. VEZÉRLÉS T0MR0 = 1000000; // Keletkezzen megszakítás! T0MCR = 3; // Számlálás indítása (triggerelés) T0TCR = 1; // Processzor megszakításfogadásának engedélyezése // Ez a library-ben található függvény (interrupts.c): // // void inline enable_interrupts() { // // ARM mód, IRQ engedélyezve, FIQ tiltva, // // System módban // asm("mov r0, #0x5f"); // // Gépi állapotregiszter módosítása // asm("msr cpsr, r0"); // enable_interrupts(); // Nem csinál semmit a processzor, most nem. for(;;); /********************************************************/ /* A megszakítás-kiszolgáló rutin */ /********************************************************/ void irq() { // Ha r0 változó legalsó bitje 1, akkor... if ((r0++) & 1) // Lámpa világít! IOPIN = 0xffffffff; else // Ha nem nulla, akkor kialuszik. IOPIN = 0x00000000; // Számláló megszakítás nyugtázása T0IR = 1; // Megszakításvezérlő megszakítás-kérésének nyugtázása // Ez is egy függvény a library-ben: // // void inline acknowledge_interrupt() { // VICVectAddr = 0xff; // acknowledge_interrupt(); A kód fordítása, feltöltése és futtatása ugyanúgy végzendő, mint az előbbi esetben. 5.1.3. 12 V-os izzó fényerejének beállítása impulzusszélesség-modulációval Most, hogy már rendelkezünk a lámpa villogtatásához szükséges tudással, meg kell ismerkednünk a lámpa fényerejének változtatásával is. Célunk, hogy impulzusszélesség-modulációval úgy módosítsuk a lámpán (és a FET-en) átfolyó áram átlagértékét, hogy a kívánt fényerőt elérjük. Ehhez nem kell bonyolult programot írnunk, mert a mikrovezérlő rendelkezik PWM képességgel. 35
5.1. VEZÉRLÉS A PWM alapja is egy számláló, ami N-ig számol (N értékét mi határozzuk meg). A számlálás kezdetekor a megfelelő (M2.0) PWM kimenet logikai alacsony szinten van, így a lámpa nem világít. Ha a számláló értéke elér egy (általunk előre beállított) M értéket (M < N), akkor a PWM kimenet magas szintre vált. Ha a számláló elérte N-t, akkor kezdi elölről (0-ról) a számolást, és a PWM kimenetet alacsony szintre állítja. Az áram átlagértéke: I avg = I 0 N M N ahol I 0 a lámpa névleges árama (teljesen nyitott FET esetén folyik). A program beállítja N értékét 3300-ra, majd M értékét lassan 0-ról 3300-ra növeli, így a lámpa egyre halványabban világít. Ezután az M értékét lassan 0-ra csökkenti, hogy a lámpa újra fényesen világítson. Jó ha tudjuk: ha a kimeneti feszültség max. értéke 3,3 V, és N = 3300-at választunk, akkor M = 1-hez éppen 3300 1 mv = 2999 mv tartozik, M = 2-höz pedig 3300 2 mv = 2998 mv... A C forrásban a 3300 M kivonást előre elvégezzük, így az M értéke a fényerősség értékével egyenesen arányos. A PWM használatához szükséges C forráskód a következő: #include "lpc2103.h" #include "../LPC210x/library.h" PROTO_MAIN(); PROTO_IRQ(); unsigned int r0 = 0x55; unsigned int value = 1; int main() { SETUP_STACKS(); // Minden port legyen kimenet, tulajdonképpen mindegy IODIR = 0xffffffff; // Írunk valamit a kimenetre, valójában nem használjuk IOPIN = 0xaaaaaaaa; for (;;) { // M értéke 0 és N = 3300 között változik (növekedik)... for(value = 0; value < 3300; value++) { // Ha a kimeneti feszültség max. értéke 3,3 V, és // N = 3300-at választunk, akkor M = 1-hez 1 mv tartozik set_pwm_20(value, 3300); // Várunk kicsit for (r0 = 0; r0 < 512; r0++); //... M csökken. for(; value > 0; value--) { set_pwm_20(value, 3300); for (r0 = 0; r0 < 512; r0++); void irq() { A set_pwm_20(int M, int N) függvény a library (függvénykönyvtár) pwm.c fájljában található: 36
5.2. MÉRÉSADATGYŰJTÉS void inline set_pwm_20(int value, int period) { PINSEL0 &= ~(3 << 14); // Engedélyezi a MAT2.0 kimenetet PINSEL0 = (2 << 14); T2TC = T2IR = T2CTCR = 0; T2PR = 0; // Időzítő órajelének forrása a PCLK // Előosztó tiltása T2MR0 = (period - value); // Kimenet átlagértékének beállítása (M) T2MR3 = period; // Periódusidő (N) beállítása T2MCR = (1 << 10); // Ha eléri a számláló N-t, újraindul T2EMR = (2 << 4) (1 << 10); // Ha a számláló eléri M-et, akkor 1-be // vált a MAT2.0, és kigyullad a lámpa T2PWMCON = 1; // Engedélyezi a PWM üzemmódot T2TCR = 2; T2TCR = 1; // Törli a számláló értékét // Engedélyezi a számlálást Az ebben a részben megismert példák nem használták a soros kommunikációt, mert a vezérlési feladatok olyan egyszerűek voltak, hogy nem lett volna értelme túlbonyolítani azokat. A következő részben felhasználjuk az előző fejezetben megírt kommunikációs rutinokat. 5.2. Mérésadatgyűjtés Ebben a fejezetben arról olvashatunk, hogy hogyan lehet az eddig megszerzett tudást mérésadatgyűjtő berendezés építésére felhasználni. Maga a mérésadatgyűjtő nem egy bonyolult készülék, egyetlen feladata, hogy a mérésvezérlő által meghatározott időben megmérje az analóg bemenetén lévő jel nagyságát (feszültségét), és ezt válaszként tudassa a mérésvezérlővel. A mérésvezérlő (számítógép) végzi az eredmények tárolását, értékelését és megjelenítését. Mivel a mérésvezérlőnek jeleznie kell azt, hogy szüksége van a mért értékre, ezért valamilyen kommunikációra lesz szükség a számítógép és a mikrovezérlő között. A TokenRing RS-232 hálózatunk erre a célra éppen megfelel. Könnyű kiválasztani, hogy milyen mennyiséget mérjünk, mert érzékelőnk a fényerősség megállapítására alkalmas. Legyen tehát a mért jellemző a szobámban tapasztalható fényerősség (a fotodióda felületére érkező fotonok számával arányos fizikai mennyiség legyen az bármi is). A méréshez három dologra lesz szükségünk: 1. a mérőhardverre (processzor modul és érzékelő) 2. a mikrovezérlő programja (firmware) 3. a személyi számítógép programja (szoftvere) Az első tétel könnyen biztosítható, mert a processzor modul és a végrehajtó-érzékelő modul rendelkezésünkre áll. Ha helyesen építettük meg az áramkört, akkor az módosítás nélkül használható a mérés során. A kommunikációhoz ne felejtsük el csatlakoztatni a TTL RS-232 illesztőt is! 37
5.2. MÉRÉSADATGYŰJTÉS A mérésadatgyűjtés egyszerűsített rajza A mikrovezérlő szoftvere alig bonyolultabb, mint idáig. Ezt az eszközt olyan jól tervezte meg a gyártó, hogy a hardver programozása nagyon könnyen elvégezhető. Lássuk, hogy hogyan! #include "lpc2103.h" #include "../LPC210x/library.h" #include "commands.h" PROTO_MAIN(); PROTO_IRQ(); unsigned int temp = 0xdeadbeef; struct Packet packet = {0x04, 0x03, 0x02, 0x01, 0x55aaff00; unsigned char my_id = 0x1; int main() { SETUP_STACKS(); IODIR = 0xffffffff; IOPIN = 0xaaaaaaaa; setup_usart0(0); my_id = 0x1; while(1) { if (detect_packet()) { if (receive_packet(&packet, my_id) == 1) { switch (packet.command) { case COMM_HELLO: break; case COMM_SET_ID: my_id = (unsigned char) (packet.data & 0xff); packet.data = 0xdeadbeef; break; case COMM_GET_ANALOG: packet.data = get_adc3(); break; packet.dst_id = 0xff; calc_checksum(&packet); send_packet(&packet); 38
5.2. MÉRÉSADATGYŰJTÉS Egyetlen különbség a már megismert kommunikációs algoritmushoz képest az, hogy feldolgozzuk a nekünk szóló COMM_GET_ANALOG parancsot, és válaszként az AD0.3 bemeneten mérhető feszültség értékét adjuk vissza (a packet.data alsó 10 bitjén). A get_adc3() alprogram a library függvénykönyvtár adc.c állományában kapott helyet. Forráskódja igen egyszerű: int get_adc3() { // Bemenetek kiválasztása PINSEL0 &= ~(3 << 20); PINSEL0 = 3 << 20; // AD0.3-as csatorna, 10 bit, kezdje a mérést MOST! ADCR = (1 << 3) (2 << 8) (1 << 21) (1 << 24); // Megvárjuk, amíg végez while (!(ADGDR & 0x80000000)); // Visszaadjuk az eredményt az alsó 10 biten return (ADGDR >> 6) & 0x3ff; A listázott két forrásból kitűnik, hogy a mikrovezérlő csak akkor mér, ha megfelelően kitöltött csomag segítségével a mérésvezérlő erre utasítja. Az eredményt pedig válaszcsomag formájában közli a számítógéppel. A vezérlő szoftvere ennek megfelelően a következő: // A szükséges, szabványos C függvények prototípusa #include <termios.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> // Saját kommunikációs függvényeink prototípusa #include "usart.h" // Parancsok kódja #include "commands.h" // Mennyi ideig mérjen a műszer (2 * 86400 s = 2 nap) #define MAX_TIME 2*86400 int meresadatgyujtes(file *tty, char *filename) { FILE *data; int time; struct Packet packet; // Adatfájl megnyitása, hiba esetén üzenetet küld a felhasz- // nálónak, és kilép a programból if ((data = fopen(filename, "w")) == NULL) { perror("controller:meresadatgyujtes():fopen()"); exit(2); // Két nap... 39
5.2. MÉRÉSADATGYŰJTÉS for (time = 0; time < MAX_TIME; time++) { // Vár 1 másodpercet sleep(1); // Kitölti a csomag mezőit // DST_ID = 0, mert az első elérhető mikrovezérlőnek üzen packet.dst_id = 0; // SRC_ID = 0, vagyis a mérésvezérlő packet.src_id = 0; // Analóg érték lekérdezése packet.command = COMM_GET_ANALOG; // Ennek nincs jelentőssége, majd a mérőberendezés átírja packet.data = 0; // Helyes ellenőrző összeg nélkül nem fogadja el a mikro- // vezérlő a csomagot calc_checksum(&packet); // Üzenet küldése send_packet(tty, &packet); // Válaszüzenet fogadása, ami úgyis csak a mikrovezérlőtől // jöhet receive_packet(tty, &packet); // Mérési eredmény mentése az adatfájlba fprintf(data, "%d %d\n", time, packet.data); // Pufferek ürítése fflush(data); return 0; int main(int argc, char **argv) { FILE *tty; struct Packet packet; int length, id; // Soros port paramétereinek beállítása if ((tty = setup_usart0("/dev/ttys0")) == NULL) { perror("controller:main():setup_usart()"); return 1; // Gyűrű hosszának megállapítása a dinamikus ID-k kiosztásához printf("\n\ngyűrű hossza: %d", length = get_chain_length(tty)); // Dinamikus azonosítók kiosztása printf("\nazonosítók kiosztása..."); for (id = 1; id <= length; id++) { 40
5.2. MÉRÉSADATGYŰJTÉS packet.dst_id = id - 1; packet.src_id = 0; packet.command = COMM_SET_ID; packet.data = id; calc_checksum(&packet); send_packet(tty, &packet); receive_packet(tty, &packet); // Ha az első paraméter egy "m" betű, és utána megadtunk egy // fájlnevet, akkor kezdődhet a mérésadatgyűjtés (2 napig!!!) if ((argc >= 3) &&!strcmp(argv[1], "m")) meresadatgyujtes(tty, argv[2]); // Ha végeztünk, akkor fájlok bezárása, és kilépés fclose(tty); printf("\n\n"); return 0; A program használatához annyit mindenképpen tudni érdemes, hogy bemenetként két paramétert vár, az első egy m (mérés) betű, míg a második egy fájlnév. Ez utóbbiba menti a program a mért értékeket. Ha minden megfelelően működik, akkor hasonló eredményt láthatunk a terminál ablakban (a küldött és fogadott csomagok nyomkövetési információi): KÜLDÖTT ------------------------ Cél: 00 Forrás: 00 Parancs: 06 Összeg: FA Adat: 00000000 FOGADOTT ----------------------- Cél: FF Forrás: 00 Parancs: 06 Összeg: B5 Adat: 00000145 Ellenőrizve. Válasz csomag érkezett. A paraméterként megadott adatállományban pedig gyűlnek-gyűlnek a mérési eremények (első 15 másodperc): 0 774 1 325 2 802 3 347 4 758 5 357 6 754 7 887 8 395 9 760 10 357 11 760 12 415 41
5.3. TRANSZFER KARAKTERISZTIKA FELVÉTELE 13 790 14 331 Ha a mérési eredményeket feldolgozzuk (zajszűrés), és egy megjelenítő programmal (pl. a gnuplot-tal) ábrázoltatjuk, akkor hasonló eredményt kapunk: A mért adatok diagramja A nyers (feldolgozatlan) mérési adatok diagramja A fejezet lezárásaként vonjuk le a tanulságot: nappal világos van, este pedig sötét. Lám, most már méréssel is bebizonyítottuk azt a természeti törvényt, amit eddig csak csalóka érzékszerveinkkel tudtunk megfigyelni. 5.3. Transzfer karakterisztika felvétele Ha utasítjuk a mikrovezérlőt, hogy analóg kimenetén produkáljon megadott értékű egyenfeszültséget, és ezt a jelet egy négypólus bemenetére kapcsolva mérjük a négypólus válaszát, akkor felrajzolhatjuk annak transzfer karakte- 42
5.3. TRANSZFER KARAKTERISZTIKA FELVÉTELE risztikáját 4. Minden programozási tudás rendelkezésünkre áll, már csak néhány sorral kell kiegészítenünk a mikrovezérlő és a számítógép forráskódját. A számítógép 0 V-tól 3,3 V-ig változtatja a mikrovezérlő analóg kimeneti feszültségét (például 1 mv-os lépésközzel), és minden léptetésnél megméri a vizsgált négypólus válaszát (megfelelő időt hagyva a tranziens átmeneti jelenségek lecsengésére). A mikrovezérlő kódjában a következő változtatásokat kell elvégezni: switch(packet.command) {... case COMM_SET_ANALOG: // Állítsa be az analóg kimenet értékét set_pwm_20(packet.data & 0xffff, packet.data >> 16); // Szokásos válasz: elhullott tehén packet.data = 0xdeadbeef; break;... A mérésvezérlő szoftverében több módosítanivalónk lesz: egy új függvény jelenik meg transzfer néven. Ez a programrész a már előbb elmondott műveleteket végzi el, és közben a mérési adatokat a megadott fájlba menti. int transzfer(file *tty, char *filename) { FILE *data; int value; struct Packet packet; // Megnyitja az adatfájlt, hiba esetén üzenetet küld if ((data = fopen(filename, "w")) == NULL) { perror("controller:transzfer():fopen()"); exit(3); // "value" értékét 0 mv és 3300 mv között változtatja (növeli) for (value = 0; value <= MAX_VALUE; value++) { // vár, amíg lecsengenek a tranziensek usleep(delay); // Előkészíti a feszültségbeállító csomagot packet.dst_id = 0; packet.src_id = 0; packet.command = COMM_SET_ANALOG; packet.data = value (MAX_VALUE << 16); calc_checksum(&packet); // És elküldi send_packet(tty, &packet); receive_packet(tty, &packet); // Tranziensek lecsengése usleep(delay); // Lekérdező csomag összeállítása packet.dst_id = 0; packet.src_id = 0; 4 A bementre adott egyenfeszültség és a kimeneten mérhető egyenfeszültség között ad függvénykapcsolatot (statikus üzemben) 43
5.3. TRANSZFER KARAKTERISZTIKA FELVÉTELE packet.command = COMM_GET_ANALOG; packet.data = 0; calc_checksum(&packet); // Analóg lekérdező csomag küldése, válasz fogadása send_packet(tty, &packet); receive_packet(tty, &packet); // Mérési adatok kiírása fájlba fprintf(data, "%d %d\n", value, packet.data); fflush(data); return 0;... // Program használata:./controller t data/transzfer.txt if ((argc >= 3) &&!strcmp(argv[1], "t")) transzfer(tty, argv[2]);... Egy elkészített eszköz transzfer karakterisztikáját mutatja a következő ábra. A négypólus kapcsolási rajzát nem adom meg, felépítésének kitalálását az Olvasóra bízom. A transzfer karakterisztika 44
5.3. TRANSZFER KARAKTERISZTIKA FELVÉTELE A nyers (feldolgozatlan) transzfer karakterisztika 45
6. fejezet Összefoglalás, végkövetkeztetés A bevezetőben hangot adtam annak a szándékomnak, hogy bemutassam: egy modern mikrovezérlő általános célú mérőműszerként való alkalmazása nem jelent komoly kihívást, sokkal inkább érdekes izgalmas tanulási folyamatot. Remélem, sikerült ugyanezt a véleményt kialakítanom a kedves Olvasóban is. Sajnos még számos mérést elvégezhettünk volna, de terjedelmi okokból és nem kevésbé időhiány miatt erre nem kerülhetett sor. Bízom benne, hogy az Olvasó érdemesnek találta a művet az elolvasásra, sok új ismeretre, tapasztalatra tett szert a dolgozat elolvasása (és esetleg az áramkörök utánépítése) során. Ha bármilyen megjegyzése,javaslata van a dolgozattal kapcsolatban, esetleg kétség gyötri, bátran vegye fel velem a kapcsolatot a Fuszenecker Róbert <hg8lhs@gmail.com> email-címen. 46
7. fejezet Felhasznált irodalom ARM7TDMI Technical Reference Manual (Rev 3), ARM Limited, 2001. UM10161 Volume 1: LPC2101/02/03 User Manual, Philips Semiconductors, 2006. január 12. AT91 ARM Thumb-based Microcontrollers, ATMEL Corporation, 2006. november 22. 47
8. fejezet Felhasznált szoftverek A dolgozat megírásához és a programok elkészítéséhez a következő szoftvereket használtam fel: Ubuntu Linux 6.06 (Dapper Drake) [Linux kernel 2.6.15-27-k7] VIM VI IMproved version 6.4.6 L A TEX2ε (pdfetex, Version 3.141592-1.21a-2.2) The GIMP 2.2.11 aspell (International Ispell Version 3.1.20 (but really Aspell 0.60.4)) Dia 0.94 Eagle 4.16r2 for Linux, Light Edition binutils 2.17 (using BFD version 2.17) gcc 4.0.3 openocd Open On-Chip Debugger (2007-05-30 17:45 CEST) 48
49
9.1. JTAG CSATLAKOZÓ KIOSZTÁSA 9. fejezet Mellékletek 9.1. JTAG csatlakozó kiosztása 50
9.2. ARM JTAG CSATLAKOZÓ LEÍRÁSA 9.2. ARM JTAG csatlakozó leírása 51
9.3. USB-JTAG KAPCSOLÁSI RAJZA 9.3. USB-JTAG kapcsolási rajza 52
9.4. JTAGKEY ELVI RAJZA 9.4. JTAGKEY elvi rajza 53