การโปรแกรมภาษาแอสเซมบลีนอกจากต้องศึกษาถึงคำสั่งในบทแรกเราได้ศึกษามาแล้วว่าเฉพาะตัวเลขฐานสองเท่านั้นที่ใช้ได้กับการโปรแกรมภาษาเครื่อง แต่เมื่อใช้ภาษาแอสเซมบลี ผู้เขียนโปรแกรมสามารถใช้ระบบเลขฐานอื่นเพื่อความสะดวกในการเขียนโปรแกรมยิ่งขึ้น เช่น ใช้เลขฐานสิบ หรือเลขฐานสิบหก เป็นต้น นอกไปจากนี้ภาษาแอสเซมบลียังเอื้ออำอำนวยต่อกำหนดแบบข้อมูลเบื้องต้นสำหรับใช้ในตัวโปรแกรม ด้วยเหตุนี้ผู้เขียนโปรแกรมจึงจำเป็นต้องทำความเข้าใจถึงระบบเลขฐานและวิธีการ แทนข้อมูลตามแบบที่ภาษาแอสเซมบลีกำหนดซึ่งประกอบไปด้วยเนื้อหาหลักดังนี้
ระบบเลขฐาน
การแทนเลขบวกและลบ
ค่าบูลีน
เลขรหัสไบนารีฐานสิบ
เลขทศนิยม
อักขระและสายอักขระ
การใช้รีจิสเตอร์
คำสั่งในภาษาแอสเซมบลีแทบทุกคำสั่งจะใช้รีจิสเตอร์เป็นตัวเก็บข้อมูลที่ต้องประมวลผล ในที่นี้เราจะศึกษาถึงการใช้งานรีจิสเตอร์เบื้องต้น 4 ตัว ได้แก่ AX, BX, CX และ DX ตัวอย่างคำสั่งคำนวณเบื้องต้นต่อไปนี้เป็นคำสั่งกำหนดให้รีจิสเตอร์ AX เท่ากับ 5 และบวกค่าเพิ่มอีก 1 ให้ AX เท่ากับ 6
mov ax,5
add ax,1
คำสั่ง MOV สามารถใช้เคลื่อนย้ายข้อมูลระหว่างรีจิสเตอร์ด้วยกันได้ ตัวอย่างเช่น หากให้ AX มีค่าเท่ากับ 6 คำสั่งต่อไปนี้จะทำให้รีจิสเตอร์ BX, CX และ DX มีค่าเท่ากับ 6 ด้วยเช่นกัน
mov ax,bx
mov cx,bx
mov dx,ax
8086 มีคำสั่ง INC ใช้เพิ่มค่ารีจิสเตอร์ครั้งละหนึ่ง แทนการบวกค่าด้วยคำสั่ง ADD คำสั่งต่อไปนี้จะเพิ่มค่าให้รีจิสเตอร์ DX ขึ้นอีกหนึ่ง
inc dx
8086 ยังมีคำสั่งคำนวณอื่นเกี่ยวกับการลบและการคูณ เช่น ชุดคำสั่งต่อไปนี้
mov ax,6
mov bx,5
sub ax,2
mul bx
คำสั่งข้างต้นทำหน้าที่คำนวณนิพจน์ (6-2)*5 คำสั่ง MUL จะนำค่า AX คูณกับ BX ได้ 14H ผลลัพธ์การคูณเลข 16 บิต จะได้เลขขนาด 32 บิตเสมอ ค่าที่ได้จากคำสั่งคูณข้างต้นคือ 00000014Hตัวเลขขนาด 32 บิตนี้จะถูกแบ่งเก็บไว้ในรีจิสเตอร์ DX และ AX อย่างอัตโนมัติ โดย DX เก็บค่า 0000H และ AX เก็บค่า 0014H แต่ละคำสั่งจะทำให้รีจิสเตอร์แต่ละตัวเปลี่ยนแปลงไปดังนี้
รีจิสเตอร์
AX
BX
CX
DX
ค่าเริ่มต้น
?
?
?
?
mov ax,6
6
?
?
?
mov bx,5
6
5
?
?
sub ax,2
4
5
?
?
mul bx
14H
5
?
0
การใช้รีจิสเตอร์ 8 บิต และ 16 บิต
การเคลื่อนย้ายข้อมูลระหว่างรีจิสเตอร์จะต้องเกิดขึ้นกับรีจิสเตอร์ที่มีขนาดเท่ากันเสมอ ตัวอย่างเช่นการเคลื่อนย้ายข้อมูลแบบ 8 บิตและ 16 บิต ดังตัวอย่าง
mov al,bl
mov dh,cl
mov ax,bx
mov cx,dx
แต่คำสั่งต่อไปนี้ไม่อนุญาตให้มีได้ในโปรแกรม
mov al,dx
mov cx,bh
รีจิสเตอร์ย่อย 8 บิต ถือว่าเป็นส่วนหนึ่งของรีจิสเตอร์ 16 บิตด้วย การเปลี่ยนแปลงค่าในรีจิสเตอร์ 8 บิตย่อมกระทบถึงรีจิสเตอร์ 16 บิต โดยตรง ตัวอย่างเช่นคำสั่ง
mov ch,32h
mov cx,152eh
inc ch
จะทำให้ค่าในรีสเตอร์ CH เท่ากับ 16H และ CX เท่ากับ 162EH ตามขั้นตอนต่อไปนี้
CH
CL
CX
??
??
????
mov ch,32h
32h
??
32??
mov cx,152eh
15h
2eh
152eh
inc ch
16h
2eh
162eh
คำสั่งเกี่ยวกับรีจิสเตอร์ 8 บิต อาจทำให้เกิดความคลุมเครือระหว่างชื่อรีจิสเตอร์และตัวเลขฐานสิบหก ดังเช่น
mov cl,ah
อาจหมายถึงการนำค่าจากรีจิสเตอร์ AH ไปไว้ที่ CL หรือ อาจหมายถึงค่า 0AH ไปไว้ที่ CL
เพื่อป้องกันความสับสนนี้ แอสเซมเบลอร์กำหนดให้ตัวเลขฐานสิบหกที่ขึ้นต้นด้วย A ถึง F จะต้องมีเลข 0 นำหน้าเสมอ เช่นค่า AH จะต้องเขียนแทนด้วย 0AH หรือค่า E12DH ก็ต้องเขียนเป็น0E12DH
การอ้างอิงหน่วยความจำ
คำสั่งใน 80x86 มีรูปแบบอ้างอิงกับหน่วยความจำได้หลายแบบ และอาจเป็นการอ้างอิงกับข้อมูล 8 บิต หรือ 16 บิต ก็ได้ตัวอย่างเช่นคำสั่งเคลื่อนย้ายข้อมูล
mov bl, [20h]
เป็นคำสั่งนำค่าจากหน่วยความจำตำแหน่ง 20h มาไว้ในรีจิสเตอร์ bl และคำสั่ง
mov ax, [20h]
เป็นคำสั่งนำค่าจากหน่วยความจำตำแหน่ง 20h และ 21h มาไว้ในรีจิสเตอร์ ax เครื่องหมายวงเล็บเหลี่ยมหมายถึงการนำค่าจากหน่วยความจำตำแหน่งที่กำหนด ความหมายของ [20h] จึงเท่ากับค่าในแอดเดรส 20h สำหรับการเคลื่อนย้ายข้อมูลจากรีจิสเตอร์ไปสู่หน่วยความจำจะสลับตำแหน่งของโอเปอร์แรนด์ดังเช่นคำสั่ง
mov [20h],ax
หมายถึงการนำค่าจากรีจิสเตอร์ ax ไปเก็บไว้ที่หน่วยความจำตำแหน่ง 20h หากรีจิสเตอร์ ax มีค่า 2e3fh คำสั่งดังกล่าวจะทำให้ตำแหน่ง 20h มีค่า 3fh และตำแหน่ง 21h มีค่าเป็น 2eh ทั้งนี้เป็นไปตามหลักของตำแหน่งนัยสำคัญมากสุดและน้อยที่สุดดังรูปที่ 4.1
จากหลักการเรื่องเซกเมนต์ของซีพียูตระกูล 8086 หน่วยความจำที่อ้างอิงอยู่ในรูปของวงเล็บก็คือค่าออฟเซ็ตของเซกเมนต์ข้อมูล อย่างเช่นคำสั่ง
mov al,[100h]
ซึ่งเป็นการอ้างอิงถึงออฟเซตที่ 100h ของเซกเมนต์ข้อมูลที่ชี้อยู่ด้วยรีจิสเตอร์ ds ค่าแอดเดรสสัมบูรณ์ขนาด 20 บิต ที่ซีพียูติดต่อกับหน่วยความจำ จะหาได้จากหลักการที่ได้กล่าวมาไว้ในบทที่แล้ว
mov ax,[20h]
ah
al
ออฟเซ็ต
ax
3fh
2eh
2eh
20h
3fh
21h
mov [20h],ax
ah
al
ออฟเซ็ต
ax
3fh
2eh
2eh
20h
3fh
21h
รูปที่ 4.1 การเคลื่อนย้ายข้อมูลระหว่างรีจิสเตอร์กับหน่วยความจำแบบ 16 บิต
คุณสมบัติพิเศษของรีจิสเตอร์ BX
รีจิสเตอร์ bx เป็นรีจิสเตอร์ตัวหนึ่งที่มีคุณสมบัติพิเศษในการใช้อ้างอิงข้อมูลกับหน่วยความจำ เนื่องจากเราสามารถใช้ค่าของรีจิสเตอร์เป็นตัวชี้ไปยังหน่วยความจำที่ต้องการติดต่อด้วยแทนการกำหนดด้วยตัวเลขหน่วยความจำตรงๆ ดังเช่น
mov al,[bx]
หมายถึงให้นำค่าจากหน่วยความจำที่ตำแหน่งซึ่งกำนหดด้วย bx มาไว้ใน al หรือเรียกว่า นำค่าจากตำแหน่งที่ชี้ด้วย bx มาไว้ใน al
ในกลุ่มของรีจิสเตอร์เอนกประสงค์ 4 ตัว คือ ax, bx, cx และ dx นั้น มีเพียงเฉพาะรีจิสเตอร์ bx เพียงตัวเดียวเท่านั้นที่ใช้การอ้างอิงนี้ได้ การใช้รีจิสเตอร์อื่นเช่น cx ด้วยคำสั่ง
mov al,[cx]
ถือว่าผิดรูปแบบ หากแอสเซมเบลอร์ตรวจพบก็จะแสดงข้อผิดพลาดออกมา
การใช้รีจิสเตอร์ bx ตามรูปแบบข้างต้นมีประโยชน์อย่างยิ่งต่อการเขียนโปรแกรมที่ต้องอ้างอิงกับกลุ่มหน่วยความจำอย่างต่อเนื่องทีละไบต์หรือทีละเวิร์ด โปรแกรมประเภทนี้มักเป็นโปรแกรมที่ทำงานเป็นวงรอบและใช้คำสั่งกระโดดแบบตัดสินใจอย่างเช่นคำสั่ง jnz (กระโดดเมื่อผลลัพธ์คำนวณ ล่าสุดไม่เป็นศูนย์) ตามโปรแกรมตัวอย่างต่อไปนี้
mov bx,500h
mov cl,50h
next_inc: mov al,[bx]
inc al
mov [bx],al
dec cl
jnz next_inc
เมื่อเสร็จสิ้นการทำงานของโปรแกรมข้างต้น ค่าในตำแหน่ง 500h ถึง 54fh จะเพิ่มค่าตำแหน่งละหนึ่ง
การจัดสรรเนื้อที่หน่วยความจำ
ผู้เขียนโปรแกรมภาษาแอสเซมบลี ต้องจัดสรรเนื้อที่หน่วยความจำสำหรับใช้เป็นที่เก็บข้อมูลสำหรับใช้เป็นตัวแปรหรือค่าคงตัว มาโครแอสเซมเบลอร์มีคำสั่งเทียมสำหรับใช้จัดสรรเนื้อที่หน่วยความจำรวม 5 คำสั่งคือ
db (define byte)
dw (define word)
dd (define doubleword)
dq (define quadword)
dt (define tenbytes)
แต่ละคำสั่งจะกำหนดขนาดหน่วยความจำแตกต่างกันไปเรียงจาก 1, 2, 4, 8 และ 10 ไบต์ ตามลำดับ ตามตัวอย่าง
db 2ah ; จัดสรรเนื้อที่ 1 ไบต์ ให้เก็บค่า 2ah
db ? ; จัดสรรเนื้อที่ 1 ไบต์ โดยไม่กำหนดค่า
db 's' ; จัดสรรเนื้อที่ 1 ไบต์ เก็บรหัสแอสกีของ 's'
dw 0a3ch ; จัดสรรเนื้อที่ 2 ไบต์ เก็บค่า 0a3ch
dd 1a2b3c4dh ; จัดสรรเนื้อที่ 4 ไบต์ เก็บค่า 1a2b3c4dh
dq 05h ; จัดสรรเนื้อที่ 8 ไบต์ เก็บค่า 05h
dt ? ; จัดสรรเนื้อที่ 10 ไบต์ โดยไม่กำหนดค่า
ผู้เขียนโปรแกรมสามารถใช้คำสั่งเทียมกำหนดข้อมูลในหน่วยความจำแบบต่อเนื่องกันไปโดยใช้เครื่องหมายจุลภาคคั่นระหว่างข้อมูลแต่ละตัวดังเช่น
db 35h, 7ah, 'a', 'z'
dw 0135h, 0a1a1h
คำสั่งข้างให้ผลการจัดสรรเนื้อที่เช่นเดียวกับการแยกใช้คำสั่งเทียมหนึ่งคำสั่งต่อหนึ่งบรรทัดดังต่อไปนี้
db 35h
db 7ah
db 'a'
db 'z'
dw 0135h
dw 0a1a1h
การจัดสรรเนื้อที่หน่วยความจำให้กับสายอักขระ นิยมใช้เครื่องหมายคำพูดกำกับส่วนต้นและส่วนท้ายสายอักขระดังเช่น
db 'Assembly language'
คำสั่งเทียม dup
แอสเซมเบลอร์ยังมีคำสั่งเทียม dup (duplicate) สำหรับใช้ร่วมกับคำสั่ง db,dw, ฯลฯ เพื่อจัดสรรข้อมูลเป็นกลุ่มซ้ำค่ากันเช่น
db 8h dup (10h) ; จัดสรรเนื้อที่ 8 ไบต์ ให้ทุกไบต์มีค่า 10H
db 20h dup (?) ; จัดสรรเนื้อที่ 32 ไบต์ โดยไม่กำหนดค่า
db 0ffh dup ('a') ; จัดสรรเนื้อที่ 256 ไบต์ ให้ทุกไบต์มีค่า 'a'
dw 20h dup (0) ; จัดสรรเนื้อที่ 32 เวิร์ด ให้ทุกเวิร์ดมีค่าเป็นศูนย์
ตัวแปร
ภาษาแอสเซมบลีมีวิธีกำหนดตัวแปรด้วยคำสั่งเทียมจัดสรรหน่วยความจำ และใช้ชื่อเลเบลเป็นชื่อประจำตัวตัวแปรนั้น ตัวอย่างเช่นการกำหนดตัวแปรดังต่อไปนี้
factor dw 0050h
accept db 'y'
reject db 'n'
เมื่อต้องการใช้ตัวแปรใดก็อ้างด้วยชื่อเลเบลใด เช่นคำสั่งอ่านค่าในตัวแปร factor มาเก็บไว้ในรีจิสเตอร์ 16 บิต ตามขนาดของตัวแปรให้ใช้คำสั่ง
mov ax,factor
หรือเปลี่ยนค่าตัวแปร factor ไปเป็น 3100h ด้วยคำสั่ง
mov bx,3100h
mov factor,bx
การอ้างอิงกับตัวแปร accept และ reject ซึ่งเก็บอักขระ 8 บิต ก็ให้ใช้รีจิสเตอร์ขนาด8 บิต ด้วย ดังเช่นคำสั่ง
mov al,accept
mov bl,reject
แอสเซมเบลอร์จะทำหน้าที่คำนวณค่าออฟเซ็ตประจำตัวแปรนั้นแล้วแปลงชื่อตัวแปรไปเป็นตัวเลขเมื่อเราสั่งแปลโปรแกรม ตัวอย่างเช่นหากว่าตัวแปร factor อยู่ที่ตำแหน่ง 3000h:5100h แอสเซมเบลอร์จะแปลงคำสั่ง
mov ax,factor
ไปเป็น
mov ax,[5100h]
ออฟเซ็ต
แอสเซมเบลอร์มีคำสั่งเทียม offset สำหรับใช้อ้างอิงถึงค่าออฟเซ็ตของตัวแปรในโปรแกรม ขอให้พิจารณาถึงตัวแปร factor ที่ได้อธิบายผ่านมา หากต้องการค่าออฟเซ็ตของ factor ให้ใช้ คำสั่งเทียม offset นำหน้าชื่อตัวแปรดังนี้
mov ax, offset factor
แอสเซมเบลอร์จะแปลงคำสั่งข้างต้นเป็น
mov ax, 5100h
หรือนำค่าออฟเซ็ตของ factor มาใช้
การกำหนดเซกเมนต์
ในบทที่ 3 เราได้ศึกษาถึงหลักการของเซกเมนต์ในซีพียู 8086 มาแล้ว ในหัวข้อนี้จะได้กล่าวถึงวิธีการกำหนดเซกเมนต์ขึ้นใช้ในตัวโปรแกรม
ผู้เขียนโปรแกรมภาษาแอสเซมบลีควรจัดให้มีเซกเมนต์ในโปรแกรม 3 เซกเมนต์เสมอได้แก่ เซก เมนต์คำสั่ง เซกเมนต์ข้อมูล และ เซกเมนต์สแต็ก
เซกเมนต์แสต็ก
เซกเมนต์ที่ทำหน้าที่เป็นสแต็ก ผู้เขียนโปรแกรมจะต้องบ่งบอกอย่างเฉพาะเจาะจงให้แอสเซม เบลอร์ทราบว่าเป็นเซกเมนต์สแต็ก โดยใช้คำสั่งเทียม .stack ดังตัวอย่างต่อไปนี้
.stack 100h dup(?)
การกำหนดรีจิสเตอร์ DS
โปรแกรมทุกโปรแกรมที่มีเซกเมนต์แยกต่างหากออกจากเซกเมนต์คำสั่ง ผู้เขียนโปรแกรมจะต้องเขียนคำสั่งกำหนดค่ารีจิสเตอร์ ds ให้ชี้ไปที่ต้นเซกเมนต์ข้อมูลก่อนคำสั่งอื่นใด การกำหนดค่าให้กับรีจิส เตอร์เซกเมนต์ใดๆ จะต้องอาศัยรีจิสเตอร์ 16 บิต ตัวอื่นเป็นตัวช่วยเสมอ เนื่องจาก 8086 ไม่มีคำสั่งกำหนดค่าตัวเลขโดยตรงให้กับรีจิสเตอร์เซกเมนต์ ในกรณีเช่นนี้เราอาจเลือกรีจิสเตอร์ ax และใช้คำสั่งดังต่อไปนี้
mov ax,@data
mov ds,ax
คำสั่งจบโปรแกรม
แอสเซมเบลอร์จะทำหน้าที่แปลโปรแกรมจนกว่าจะพบคำสั่งเทียม end ผู้เขียนโปรแกรมจะต้องบอกให้แอสเซมเบลอร์ทราบว่าคำสั่งแรกสุดที่โปรแกรมจะต้องทำงานอยู่ที่ตำแหน่งใด โดยการใส่เลเบลประจำคำสั่งนั้นตามหลังคำสั่งเทียม end
เนื่องจากเราเขียนโปรแกรมโดยแยกเซกเมนต์ขอมูลออกจากเซกเมนต์คำสั่ง และต้องกำหนดค่ารีจิสเตอร์ ds เป็นอันดับแรก เลเบลประจำคำสั่งแรกจึงอยู่ที่คำสั่ง mov ax,@data ขอให้ย้อนกลับไปพิจารณาเลเบล start จากตัวอย่างโปรแกรมใบบทที่ 1 ซึ่งตัดตอนมาดังนี้
:
:
.code
start: mov ax,@data
mov ds,ax
:
:
end start
ในการเขียนโปรแกรมที่มีโมดูลเดียวไม่จำเป็นต้องใส่เลเบลประจำคำสั่งแรกต่อท้าย end แต่ในการพัฒนาโปรแกรมขนาดใหญ่และต้องจัดแบ่งโปรแกรมออกเป็นโมดูลย่อยหลาย ๆ โมดูล (มีแฟ้ม .asm หลาย ๆ แฟ้ม) แอสเซมเบลอร์ย่อมไม่ทราบว่าโมดูลใดเป็นโมดูลหลักที่บรรจุคำสั่งเริ่มต้นทำงาน นอกเสียจากว่าผู้เขียนโปรแกรมจะบ่งบอกด้วยการใช้เลเบลต่อท้ายคำสั่ง end ตามวิธีข้างต้น
การจัดลำดับเซกเมนต์
มาโครแอสเซมเบลอร์ masm ให้อิสระต่อผู้เขียนโปรแกรมในการจัดวางลำดับเซกเมนต์ และแอสเซมเบลอร์ปรับรีจิสเตอร์ ip ให้ชี้ไปที่เลเบลตามที่กำหนดหลังคำสั่งเทียม end ตัวอย่างการจัดลำดับเซกเมนต์ในหนังสือเล่มนี้ใช้ลำดับของเซกเมนต์โดยขึ้นต้นด้วยเซกเมนต์สแต็ก, เซกเมนต์ข้อมูล และปิดท้ายด้วยเซกเมนต์คำสั่งดังรูปที่ 4.2
รูปที่ 4.2 นี้สามารถใช้เป็นต้นแบบสำหรับเขียนโปรแกรมภาษาแอสเซมบลีเบื้องต้นได้ต่อไป เพื่อประหยัดเวลาการสร้างโปรแกรม
;--------------------------------------
; upcase.asm
; read string from keyboard and change
; to upper case (undetect errors)
;--------------------------------------
;
.modell small
;
;reserved space for satck
.stack 100h
.data
;define data here
.code
; init data segment
start: mov ax,@data
mov ds,ax
; start of code
; return to dos with errorlevel 0
mov ax,4c00h
int 21h
end start
รูปที่ 4.2 โปรแกรมต้นแบบ แสดงการจัดวางเซกเมนต์
ดอสฟังก์ชัน
เอ็มเอสดอสมีฟังก์ชันบริการงานหลายอย่างที่เกี่ยวข้องกับอุปกรณ์อินพุตเอาต์พุตของไมโครคอมพิวเตอร์ ตัวอย่างฟังก์ชันของดอสได้แก่ฟังก์ชันการอ่านปุ่มกดแป้นพิมพ์ ฟังก์ชันแสดงอักขระบนจอภาพ และฟังก์ชันอ่านหรือเขียนดิสค์เป็นต้น ฟังก์ชันเหล่านี้ช่วยลดงานของผู้เขียนโปรแกรมได้มาก เนื่องจากผู้เขียนโปรแกรมเพียงแต่ศึกษาวิธีการเรียกใช้ฟังก์ชันโดยไม่ต้องคำนึงถึงรายละเอียดการทำงานภายในซึ่งมักจะมีการเชื่อมต่อไปยังฮาร์ดแวร์ของเครื่อง การเรียกใช้ฟังก์ชันของดอสใช้คำสั่ง int (interrupt) ซึ่งเป็นคำสั่งที่ทำให้เกิดการกระโดดไปยังทำงานในบริเวณโปรแกรมที่เป็นส่วนของเอ็มเอสดอส
ซีพียู 8086 ได้รับการออกแบบให้มีอินเตอร์รัพต์แตกต่างกันถึง 256 แบบ แต่ละแบบกำหนดด้วยหมายเลข 00h ถึง ffh เอ็มเอสดอสจะใช้หมายเลข 21h เป็นอินเทอร์รัพต์ประจำตัว และภายในอินเทอร์รัพต์ 21h ของดอสจะบรรจุฟังก์ชันย่อย ๆ มากมายดังที่ยกตัวอย่างไว้ข้างต้น
แต่ละฟังก์ชันของอินเทอร์รัพต์ 21h จะมีหมายเลขประจำตัวเพื่อกำหนดการเรียกใช้ ผู้เขียนโปรแกรมจะต้องใส่หมายเลขประจำฟังก์ชันที่ต้องการไว้ในรีจิสเตอร์ ax ก่อนการใช้ คำสั่ง int 21h ตารางที่ 4.1 แสดงถึงฟังก์ชันสำคัญจำนวนหนึ่งของดอส
ในกลุ่มฟังก์ชันตามตารางที่ 4.1 ฟังก์ชันที่ใช้งานเป็นประจำได้แก่ฟังก์ชัน 1 (อ่านแป้นพิมพ์) ฟังก์ชัน 2 (แสดงอักขระบนจอภาพ) ฟังก์ชัน 9 (แสดงสายอักขระบนจอภาพ) และฟังก์ชัน 0AH (อ่านสายอักขระจากแป้นพิมพ์) หัวข้อถัดไปจะอธิบายถึงการใช้ฟังก์ชันเหล่านี้โดยละเอียด
ตารางที่ 4.1 ตัวอย่างฟังก์ชันสำคัญของดอส
ฟังก์ชัน
ฟังก์ชันการทำงาน
รายละเอียด
01h
อ่านแป้นพิมพ์
รอจนกระทั่งมีการพิมพ์อักขระหนึ่งตัวจากแป้นพิมพ์ และแสดงอักขระบนจอภาพรหัสแอสกีของอักขระนั้นจะเก็บอยู่ในรีจิสเตอร์ al ปุ่ม Ctrl-Break เป็นปุ่มยกเลิกการทำงานของฟังก์ชัน
02h
แสดงอักขระทางจอภาพ
นำรหัสแอสกีของอักขระจากรีจิสเตอร์ DL ไปแสดงผลทางจอภาพที่ตำแหน่งเคอร์เซอร์ปัจจุบัน
05h
พิมพ์อักขระทางเครื่องพิมพ์
นำรหัสแอสกีของอักขระซึ่งเก็บอยู่ในรีจิสเตอร์ DL ไปพิมพ์ออกทางเครื่องพิมพ์
07h
อ่านแป้นพิมพ์โดยไม่แสดงอักขระ
รอรับอักขระจากแป้นพิมพ์ และเก็บไว้ในรีจิสเตอร์ AL โดยไม่แสดงอักขระนั้น ฟังก์ชันนี้ไม่รับปุ่ม Ctrl-Break เพื่อหยุดการทำงาน
08h
อ่านแป้นพิมพ์โดยไม่แสดงอักขระ
เช่นเดียวกับฟังก์ชันหมายเลข 7 หากแต่หยุดการทำงานของได้ด้วย ปุ่ม Ctrl-Break
09h
แสดงสายอักขระ
แสดงสายอักขระบนจอภาพ โดยใช้รีจิสเตอร์ ds:dx เป็นตัวชี้ตำแหน่งเริ่มต้นของสายอักขระ ฟังก์ชันจะอ่านสายอักขระมาพิมพ์จนกว่าจะพบรหัส $ แต่จะไม่พิมพ์รหัส $ บนจอภาพ
0ah
อ่านสายอักขระ
อ่านสายอักขระจากแป้นพิมพ์และแสดงผลบนจอภาพจนกว่าจะกดปุ่ม Enter
4ch
กลับสู่ดอส
จบการทำงานของโปรแกรมและกลับสู่ดอส ค่าในรีจิสเตอร์ AL จะใช้เก็บรหัสการจบโปรแกรมซึ่งสามารถตรวจสอบได้ด้วยคำสั่ง if errorlevel ของ ดอส
ฟังก์ชัน 01h
เข้า: ah = 01h
ออก: al = รหัสแอสกี
หมายเหตุ: การทำงานของฟังก์ชันจะไม่เปลี่ยนแปลงค่าในรีจิสเตอร์ ah,bx,cx,dx,si,di, bp,cs,ds,es,ss และ แฟล็ก
ตัวอย่าง: ตามหลักการใช้ฟังก์ชันของดอสที่กล่าวไปแล้วว่า ให้กำหนดหมายเลขฟังก์ชันไว้ในรีจิสเตอร์ ax และใช้คำสั่ง int 21h การใช้ฟังก์ชัน 1 เพื่ออ่านการกดแป้นพิมพ์จึงมีเพียง 2 คำสั่งดังนี้
mov ah,1
int 21h
ฟังก์ชัน 01h จะรอการกดอักขระจากแป้นพิมพ์หนึ่งตัว แล้วแสดงอักขระนั้นบนจอภาพและจะนำรหัสแอสกีของอักขระนั้นไปไว้ที่รีจิสเตอร์ al
รหัส cr จะทำให้เคอร์เซอร์เลื่อนไปอยู่ซ้ายสุดหรือคอลัมน์แรกของบรรทัด ส่วนรหัส lf จะทำให้เคอร์เซอร์เลื่อนลงมาหนึ่งบรรทัด รวมกันเท่ากับการขึ้นบรรทัดใหม่ ชุดคำสั่งต่อไปนี้แสดงการพิมพ์ข้อความและขึ้นบรรทัดใหม่โดยใช้รหัสทั้งสอง
mov ah,2
mov dl,'o'
int 21h
mov dl,'h'
int 21h
mov dl,0dh
int 21h
mov dl, 0ah
int 21h
ฟังก์ชัน 02h
เข้า: ah = 02h
dl = รหัสแอสกีที่จะแสดงผล
ออก: ไม่มี
หมายเหตุ: การทำงานของฟังก์ชันจะไม่เปลี่ยนแปลงค่าในรีจิสเตอร์ใด
ตัวอย่าง: ฟังก์ชัน 2 กำหนดให้ใส่รหัสแอสกีของอักขระที่ต้องการแสดงผลไว้ในรีจิสเตอร์ dl หากต้องการแสดงอักขระ 'A' ซึ่งมีรหัสแอสกีเท่ากับ 41h ต้องใช้คำสั่งดังนี้
mov ah,2
mov dl,41h
int 21h
ฟังก์ชัน 09h
เข้า: ah = 09h
ds = เซกเมนต์ของหน่วยความจำที่เก็บสายอักขระ
dx = ค่าออฟเซ็ตของหน่วยความจำที่เก็บสายอักขระ
ออก: ไม่มี
หมายเหตุ: การทำงานของฟังก์ชันจะไม่เปลี่ยนแปลงค่าในรีจิสเตอร์ใด
ตัวอย่าง: ฟังก์ชัน 9 กำหนดให้ใส่ออฟเซ็ตของสายอักขระที่ต้องการส่งไปแสดงผลไว้ในรีจิสเตอร์ dx ออฟเซ็ตของสายอักขระจะอ้างอิงกับเซกเมนต์ข้อมูลเสมอ (ผ่านรีจิสเตอร์ ds) ตัวอย่างเช่นในเซกเมนต์ข้อมูลมีการกำหนดตัวแปรสายอักขระ "Assembly language program" ดังนี้
.data
: :
: :
msg db 'Assembly language program$'
: :
ชุดคำสั่งซึ่งทำหน้าที่พิมพ์ข้อความในตัวแปร msg จะเป็นดังนี้
mov ax,@data
mov ds,ax
: :
: :
mov dx, offset msg
mov ah,9
int 21h
ฟังก์ชัน 0AH
เข้า: ah = 0ah
ds = เซกเมนต์ของหน่วยความจำที่ใช้เก็บสายอักขระ
dx = ค่าออฟเซ็ตของหน่วยความจำที่ใช้เก็บสายอักขระ
ออก: ไม่มี
หมายเหตุ: การทำงานของฟังก์ชันจะไม่เปลี่ยนแปลงค่าในรีจิสเตอร์ใด
ตัวอย่าง: ฟังก์ชัน 0ah กำหนดให้ใช้ ds:dx ชี้ไปยังหน่วยความจำที่ใช้เป็นบัฟเฟอร์เก็บสายอักขระ ผู้เขียนโปรแกรมจะต้องใส่ตัวเลขบ่งบอกจำนวนอักขระที่จะยอมให้ป้อนได้ (นับรวมทั้งปุ่ม enter ด้วย) ไว้ที่ไบต์แรกของบัฟเฟอร์ ส่วนไบต์ที่สองของบัฟเฟอร์ให้กันเนื้อที่ไว้ เนื่องจากฟังก์ชันจะนับจำนวนอักขระที่ผู้ใช้ป้อนเข้ามา แล้วนำค่ามาเก็บที่ไบต์ที่สองนี้เมื่อจบสิ้นการทำงาน ฟังก์ชันจะจบการทำงานเมื่อกดปุ่ม enter และยังนำรหัสแอสกีของ enter (รหัส 0dh) ใส่ต่อท้ายอักขระตัวสุดท้ายที่ป้อนจากแป้นพิมพ์
.data
: :
maxlen db 30h
charin db ?
strbuff db 30 dup (20h)
: :
ชุดคำสั่งซึ่งทำหน้าที่รับสายอักขระจะเป็นดังนี้
.code
mov ax,@data
mov ds,ax
: :
: :
mov dx, offset maxlen
mov ah,0ah
int 21h
ตัวอย่างโปรแกรม
โปรแกรม UPCASE.ASM ตามรูปที่ 4.5 เป็นโปรแกรมอ่านสายอักขระจากแป้นพิมพ์และนำมาแปลงเป็นอักษรตัวใหญ่ โปรแกรมจะถือว่าผู้ใช้ต้องป้อนอักษรตัวเล็ก และจะนำรหัสแอสกีของอักษรนั้นมาลบด้วยค่า 20H โปรแกรมนี้จะไม่ตรวจสอบก่อนว่ารหัสก่อนที่จะนำมาลบ เป็นอักษรตัวเล็กหรือไม่ หากมีการป้อนอักขระอื่นเข้ามา โปรแกรมจะนำค่าไปลบด้วย 20H เสมอ ทำให้อาจได้อักขระอื่นที่ไม่ใช่ อักษรตัวใหญ่ ภายในโปรแกรมมีการใช้ฟังก์ชันดอสหลายฟังก์ชันคือฟังก์ชัน หมายเลข 2, 9 และ 0AH
;--------------------------------------
; upcase.asm
; read string from keyboard and change
; to upper case (undetect errors)
;--------------------------------------
;
dosseg
.model small
;reserved space for satck
.stack 100h
; variable and constant
.data
max_len db 25h
inp_len db ?
str_buff db 26h dup (20h)
msg_out1 db 'input string : $'
msg_out2 db 0dh,0ah,'upper case : $'
;
;start of program
;
.code
; init data segment
start: mov ax,@data
mov ds,ax
; display input message
mov ah,9
mov dx,offset msg_out1
int 21h
; read string
mov dx,offset max_len
mov ah,0ah
int 21h
; display output message
mov ah,9
mov dx, offset msg_out2
int 21h
; prepare pointer and counter
mov bx, offset inp_len
mov cl,[bx]
inc bx
; convert to upper case and display
next_char: mov ah,2
mov dl,[bx]
sub dl,20h
int 21h
inc bx
dec cl
jnz next_char
; return to dos with errorlevel 0
mov ax,4c00h
int 21h
end start
รูปที่ 4.5 โปรแกรม upcase.asm
การสร้างแฟ้ม UPCASE.EXE ให้เรียกใช้ MASM และ LINK ดังนี้
C:\>masm upcase;
microsoft (r) macro assembler version 5.00
copyright (c) microsoft corp 1981-1985, 1987. all rights reserved.
50850 + 406270 bytes symbol space free
0 warning errors
0 severe errors
c:\>link upcase;
microsoft (r) segmented-executable linker version 5.13
copyright (c) microsoft corp 1984-1991. all rights reserved.
เมื่อเรียกโปรแกรม upcase โปรแกรมจะรอให้ป้อนอักขระ และแปลงอักขระนั้นไปเป็นตัวใหญ่ดังตัวอย่างต่อไปนี้
C:\>upcase
input string : please convert it
upper case : PLEASE CONVERT IT
ลิงค์ถึงเพื่อน
15 ปีที่ผ่านมา
ไม่มีความคิดเห็น:
แสดงความคิดเห็น