NOKIA 5110 LCD kijelző beröffentése

Eredetileg NOKIA 5110 mobiltelefon kijelzőjeként (ill sok hasonló Nokia modell, pl. 3210, 3310 stb) funkcionált ez a kis display valamikor, manapság pedig potom fillérekért lehet beszerezni, de akár a régi telefonokból is bonthatunk használható példányokat. Egy szerény, 84×48 pixel felbontású monokróm hagyományos LCD kijelzőről beszélünk, barnás-szürke háttere van, melyen háttérvilágítás nélkül is jól látható a fekete pixelekből felépülő kép. A telefonokban annó többnyire zöld megvilágítása volt, manapság sokféle színű megvilágítással kapható. Egy kis kényelmesen szerelhető NYÁK-lapra ültetve kapjuk meg az eszközt, melyről 1rm (2.54mm) lábtávolságú tüskesoron keresztül tudjuk bekötni a céláramkörbe. Bár működés közben kissé elavult érzést kelt az emberben, azért bizonyos feladatokra még ma is megállja a helyét. Különösen akkor hasznos, ha az energiafogyasztás is lényeges.

A lelke a folyadékkristályos grafikus 84×48 pixeles LCD üveglapka, a szíve pedig egy PCD8544 vezérlőchip. Az adatátvitel soros protokoll (SPI) szerint működik, a vezérlő katalógusa szerint 4MHz órajelig képes fogadni a soros vonalon érkező biteket. Inicializálása sokkal egyszerűbb és problémamentesebb, mint az általam is használt karakteres 1602, 2004 LCD moduloké, nagyon könnyen működésre bírtam a kis panelt. Egyedül a soros protokollt kell kicsit aprólékosabban leprogramozni, a többi rész gyerekjáték. Természetesen hardveres támogatású SPI is használható lenne, és a későbbiekben talán erre is teszek egy kísérletet (ha másért nem is, a hardveres SPI kipróbálása miatt érdemes futni egy kört vele). Ebben az esetben nem nekünk kell szoftveresen legenerálni a soros órajelet és az adatbitek kiküldését, hanem a küldendő bájtot egyszerűen csak beírjuk a mikróvezérlő egy regiszterébe, a mikróvezérlő pedig hardveresen (shift regiszter segítségével) megoldja a bitekre bontást és küldést. A fogadó oldali 4MHz órajel nagyon rugalmas, gyakorlatilag nem igényel a kódban késleltető függvényeket, mint a korábbi karakteres modul, ha nagyon gyors a CPU, akkor is legfeljebb néhány közbeszúrt nop utasítás elég a soros kiment bitsebességének korlátozásához. Nálam még 16MHz órajel mellett is stabilnak bizonyul, bár ennek ellenére bizonyos helyekre beírtam nop-okat. Energiafogyasztás még világítással is olyan csekély, hogy az egész kijelző tápellátása megoldható a mikróvezérlő egy kimenetéről (1-2mA), világítás nélkül meg még sokkal kevesebb! Kizárólag bitminta megjelenítésére alkalmas, a teljes kétszínű monokróm kép leképezése 84×48/8=504 bájtot igényel. Ha feliratokat is meg szeretnénk jeleníteni, akkor nekünk kell leprogramoznunk az ehhez szükséges karakterkiíró függvényeket, ill letárolni a kódmemóriában az egyes karakterek bitképét. Nem kell izgulni, ez a dolog se olyan bonyolult, mint amilyennek elsőre tűnik! (Bár hozzáteszem, ez csak assembly őrülteknek szól, akik C, Arduino, stb platformokon programoznak, azok kész függvénykönyvtárakat találnak a chiphez, amikkel azonnal működik.) Nagyon fontos, hogy ez a panel legfeljebb 3.6V tápfeszről üzemeltethető, ill. az adatlábakon sem lehet ennél (ill a saját tápfeszénél) nagyobb feszültség! Nem szabad 5V-ra kapcsolni, 5V-os környezetben az adatlábakra ellenállásosztót célszerű tenni, amivel 3V-ra csillapítjuk a magas szintű jel nagyságát.

Hardveres installáció MSP430 LaunchPad-hez

Mivel a cucc 3V környékén táplálható, így célszerűnek tűnt a 3.3V-os MSP430-al próbálni. A panel 8 kivezetése a következő:

