[ บทความ : เรียนรู้ z80 ] ตอนที่ 9 เรื่อง คำสั่งเคลื่อนย้ายข้อมูล แบบกลุ่ม

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

คำสั่งเคลื่อนย้ายข้อมูลแบบกลุ่ม

หายหน้าหายตาไปนานเหมือนเดิมครับ .... เหตุผลนั้นมากมายเลยครับ ตั้งแต่ เรื่องเรียน (ช่วงนี้งานโถมเข้ามาพร้อมๆ กัน เยอะมาก) , เรื่องที่สนใจ (กำลัง เขียน assembler/ compiler / monitor ที่เป็น virtual machine ของ stack machine ที่ผม ออกแบบเอาไว้ ...) แต่ที่แน่ ๆ ตอนนี้เรามาต่อเรื่องคำสั่งของ Z80 กันต่อดีกว่า ... มีนักศึกษาของผมหลายคนครับ ที่ถามว่า ทำไมผมยังจะมาสนใจ Z80 อยู่อีก ผมก็บอกว่า มันง่าย .. มันไม่มีอะไรพิเศษเลยสักอย่าง ... ไม่มีแม้กระทั่งคำสั่ง คูณ และ หาร ... ไม่มีพอร์ตในตัว .. ไม่มี RS232 ให้เราใช้งาน ... จะทำอะไรก็ทำเอาเอง ... สะใจดี .... ผมคิดเสมอครับ การเขียนโปรแกรมแบบลำบากๆ นั้นย่อมสร้างนักเขียนโปรแกรมฝีมือดีๆ ได้มากมาย ... ดังนั้น เวลาเราผ่านงานยากๆ ที่ทำโดยฝีมือของเราเองทั้งหมด พอเจอระบบ ที่มันสำเร็จรูป เราเองก็สามารถเรียนรู้ได้โดยง่าย ... ดีไม่ดี ยังสามารถดัดแปลง เพิ่มประสิทธิภาพให้ดียิ่งขึ้นได้อีกด้วย ... เพราะฉะนั้น ใครที่กำลังศึกษา Z80 ก็จงพยายามเรียนรู้ให้เข้าใจถึงหลักการออกแบบ และการทำงานของมันให้ดีนะครับ ... เพราะ เวลาที่เราเปลี่ยนไปใช้พวกไมโครคอนโทรลเลอร์ เราจะได้เข้าใจมันได้ง่ายขึ้น ... ความแตกต่างเรื่องภาษานั้นเป็นเรื่องปกติครับ โปรเซสเซอร์แต่ละรุ่นไม่มีความเหมือนกันอยู่แล้ว ... ยังไงก็ต้องเขียนกันใหม่อยู่ดี ... แต่ถ้าเราเข้าใจหลักการว่า เราจะทำงานนั้นๆ ได้อย่างไร หรือที่เรียกว่าเข้าใจการทำงาน และออกแบบการทำงานได้ด้วยตนเองนั้น (เรียกสั้นๆ แบบภาษาต่างประเทศว่า algorithm) เวลาที่เปลี่ยนภาษาแนวคิดนั้น ก็ยังเดิมๆ นั่นแหละครับ ... ยิ่งเราผ่านการเขียนโปรแกรมมามาก เวลานำไปใช้นี่แทบเรียกได้ว่า มาจากหัว ... ไม่ต้องเสียเวลาคิดออกแบบในกระดาษเลยล่ะครับ ... โอ ... เกริ่นเยอะพอควรแฮะ... มาเข้าเรื่องดีกว่าครับ

คำสั่งในคราวนี้เป็นเรื่องของการถ่ายโอนข้อมูลแบบเป็นกลุ่ม หรือบล็อก ... ถ้าจำกันได้ เราเคยศึกษาเรื่อง คำสั่งถ่ายโอนข้อมูลกันไปแล้ว นั่นก็คือ LD แต่ว่า คำสั่ง LD นั้น เป็นการถ่ายข้อมูลทีละ 1 ชุดเท่านั้น ... แต่ในคราวนี้เราจะกล่าวถึงการถ่ายโอนข้อมูล ในหน่วยความจำเป็นครั้งละจำนวนมากๆ เท่าที่เราจะสั่งการ ... คำสั่งที่เราจะใช้นั้นก็คือ LDD, LDDR, LDI, และ LDIR ซึ่งรูปแบบของคำสั่งจะเป็นดังนี้ครับ

