<docere>http://www.docere.ro/ |

Codul ASCII şi generarea caracterelor
Când s-au înfiinţat serviciile poştale, mesajele au putut fi transmise la distanţă cu tolba (în goana calului, vezi filmul The Postman); când a apărut telegraful electric, s-a pus problema transmiterii la distanţă de mesaje scrise fără a transporta pe sârme şi înseşi literele componente. Samuel Morse a rezolvat problema prin 1837, creând codul Morse - prin care literele erau reprezentate prin combinaţii distincte de câte două impulsuri standard, reprezentate în scris prin . (iar verbal, prin: di sau did) şi — (impuls lung, verbal: dah); de exemplu, litera A era transmisă prin secvenţa .— (verbal: di dah), iar litera B prin —... (dah di di did).
La vechiul telegraf, cel care recepţiona trebuia să scrie prin puncte şi linioare ceea ce auzea în cască şi apoi să decodifice mesajul, transformând în litere secvenţele notate; odată cu apariţia calculatoarelor, monitoarelor, imprimantelor - s-a pus şi problema transformării automate a semnalelor recepţionate, în caractere scrise (sau afişate) obişnuit.
Când se apasă o tastă, în bufferul tastaturii se înregistrează un anumit cod numeric scan code, corespunzător poziţiei tastei în cadrul tastaturii; codul respectiv permite selectarea unei anumite intrări într-un tabel de coduri preexistent, de unde se determină eventual codul ASCII corespunzător (care este un număr 0..255, nu reprezintă forma grafică obişnuită a literei!); prin ce mecanisme, acest cod este transpus pe ecran în forma grafică obişnuită pentru litere? Aproape evident, există un tabel în care fiecare cod ASCII de caracter selectează printr-un anumit mecanism, descrierea grafică necesară afişării acelui caracter.
Imaginea curentă a întregului ecran este memorată într-o zonă de memorie prestabilită, într-o anumită codificare digitală: în modul text, unitatea de măsură este caracterul, reprezentat în memoria video printr-o pereche de octeţi - codul ASCII şi octetul atributelor de afişare (culoarea cu care este afişat caracterul, culoarea fundalului, etc.); în modul grafic, unitatea de adresare este pixelul, având asociat câte un anumit bit în mai multe "plane" posibile (adresate în paralel) ale memoriei video.
Controlerul video dispune de nişte numărătoare (de caractere pe linie, de linii pe ecran), pe baza cărora se generează spre memoria video adresa următorului cod ASCII de afişat; codul adresat astfel este dirijat ca intrare în tabelul de caractere, pentru a selecta matricea grafică aferentă; fiecare linie de "puncte" din această matrice este emisă spre monitor (iar conversia în semnal video şi compunerea cu semnale de sincronizare specifice conduc la afişarea obişnuită a caracterului).
La pornirea calculatorului, CPU primeşte spre execuţie programul de iniţializare înscris în modulul ROM BIOS de pe placa de bază, prin care se testează funcţionarea componentelor de bază; eventualele erori sunt semnalate acum prin coduri audio, căci sistemul video încă nu este iniţializat. După ce este completată şi tabela vectorilor de întrerupere, urmează căutarea şi lansarea programului ROM BIOS conţinut în placa adaptoare video; acestui program îi corespunde în memorie adresa de bază 0xC000:0000 (în cazul plăcilor VGA) :

