[ บทความ : เรียนรู้ z80 ] ตอนที่ 18 เรื่อง การเขียนโปรแกรมย่อย และปิดฉาก Z80

เรียนรู้ Z80 ตอนที่ 18

การเขียนโปรแกรมย่อย และปิดฉาก Z80

สวัสดีครับ หายหน้าหายตากันไป 2 สัปดาห์ มาคราวนี้ เป็นบทความตอนสุดท้ายของ Z80 กันแล้วนะ ... ที่ผ่านๆ มาเป็นอย่างไร กันบ้างครับ หวังว่าผู้อ่านคงได้ประโยชน์จากบทความชุดนี้กันนะครับ ... มาเข้าเรื่องของเรากันดีกว่า ...

ในบทความที่ผ่านๆ มานั้น เราได้เขียนโปรแกรมไปหลายโปรแกรมแล้วใช่ไหมครับ ... ผู้อ่านสังเกตเจออะไรบ้างไหมครับ ... รู้สึกไหมว่าโปรแกรมที่เราเขียนนั้นเป็นโปรแกรมที่จบในตัวของมันเอง มีงานที่ไม่ซับซ้อน ต้องการให้มันทำงานอะไรก็เขียนเพิ่มเข้าไป ... ซึ่งเราจะต้องเขียนใหม่กันทั้งหมด (บางท่านอาจจะใช้วิธีการคัดลอก จากสิ่งที่มีอยู่แล้วก็ไม่ว่ากันครับ) ... มาลองดูตัวอย่างโปรแกรม ประกอบบทความกันก่อนครับ


;
; Filename : Z00.asz
; Author   : Supachai Budsaratij   (raek@se-ed.net)
; Date     : May 27, 2001
; Hardware : ET-Board V6 (Z80 Mode)
;            ET-Board V5
;            ET-Board V4
;            ET-Board V3.5 NewPower
;            CP-jr180 Plus
;
                INCL    "etv6.inz"         ; Include header for ET-V6
;                INCL    "etv5.inz"         ; Include header for ET-V5
;                INCL    "etv4.inz"         ; Include header for ET-V4
;                INCL    "etv35.inz"        ; Include header for ET-V3.5
;                INCL    "jr180.inz"        ; Include header for CP-jr180 Plus

P_DIGIT         EQU     S8255_PA
P_SEGM          EQU     S8255_PB
P_LEDFLAG       EQU     6

                ORG     UMEM_ORG        ; Start at UMEM_ORG

main            LD      SP, UMEM_END
loop            LD      A,P_LEDFLAG     ; Display data (start)
                OUT     (P_DIGIT),A      ;
                LD      A,0             ;
                OUT     (P_SEGM),A      ; Display data (stop)
                LD      HL,8000h        ; Delay (start)
                LD      BC,1            ;
delay1          SBC     HL,BC           ;
                JP      NZ,delay1       ; Delay (stop)

                LD      A,P_LEDFLAG     ; Display data (start)
                OUT     (P_DIGIT),A      ;
                LD      A,08h           ;
                OUT     (P_SEGM),A      ; Display data (stop)
                LD      HL,8000h        ; Delay (start)
                LD      BC,1            ;
delay2          SBC     HL,BC           ;
                JP      NZ,delay2       ; Delay (stop)

                LD      A,P_LEDFLAG     ; Display data (start)
                OUT     (P_DIGIT),A      ;
                LD      A,80h           ;
                OUT     (P_SEGM),A      ; Display data (stop)
                LD      HL,8000h        ; Delay (start)
                LD      BC,1            ;
delay3          SBC     HL,BC           ;
                JP      NZ,delay3       ; Delay (stop)

                JP      loop

                END

จากโปรแกรมตัวอย่าง จะพบว่าโปรแกรมมีส่วนที่ซ้ำกันอยู่ ด้วยกัน 2 ส่วน คือ ส่วนสำหรับการหน่วงเวลา กับ ส่วนที่ทำหน้าที่ เลือก และส่งข้อมูลออกทางพอร์ต LED ..

ข้อดี (advantage) ของการเขียนโปรแกรมด้วยวิธีนี้ก็คือ เราสามารถเขียนโปรแกรม ต่อเติมไปได้เรื่อยๆ โดย ไม่จำเป็นจะต้องออกแบบโปรแกรมมาก่อนล่วงหน้า