Instruction
Source/Target
Flag
Operation
LDD
ไม่มี
C , Z , P/V , S
(DE) <- (HL)
DE <- DE-1
HL <- HL-1
BC <- BC-1
LDDR
ไม่มี
C , Z , S
เหมือน LDD แต่ว่า คำสั่งนี้ จะทำการโอนข้อมูล ไปเรื่อยๆ จนกว่า BC จะเป็น 0
LDI
ไม่มี
C , Z , P/V , S
(DE) <- (HL)
DE <- DE+1
HL <- HL+1
BC <- BC-1
LDIR
ไม่มี
C , Z , S
เหมือน LDI แต่ว่า คำสั่งนี้ จะทำการโอนข้อมูล ไปเรื่อยๆ จนกว่า BC จะเป็น 0
ตาราง 9-1 ชุดคำสั่งสำหรับเคบื่อนย้ายข้อมูลเป็นกลุ่ม

จะเห็นว่า คำสั่งทั้ง 4 นั้น จะทำงานกับหน่วยความจำเพียงอย่างเดียวเท่านั้น ... และความแตกต่างระหว่าง LDI กับ LDIR และ LDD กับ LDDR ก็คือว่า LDI , LDD นั้นจะทำงานเพียงครั้งเดียว ถ้าเราจะต้องการให้ทำงานต่อ เราต้องตรวจสอบค่า Flag เอาเอง แต่ LDIR, LDDR นั้น จะทำงานวนรอบไปเรื่อยๆ จนกว่าค่าของ BC จะเป็น 0

มาดูตัวอย่างโปรแกรมสำหรับ คำสั่งที่เกี่ยวกับ การแลกเปลี่ยนข้อมูล กันครับ ...

	;
	; Filename : TLDEx2.asz
	; Author   : Supachai Budsaratij   (raek@se-ed.net)
	; Date     : Jan 18, 2001
	; Hardware : ET-Board V6 (Z80 Mode)
	;            CP-jr180
	;            ET-Board V5
	;            ET-Board V3.5 New Power
	;
	;
	;        INCL    "etv6.inz"         ; Inlude header for ET-V6
	;        INCL    "etv35.inz"         ; Inlude header for ET-V3.5
	        INCL    "jr180.inz"         ; Inlude header for CP-jr180

	        ORG     UMEM_ORG        ; Start at UMEM_ORG

	main:   ; --- My code here
	; Load data from 9100h to 9001h keep into 9000h to 8F01h
	        LD      DE,09000h       ; DE point to target address.
	        LD      HL,09100h       ; HL point to source address.
	        LD      BC,0FFh         ; BC = counter.
	        LDD                     ; Load data from (HL) and keep into (DE).
	                                ; Do until the value of BC equal to 0.
	                                ; (This instruction to BC times.)
                	                ;
	        LD      DE,09000h
	        LD      HL,09100h
	        LD      BC,0FFh
	        LDDR                    ; Similar to LDD but repeat until
                                ; Z flag equal to 1 or BC equal to 0.
                                ;

	        ; ---End of program
	        HALT

	END