1. RST   - Reset - lefutó élre reszetel a vezérlőchip
2. CE    - Chip Enable - 0:chip kiválasztva (engedélyezve) 1:tiltva
3. DC    - 0=Parancs / 1=Adat mód (parancsot küldünk, vagy rajzolunk)
4. DIN   - Data In - adatvonal
5. CLK   - Az átvitel órajele
6. VCC   - Tápfesz, max 3.6V!!!
7. Light - Háttérvilágítás 0=be, 1=ki
8. GND   - föld

A csatlakozáshoz semmilyen komolyabb mesterkedés nem kellett, egyszerűen csak rádugtam a beforrasztott tüskesor segítségével az MSP egyik csatlakozósorába a következőképpen:

P1.0 → RST
P1.1 → CE
P1.2 → DC
P1.3 → DIN
P1.4 → CLK
P1.5 → VCC
P2.0 → LIGHT
P2.1 → GND

A földet a P2.1 port kimenetéről, annak alacsony szintbe vezérelésével kapja, a tápot a P1.5 láb magas szintbe vezérelt kimenetről. A többi adatjel, ill. a világításnál nem tudom, a LIGHT láb földre kötésekor közvetlenül a LED-eket hajtom-e meg, vagy hogy ez egy vezérlőbemenet. (Ki lehetne mérni, de nem fogom, amíg ez nem lényeges…) A tényleges adatátviteli vezérlés négy lábon valósul meg: CE, DC, DIN, CLK. A reset (RST) csak inicializáláskor van, ill a lámpa esetleg külön kapcsolgatható.

Szoftveres vezérlés

Elsőként egy grafikát jelenítettem meg a kijelzőn, majd második példának egy magyar ékezetes karakteres szövegmegjelenítő kód következik ISO8859-2 (Lanin-2) karakterkódolás mellett. Azért ezt a kódolást választottam, mert minden magyar ékezetes betűt tartalmazza, és az ékezetes karakterek ASCII kódja csak egy bájt (szemben pl az UTF-8-al, ahol két bájtosak lennének).  Először azonban magát a szoftveres vezérlést tárgyaljuk, ez a két példánál megegyezik.

Az első példakód tehát beállítja az MPS430G5223 mikróvezérlő kijelzőmodulhoz kapcsolódó portjait kimenetre, inicializálja a kijelzőmodult, megvalósítja a szoftveres SPI-t (soros adatküldést),  majd egy lementett 504 bájtos bitképet fog beírni a kijelzőmodul RAM-jába.

Nézzük, milyen lépésekben tudjuk inicializálni ezt az eszközt:

  • Rákapcsoljuk a panelre a tápot, ill. ezzel egyidőben magas szintet állítunk be a CE és RST lábakon.
  • Várakozás (kb 100ns kell a chipnek, ez nagyon kevés!)
  • RST lára 0 szintet kapcsolunk (Reset jel kezdete)
  • Várakozás (kb 100ns)
  • RST lábra 1 szintet (Reset jel vége)
  • Kiterjesztett utasításmódba váltás (kontraszt beállításhoz kell) (ld. adatlap: H=1 mód)
  • Kontraszt trimmerelése
  • Visszaváltás normál utasításkészletbe (adatlap H=0 mód)
  • “Normál üzemmódba” tesszük a kijelzőt (részletek az adatlapon)

Ezután a kijelző üres képernyővel használatra kész, a RAM pointer a RAM memória legelső bájtján áll, ami a kijelző bal felső sarkának felel meg. (ld. később) Lekódolva az MSP430-on így néz ki:

; portok előkészítése

	mov.b	#0,P1OUT           ; minden LCD kimenet 0
	mov.b	#0,P2OUT
	mov.b	#00111111b,&P1DIR  ; Portok kimenetre állítása az LCD felé
	mov.b	#00000011b,&P2DIR

; LCD init

	bis.b	#(1<<1)+(1<<5)+1,&P1OUT ; Táp be, Reset magasba, CE magasba
	nop
	bic.b	#1,&P1OUT             ; RESET kiadás
	nop
	bis.b	#1,&P1OUT             ; RESET vége
	mov.b	#00100001b,r7         ; kiterjesztett utasításkészlet mód
	call	#spi_cmd
	mov.b	#210,r7               ; kontraszt beállítása (128-255 között lehet értéket adni, egyedileg kell trimmerelni midnen LCD modulnál!)
	call	#spi_cmd
	mov.b	#00100000b,r7         ; normál utasításkészlet mód
	call	#spi_cmd
	mov.b	#00001100b,r7         ; kijelző normal mód
	call	#spi_cmd