ข้อเสีย (disadvantage)แต่ถ้าโปรแกรมของเราโตขึ้นเรื่อยๆ เราจะประสบปัญหาเกี่ยวกับ การหาข้อผิดพลาด และการแก้ไขโปรแกรม (เพราะโปรแกรมจะมีจำนวนบรรทัดเยอะมากๆ และจะต้องอ่านโปรแกรมตั้งแต่ต้น เพื่อหาว่าผิดพลาด จุดใด) แถม การเขียนโปรแกรมแบบนี้ยังกินพื้นที่หน่วยความจำเยอะด้วย (เพราะปริมาณโค้ดเยอะ เวลาคอมไพล์ก็ได้ ไฟล์ผลลัพธ์ขนาดใหญ่ขึ้น)

ด้วยเหตุดังกล่าว จึงมีการออกแบบให้ผู้เขียนโปรแกรม สามารถแยกโปรแกรมออกเป็นส่วนๆ เพื่อลดความซ้ำซ้อนของโปรแกรม ดังนั้น ถ้าเราแยกส่วนที่เรียกซ้ำกันมากๆ ออกมาเป็นโปรแกรมย่อย นอกจากจะทำให้เราแก้ไขโปรแกรมได้ง่ายขึ้น, อ่านโปรแกรมได้ง่ายขึ้น แล้ว ยังทำให้ไฟล์ HEX ที่ได้นั้น มีขนาดเล็กลงด้วย

โปรแกรมย่อย

โปรแกรมย่อย คือ ส่วนของโปรแกรมที่เราเขียนนี่ล่ะครับ แต่เป็นการเขียนโดยออกแบบมาเพื่อ ลดจำนวนบรรทัดของโปรแกรม และลดความซ้ำซ่อนของโค้ดที่เราเขียน นั่นตือ ถ้าเรามีส่วนของโปรแกรมที่เขียนเหมือนๆกัน (ซ้ำๆกัน) เราก็เอาส่วนของโปรแกรมนั้น แยกออกมาเป็นโปรแกรมย่อยซะ (นั่นเป็นหลักการง่ายๆ) ... ซึ่งถ้าเราต้องการใช้งาน เมื่อไร ก็ค่อยเรียกโปรแกรมย่อยส่วนนั้นๆ ขึ้นมาใช้งาน ... ก่อนอื่น มาดูรูปแบบคำสั่งที่เกี่ยวกับการเขียนโปรแกรมย่อยกันก่อนครับ

ตาราง 18-1 คำสั่งที่เกี่ยวข้องกับการเขียนโปรแกรมย่อย

Instruction
Source/Target
Flag
Operation
CALL
nn
ไม่มีผลกับ flag
(SP-1) <- PCH
(SP-2) <- PCL
PC <- nn
CALL
cc, nn
ไม่มีผลกับ flag
if cc is true then
(SP-1) <- PCH
(SP-2) <- PCL
PC <- nn
end if
RET
-
ไม่มีผลกับ flag
PCL <- (SP)
PCH <- (SP+1)
RET
cc
ไม่มีผลกับ flag
if cc is true then
PCL <- (SP)
PCH <- (SP+1)
else
continue
end if
RETI
-
ไม่มีผลกับ flag
Return from interrupt
RETN
-
ไม่มีผลกับ flag
return from nonmaskable interrupt
RST
pp
ไม่มีผลกับ flag
(SP-1) <- PCH
(SP-2) <- PCL
PCH <- 0
PCL <- pp
โดยที่ pp คือ 00h, 08h, 10h, 18h, 20h, 28h, 30h, หรือ 38h

หมายเหตุ

cc คือ NZ, Z, NC, C, PO, PE, P หรือ M
NZ คือ nonzero
Z คือ zero
NC คือ noncarry
C หคือ carry
PO คือ parity odd
PE คือ parity even
P หคือ sign positive
M คือ sign negative

คราวนี้เรามาออกแบบโปรแกรมย่อยกันก่อนดีกว่า ... จากโปรแกรมด้านบน เราจะเขียนโปรแกรมขึ้นมาใหม่โดยใช้หลักการ ของโปรแกรมย่อย ... เราจะได้โปรแกรมย่อยดังต่อไปนี้

1. delay สำหรับหน่วงเวลา
2. display สำหรับเลือกพอร์ต และส่งข้อมูลออกทางพอร์ตของ LED โดยมีข้อกำหนดว่า ก่อนเรียกโปรแกรมย่อยนี้ เราต้อง ใช้ register D สำหรับ กำหนดข้อมูลที่จะส่งมาแสดงผล
ผังงานของโปรแกรมย่อย delay


	delay           LD      HL,8000h
	                LD      BC,1
	dloop           SBC     HL,BC
	                JP      NZ,dloop
	                RET