เมื่อเราทำการแปลคำสั่งเรียบร้อยแล้ว เราก็จะได้ไฟล์ .LST ดังนี้


                        ;
                        ; Filename : TLDEx2.asz
                        ; Author   : Supachai Budsaratij   (raek@se-ed.net)
                        ; Date     : Jan 18, 2000
                        ; Hardware : ET-Board V6 (Z80 Mode)
                        ;            CP-jr180
                        ;            ET-Board V5
                        ;            ET-Board V3.5 New Power
                        ;
                        ;
                        ;        INCL    "etv6.inz"         ; Inlude header for ET-V6
                        ;        INCL    "etv35.inz"         ; Inlude header for ET-V3.5
                                INCL    "jr180.inz"         ; Inlude header for CP-jr180
                        ;
                        ; filename  : jr180.inz
                        ; assembler : az80
                        ; author    : Supachai Budsaratij (raek@se-ed.net)
                        ; hardware  : CP-jr180 Plus
                        ; date      : Dec 5, 2000
                        ;
                        ; Note  : Must use jr180 debugger.
                        ;
                        
                        ; --- MEMORY (Logical address)
                        ;
   8000                 UMEM_ORG        EQU     08000h   ; User RAM start (10000h - Physical address)
   ffff                 UMEM_END        EQU     0FFFFh   ; User RAM end (17FFFh - Physical address)
                        ; 
                        ; --- MEMORY (Physical address)
   0000                 P_UMEM_ORG   EQU	10000h
   7fff                 P_UMEM_END    EQU   17FFFh
   8000                 P_XMEM_ORG        EQU     018000h   ; Expand RAM end
   ffff                 P_XMEM_END        EQU     01FFFFh   ; Expand RAM end
                        
                        ; --- I/O
                        ;
   0080                 U8255_PA        EQU     80h     ; User 8255 port A
   0081                 U8255_PB        EQU     81h     ; User 8255 port B
   0082                 U8255_PC        EQU     82h     ; User 8255 port C
   0083                 U8255_CT        EQU     83h     ; User 8255 control port
                        ;
                        ; --- for Character LCD
   00c0                 CLCD_WC   EQU  0C0h ; Write instruction to LCD
   00c2                 CLCD_WD  EQU  0C2h  ; Write data to CG or DD RAM
   00c4                 CLCD_RD   EQU   0C4h ; Read busy flag and address
                        ; --- for Graphics LCD
   00c0                 GLCD_WC   EQU  0C0h ; Write instruction to LCD (Page1)
   00c1                 GLCD_WC2   EQU  0C1h ; Write instruction to LCD (Page2)
   00c2                 GLCD_WD  EQU  0C2h  ; Write data to CG or DD RAM (Page1)
   00c3                 GLCD_WD2   EQU  0C3h ; Write data to CG or DD RAM (Page2)
   00c4                 GLCD_RD   EQU   0C4h ; Read busy flag and address (Page1)
   00c5                 GLCD_RD2   EQU  0C5h ; Read busy flag and address  (Page2)
                        ;
   00e0                 WATCH_DOG   EQU  0E0h
                        
   8000                         ORG     UMEM_ORG        ; Start at UMEM_ORG
                        
   8000                 main:   ; --- My code here
                        ; Load data from 9100h to 9001h keep into 9000h to 8F01h
   8000   11 00 90              LD      DE,09000h       ; DE point to target address.
   8003   21 00 91              LD      HL,09100h       ; HL point to source address.
   8006   01 ff 00              LD      BC,0FFh         ; BC = counter.
   8009   ed a8                 LDD                     ; Load data from (HL) and keep into (DE).
                                                        ; Do until the value of BC equal to 0.
                                                        ; (This instruction to BC times.)
                                                        ;
   800b   11 00 90              LD      DE,09000h
   800e   21 00 91              LD      HL,09100h
   8011   01 ff 00              LD      BC,0FFh
   8014   ed b8                 LDDR                    ; Similar to LDD but repeat until
                                                        ; Z flag equal to 1 or BC equal to 0.
                                                        ;
                       
                                ; ---End of program
   8016   76                    HALT
                        
   8017                 	END

00c4  CLCD_RD       00c0  CLCD_WC       00c2  CLCD_WD       00c4  GLCD_RD   
00c5  GLCD_RD2      00c0  GLCD_WC       00c1  GLCD_WC2      00c2  GLCD_WD   
00c3  GLCD_WD2      7fff  P_UMEM_END    0000  P_UMEM_ORG    ffff  P_XMEM_END
8000  P_XMEM_ORG    0083  U8255_CT      0080  U8255_PA      0081  U8255_PB  
0082  U8255_PC      ffff  UMEM_END      8000  UMEM_ORG      00e0  WATCH_DOG 
8000  main          

ส่วนรายละเอียดของไฟล์ฐานสิบหก ก็จะเป็นดังนี้ครับ