;	bis.b	#1,&P2OUT             ; világítás ki

Egyedül a kontraszt szorul különösebb magyarázatra: A kontraszt utasításkódja 1xxxxxxx, ahol az x-ek helyére a kontraszt értéke kerül 0-127 között. Lényegében tehét egy 128-255 közötti számot kell kiküldeni. Sajnos a kijelzők gyártási szórása nagy, nem véletlen, hogy ezt az értéket szoftveresen kell trimmerelni. Ha sok, akkor látszanak a pixelek 0 állapotban, ha kevés, akkor meg nem látszanak a pixelek (vagy halványak) 1 állapotban. Nálam a 210 érték megfelelő képet adott, de láttam olyan netes kódot, ahol 185-el operáltak, nincs tehát általánosan jó érték, mindig egyedileg kell megkeresni a jót.

Szoftveres SPI

A protokoll nem csak egy adat és órajel vonalat tartalmaz, kell még két vezérlőjel is a működéshez:

CE = Chip Enable, 0 esetén fogadja az adatokat, 1 esetén nem
DC = Data/Command, 1 esetén adatot vár (rajzolunk), 0 esetén parancsot

A küldés menete tehát a következő:

  1. Engedélyezzük a chipet: CE=0
  2. Kiválasztjuk a küldés célját (DC=0 ha parancs, DC=1 ha rajzolás)
  3. Elküldjük az adatbájt 8 bitjét egyenként, egymás után, a legmagasabb helyiértékű bittel kezdve. A küldéskor az órajel felfutó élére íródik be a küldött bit, az órajelnek nem kell stabil ütemet követnie.
  4. Lezárjuk a kommunikációt: CE=1

Nem megyek bele túlzottan a részletekbe, a következő kód lényegében pontosan ezt teszi:

spi_data                       ; belépési pont adatküldésre
	bic.b	#10b,&P1OUT    ; CE=0 (soros adatküldés engedélyezés)
	bis.b	#100b,&P1OUT   ; DC=1 (adat módra váltás)
	jmp	spi_start      ; ugrás a soros adatkiiratásra
spi_cmd                        ; belépési pont parancsküldésre
	bic.b	#10b,&P1OUT    ; CE=0 (soros adatküldés engedélyezés)
	bic.b	#100b,&P1OUT   ; DC=0 (parancs módra váltás)
spi_start                      ; soros adatküldés indul
	push.b	r7             ; mentjük, mert a rotáció miatt elvész az értéke mire lefut a rutin
	push.b	r8             ; mentjük, ciklusszámlálónak fogjuk használni
	mov.b	#8,r8          ; ciklus kezdőérték (8 bit kiküldése)
spi_loop1
	bic.b	#(1<<4),&P1OUT ; clk jel törlése
	rla.b r7               ; balra rotálás MSB->C (a küldés MSB->LSB felé haladva történik)
	jc	spi_skip1
	bic.b	#(1<<3),&P1OUT ; 0 bit az adatvonalra
	jmp	spi_skip2
spi_skip1
	bis.b	#(1<<3),&P1OUT ; 1 bit az adatvonalra
	nop                    ; késleltetés
	nop
spi_skip2
	bis.b	#(1<<4),&P1OUT ; clk jel kiadás (bit elküldése)
	dec.b	r8             ; ciklusvezérlés
	jne	spi_loop1
	bis.b	#10b,&P1OUT    ; CE=1 (soros adatküldés befejezése)
	pop.b	r8             ; r7, r8 regiszterek visszaállítása
	pop.b	r7
	ret                    ; szubrutin vége

Két belépési ponton hívhatjuk, spi_data az adatküldésnél ill. spi_cmd a parancsnál. A bemeneti feltételes szerkezetben kerül beállításra a CE és DC láb. A küldendő adatbájtot az R7 regiszterben kell átadnunk. Használja még a rutin az R8 regisztert is ciklusváltozónak, de ez a főprogramot nem befolyásolja, mert menti és visszaállítja az értékét a verem segítségével. A 8-at futó ciklusban alaphelyzetbe teszi a CLK jelet, kihelyezi az adatot a DIN lábra, kiküldi a CLK jelet és ezt ismétli, amíg minden adat el nem megy. Az adatot balra rotálással bontja bitjeire, és a C flag vizsgálatával dönti el, hogy mi kerül az adatvonalra. Az 1. bit küldése utáni a két nop nagyjából szimmetrizálja az adatküldést, kompenzálja a 0 küldésénél a jmp késését, de igazából ennek nincs komoly jelentősége, nop-ok nélkül is tökéletesen működik a kód.