Primii doi octeţi 0x55, 0xAA - constituie o "semnătură" pentru "început de modul valid"; următorul octet 0x64 indică dimensiunea modulului respectiv (= 0x64 × 0x200 = 0xC800 =51200 octeţi). Octetul următor 0xE9 este codul unei instrucţiuni JMP, de salt la rutina de iniţializare propriu-zisă (deplasamentul acestui salt fiind 0xC14B; dezasamblând cei trei octeţi ai instrucţiunii cu uC000:0003 găsim JMP 0xC151).
Rutina respectivă (care se poate dezasambla prin comanda uC000:C151) configurează corespunzător diverşi regiştri ai controlerului video şi înregistrează setările rezultate şi alte informaţii specifice adaptorului video respectiv, în anumite zone de date rezervate BIOS-ului. Astfel, în segmentul de date BIOS 0x00400:
C:\>debug -d 0000:0400 0000:0400 F8 03 F8 02 E8 03 E8 02-BC 03 78 03 78 02 80 9F ..........x.x... 0000:0410 23 C8 00 80 02 28 00 00-00 00 3A 00 3A 00 64 20 #....(....:.:.d 0000:0420 20 39 30 0B 30 0B 30 0B-30 0B 3A 27 30 0B 34 05 90.0.0.0.:'0.4. 0000:0430 34 05 08 0E 30 0B 30 0B-0D 1C 00 00 00 00 01 00 4...0.0......... 0000:0440 9B 00 38 00 FF 59 FA E0-35 03 50 00 40 1F 00 00 ..8..Y..5.P.@...
găsim la adresa 0x00449 valoarea 03 pentru modul de afişare activ = "text"; la adresa 0x0044A avem numărul de coloane-ecran = 0x0050 (= 80 de caractere pe linia de ecran), iar următorii doi octeţi dau dimensiunea unei pagini-ecran = 0x1F40 = 8000 octeţi. Mai departe sunt stocate informaţii despre pagina-ecran activă (numărul de ordine al ei, offsetul faţă de baza memoriei video), despre poziţia cursorului în fiecare pagină ecran şi despre tipul cursorului.
Adresa rutinei BIOS care asigură serviciile video standard este înregistrată pe a 16-a intrare în tabelul vectorilor de întrerupere; deci această rutină va putea fi apelată prin INT 0x10. Rolul de selector de servicii este rezervat registrului AH; de exemplu, pentru a afişa caracterul de cod ASCII 1:

(comanda g Go pune în execuţie secvenţa de instrucţiuni, până la instrucţiunea de întrerupere INT 3)
Folosind comanda de trasare t pentru o secvenţă precum cea redată mai sus, putem parcurge pas cu pas inclusiv rutina BIOS apelată cu INT 10h (studiu pretenţios, dar foarte instructiv: necesită răbdare, concentrare, capacitate de reluare şi putere de sinteză). Se pot observa astfel, următoarele etape, parcurse la fiecare apel al rutinei BIOS indicate de vectorul 0x10:
— mai întâi sunt investigaţi mai mulţi parametri din zona de date BIOS (mod video curent, număr coloane ecran, etc.) - ramificând execuţia după setările găsite;
— se face apoi o filtrare pe tipuri de servicii; anume, se compară valoarea transmisă în AH cu anumite limite de încadrare, ramificând execuţia corespunzător tipului de serviciu depistat astfel. Aceasta permite executarea unor operaţii preliminare care sunt comune mai multor servicii de un acelaşi tip;
— în sfârşit, se caută într-un tabel de adrese offsetul subrutinei corespunzătoare valorii din AH, se apelează această subrutină şi apoi se încheie (cu IRET, nu cu RET).
Programul angajat cu INT 0x10, cu etapele de mai sus - este ca să zicem aşa, cam năcăjât; el ar putea fi rescris mai bine, dacă s-ar folosi instrucţiunile CPU mai noi - numai că trebuie să se aibă în vedere faptul că BIOS-ul respectiv trebuie să poată deservi o gamă cât mai largă de microprocesoare (nu numai Pentium, dar chiar şi I8086). Linux nu foloseşte serviciile BIOS (nu are apeluri de sistem similare cu INT 0x10 din DOS).
În etapa finală, fiecare serviciu video din BIOS accesează şi înscrie corespunzător regiştrii programabili ai controlerului video, folosind instrucţiuni OUT port, valoare; dacă se doreşte sporirea vitezei de lucru, atunci se pot ocoli serviciile video oferite de BIOS, angajând direct instrucţiuni OUT (desigur, aceasta înseamnă a lucra direct cu hardware-ul, ocolind cumva sistemul de operare - ceea ce necesită de obicei permisiuni speciale).
Controlerul video are cinci seturi de regiştri programabili; câte un registru-index din fiecare set este destinat selectării unuia dintre regiştrii existenţi în setul respectiv (la fel cum AH permite selectarea unui serviciu la INT 0x10), iar un al doilea registru (registrul de date al setului) permite emiterea unei date către registrul selectat. Pentru o exemplificare, vizăm aici lucrul cu setul regiştrilor de control (în general, aceştia controlează formatul ecranului, modul de afişare, dimensiunea, forma şi poziţia cursorului, adresa de start a paginii ecran).
În zona de date BIOS, la adresa 0000:0463 se află înregistrată adresa 0x03D4 care reprezintă portul de acces la registrul-index al setului de regiştri de control. Portul imediat următor 0x3D5, corespunde registrului de date asociat setului regiştrilor de control. Portul 0x3D4 (pentru plăcile VGA) permite selectarea prin index a unui registru de control:
mov DX, 03D4h mov AL, index out DX, AL
După selectarea registrului dedicat realizării operaţiei dorite, urmează setarea lui la valoarea necesară - prin emiterea datei respective la portul de date 0x3D5:
mov AL, valoare_de_programat inc DX ; acum, DX = 3D5h (portul de date) out DX, AL
Ambele operaţii - selectarea registrului şi emiterea datei către el - pot fi realizate şi într-un singur pas (dat fiind că cele două porturi sunt adrese consecutive), folosind OUT DX, AX (prin care se va depune indexul AL la portul [DX] şi totdată se va emite AH la portul următor [DX + 1]).
Pentru exemplu, să modificăm cursorul; în modul text 3, cursorul ocupă în mod implicit penultimele două linii (13 şi 14) dintr-o boxă de caracter 9×16 pixeli (tipul curent al cursorului este înregistrat în zona de date BIOS 0000:0460, anume 0x0E 0x0D - adică pe liniile de pixeli 13 şi 14 din matricea de pixeli care corespunde unui caracter afişat pe ecran).
În setul de regiştri indexaţi prin registrul de index 0x3D4, la offseturile 0x0A şi 0x0B - se află regiştrii dedicaţi gestionării dimensiunii cursorului şi îi vom putea folosi astfel:
mov DX, 03D4 ; sub DEBUG, valorile sunt implicit în hexazecimal mov AX, 000A ; Selectează registrul 0x0A start-cursor, setând linia ; de început a cursorului la linia 0 a boxei de caracter. out DX, AX ; Ca urmare (sub DEBUG se vede imediat), deja cursorul se întinde între ; liniile 0 şi 14, având forma ▉ inc AL ; Pentru a selecta registrul 0x0A + 1 = 0x0B end-cursor. mov AH, 7 ; Pentru a fixa linia 7 ca linie de sfârşit a cursorului. out DX, AX ; Acum, cursorul va ocupa liniile 0..7: ▀
Desigur, acelaşi efect (sigur mai încet, dar şi cu actualizările necesare: de exemplu, la adresa 0000:0460 trebuie înscrisă noua formă de cursor) se putea obţine apelând la BIOS, prin INT 0x10 cu AH = 1 şi CH = 0 (linia de început a cursorului), CL = 7 (linia de sfârşit a cursorului).
Mai sus, am afişat cu DEBUG caracterul de "cod ASCII" 1, similar ca formă cu ☺. De fapt, nu mai este vorba chiar de ASCII - în cadrul codului ASCII propriu-zis, primele 32 de coduri 0..31 desemnează "caractere de control", fără imagine grafică; este vorba de pagina de coduri CP437 (mai denumită şi "Extended ASCII", sau "Original Equipment Manufacturer-font") - un tabel cu 256 intrări, în care intrarea de rang K = 0..255 cuprinde o matrice de 8 linii şi 8 coloane pe care este "desenat" un caracter (chiar cel desemnat prin codul ASCII K, dacă K = 32..127). CP437 - creată de IBM, prin 1980 - este înscrisă în memoria ROM a plăcilor video (pe lângă alte câteva asemenea seturi de caractere) şi este primul font folosit pentru afişare pe ecran în momentul pornirii calculatorului.
Găsind (cum arătăm mai încolo) adresa la care este mapat tabelul CP437 şi făcând un "dump":