:1780000011009021009101FF00EDA811009021009101FF00EDB87613
:0080170168

คราวนี้เรามาลอง debug กันนะครับ... อ๊ะๆ ... คราวนี้ผมขอใช้บอร์ด CP-jr180 V2 นะครับ เพราะไม่มีบอร์ด ET-V6 แถวๆ นี้เลย แต่โปรแกรมสามารถทำงานได้กับ ET-V6 ได้ด้วยการเปลี่ยนแปลงบรรทัด

	;        INCL    "etv6.inz"         ; Inlude header for ET-V6
	;        INCL    "etv35.inz"         ; Inlude header for ET-V3.5
	        INCL    "jr180.inz"         ; Inlude header for CP-jr180

ให้เป็น

	        INCL    "etv6.inz"         ; Inlude header for ET-V6
	;        INCL    "etv35.inz"         ; Inlude header for ET-V3.5
	;        INCL    "jr180.inz"         ; Inlude header for CP-jr180
ก็เริ่มตั้งแต่โหลดโปรแกรมเลยนะครับ

หลังจากนั้นเราก็เริ่มสั่ง t 8000 เพอื่เริ่ม debug คำสั่งแรกเป็นการกำหนดให้ DE เป็น 9000h อันนี้จะเห็นว่า เรากำหนดล่ะนะว่า ปลายทางอยู่ที่ 9000h

ขั้นตอนต่อมาเราก็กำหนดให้ HL เป็น 9100h ซึ่งก็คือ กำหนดจุดเริ่มมต้นของข้อมูลเอาไว้ที่ 9100h

กำหนดจำนวนข้อมูลที่จะทำการถ่ายโอน ... โดยกำหนดผ่านทาง BC ... ตอนนี้เรากำหนดให้ทำ FFh รอบ

ทดลองเรียก LDD จะเห็นว่าค่าของ BC, DE, และ HL จะลดลง (ถ้าเป็น LDI ค่า DE กับ HL จะเพิ่มขึ้น)

อ้าว ... ทำไม มากำหนดให้ DE เป็น 9000h ใหม่ล่ะ ... อืม ... อืม ... อ๋อ ... คำสั่งก่อนหน้านี้เป็น LDD นี่นา ฬฬฬ มันก็ทำครั้งเดียวสิ ... อืม .. อืม .. เข้าใจๆ ล่ะ (เข้าใจอย่างผมไหมครับ)

ว่าแล้วก็กำหนดข้อมูลเริ่มต้นอยู่ที่ 9100h กันใหม่อีกรอบ

ทำ FFh ครั้งเหมือนเดิมนะ

เอาล่ะ เจ้า LDDR ทำงานได้ ... อ๊ะอ๋า .. คราวนี้ BC กลายเป็น 0 , DE เป็น 8F01 (9000-FF = 8F01) และ HL ก็เป็น 9001 (9100-FF = 9001) เห็นความแตกต่างแล้วใช่ไหมครับ

อ้าว จบโปรแกรมเสียแล้ว ...

เป็นอย่างไรบ้างครับ LDD กับ LDDR ส่วน LDI กับ LDIR นั้น ก็ขอให้ผู้อ่านลองเปลี่ยนคำสั่งดูเองล่ะกันครับ ... คราวหน้าเป็นคำสั่งอะไรดีหนอ ... อืม ... เอาคำสั่งค้นหาข้อมูลในหน่วยความจำกันก่อน แล้ว มาลองบวกลบ กันดูดีกว่า ... หลังจากนั้นก็ค่อยเป็นคำสั่งเงื่อนไข ... ต่อไปเริ่มสนุกแล้วนะครับ ... . เราก็จะว่าด้วย การติดต่อกับ I/O กันต่อ ส่วนที่เหลือ คงเป็นตัวอย่างจากบอร์ดรุ่นต่างๆ ล่ะนะครับ ... คิดว่า ถ้าหมด ภาคคำสั่ง แล้วผู้อ่าน เข้าใจหน้าที่ของคำสั่งต่างๆ เจ้าตัวอย่างโปรแกรมของบอร์ด ก็คงอ่านได้ไม่ยากจริงไหมครับ ...



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