Bitminta rajzolása

Először azt kell megnézni, hogy milyen formában is jeleníti meg a képet a kijelző.

A képet függőlegesen elhelyezkedő 1 pixel széles és 8 pixel magas csoportokra osztjuk. Ebből értelemszerűen 84db van egy sorban, és 48/8 azaz 6db ilyen sor van egymás alatt. A pixelcsoport bitjei fentről lefelé követik egymást, azaz a bájt legkisebb helyiértékű bitje fogja a legfelső pixelt kigyújtani, a legnagyobb, pedig a legalsót. Az így kialakított 1×8-as csoportok bejárása azonban kétféle lehet (ezt a normál mód beállításakor lehet megadni): Vagy soronként pásztázzuk a képet balról jobbra, és ha elértük a jobb szélt, akkor következik az alatta lévő sor, vagy pedig fentről lefelé, és ha elértük a kijelző alját, akkor egyet lépünk jobba. Mi az első változatot fogjuk használni, a másodiknak pl. grafikonok rajzolásánál lehet előnye.

Ez a módszer azért is jó, mert sokkal könnyebben tudunk majd betűket rajzolni, hisz így minden sor 8 pixel magas, és úgy pásztázunk balról-jobbra, sorról-sorra, mint egy 8 tűs mátrix nyomtató a papíron. Példaként tegyük fel, hogy a bal felső sarokba egy 5×7 szegmenses “A” betű képét szeretnénk kirajzolni. Ekkor a RAM 0-ik címétől kezdődően a következő bitmintát kell kiírni:

0: 01111100
1: 00010010
2: 00010001
3: 00010010
4: 01111100

És nézzük csak meg, 90 fokkal balra fordítva mi rajzolódik ki az 1-ekből?

Ebből már előre láthatjuk, hogy a betűk -különösen az 5×7 szegmenses karakterek- megjelenítése nem okozhat majd nagyobb nehézséget, egyszerűen csak le kell tárolni minden karakterről egy 5 bájtos ilyen mintát, és ezeket kell kiküldeni a kijelző felé. A különféle grafikákat szintén ilyen alakba kell átkódolni, erre amúgy írtak már szoftvert is. Egyetlen kép kirajzása tulajdonképpen egy 504-et futó ciklusban megvalósul, ahol a kódmemóriából kiolvasunk egymás után 504 bájtot és küldjük tovább a kijelzőre:

	mov.w	#504,r9
	mov.w	#picture,r10
main_1
	mov.b	@r10+,r7
	call	#spi_data
	dec.w	r9
	jne	main_1

A picture labeltől kezdődik a kép a kódmemóriában, ezt R10 regiszterbe írjuk. (figyelem, 16 bites regisztereink vannak!) R9 ciklusváltozó (és itt is milyen jól jön, hogy az 504 érték is egy regiszterbe belefér, egyszerű ciklusszerkezet valósítható meg!) Un. inkrementált regiszter-indirekt címzéssel (az adatunk memóriacímét a regiszter tárolja -és egyben címzi is meg- , és minden olvasás végén automatikusan növekszik az értéke egyel, ilyen címzésmóddal az AVR chipeknél is találkoztunk) végigjárjuk a tárolt kép összes bájtját, és kiküldjuk a szoftveres SPI-vel a kijelzőre. Egyszerű, mint a faék…

Magyar ékezetes ASCII karakterek kiiratása

Karakterek kijelzésekor tehát egy tömbből olvassuk ki a karakterek bitképét, és ezt a tömböt első megközelítéssel a karakter ASCII kódjával szeretnénk indexelni. Nézzük akkor, hogyan is néz ki ez a bizonyos ASCII táblázat:

ASCII 8 bites táblázat ISO8859-2 kódolással
ASCII 8 bites táblázat ISO8859-2 kódolással