ผังงานของโปรแกรมย่อย display


	display         LD      A,P_LEDFLAG
	                OUT     (P_DIGIT),A
	                LD      A,D
	                OUT     (P_SEGM),A
	                RET

ผังงานของโปรแกรมหลัก


	main            LD      SP, UMEM_END
	loop            LD      D,0
	                CALL    display
	                CALL    delay
	                LD      D,08h
	                CALL    display
	                CALL    delay
	                LD      D,80h
	                CALL    display
	                CALL    delay
	                JP      loop

จากผังงานจะได้ว่า โปรแกรมของเราเริ่มต้นด้วยการกำหนดค่า SP ใหม่ หลังจากนั้นกำหนดให้ D=0 แล้วเรียกโปรแกรมย่อย display ขึ้นมาทำงาน ตัว Z80 ของเราก็จะกระโดดไปทำงานในโปรแกรมย่อย display เมื่อมันทำงานจนเสร็จ ก็จะกลับมาที่โปรแกรมหลัก ... แล้วก็เจอคำสั่งให้เรียกโปรแกรมย่อย delay ขึ้นมาทำงาน คราวนี้ Z80 ก็ไปทำงานที่ delay ตามที่เราสั่ง เมื่อเสร็จการทำงานใน delay ก็จะกลับมาที่โปรแกรมหลักอีกครั้ง ... และจะทำอย่างนี้ไปเรื่อยๆ ... (พอจะเข้าใจไหมครับ :D)

มาดูโปรแกรมเต็มๆ กันครับ


;
; Filename : Z01.asz
; Author   : Supachai Budsaratij   (raek@se-ed.net)
; Date     : May 27, 2001
; Hardware : ET-Board V6 (Z80 Mode)
;            ET-Board V5
;            ET-Board V4
;            ET-Board V3.5 NewPower
;            CP-jr180 Plus
;
                INCL    "etv6.inz"         ; Include header for ET-V6
;                INCL    "etv5.inz"         ; Include header for ET-V5
;                INCL    "etv4.inz"         ; Include header for ET-V4
;                INCL    "etv35.inz"        ; Include header for ET-V3.5
;                INCL    "jr180.inz"        ; Include header for CP-jr180 Plus

P_DIGIT         EQU     S8255_PA
P_SEGM          EQU     S8255_PB
P_LEDFLAG       EQU     6

                ORG     UMEM_ORG        ; Start at UMEM_ORG

main            LD      SP, UMEM_END
loop            LD      D,0
                CALL    display
                CALL    delay
                LD      D,08h
                CALL    display
                CALL    delay
                LD      D,80h
                CALL    display
                CALL    delay
                JP      loop


;-- display module (start)
display         LD      A,P_LEDFLAG
                OUT     (P_DIGIT),A
                LD      A,D
                OUT     (P_SEGM),A
                RET
;-- display module (stop)

;-- delay module (start)
delay           LD      HL,8000h
                LD      BC,1
dloop           SBC     HL,BC
                JP      NZ,dloop
                RET
;-- delay module (stop)

                END

เป็นอย่างไรบ้างครับกับบทความ Z80 (แบบง่ายๆ หรือขั้นเริ่มต้นนั่นแหละครับ) ... มาถึงจัดนี้ เรายังขาดเรื่องที่ซับซ้อน ไป บ้าง เช่น การทำงานของระบบ interrupt กับ การ interface กับอุปกรณ์ภายนอก (แถม timing diagram ก็ไม่ได้กล่าวอย่างละเอียด) ... ซึ่งทั้ง 2 ส่วนนี้ผม คาดหวังเอาไว้ว่า ผู้อ่านน่าจะศึกษาเพิ่มเติม ได้ไม่ยาก ... และสุดท้ายนี้ขอให้ผู้ที่สนใจจะเขียนโปรแกรมด้วย Z80 ... จงประสบความสำเร็จ ในการเขียนโปรแกรมด้วยครับ ... สำหรับบทความนี้ คงจบเพียงเท่านี้ครับ ... สวัสดีครับ


เขียนโดย : ศุภชัย บุศราทิจ
Author : Supachai Budsaratij
e-mail : raek@se-ed.net
วันที่ทำการปรับปรุง : ๒๗ พ.ค. ๒๕๔๔