Prima intrare de 8 octeţi (toţi nuli) corespunde codului 0; următorii 8 octeţi sunt asociaţi codului 1 şi compun forma grafică următoare (░ reprezintă un bit 0, iar ▒ = 1):
| 7E | 01111110 | ░▒▒▒▒▒▒░ |
| 81 | 10000001 | ▒░░░░░░▒ |
| A5 | 10100101 | ▒░▒░░▒░▒ |
| 81 | 10000001 | ▒░░░░░░▒ |
| BD | 10111101 | ▒░▒▒▒▒░▒ |
| 99 | 10011001 | ▒░░▒▒░░▒ |
| 81 | 10000001 | ▒░░░░░░▒ |
| 7E | 01111110 | ░▒▒▒▒▒▒░ |
INT 0x10 şi în particular, serviciile BIOS de încărcare a unui set de caractere sunt investigate amănunţit în Limbaje şi calculator şi nu mai este cazul să actualizăm aici. Secvenţa următoare va încărca setul CP437 şi va furniza informaţii corecte asupra lui:

Informaţiile returnate sunt CL = 8 = numărul de octeţi pe caracter; DL = 0x31 = 49 = rangul ultimei linii ecran (numărând de la 0); ES:BP = 0xC000:0x8021 = adresa de încărcare a setului de caractere (adresă la care făcusem "dump" mai sus). Să precizăm că "numărul de octeţi pe caracter" se referă la dimensiunea unei intrări în pagina de coduri respectivă; pe ecran nu-i corespunde o lăţime de 8 pixeli - ci de 9 pixeli (încât lăţimea ecranului este de 9 × 80 caractere = 720 pixeli), o a noua coloană de pixeli fiind inserată automat în scopul unei mai clare separări a caracterelor.
Un set de caractere din ROM BIOS descrie 128 sau 256 de caractere; le vom reprezenta în succesiuni de câte NR_MAT = 8 matrici alăturate orizontal; primul şir de matrici din fişier va constitui vizualizarea grafică a caracterelor de coduri 0..7, al doilea şir de matrici corespunde codurilor 8..15, ş.a.m.d. În total, fişierul va conţine 256/NR_MAT şiruri de câte NR_MAT matrici-caracter. Fiecare astfel de şir de matrici acoperă un număr de ROW linii-text, unde ROW este numărul de octeţi pe caracter specific setului (ROW = 8 pentru CP437). O linie-text a fişierului - dacă nu separă ("\n") două şiruri consecutive de matrici - vizualizează octeţii de acelaşi rang 0..(ROW - 1) din descrierile a NR_MAT caractere consecutive din setul respectiv; pentru vizualizarea formelor grafice vom marca bitul 0 prin ░, iar bitul 1 prin ▒.
Pentru a evita calcularea repetată a componenţei binare a octetului curent (dintre cei ROW octeţi care compun descrierea caracterului curent din set), vom genera în prealabil toate cele 256 de modele de linie-caracter (de la "00000000" până la "11111111"); rezervăm în acest scop 256×8 octeţi (iniţializaţi cu ░) la adresa MODELE, în cadrul zonei de date. De exemplu, dacă octetul curent din descrierea caracterului ar fi 0x7E, atunci forma grafică a liniei-caracter corespunzătoare va putea fi preluată direct din zona MODELE, de la intrarea 0x7E × 8 = 0x7E (se înmulţeşte cu 8 fiindcă fiecărui cod îi corespund în MODELE câte 8 octeţi, ░ sau ▒).
Cele NR_MAT modele binare obţinute astfel (pentru octeţii de acelaşi rang 0..(ROW - 1), din descrierile a NR_MAT caractere consecutive din set), vor fi concatenate succesiv (intercalând câte un spaţiu separator) în bufferul BUF_ROWS, care va fi scris apoi în fişier.
; C:\> nasm -fobj cargen.asm ; C:\> tlink cargen.obj %define NR_MAT 8 ; analog cu #define în C/C++ section .data align 4 file db "map437.txt", 0 ; nume fişier (şir ASCIIZ, cerut de DOS FN 40h) newline db 13, 10 ; codul ASCII de "newline" pentru DOS bitgr db 176 ; caracter grafic (cod CP437) pentru bit = 0 section .bss align 4 MODELE resb 256*8 ; 8 biţi de fiecare octet 0..255 (bit=0 —> 0xB0, bit=1 —> 0xB1) BUF_ROWS resb 9*NR_MAT ; linia de scris în fişier: câte 8 caractere 'bitgr' plus spaţiu, ; pentru octeţii de acelaşi rang din NR_MAT caractere consecutive file_id resw 1 ; descriptorul fişierului ROW resw 1 ; nr. octeţi/caracter (= 8 pentru CP437) %macro EXIT 0 mov AX, 4C00h ; funcţia DOS FN 4Ch, pentru EXIT (AH = 0x4C) int 21h %endm section .text group DGROUP bss data ..start: ; punctul de intrare în execuţie (IP = start) mov AX, data ; fixează DS = DGROUP pentru a putea adresa mov DS, AX ; datele prin intermediul lui DS mov DX, file ; DOS FN 3Ch creează fişierul mov CX, 0 mov AH, 3Ch int 21h jnc begin ; Daca fişierul nu poate fi creat - EXIT ; - abandonează în acest punct begin: mov [file_id], AX ; Salvează descriptorul fişierului creat. ; Mai întâi generăm la adresa MODELE, modelele binare grafice ale codurilor 0..255 ; SHL scoate in Carry bitul de rang curent din codul respectiv; pentru Carry = 0 ; înscriem 0xB0, iar pentru Carry = 1 înscriem 0xB1 (pe locul curent în MODELE) xor AL, AL ; AL va enumera crescător codurile 0..FFh mov DI, MODELE mov DL, [bitgr] ; iniţial, înscrie 0xB0; S0: mov CX, 8 ; In ciclul S1 se obţin succesiv cei 8 biţi constitutivi ai mov BL, AL ; codului curent din AL (folosind SHL) S1: shl BL, 1 ; Daca bitul de rang curent este 0, mov [DI], DL ; la adresa DS:DI rămâne B0h, altfel adc [DI], byte 0 ; (adunând Carry = 1) se trece la B1h inc DI ; Vizează următoarea poziţie din MODELE loop S1, CX inc AL ; INC şi DEC nu afectează flagurile... and AL, AL ; Seteaza Zero-flag, daca AL a ajuns 0 (FFh + 1 = 100h => AL = 0). jnz S0 ; Reia de la S0, daca AL <= 0xFF. ; Se încarcă setul ROM BIOS indicat prin BL (folosind INT 10h, AH = 11h, AL = BL) şi ; se determină adresa setului şi valoarea ROW (cu INT 10h, AX = 1130h, BH primit). mov BX, 0312h ; BH = 3, pentru a obţine informaţii asupra setului CP437 mov AH, 11h ; Se încarcă setul ROM corespunzător valorii primite în BL mov AL, BL xor BL, BL int 10h mov AX, 1130h mov BH, 3 ; se obţine adresa ES:BP a setului respectiv şi int 10h ; CX = numărul de octeţi din descrierea fiecărui caracter mov [ROW], CX ; În ciclurile imbricate P0, P1, P2 se constituie repetat BUF_ROWS şi se scrie in fişier mov CX, 256 / NR_MAT ; în ciclul exterior P0 se va înregistra în fişier câte unul ; dintre cele 256/NR_MAT şiruri de matrice alăturate P0: push CX mov CX, [ROW] push BP ; în ciclul P1 se va înregistra în fişier câte unul (de la ES:BP până P1: ; la ES:BP + 8×NR_MAT) dintre cele 256/NR_MAT şiruri de matrice alăturate push CX mov CX, NR_MAT ; în ciclul P2 se obţine o linie-text BUF_ROWS, conţinând modelele mov DI, BUF_ROWS ; binare ale octeţilor de rang curent (indicat de contorul lui P1) push BP ; din descrierile a NR_MAT caractere consecutive din setul respectiv P2: xor BX, BX mov BL, [ES:BP] ; octetul grafic curent (din 8 în 8) din ROM shl BX, 3 ; înmulţeşte cu 8, găsind intrarea corespunzătoare în MODELE mov SI, MODELE add SI, BX ; SI referă cei 8 octeţi grafici B0h/B1h pentru octetul din ROM push ES ; salvează ES (pentru reluare de la ES:BP) şi pune ES = DS, mov AX, DS ; pentru a transfera cu MOVSD cei 8 octeţi grafici mov ES, AX ; din MODELE, în BUF_ROWS movsd movsd mov [DI], byte 20h ; adaugă (în BUF_ROWS) un spaţiu, după cei 8 octeţi grafici inc DI pop ES ; recuperează ES add BP, 8 ; BP referă acum octetul din caracterul grafic următor din ROM, de loop P2, CX ; acelaşi rang cu octetul tocmai prelucrat, din caracterul grafic curent call write ; scrie BUF_ROWS în fişier, adăugând şi un "newline" call writenl pop BP ; recuperează referinţa de bază a octeţilor de acelaşi rang inc BP ; şi trece la rangul următor pop CX loop P1, CX call writenl ; desparte prin "newline" un şir de matrici de cel următor pop BP ; actualizează referinţa la un set de 8×NR_MAT caractere consecutive din ROM add BP, 8*NR_MAT pop CX loop P0, CX mov AH, 3Eh ; DOS FN 3Eh închide fişierul (disponibilizând descriptorul) mov BX, [file_id] int 21h EXIT ; redă controlul sistemului de operare write: mov AH, 40h ; DOS FN 40h scrie în fişierul cu descriptorul BX mov bx, [file_id] ; un număr de CX caractere, de la adresa DS:DX mov cx, 9*NR_MAT mov dx, BUF_ROWS int 21h ret writenl: mov ah, 40h mov bx, [file_id] mov cx, 2 mov dx, newline int 21h ret
Maniera în care am specificat secţiunile (folosind group DGROUP), punctul de intrare ..start, precum şi maniera de adresare a memoriei (pe 16 biţi) - sunt specifice formatului obj de fişier-obiect (indicată prin opţiunea -f a asamblorului NASM) care este recunoscut şi de editorul de legături tlink (Borland):