Látjuk tehát, hogy nem csak karaktereket tartalmaz, hanem üres részeket és vezérlőbajtokat is. Pl. a 7 bites ASCII első 32 értéke csupa vezérlőelem, mint pl. tabulátor, soremelés, kocsi-vissza, csengő stb. A 32-es kóddal a szóköz karakterrel indulnak a tényleges karakterek. Ha valamilyen bővített 8 bites, esetünkben ISO8859-2 kódolású ASCII táblázatot nézzük, akkor a 128-160 tartományban megint csak vezérlőelemeket találunk (üres, a fenti ábrán zölddel jelölt terület). Természetesen lehetne ezeken a helyeken üres (szóközzel egyező) karaktermintákat tárolni, csakhogy könnyű legyen megcímezni, ezzel egy nagy 256 elemű tömböt alkotva, de ez pazarlás. Vágjuk tehát ki ezt a két 32 bájtos részt! Az első 32 üres hely kilövéséhez pl. a karakter ASCII értékéből el kell venni 32-őt, és máris egy 0-96 közé eső számot kapunk indexnek. Ha viszont a felső (128 feletti) rész is kell, -és kell, mert ott vannak az ékezetes betűk- akkor amennyiben ebbe a tartományba esik a kérdéses karakter ASCII kódja, akkor már 2x 32-őt, azaz 64-et kell kivonni. Tehát a logika a következő:

HA ( ASCII < 128 ) AKKOR KARAKTER_INDEX = ASCII - 32
HA ( ASCII >= 128 ) AKKOR KARAKTER_INDEX = ASCII - 64

Már csak az maradt, hogy kiszámoljuk, hogy az adott karakter a kódmemóriában hol van. Ha 5 bájt egy karakter képe, akkor 5 bájtonként jön egy másik karakter, tehát ha az n-edik karakter képét akarjuk kiolvasni, akkor az az n*5-ödik bájtján kezdődik az adattömbnek. (a fenti példa szerint KARAKTER_INDEX * 5 képlettel kapjuk meg) Ehhez még hozzá kell adni a kezdőcímet, és máris előállítottuk azt a memóriacímet, ahonnan be kell olvasni 5 bájtot: MEMÓRIACÍM = KEZDŐCÍM + KARAKTER_INDEX * 5. Assemblyben ezt úgy fogjuk csinálni, hogy betöltjük egy regiszterbe a kezdőcímet, majd nemes egyszerűséggel ötször hozzáadjuk a karakter indexet. Lehet kicsit elegánsabban is csinálni, hogy egyszer hozzáadjuk az indexet, majd kettőt rotálunk rajta balra (ezzel 4-el szorozzuk) és újból hozzáadjuk a címhez MEMÓRIACÍM = KEZDŐCÍM + KARAKTER_INDEX + 4 * KARAKTER_INDEX formában. Érdemes megfigyelni, hogy amennyiben 8×8 lenne egy karakter mérete, akkor a címzéskor 3-szor balra rotálva az indexszet, megkapnánk az offszetcímet, könnyebben kezelhető alak lenne, de így csak 10 karakter férne el egy sorban (és még maradna egy fél karakterhely), a jelenlegi 14 helyett. Ha megvan a cím, beolvasunk a flash memóriából 5 bájtot és kiküldjük az SPI-n.

writeChar
	push.w	r10             ; mentjük az általunk használt regisztereket (r10 memóriacím, r7 adatbájt)
	push.w	r7
	and.w	#0x00FF,r7      ; lemaszkoljuk az adatot hozó regiszter felső bájtját, mert ha ott maradt valami korábbról, az bajt okozhat
	mov.w	#charset,r10    ; karakter bitkép adatok kezdőcíme
	tst.b	r7              ; kiirandó ASCII karakter tartomány ellenőrzése
	jge	writeChar_skip1 ; alsó tartomány
	sub.b	#64,r7          ; felső tartomány (160-255) 2x32-őt kell kivonni belőle
	jmp	writeChar_skip2
writeChar_skip1
	sub.b	#32,r7          ; alsó tartomány (32-127) 32-őt kell kivonni belőle
