-
در اين قسمت يك تمرين ديگر با هم انجام ميدهيم و برنامه اي مينويسيم كه تعداد 200
رنگ از 256 رنگ موجود در حالت 320x200 گرافيكي را نمايش دهد .
تابع شماره 00h از وقفه 10h مربوط به تعيين حالت نمايش است . كد مربوط به
حالت صفحه نمايش در ثبات AL قرار گرفته و وقفه فراخواني ميشود:
شماره تابع برابر AH=00h
حالت صفحه نمايش با استفاده از جدول AL= INT 10h
حالت صفحه نمايش در AL از جدول مخصوص موجود در كتابهاي اسمبلي بدست مي آيد.
كد مربوط به حالت 256C َ320x200 برابر 13h است بنا براين AL را برابر 13h قرار
ميدهيم .
براي نمايش و روشن كردن يك نقطه (Pixel) در حالت گرافيكي از تابع 0Ch همين
وقفه استفاده ميكنيم . يعني شماره ستون را در CX ، سماره سطر را در DX و شماره
رنگ را در AL قرار داده و وقفه را اجرا ميكنيم . براي اينكه از ستون 199 تا ستون
شماره صفر نقطه روشن كنيم ، CX را برابر 319 قرار داده و با دستور LOOP نقاط را
در يك سطر روشن ميكنيم .
MOV CX/319 ; COLUMN 319 = START COLUMN
COL:
INT 10H ; CALL INTERRUPT 10H
LOOP COL
سپس DX يا همان شماره سطر را يكواحد افزايش ميدهيم و مقدار آن را با 199 مقايسه
ميكنيم (چون از 0 تا 199 سطر داريم ) و اگر برابر نبود دوباره عمليات بالا را
انجام ميدهيم .
بعد از اينكه اين عمليات انجام شد، تابع 00h از INT 16h را فراخواني ميكنيم تا
منتظر دريافت يك كليد از صفحه كليد شود . به اين ترتيب ميتوانيم نتيجه برنامه
را مشاهده كنيم و كليدي را براي اتمام برنامه بزنيم .
در نهايت بايد حالت صفحه نمايش را به مود متني برگردانيم .
براي اينكار از همان تابع تعيين مود نمايشي استفاده ميكنيم و حالت صفحه نمايش
كه با AL مشخص ميشود را برابر 3 قرار ميدهيم .
برنامه را با روش گفته شده تايپ و كامپايل كنيد . اگر از توربو اسمبلر استفاده
ميكنيد با فرمان ASM/T.َTASM VGA256 آن را به فرم COM. ترجمه كنيد.
. MODEL SMALL
. CODE
ORG 100H
START :
MOV AH/00H
MOV AL/13H
MOV BX/00H ; PAGE NUMBER
INT 10H ; SET TO 320x200 256 COLORS
MOV AH/0CH ; PUTPIXEL FUNCTION
MOV AL/25 ; COLOR #25
MOV DX/0 ; ROW 0
ROW :
MOV CX/319 ; COLUMN 319 = START COLUMN
COL :
INT 10H ; CALL INTERRUPT 10H
LOOP COL ; DOWN TO CX=0
INC DX ; DX=DX+1
INC AL ; AL=AL+1( COLOR NUMBER )
CMP DX/199 ; IF DX=199
JNZ ROW ; ELSE JUMP TO ROW
MOV AH/00H
INT 16H
MOV AH/00H ; VIDEO MODE SET
MOV AL/03H ; 80x25 16 COLORS
INT 10H ; CALL INT .10H
INT 20H ; TERMINATE PROGRAM
END START
-
حتما با ثابتها در زبانهائي مثل پاسكال آشنائي داريد . بعنوان مثال با جمله
ے Const MaxLen=1024; ، ثابتي بنام MaxLen تعريف شده و مقدار آن برابر 1024 قرار
ے قرار ميگيرد . پس از آن كامپايلر در هرجا كه MaxLen را مشاهده كند عدد 1024 را
بجاي آن قرار ميدهد .
در زبان اسمبلي براي تعريف يك ثابت از معرفه EQU به شكل زير استفاده ميكنيم
مقدار EQU نام ثابت
مثلا : MaxLen EQU 1024
ے به اين ترتيب اسمبلر هميشه بجاي MaxLen عدد 1024 را قرار ميدهد . بهمين دليل
ثابتهاي برنامه را بايد قبل از جمله CODE. بنويسيم . مثال :
. MODEL SMALL
SECTORS EQU 18
SIDES EQU 2
. CODE
:
:
ے به اين خاطر ثابتها را قبل از CODE. تعريف ميكنيم كه در برنامه كامپايل شده اثري
از نام ثابت نبوده بلكه مقدار هر ثابت در جاي لازم قرار گرفته است .
مثال :
. MODEL SMALL
BELL EQU 7
. CODE
ORG 100H
MOV AH/0EH
MOV AL/BELL
INT 10H
INT 20H
END
متغيرها
ے از متغيرها براي نگهداري موقتي داده ها استفاده ميكنيم . مثلا در زبان پاسكال
ے ميتوانيم با عبارت Var يك متغير تعريف كنيم مثل Var Buffer:Byte; و در زبان
سي مثل unsigned char Buffer; .
ے متغيرها در زبان اسمبلي بايد حتما در داخل قطعه داده (DS) تعريف بشوند و در
ے برنامه هاي COM. هم از آنجائي كه قطعه داده ها و كد يكي است ميتوانيم در قطعه كد
نيز تعريف كنيم .
ے براي تعريف يك متغير بايد بعد از نام آن يكي از عبارات ..DB/DW/DD/ را
ے بياوريم . DB مشخصه نوع بايت ،DW مشخصه نوع Word (دوبايتي ) و DD مشخصه نوع
(Double Word) 4 بايتي است .
مثلا :
. CODE
SIZE DW 1024
BELL DB 7
ے در اين مثال Size يك متغير دو بايتي بوده و مقدار اوليه ان 1024 است و BELL نيز
يك متغير تك بايتي با مقدار 7 ميباشد .
ے اگر نميخواهيم به متغير مقدار اوليه بدهيم ، ميتوانيم از علامت (?) بجاي مقدار
استفاده كنيم مانند : MaxLen DW ?
ے براي تعريف يك رشته كاراكتري از معرفه DB استفاده كرده و محتواي رشته را داخل
('') يا ("") قرار ميدهيم . مثلا :
MSG DB "ASSEMBLY / A QUICK LOOK ! "
ے در اين مثال MSG يك متغير كاراكتري است . در اسمبلي ميتوانيم از كد اسكي
ے كاراكتر ها نيز استفاده كنيم . مثلا اگر در تعريف DB بخواهيم كدهاي اسكي 13و 10
را به MSG اضافه كنيم ميتوانيم با كاما اين كار را انجام دهيم :
MSG DB "ASSEMBLY / A QUICK LOOK ! "/13/10
يا : MSG DB "ASSEMBLY / A QUICK LOOK ! "/0Ah/0Dh
يا حتي : MSG DB "ASSEMBLY / A QUICK LOOK ! "/0Ah/0Dh/'$'
اين تركيبها همه يك رشته كاراكتري معرفي ميكنند .
براي تعريف آرايه ها نيز از روشي مشابه و به شكل زير استفاده ميكنيم :
(مقدار اوليه )DUP تعداد عناصر DB/DW/DD نام متغير
مانند: BUFFER DB 1024 DUP(0 )
كه ارايه اي يك كيلوبايتي تعريف كرده و همه عناصر آن را با 0 پر ميكند.
اگر نخواهيم مقدار اوليه اي در نظر گرفته شود از ? استفاده ميكنيم .
مانند: BUFFER DB 1024 DUP(? )
و براي تعريف يك آرايه حرفي بايد با يك حرف يا عبارت آن را پر كنيم : BUFFER DB 1024 DUP("A" )
و حتي : BUFFER DB 1024 DUP("STACK" )
ے گفتيم كه متغيرها هميشه (در برنامه هاي COM.) در قطعه كد و بعد از CODE. نوشته
ے ميشوند ، بنا براين اسمبلر هميشه سعي خواهد كرد كه آنها را بصورت يك كدماشين
ے قابل اجرا تفسير كند. به همين دليل هميشه بايك دستور JMP از روي آنها پرش
ميكنيم . مثال :
. MODEL SMALL
RDISK EQU 2
. CODE
ORG1 100H
START :
JMP MAIN
BUFFER DB 512 DUP(0 )
MSG DB "DISK DUP."/13/10/'$'
MAIN :
مجموعه كدهاي اجرايي برنامه :
: END START
همانطور كه ميبينيد با دستور JMP MAIN از قسمت تعريف داده ها پرش كرده ايم .
-
در اين قسمت نحوه دسترسي به مقادير متغير ها را ياد ميگيريم .
ے وقتي كه ميخواهيم مقدار يك متغير را به يك متغير يا ثبات ديگر منتقل كنيم بايد
ے به اندازه آن توجه داشته باشيم . مثلا اگر متغيري بصورت LOCATE DB 10 تعريف كرده
ے باشيم ، به دليل تك بايتي بودن ، نميتوانيم آن را به يك ثبات كامل مثل AX يا
متغير دوبايتي كه با DW تعريف شده است ارسال كنيم .
ے اما انتقال آن به يك نيم ثبات مثل ALيا AHا و ... مجاز است مانند . MOV BH/LOCATE
ے از متغيرها بيشتر براي نگهداري موقت داده ها استفاده ميشود . مثلا وقتي كه
ے برنامه اي براي كار با قطاعهاي ديسك مينويسيم ، بايد يك محل موقتي براي ذخيره
ے محتواي قطاع هاي خوانده شده ايجاد كنيم . در اين موقع يك متغير به شكل (ترجيحا)
آرايه تعريف ميكنيم .
ے وقتي به اين شكل با متغيرها برخورد ميشود، به دانستن آدرس آن نياز پيدا ميكنيم
فرض كنيد ميخواهيم جمله A QUICK START TO ASSEMBLY PROGRAMMING را چاپ كنيم .
در قدم اول بايد متغيري تعريف كرده و اين جمله را داخل آن قرار دهيم .
پس : MSG DB 'A QUICK START TO ASSEMBLY PROGRAMMING'/13/10/'$'
ے اعداد 13وَ10 انتهاي رشته براي انتقال مكان نما به سطر بعد هستند و كاراكتر '$'
ے از اين جهت وجود دارد كه تابع چاپ رشته انتهاي رشته كاراكتري را با بودن $
تشخيص ميدهد.
ے براي چاپ رشته كاراكتري راه هائي وجود دارد كه يكي از آنها استفاده از تابع 9h
مربوط به INT 21h ميباشد .
براي فراخواني آن بايد به اين صورت رجيستر ها را پر كنيم : AH=09H
آدرس رشته كاراكتري DS:DX = INT 21H
ے عبارت DS:DX نشان ميدهد كه مقدار قطعه (Segment) رشته كاراكتري ، يعني آن قطعه
ے اي كه متغير تعريف شده در آن قرار گرفته است ، را بايد در DS قرار بدهيم . به
همين صورت نيز مقدار آفست (Offset) آن را به DX انتقال ميدهيم .
براي بدست آوردن شماره قطعه يك متغير از عملگر SEG استفاده ميكنيم .
ے مثلا براي بدست آوردن شماره قطعه MSGاز MOV AX/Seg MSGز استفاده ميكنيم . اين
دستور شماره سگمنت MSG را پيدا كرده و در AX قرار ميدهد .
براي بدست آوردن شماره آفست هم از OFFSET استفاده ميكنيم مثلا MOV DX/OFFSET MSG
پس براي چاپ رشته MSG بايد به اين صورت عمل كنيم :
MOV AH/09H
MOV DX/OFFSET MSG
INT 21H
ے اين قطعه كاري كه ما ميخواهيم را انجام ميدهد و اگر دقت كنيد متوجه ميشويد كه
ے اصلا شماره قطعه (Segment) را محاسبه نكرده ايم . علت اينست كه متغير ما به دليل
ے COM. بودن برنامه در Code Segment ( كه با CODE. مشخص ميشود) تعريف شده پس خود
ے بخود DS حاوي مقدار سگمنت آن هست . ( باز هم ياد آوري ميكنيم كه CS حاوي شماره
ثبات كد و DS حاوي ثبات داده ها است و در برنامه هاي COM. مقدار برابر دارند)
ے يك دستور خلاصه براي بدست آوردن عدد آفست وجود دارد بنام LEA .كل كاري كه اين
ے دستورالعمل انجام ميدهد اينست كه ديگر احتياج به نوشتن OFFSET نخواهد بود . به
عنوان مثال MOV DX/OFFSET MSGبا LEA DX/MSGا برابر است .
با اين تفاسير كل برنامه به اين شكل خواهد بود .
. MODEL SMALL
. CODE
ORG 100H
START :
JMP MAIN ; skip to main codes
MSG DB 'A QUICK START TO ASSEMBLY PROGRAMMING'/13/10/'$'
MAIN :
LEA DX/MSG ; get MSG offset
MOV AH/09 ; write string function
INT 21H ; call interrupt 21h
INT 20H ; terminate program
END START
تمرين :
ے براي اينكه تمرين بهتري داشته باشيم ، ميخواهيم خودمان و فقط با استفاده از وقفه
ے مربوط به چاپ كاراكتر همين جمله را چاپ كنيم . قبلا گفتيم كه تابع 0Eh از وقفه
ے 10h يك كاراكتر را در محل مكان نما چاپ كرده و مكان نما را يك خانه به راست
انتقال ميدهد. ميخواهيم رشته كاراكتري بالا را تا رسيدن به علامت $ چاپ كنيم .
ے بهترين كار اينست كه عدد آفست را در BX قرار بدهيم . در اينموقع آفست اولين
ے كاراكتر در BX است . مقدار داخل اين آفست را بصورت MOV al/[bx] به ثبات AL
ے منتقل كرده و بعد چاپ ميكنيم . براي كاراكتر بعدي يكواحد به BX اضافه ميكنيم و
ے دوباره همان كارهاي قبلي ... . اين عمليات را بايد تا رسيدن به كاراكتر '$' ادامه
بدهيم .
ے ** اين برنامه را خودتان و بدون توجه به راه حل ارائه شده بنويسيد و فايل COM.
آن را بسازيد.
. MODEL SMALL
. CODE
ORG 100H
START :
JMP MAIN ; jump to MAIN
MSG DB 'A QUICK START TO ASSEMBLY PROGRAMMING'/13/10/'$'
MAIN :
LEA BX/MSG ; get MSG offset
MOV AH/0EH ; write char function
LOOP :_
MOV AL/[BX] ; move [BX] to AL: charactre code
CMP AL/'$' ; if al is equal with '$'
JE END _; then jump to END _
INT 10H ; otherwise call interrupt 10h
INC BX ; BX=BX+1
JMP LOOP _; jump to next caharcter
END :_
INT 20H ; terminae program
END START
-
تابع شماره 2(AH=2() از وقفه 13h كه وقفه ديسك ميباشد ، براي خواندن سكتورهاي
ديسك بكار ميرود . وقتي شماره هد، شيار،سكتور و ... را مشخص كرده و وقفه را
فراخواني نموديم ، محتواي قطاع خوانده شده در محلي كه با جفت ثبات ES:BX مشخص
ميشود قرار ميگيرد. به همين دليل ما يك آرايه تعريف ميكنيم وآدرس آن را در ES:BX
قرار ميدهيم تا اطلاعات خوانده شده در آن قرار بگيرد. BUF DB 512 DUP(0)
در اين مثال ما ميخواهيم برنامه اي بنويسيم كه محتواي قطاع بوت كننده ديسكي را
خوانده و نمايش دهد . چون ميخواهيم يك سكتور را بخوانيم و اندزه هر قطاع 512
بايت است ، پس آرايه اي با 512 عنصر تك بايتي تعريف كرده ايم .
در قدم بعدي شماره شيار (0-79) را در CH، شماره قطاع (1-18) را در CL، شماره
درايو را در DL(.../.:0=A ) ، رويه ديسك را در DH(0=1st Side() و تعداد قطاعهائي
كه بايد خوانده شوند را در AL قرار ميدهيم .
MOV AX/0201H
MOV CH/0 ;TRACK NUMBER
MOV CL/1 ;SECTOR NUMBER
MOV DH/0 ; SIDE #1
MOV DL/0 ; 0=A / 1=B /( ...DRIVE NUMBER )
حالا بايد آدرس بافر تعريف شده (BUF) را به ES:BX منتقل كنيم . گفتيم كه براي
بدست آوردن شماره سگمنت هر متغير از Seg و براي بدست آوردن مقدار آفست از Offset
استفاده ميكنيم . چون برنامه ما COM. است ، پس عدد سگمنت بطور خودكار در
ثبات DS هست و احتياجي به يافتن آن نداريم . بلكه فقط بايد آن را به ES منتقل
كنيم .
گفتيم كه نميتوانيم ثبات ESو DSو را مستقيما و با MOV به هم منتقل كنيم ، بلكه
بايد از يك ثبات همه منظوره مثل AX كمك بگيريم . بنا براين و براي اينكه مقدار
فعلي ثبات AX از بين نرود، ابتدا AX را در Stack قرار داده و مقدار DS را در آن
قرار ميدهيم : PUSH AX ; SAVE AX
MOV AX/DS
و پس از آن محتواي AX را به ES داده و دوباره مقدار AX را از پشته POP ميكنيم : MOV ES/AX
POP AX
پس از آن با دستور LEA مقدار آفست BUF را بدست مي آوريم : LEA BX/BUF
و در نهايت فراخواني اينتراپت 13h. INT 13H
بقيه برنامه عبارتست از چاپ كاراركترهاي داخل آرايه BUF كه قبلا آن را ياد
گرفتيم . فقط به اين نكته توجه ميكنيم كه كدهاي اسكي كوچكتر از 32 كدهاي كنترلي
بوده و به جاي آنها بايد كاراكتر (.) چاپ كنيم به همين دليل هم قطعه كدي براي آن
نوشته ايم :
MOV AL/BUF[BX]
CMP AL/32
JB SKIP
:
:
SKIP :
MOV AL/'.'
INT 10H
:
:
ليست كامل :
.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
BUF DB 512 DUP(0)
MAIN:
MOV AX/0201H
MOV CH/0 ;TRACK NUMBER
MOV CL/1 ;SECTOR NUMBER
MOV DH/0 ; SIDE #1
MOV DL/0 ; 0=A / 1=B /( ...DRIVE NUMBER)
PUSH AX ; SAVE AX
MOV AX/DS
MOV ES/AX
POP AX
LEA BX/BUF
INT 13H
MOV BX/1
MOV AH/0EH ; WRITE CHAR.
PRINT:
MOV AL/BUF[BX]
CMP AL/32
JB SKIP
INT 10H
JMP CONT
SKIP:
MOV AL/'.'
INT 10H
CONT:
INC BX
CMP BX/513
JNZ PRINT
INT 20H
END START
-
عملگرهاي بيتي
عملگرهاي بيتي مانند عملوندهاي حسابي هستند با اين تفاوت كه روي بيت ها كار
ميكنند. اين عملگرها عبارتند از : ... AND/OR/XOR/SHR/SHL/RCL/RCR/ .
عملگر AND : اين اپراتور بيتهاي دو عدد(متغير) را با هم AND كرده و حاصل را در
متغير (يا ثبات ) سمت چپ قرار ميدهد . اگر فرض كنيم كه هميشه 1 بودن بيت به
معناي Trueو 0و بودن آن به معناي False است ، AND هميشه در صورتيكه هر دوبيت
مقايسه شونده 1 باشند، حاصل 1يا Trueا را بر ميگرداند .
جدول ارزشي AND :
X | Y | X and Y |
1 | 1 | 1 |
1 | 0 | 0 |
0 | 1 | 0 |
0 | 0 | 0 |
پس وقتي ما عملوند AND را با دو رجيستر بكار ميبريم ، بصورتي كه گفته شد بيتها
با هم مقايسه شده و حاصل مقايسه در محل متناظر بيتها در ثبات سمت چپ قرار
ميگيرند . مثلا اگر دستور AND Ah/Dh را اجرا كنيم ، حالتي نظير شكل زير را داريم :
AH : 01101010
DH : 01111101
AH :AH AND DH : 01101000
به نتيجه بدست آمده توجه كنيد .
هر وقت كه بخواهيم بيت هاي خاصي از يك رجيستر را 0 كنيم ، يك عدد باينري كه
همه بيتهاي آن ، بجز بيتهاي مورد نظر 1 هستند را در نظر گرفته با رجيستر مورد نظرAND
ميكنيم .مثلا اگر بخواهيم بيتهاي دوم و سوم ثبات AX را صفر كنيم : AND AX/11111001b
عملگر OR :
اين عملوند بيتهاي دو عدد را با هم مقايسه كرده و اگر يكي از آن دو 1 بود ، بيت
متناظر در ثبات سمت چپ را 1 ميكند . مثلا با دستور OR AH/DH بيتهاي AHبا DHا
مقايسه شده و هر دو بيت متناظر كه با هم 0 بودند ، بيت تناظر در AHهم 0 ميشود
AH : 01101010
DH : 01111100
AH : AH OR DH : 01111110
هرگاه كه بخواهيم بيت هاي خاصي از يك متغير يا رجيستر را 1 كنيم ، يك عدد
باينري كه همه بيتهاي آن غير از بيتهاي مورد نظر 0 هستند در نظر گرفته و با ثبات
مورد نظر OR ميكنيم . مثلا اگر بخواهيم دو بيت پائين AHرا 1ا كنيم منويسيم : OR AH/00000011b
عملگر : XOR
عملوند XOR تنها در صورتي نتيجه 1 ميدهد كه دو بيت مقايسه شونده غيرهم ارزش
باشند . يعني يكي 1 و ديگري 0 باشد .
بعنوان مثال با اجراي XOR AH/DH اين عمليات روي بيتها انجام ميشود
AH : 01101010
DH : 01111100
AH : AH XOR DH : 11101001
وقتي بخواهيم يك مقدار ثبات را برابر صفر قرار بدهيم ، معمولا از آن را با خودش XOR
ميكنيم . مثلا XOR CX/CX محتواي ثبات CX را برابر 0 قرار ميدهد .
عملگرهاي SHRو SHLو :
اين عملگرها، بيتها را به راست و چپ شيفت ( انتقال ) ميدهند .
SHR Reg.nnum و SHL Reg.nnum
.Reg اسم يك ثبات است مثلا AXو numو معلوم ميكند كه چند بيت بايد به طرف
راست يا چپ انتقال پيدا كند . مثلا SHR AX/6 بيتهاي AXرا 6ا واحد به راست
انتقال داده و بيتهاي چپ را با 0 پر ميكند . AX :10100010
AX :SHR AX/4 : 00001010
SHL
هم عكس اين عمل را انجام ميدهد . يعني بيتها را به چپ شيفت داده و از
طرف راست با 0 پر ميكند . AX :10100010
AX :SHR AX/4 : 00100000
مثال : اگر بخواهيم كه محتواي نيم ثبات CL را به نيم ثبات CH منتقل كنيم ،
كافيت كه CXرا 8ا بيت به سمت چپ شيفت بدهيم . يعني SHL CX/8
CL CH | 10110100 | 00101101 |
محتواي اوليه CX CX
CL CH | 00101101 | 00000000 |
محتواي CX بعد از SHL CX/8
انتقال
-
عملگر Not : عملوند Not ارزش همه بيتهاي يك بايت يا كلمه را برعكس ميكند .
يعني تمام بيتهاي 1را 0ا و تمام بيتهاي 0را 1ا ميكند . بعنوان مثال اگر AH حاوي
مقدار 10101101 باشد، بعد از اجراي Not Al ، محتواي AL بصورت 01010010 خواهد
بود.
جدول ارزشي Not Not X X N
F | T
T | F
عملگر Neg . اين اپراتور معمولا با Not اشتباه ميشود در صورتي كه كمتر شباهتي بين
آنها وجود دارد . Neg ارزش عددي يك عدد علامتدار را برعكس ميكند . يعني يك عدد
منفي را مثبت ميكند و برعكس . در اعداد علامتدار ( همانطور كه بعدا هم خواهيم
ديد )، اولين بيت سمت چپ ( بيت هشتم ) بيت علامت است . 1 بودن آن نشاندهنده
منفي بودن و 0 بودن آن نشان دهنده مثبت بودن است .
عملگر Neg با عكس كردن بيت علامت ، ارزش عدد را عكس ميكند .
اين عملوند را در مبحث اعداد علامتدار مفصلا ميخوانيم .
مثال : ميخواهيم برنامه اي بنويسيم كه تمام حروف كوچك يك عبارت را به حروف
بزرگ تبديل كند . از نظر مقدار عددي ، تفاوت حروف كوچك و بزرگ در اينست كه
بيت پنجم در حروف بزرگ برابر 0 و در حروف كوچك 1 است . مثلا كداسكي حرف a
به باينري برابر 01100001 و كد A برابر 01000001 است . پس برنامه اي مينويسيم
كه تمام كاراكترهاي رشته كاراكتري مورد نظر را خوانده و بيت پنچم آنها را 0 كند
. در قسمت قبلي ديديم كه براي 0 كردن يك بيت ، يك عدد باينري كه تمام بيتهاي
آن 1 ، و بيت مورد نظر (براي 0 كردن ) 0 باشد را با عدد مورد نظر AND ميكنيم .
. MODEL SMALL
. CODE
ORG 100H
START :
JMP MAIN
MSG DB ' this is an example/ ..$'
MAIN :
بدست آوردن آدرس رشته ; LEA BX/MSG LOOP :_
قرار دادن كاركتر در AL ; MOV AL/[BX]
آيا كاراكتر $ است ? ; CMP AL/'$'
بله ، به MAIN برو ; _JZ END
بيت پنجم را صفر كن ; AND [BX]/11011111B
يكواحد به BX اضافه كن - كاراكتر بعدي ; INC BX JMP LOOP _;
END :_
قرار دادن آفست رشته در DX ; MOV DX/BX
تابع 9 براي نمايش رشته ; MOV AH/9
قفه 21h ; INT 21H
پايان برنامه ; INT 20H END START
معمولا برنامه هائي كه پيغامهاي خود را كد ميكنند ( مثل ويروسها ) از اين روش يا
روشي مشابه براي Decode كردن پيغامها استفاده ميكنند .
مثال :
بايت وضعيت صفحه كليد كه مربوط به وضعيت كليد هاي كنترلي CapsLock/NumLock
در بايوس هاي AT/PS2 در آدرس 0017h:0040h قرار دارد.
بيتهاي اين بايت نشان ميدهد كه كدام كليد فعال است . 1 بودن به معني روشن بودن
و 0 به معني خاموش بودن آن است . در مثال زير بيت ششم براي كليد CapsLockرا 1ا
ميكنيم تا Capslock روشن شود .
.MODEL SMALL
.CODE
ORG 100h
START:
PUSH ES
MOV AX/0040h
MOV ES/AX
MOV AL/ES:[17h]
OR AL/32
MOV BYTE PTR ES:[17h]/AL
POP ES
MOV AH/1
INT
-
ضرب و تقسيم با 8088
8088
براي ضرب دو عدد از دستورالعمل MUL با كد ماشين F7h استفاده ميكند .
اين دستورالعمل روي كلمه ها ( دوبايتي ها) كار ميكند . بنا براين حاصلضرب دو عدد16
بيتي ميتواند 32 بيتي يا 2 كلمه اي باشد . به همين دليل براي ذخيره نتيجه ضرب
يك ثبات تنها كافي نيست . MUL
هميشه محتواي يك ثبات را در محتواي ثبات AX ضرب كرده و حاصلضرب را در
جفت ثبات DX:AX ذخيره ميكند . به اينصورت كه دوبايت بالا را در DX و كلمه
پائين را در AX قرار ميدهد. وقتي از حساب 8088 صحبت ميكنيم ، نوشتن DX:AX به
معني نگهداري عدد 32 بيتي در آن جفت ثبات است نه محلي از حافظه كه با DX:AX
مشخص ميشود.
براي ضرب محتواي يك رجيستر در AX ( هميشه در AX ضرب ميشود) از MUL بصورت زير
استفاده ميكنيم : MUL Register
كه منظور از رجيستر يك ثبات مانند DXيا BXا است .
چون هميشه محتواي رجيستر مورد نظر در AX ضرب ميشود، نيازي به نوشتن AX نداريم .
بعنوان مثال اگر AX برابر 100hو BXو برابر 7C4Bh باشد ، حاصل MUL BX برابر 7C4B00h
ميشود و چون اين مقدار يك عدد 32 بيتي است ، در جفت ثبات DX:AX
نگهداري ميشود به شكلي كه DX=7C4Bhو AXو برابر 0000h باشد.
عمل تقسيم هم به شيوه مشابهي انجام ميشود.
وقتي كه دوعدد را به هم تقسيم ميكنيم حاصل تقسيم ( قسمت صحيح يا خارج قسمت ) در AX
و باقيمانده در DX قرار ميگيرد.
براي تقسيم كردن دو ثبات به هم ، از دستور DIV استفاده ميكنيم . وقتي از DIV براي
تقسيم استفاده ميكنيم ، CPU محتواي جفت ثبات DX:AX را بر محتواي ثبات ذكر
شده تقسيم ميكند بنا براين در هنگام استفاده از DIV فقط نام همان ثبات را ذكر
ميكنيم . مثلا اگر بخواهيم ( برعكس ضربي كه در بالا انجام داديم ) عدد 7CB400h را
بر 100h تقسيم كنيم ، BX را برابر 100h و جفت ثبات Dx:Ax را برابر 7CB4100h
قرار ميدهيم . براي اينكار عدد 7CB4100h را به دو نيمه 007Ch و 4100h تقسيم كرده
و در DXو AXو قرار ميدهيم . (/ DX:007Ch AX=4100h) . در نهايت با DIV BX عمل
تقسيم را انجام ميدهيم .
بعد از انجام تقسيم 7C4Bhدر AXر و 0000hدر DXر قرار ميگيرد.
مثال براي ضرب
براي ديدن مطالب گفته شده ، همان دو عدد 100hو 4CBhو را با ديباگ در هم ضرب
ميكنيم .
بنا براين Debug را اجرا كرده و سطرهاي زير را وارد ميكنيم .
17AA:0100 mov ax/100
17AA:0103 mov bx/4cb
17AA:0106 mul bx
17AA:0108 int 20
17AA:010A
- t
پس از وارد كردن برنامه بالا ، از دستور T ( مخفف Trace) كه براي رديابي اجراي
برنامه بكار ميرود استفاده ميكنيم . با هربار اجراي اين دستور يك خط از برنامه
اجرا شده و محتواي ثباتها بعلاوه وضعيت فلاگها نمايش داده ميشوند.
در اين سطر محتواي AX برابر 0100 است
AX=0100 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=17AA ES=17AA SS=17AA CS=17AA IP=0103 NV UP EI PL NZ NA PO NC
17AA:0103 BBCB04 MOV BX/04CB
- t
محتواي BX برابر 0$CB شده ...
AX=0100 BX=04CB CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=17AA ES=17AA SS=17AA CS=17AA IP=0106 NV UP EI PL NZ NA PO NC
17AA:0106 F7E3 MUL BX
- t
بعد از انجام ضرب ، محتواي DX برابر 4 و محتواي AX برابر CB00 شد ه . بنابراين
جفت ثبات DX:AX مقدار 4CB00 را نشان ميدهد . يعني همان عددي كه قبلا هم بدست
آورده بوديم .
AX=CB00 BX=04CB CX=0000 DX=0004 SP=FFEE BP=0000 SI=0000 DI=0000
DS=17AA ES=17AA SS=17AA CS=17AA IP=0108 OV UP EI PL NZ NA PO CY
17AA:0108 CD20 INT 20
- t
- q
تمرين :
همين عمل را براي تقسيم نيز با ديباگ انجام بدهيد
-
رويه ها (Procedures) .
در برنامه نويسي با زبانهاي سطح بالا ، با زيربرنامه ها يا پروسيجر ها آشنا شديم .
مثلا در زبان بيسيك به آنها Subroutine ميگوئيم يا در زبان پاسكال Procedure و..
رويه ها نوشت برنامه هاي واحدي را ممكن و آسان ميسازند و از تكرار كدهاي
برنامه جلوگيري ميكنند .
در زبان اسمبلي براي ساختن يك زيربرنامه از اعلان Proc در مقابل نام آن استفاده
ميكنيم . مثلا براي تعريف رويه Print آن را با Print Proc تعريف ميكنيم .
در واقع Proc يك برچسب ( از نوع Near) است كه اسمبلر توجه بيشتري نسبت به
يك برچسب عادي به آن ميكند.
وقتي يك اعلان زيرروال را تعريف كرديم ، كد اجرائي آن را مينويسيم . مثلا اگر
بخواهيم زير روال ما كاراكتر مشخص شده با AL را چاپ كند ميتوانيم با استفاده از
تابع 0Eh از وقفه 10h چنين چيزي بنويسيم :
Print Proc
Mov Ah/0Eh
Int 10h
:
:
وقتي به انتهاي كد مورد نظر رسيديم ، با يك دستور Ret به محلي از برنامه اصلي كه
زيرروال فراخواني شده بود برميگرديم .
Print Proc
Mov Ah/0Eh
Int 10h
Ret
:
و در پايان هم دوباره نام پروسيجر را به همراه يك كلمه End به معني پايان كار
زيرروال مي آوريم . در مورد مثال ما
: Print End Print Proc
Mov Ah/0Eh
Int 10h
Ret
Print End
نكته مهمي كه بايد به آن توجه كنيم اينست كه اعلان Proc مانند يك برچسب است و
در صورتي كه از روي آن با دستور JMP جهش نكنيم ، داخل پروسيجر هم اجرا خواهد شد
بنا براين هميشه بعد از Org 100h يا پروسيجر ها را نوشته و با يك فرمان JMP از
روي آنها به كداصلي پرش ميكنيم .
. MODEL SMALL
. CODE
ORG 100H
START :
JMP MAIN
پرش به | اعلان پروسيجر Print Proc
قسمت اصلي | Mov Ah/0Eh
برنامه | Int 10h Ret |
| پايان رويه Print End
:MAIN :
:
END START
پس از آن ميتوانيم با يك فرمان Call رويه مورد نظر را فراخواني كنيم .
مثلا Call print . قبل از فراخواني رويه مورد نظر بايد پارامترها و شراي اجراي آن
را فراهم كرده باشيم . مثلا در مورد رويه Print بايد هميشه ثبات AL كه حاوي كد
اسكي كاراكتر براي چاپ است ، مقدار دهي صحيح شده باشد .
مثال :
MAIN :
Mov Al/65 ;'A'
Call Print
:
END START
مثال : اين همان برنامه چاپ كاراكترهاي اسكي با كد 128 تا 255 است كه قبلا بدون
استفاده از رويه ها نوشتيم :
. MODEL SMALL
. CODE
ORG 100H ; IP points to 100h / starting offset
START
JMP MAIN ; Jump over the procedure
Print Proc ; procedure declaration
Mov Ah/0Eh
Int 10h
Ret ; procedure ends
Print End
MAIN :; main program
MOV BH/128
LOOP :_
MOV AL/BH ; AL = Ascii character code
CALL PRINT ; Call procedure
INC BH
CMP BH/255
JNP LOOP _; Jmp LOOP _if BH is below 255
INT 20H ; otherwise terminate normally
END START ; program ends according the START label
**
معمولا كد اصلي برنامه را هم بصورت يك Procedure مينويسند . مثلا در مثال بالا
بعد از برچسب Main ميتوان يك Prog proc و در انتهاي برنامه هم يك Prog End
قرار داد . اين امر تفاوتي در اجراي برنامه نخواهد داشت ولي معمولا و براي داشتن
انعطاف بيشتر در نوشتن برنامه اين كار را انجام ميدهند.
**
اگر دستور Ret را در برنامه اصلي بكار ببريم ، برنامه پايان مي يابد .
-
برنامه نويسي صفحه كليد
ميخواهيم با مطالبي كه تا حالا ياد گرفتيم ، به برنامه نويسي اجزاي PC بپردازيم
براي شروع هم ابتدا برنامه ريزي صفحه كليد را مطالعه ميكنيم .
براي استفاده از صفحه كليد معمولا از توابع داس (Int 21h) يا BIOS استفاده ميشود
با اينحال برنامه هاي حرفه اي و مخصوصا برنامه هاي رزيدنت از دسترسي مستقيم به
بافر صفحه كليد سود ميبرند . بنا براين بعد از يادگيري توابع داس و بايوس ،
روش دستيابي به بافر صفحه كليد را هم خواهيم خواند.
تابع 1 از وقفه / INT 21h دريافت كاراكتر همراه با نمايش
ورودي خروجي AH=1
=AL كد اسكلي كليد فشرده شده
كليد فشرده شده در صفحه هم به نمايش در مي آيد
اولين و اساسي ترين روش خواندن كليد ها همين تابع از بايوس است . اين تابع
كليدي را از صفحه خوانده و همزمان آن را در صفحه نير نشان ميدهد ضمن اينكه ^C
را هم تست ميكند. بعد از فراخواني وقفه كد اسكي (ASCII) كليد فشرده شده در AL
قرار ميگيرد.
بعلاوه 3 تابع ديگر ( مجموعا 4 تا ) نيز در DOS براي خواندن كاراكتر ها داريم كه
در جدول زير ميبينيد :
ECHO دارد | تست | CCtrl-C متظر ميماند | شماره تابع
1 | x | x | x
6 | | |
7 | x | |
8 | x | x |
براي فراخواني هر كدام از اين توابع ، عدد مربوط به تابع را در AH قرار داده و
وقفه 21h را اجرا ميكنيم . پس از آن كد اسكي كليد فشرده شده در AL قرار ميگيرد.
مثلا : MOV AH/01
INT 21h
اگر پس از خواندن كليد ، عدد 0در ALر قرار بگيرد ، يعني يكي از كليد هاي گسترش
يافته مثل SHitيا Altا و .. فشرده شده است . وقتي اين امر صورت بگيرد 2 بايت
كه محتوي يك 0 و كد اسكي كليد است در بافر صفحه كليد قرار ميگيرد . بنا براين
براي خارج كردن بايت دوم از بافر صفحه كليد ، يك بار ديگر همان تابع را اجرا
ميكنيم :
MOV AH/1 ; Read Key with echo / ^C sensitivity
INT 21H ; Call int 21h
CMP AL/0 ; Enhanced key pressed?
JNZ CONT ; No! jump to CONTinue
INT 21H ; otherwise read the 2nd byte
CONT :
مثال : ميخواهيم برنامه بنويسيم كه يك رشته كاراكتري را گرفته و چاپ كند.
در اين برنامه GETKEY يك پروسيجر است . اين رويه كاراكتري را با استفاهده از
سرويس 1 داس ميخواند و كد آن را در AL قرار ميدهد . وقتي كه كاراكتري را به اين
صورت خوانديم ، كد آن را به انتهاي رشته Str اضافه ميكنيم . سپس تست ميكنيم
كه آيا كليد فشرده شده برابر 13 يا همان Enter است ? اگر اينچني باشد ، از حلقه
خارج شده و با استفاده از سرويس 9 مربوط به Int 21h رشته دريافت شده را چاپ
ميكنيم .( قبل از آن يك كاراكتر $ با انتهاي رشته كاراكتري اضافه كرده ايم )
ENTER EQU 13
.MODEL SMALL
.CODE
ORG 100H
START:
JMP MAIN
GETKEY PROC ; getkey reads a key from keyboard
MOV AH/1
INT 21H
RET
GETKEY ENDP
PUTCHAR PROC ; displays a character
MOV AH/0EH
INT 10H
RET
PUTCHAR ENDP
STR DB 255 DUP(0) ; Buffer
MAIN:
LEA BX/STR ; 1st CHARACTER
LOOP:_
CALL GETKEY
MOV [BX]/AL
INC BX
CMP AL/ENTER ; if Enter pressed then jump over the loop
JNZ LOOP_
MOV AL/'$' ; END OF STRING
MOV [BX]/AL
LEA DX/STR ; get the Str address
MOV AL/10 ; CR
CALL PUTCHAR
MOV AL/13 ;LF
CALL PUTCHAR
MOV AH/9 ; Display Str
-
بافر صفحه كليد و بافرحلقوي
صفحه كليد از طريف وقفه سخت افزاري 9h كنترل ميشود . وقتي كه كليدي فشرده شده يا
رها شود اين وقفه فراخواني خواهد شد.
صفحه كليد هم مانند بسياري از وسايل جانبي مثل ديسك درايو و ... از طريق پورت
مخصوص به خودش با CPU مرتبط ميشود. وقتي كه كليدي فشرده ميشود پردازنده مخصوص
صفحه كليد كه يك تراشه 8048 است كدي موسوم به كد اسكن توليد كرده و در پورت 60h
قرار ميدهد. پس از آن وقفه 9h اين مقدار را از پورت 60h خوانده و در بافر صفحه
كليد قرار ميدهد . با فرصفحه كليد در ناحيه داده هاي بايوس يعني سگمنت شماره 40h
قرار دارد.
بافر حلقوي ----------
بافر صفحه كليد يك ناحيه 16 كلمه اي (32 بايتي ) در ناحيه داده هاي
كاراكتر بعدي از آنجا خوانده خواهد شد . كلمه ديگري بنام Tile هم به موقعيتي
اشاره ميكند كه كاراكتر بعدي كه تايپ ميشود در آن محل قرار ميگيرد.
وقتي يك كاراكتر تايپ ميشود head حركت ميكند و وقتي يك كاراكتر وارد ميشود Tile
يك خانه به جلو ميرود و وقتي هركدام به انتها رسيد به جاي اولش برميگردد. به
شكلي كه هميشه head در تعقيب tile است و وقتي بافر خالي باشد به آندو برهم منطبق
ميشوند . به خاطر اين شكل عملكرد بافر به آن بافرخلقوي ميگوئيم .
دستيابي به ناحيه داده ها و بافر صفحه كليد -----------------------------------------
براي دستيابي به يك سگمنت بايد دو موضوع جديد را ياد بگيريم . يكي تعريف يك
ناحيه بعنوان يك سگمنت و ديگري منطبق كردن يك ناحيه تعريف شده بر يك سگمنت حافظه
براي تعريف يك سگمنت از كلمه Segment استفاده ميكنيم . توجه داريم كه اين تعريف
حتما بايد قبل از code. باشد.
Bios_Data SEGMENT
:
:
Bios_Data Ends
به اين ترتيب يك ناحيه داده اي جديد به اسم Bios_data تعريف كرديم .
براي اينكه اين ناحيه را بر سگمنت 40h يا همان ناحيه داده هاي بايوس منطبق كنيم
راهنماي AT را هم به انتهاي تعريف سگمنت اضافه ميكنيم . Bios_Data SEGMENT AT 40H
:
Bios_Data Ends
حالا در داخل سگمنت تعريفي چند متغير را در آدرسهاي مخصوص تعريف ميكنيم . چون
بايتهاي مربوط به صفحه كليد در آفست 1Ah قرار دارند يك org 1Ah در نظر گرفته و
متغيرها را تعريف ميكنيم .
Bios_Data SEGMENT AT 40H
ORG 100H
HEAD DW ?
TAIL DW ?
BUFFER DW 10 DUP(?)
Bios_Data Ends
org 1ah
به اسمبلر ميگويد كه همه متغيرهائي كه در زير تعريف ميشوند را منطبق
بر آدرس 1ah قرار بدهد . اين كار باعث ميشود كه هميشه متغير head حاوي دوبايت 1ah
و 1bhو باشد.
ون هميشه مقدار آدرسها نسبت به محتواي DS سنجيده ميشوند ( مثلا mov al/[bx] با mov al/ds:[bx]
برابر است ) بايد به اسمبلر بگوئيم كه داده ها در كجا هستند.
براي اين منظور از ASSUME استفاده ميكنيم . به اين صورت
Bios_Data SEGMENT AT 40H
ORG 100H
HEAD DW ?
BUFFER DW 10 DUP(?)
Bios_Data Ends
. CODE
ORG 100H
START:
ASSUME DS:BIOS_DATA
بعد آدرس ناحيه Bios_data را به DS لود ميكنيم . ( با استفاده از رجيستر واسطه ) MOV BX/BIOS_DATA
MOV DS/BX
حال تست ميكنيم كه اگر headو tail برهم منطبق باشند يعني بافر خالي است و
شروع به خواندن كليد ها ميكنيم اما اگر برهم منطبق نباشند محتواي بافر را خوانده
و آن را خالي ميكنيم .
GETCHAR:
MOV BX/HEAD
CMP BX/TAIL
JE GETCHAR
واگر بافر خالي نبود BX محل آخرين كاراكتر داخل بافر را نشان ميدهد . پس محتواي
آن قسمت را خوانده و سپس Tail را هم به همان آدرس تنظيم ميكنيم كه Tailو Headو
بر هم منطبق شوند . ( يعني بافر خالي بشود) MOV DX/[BX]
و ميتوانيم تست كنيم كه اگر كليد خوانده شده Q ( به معني Quit) بود از برنامه
خارج شويم و در غير اينصورت يك كليد ديگر را بخوانيم و در صفحه چاپ كنيم .
آيا كاراكتر Q است ? ; CMP DDL/'Q' JE BYE ;
آيا كاراكتر q است ? ; CMP DL/'q' JE BYE ;
تابع 2 براي خواندن كليد ; .MOV AH/2 ; READ A CHAR
وقفه داس ; INT 21H
پرش به روتين برنامه ; JMP GETCHAR BYE :INT 20H
برچسب برای این موضوع
مجوز های ارسال و ویرایش
- شما نمی توانید موضوع جدید ارسال کنید
- شما نمی توانید به پست ها پاسخ دهید
- شما strong>نمی توانید فایل پیوست ضمیمه کنید
- شما نمی توانید پست های خود را ویرایش کنید
-
قوانین انجمن