Rulând executabilul obţinut şi folosind programul DOS edit (existent pe Windows, dacă sistemul a fost decent instalat) - putem vizualiza exact caracterele din setul respectiv (aici, setul CP437 8×8, din ROM BIOS-ul unei plăci VGA). Primele caractere grafice (de la codul 0) sunt:

Deschizând cu alt editor de text, cele două caractere prevăzute în program pentru a reprezenta biţii 0 şi 1 vor fi "înlocuite" prin caracterele de acelaşi cod din cadrul fontului de lucru curent. Cu Notepad, alegând prin meniul View/Format/Font fontul Terminal, vedem (aici am făcut o selecţie de caractere):

Iar dacă folosim browserul Firefox, alegând şi în acest caz View/Character Encoding Western (IBM-850):

Desigur, puteam obţine nu 8 ci de exemplu 16 "matrici-alăturate", schimbând definiţia pentru NR_MAT; de asemenea, putem obţine fişierul de matrici corespunzător altor seturi de caractere din ROM BIOS, schimbând folosirea INT 0x10 (de exemplu, cu AL = 0x14 şi BH = 6 am fi obţinut caracterele 8×16).
— NASM manual — Ralf Brown's Interrupt List — support.microsoft.com Troubleshooting MS-DOS-Based Programs in Windows
ORAR orarul şcolii
SitSco situaţie şcolară
ŞAH prin corespondenţă
doChess a Javascript chess engine
doPGN a Javascript PGN-browser
Cal++ ambiţiile Calului
aşaAzis momente lingvistice
Comentarii
—cum ar trebui calculată Media şcolară?
completely rethink the browser:
Google chrome