writeChar_skip2
	add.w	r7,r10          ; pointer = kezdőcím + 5 * karakterkód (egyszerűen 5-ször hozzáadjuk a pointerhez)
	add.w	r7,r10          ;   5-nél több oszlop esetén már érdemes rotálás és összegzést (fixre programozott szorzás) alkalmazni
	add.w	r7,r10
	add.w	r7,r10
	add.w	r7,r10
	mov.b	@r10+,r7        ; sorra kiolvassuk, majd kiküldjük a pixelminta 5 db bájtját
	call	#spi_data       ;   5-nél több oszlop esetén célszerű lehet ciklust szervezni
	mov.b	@r10+,r7
	call	#spi_data
	mov.b	@r10+,r7
	call	#spi_data
	mov.b	@r10+,r7
	call	#spi_data
	mov.b	@r10+,r7
	call	#spi_data
	mov.b	#0,r7           ; egy üres oszlopot is küldünk betűköznek
	call	#spi_data
	pop.w	r7              ; regiszterek visszaállítása
	pop.w	r10
	ret	                ; szubrutin vége

Úgy vélem, különösebb magyarázatra nem szorul a kódrészlet, egy ASCII karakter kiiratását végzi el. Amire érdemes figyelni -és ezt én is elrontottam-, hogy az r7 regisztert 8 bites módban használtuk, azonban az add.w 16 bites összeadó utasításkor 16 bitesen címzi meg a processzor. Ha a felső bájtjában van valamilyen (pl. korábbról maradt tartalom), akkor az igen csak hibás működéshet vezethet, ezért a felső bájtrészt egyszerűen egy 16 bites and.w utasítással kimaszkoltam 0-ra. Fontos azonban megjegyezni, hogy ez a kód kizárólag csak betűket tud kezelni, ASCII vezérlőkaraktereket nem! Így nem tud tabulálni, sort emelni, stb. Természetesen ezeket is beprogramozhatnánk szépen apránként, ill. érdemesnek tartom még olyan vezérlések megoldását, mint pl. váltás inverz betűkre, vagy dupla széles betűkre, ezek egyszerűen programozható esetek. Magát a teljes szöveget kiíró kód végül így fest:

	; DEMO szöveg kiiratása

	mov.w	#szoveg,r10 ; szöveg pointere az r10 regiszter lesz
vissza1
	mov.b	@r10+,r7    ; adatregiszter az r7 regiszter lesz
	tst.b	r7
	jz	tovabb1     ; 0 bájt -> kiugrás
	call	#writeChar  ; ASCII karakter kiiratása
	jmp	vissza1
tovabb1

	jmp	$           ; program stop

Valamint a szöveg:

szoveg

	.char "Adjon az Isten"
	.char " szebb jövőt, "
	.char " legyen úgy,  "
	.char " mint régen!  "
	.char "--------------"
	.char " flaci-avr.tk "
	.char 0

És ahogy a kijelzőn fest:

Soremelés helyett látjuk, hogy mindenhol szóközökkel kitöltjünk a soronkénti 14 karakterhelyet, így szépen egymás alá kerülnek a kiírandó szöveg sorai.

Vigyázat, ez a forráskód ISO8859-2 Latin-2 kódolású, csakis így kerülnek a forráskódba írt ékezetes betűk helyére a megfelelő ASCII kódok! (Tehát, hogy az legyen a kódban ill a lefordított .hex-ben, amit a forráskódba begépeltünk) Amikor a Code Composerben új projektet csinálsz ennek a kódnak, akkor mielőtt megnyitod a letöltött .asm fájlt, vagy belemásolod egy üres main.asm fájlba a tartalmat, előbb feltétlen állítsd át a szerkesztő kódolását:

Ezt a Project/Properties menüből érhetjük el, majd a fastruktúrában a legelső, Resource elemet kell választani, és a Text file encoding csoportban Other lehetőség mellett kell megadni. (A lenyílóban csak a 8859-1 (Latin-1) fog látszani, de átgépelhető 8859-2-re)

Update: Közben kiderült, hogy kicsit túlbonyolítottam ezt a dolgot, ugyanis az alapértelmezett CP-1250 kódolásban a magyar ékezetes karakterek ugyanazokon az ASCII kódokon vannak, így végülis semmit nem kell babrálni, maradhat CP-1250 a projekt kódolása. Készült is egy ilyen verzió, és egy kicsit több karakter bitképe is belekerült (mindet egyelőre nincs időm megrajzolni) A mellékletben letölthető az asm file, mely értelemszerűen CP-1250 kódolású. Az ékezetes karakterek is kicsit szebbek lettek. (vagy inkább talán kevésbé esetlen)

Folyt. köv…

Mellékletek:
Ráadás
Hivatkozások:

Vélemény, hozzászólás?