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

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

คำสั่งบวกและลบ

สวัสดีครับ หายไป 1 สัปดาห์ ตอนนี้เรามาศึกษาคำสั่งต่อไปกันดีกว่าครับ ... คำสั่งที่กล่าวถึงในครั้งนี้ จะเป็นคำสั่งเกี่ยวกับการ บวก และ ลบ ... คำสั่งบวก และลบ ของ Z80 นั้น มีรูปแบบการทำงานหัลก ๆ 2 ลักษณะ คือ บวก/ลบ แบบไม่สนใจ carry flag และ การบวก/ลบ แบบสนใจ carray flag ... การบวก/ลบ ของ Z80 นั้น จะสามารถใช้ได้กับข้อมูลแบบ 8 และ 16 บิต รูปแบบของคำสั่งเป็นดังนี้

Instruction
Source/Target
Flag
Operation
ADD
A , s
[ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการทดค่าจากบิต 3 ไปบิต 4 , H=1
[ 4 ] ถ้าเกิดการ Overflow , V=1
[ 5 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 6 ] N=0 เสมอ
เป็นการบวกเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A+s ... ดูความหมายของ s ที่หมายเหตุ
ADD
A , (HL)
เหมือน ADD A,s เป็นการบวกเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A+ (HL)
ADD
A , (IX+d)
เหมือน ADD A,s เป็นการบวกเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A+ (IX+d)
ADC
A , s
เหมือน ADD A,s เป็นการบวกเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A+s+CY ... ดูความหมายของ s ที่หมายเหตุ
ADD
HL , ss
[ 1 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 2 ] N=0 เสมอ
เป็นการบวกเลขขนาด 16 บิต โดยมีผลการทำงานคือ HL <- HL+ss ... ดูความหมายของ ss ที่หมายเหตุ
ADD
IX , pp
เหมือน ADD HL,ss เป็นการบวกเลขขนาด 16 บิต โดยมีผลการทำงานคือ IX <- IX+pp ... ดูความหมายของ pp ที่หมายเหตุ
ADD
IY , rr
เหมือน ADD HL,ss เป็นการบวกเลขขนาด 16 บิต โดยมีผลการทำงานคือ IY <- IY+rr ... ดูความหมายของ rr ที่หมายเหตุ
ADC
HL , ss
[ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 4 ] ถ้าเกิดการ Overflow , V=1
[ 5 ] N=0 เสมอ
เป็นการบวกเลขขนาด 16 บิต โดยมีผลการทำงานคือ HL <- HL+s+CY ... ดูความหมายของ ss ที่หมายเหตุ
SUB
s
[ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการทดค่าจากบิต 3 ไปบิต 4 , H=1
[ 4 ] ถ้าเกิดการ Overflow (ค่าเกิน 255) , P/V=1
[ 5 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 6 ] N=1 เสมอ
เป็นการลบเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A-s ... ดูความหมายของ s ที่หมายเหตุ
SBC
A, s
เหมือน SUB s เป็นการลบเลขขนาด 8 บิต โดยมีผลการทำงานคือ A <- A-s-CY ... ดูความหมายของ s ที่หมายเหตุ
SBC
HL, ss
[ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
[ 4 ] ถ้าเกิดการ Overflow , V=1
[ 5 ] N=1 เสมอ
เป็นการลบเลขขนาด 16 บิต โดยมีผลการทำงานคือ HL <- HL-ss-CY ... ดูความหมายของ ss ที่หมายเหตุ
DAA
ไม่มี
[ 1 ] ถ้าผลลัพธเป็นค่าลบ (บิตสุดท้าย หรือ MSB เป็น 1), S=1
[ 2 ] ถ้าผลลัพธ์เป็น 0 , Z=1
[ 3 ] ถ้ามีการทดค่าจากบิต 3 ไปบิต 4 , H=1
[ 4 ] P=1 เพื่อให้จำนวนบิตที่มีค่า 1 เป็นจำนวน คู่
[ 5 ] ถ้ามีการยืม หรือทดค่า MSB หรือ บิต 7, C=1
เป็นการเปลี่ยนข้อมูล ในรีจิสเตอร์ A ให้เป็นตัวเลข BCD ปกติ เราจะใช้คำสั่งนี้หลังจากใช้คำสั่ง ADD หรือ SUB เพื่อให้การบวก และลบ เป็นการบวกลบเลขแบบ BCD
ตาราง 11-1 ชุดคำสั่งสำหรับ บวก/ลบ ส่วนที่ 1

หมายเหตุ

s คือ ตัวเลขขนาด 8 บิต หรือรีจิสเตอร์ B, C, D, E, H, L และ A
ss คือ รีจิสเตอร์คู่ (pair register) อันได้แก่ BC, DE, HL และ SP
pp คือ รีจิสเตอร์คู่ (pair register) อันได้แก่ BC, DE, IX และ SP
rr คือ รีจิสเตอร์คู่ (pair register) อันได้แก่ BC, DE, IY และ SP
P/V เป็น flag register จะทำงาน 2 หน้าที่ คือ เป็น Parity Flag เมื่อทำงานกับคำสั่งทางด้าน Logic และ เป็น Overflow Flag เมื่อทำงานเกี่ยวกับการคำนวณ

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



	;
	; Filename : AddSub1.asz
	; Author   : Supachai Budsaratij   (raek@se-ed.net)
	; Date     : March 13, 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    "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

	        ORG     UMEM_ORG        ; Start at UMEM_ORG

	main
	;--- Case study 1 : Over flow.
	        LD      A,250
	        ADD     A,6

	;--- Case study 2 : Subtract by greater value.
	        LD      A,5
	        LD      B,20
	        SUB     B

	;--- Case study 3 : Add 16 bit.
	        LD      HL,00FFh
	        LD      BC,0001h
	        ADD     HL,BC

	;--- Case study 4 : BCD adding.
	        LD      A,16h
	        LD      C,55h
	        ADD     A,C
	        DAA

	;--- Case study 5 : Add with carry flag operation.
	        LD      A,0FFh
	        LD      B,01h
	        ADD     A,B
	        ADC     A,B

	;--- Case study 6 : Subtract with carry flag operation.
	        LD      A,05h
	        LD      B,07h
	        SUB     B
	        LD      A,09h
	        SBC     A,B

	        HALT

		END

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



                        ;
                        ; Filename : AddSub1.asz
                        ; Author   : Supachai Budsaratij   (raek@se-ed.net)
                        ; Date     : March 13, 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
                        ;
                        ; filename  : etv6.inz
                        ; assembler : az80
                        ; author    : Supachai Budsaratij (raek@se-ed.net)
                        ; hardware  : et-board 6 (Z-80 mode)
                        ; date      : October 12,2000
                        ;
                        
                        ; --- MEMORY
                        ;
   8000                 UMEM_ORG        EQU     08000h   ; User RAM start
   bdff                 UMEM_END        EQU     0BDFFh   ; User RAM end
   c000                 XMEM_ORG        EQU     0C000h   ; Expand RAM end
   dfff                 XMEM_END        EQU     0DFFFh   ; Expand RAM end
                        
                        ; --- I/O
                        ;
   0000                 S8255_PA        EQU     00h     ; System 8255 port A
   0001                 S8255_PB        EQU     01h     ; System 8255 port B
   0002                 S8255_PC        EQU     02h     ; System 8255 port C
   0003                 S8255_CT        EQU     03h     ; System 8255 control port
                        ;
   0020                 U8255_PA        EQU     20h     ; User 8255 port A
   0021                 U8255_PB        EQU     21h     ; User 8255 port B
   0022                 U8255_PC        EQU     22h     ; User 8255 port C
   0023                 U8255_CT        EQU     23h     ; User 8255 control port
                        ;
   004d                 SCN_INP         EQU     4Dh     ; SCN2681 - Input port (Switch)
   004e                 SCN_SEO         EQU     4Eh     ; SCN2681 - Set output port (LED)
   004f                 SCN_REO         EQU     4Fh     ; SCN2681 - Reset output port (LED)
                        ;
   0060                 CLCD_WC         EQU     60h     ; Character LCD write command
   0061                 CLCD_RC         EQU     61h     ; Character LCD read command
   0062                 CLCD_WD         EQU     62h     ; Character LCD write data
   0063                 CLCD_RD         EQU     63h     ; Character LCD read data
                        ;
   0064                 GLCD_WC1        EQU     64h     ; Graphics LCD write command (Page 1)
   0065                 GLCD_RC1        EQU     65h     ; Graphics LCD read command  (Page 1)
   0066                 GLCD_WD1        EQU     66h     ; Graphics LCD write data    (Page 1)
   0067                 GLCD_RD1        EQU     67h     ; Graphics LCD read data     (Page 1)
                        ;
   0068                 GLCD_WC2        EQU     68h     ; Graphics LCD write command (Page 2)
   0069                 GLCD_RC2        EQU     69h     ; Graphics LCD read command  (Page 2)
   006a                 GLCD_WD2        EQU     6Ah     ; Graphics LCD write data    (Page 2)
   006b                 GLCD_RD2        EQU     6Bh     ; Graphics LCD read data     (Page 2)
                        ;
                        
                        ;        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
                        
   8000                         ORG     UMEM_ORG        ; Start at UMEM_ORG
                        
   8000                 main
                        ;--- Case study 1 : Over flow.
   8000   3e fa                 LD      A,250
   8002   c6 06                 ADD     A,6
                        
                        ;--- Case study 2 : Subtract by greater value.
   8004   3e 05                 LD      A,5
   8006   06 14                 LD      B,20
   8008   90                    SUB     B
                        
                        ;--- Case study 3 : Add 16 bit.
   8009   21 ff 00              LD      HL,00FFh
   800c   01 01 00              LD      BC,0001h
   800f   09                    ADD     HL,BC
                        
                        ;--- Case study 4 : BCD adding.
   8010   3e 16                 LD      A,16h
   8012   0e 55                 LD      C,55h
   8014   81                    ADD     A,C
   8015   27                    DAA
                        
                        ;--- Case study 5 : Add with carry flag operation.
   8016   3e ff                 LD      A,0FFh
   8018   06 01                 LD      B,01h
   801a   80                    ADD     A,B
   801b   88                    ADC     A,B
                        
                        ;--- Case study 6 : Subtract with carry flag operation.
   801c   3e 05                 LD      A,05h
   801e   06 07                 LD      B,07h
   8020   90                    SUB     B
   8021   3e 09                 LD      A,09h
   8023   98                    SBC     A,B
                        
   8024   76                    HALT
                        
   8025                 	END

0061  CLCD_RC       0063  CLCD_RD       0060  CLCD_WC       0062  CLCD_WD   
0065  GLCD_RC1      0069  GLCD_RC2      0067  GLCD_RD1      006b  GLCD_RD2  
0064  GLCD_WC1      0068  GLCD_WC2      0066  GLCD_WD1      006a  GLCD_WD2  
0003  S8255_CT      0000  S8255_PA      0001  S8255_PB      0002  S8255_PC  
004d  SCN_INP       004f  SCN_REO       004e  SCN_SEO       0023  U8255_CT  
0020  U8255_PA      0021  U8255_PB      0022  U8255_PC      bdff  UMEM_END  
8000  UMEM_ORG      dfff  XMEM_END      c000  XMEM_ORG      8000  main      


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


	:208000003EFAC6063E0506149021FF00010100093E160E5581273EFF060180883E05060749
	:05802000903E09987676
	:008025015A

มา debug กันดีกว่าครับ เริ่มตั้งแต่โหลดโปรแกรมเลยนะครับ

ตัวอย่างส่วนที่ 1 : ทดสอบ Overflow

... ให้ A = FAh

... บวกค่าใน A ด้วย 6 อ๊ะ ... แต่ A เป็นรีจิสเตอร์ 8 บิต ค่าสูงสุดที่ได้ก็คือ 255 (FFh) สิ แต่ FAh + 6h มันเกิน 255 ... ดูผลลัพธ์กันก่อน FAh + 6h = 100h ใช่ไหม ... เอาล่ะ ดูที่ Flag นะครับ 100h แต่ A เป็นรีจิสเตอร์ 8 บิต ดังนั้น มันเลยเอาค่า 00 มาเก็บใน A พอ A เป็น 0 , Z ก็เลยเป็น 1 ... มีการเกิด Overflow เพราะค่าเกิน 255 ดังนั้น O น่าจะเป็น 1 แต่ debug ของบอร์ด ET-V6 ไม่ยักแสดงผล (แฮะ) แต่ผมลองใช้ Hex File ตัวนี้ไปทำงานบน Simulator ค่าของ P/V ก็ เปลี่ยนเป็น 1 นะครับ ยังไง ผมจะลองสอบถาม ทาง ETT อีกครั้งนึงล่ะกันครับ ... มาดูแฟล็ก ตัวต่อมาดีกว่า ... จากการทำงานของคำสั่งนี้ เกิดการทดจากบิต 3 ไปบิต 4 (A+6 มันเกิน F) ทำให้ H=1 และมีการทดเกิดขึ้น C เลยเป็น 1 ด้วยครับ

ตัวอย่างส่วนที่ 2 : ทดสอบการลบด้วยค่าที่มากกว่า

ให้ A เก็บค่า 5

ให้ B เก็บค่า 14h

ทำการลบค่าที่เก็บใน A ด้วย B ... ผลที่ได้คือ S=1 ทั้งนี้เพราะ ตัวตั้งมีค่าน้อยกว่า ตัวที่นำไปลบ ทำให้ผลลัพธ์ที่ได้เป็นค่าลบ N =1 เพราะเป็นคำสั่ง ลบ และ C เป็น 1 เนื่องจากเกิดการยืมค่า ... ง่ายใช่ไหมครับ ... เอ๋ ผลลัพธืที่ได้เป็น F1h ทำไมเป็น F1h ล่ะ 5 ลบด้วย 20 ต้องได้ -15 จริงไหม ... ลองมาคิดกันนะครับ เดิม A มีค่า 5 เขียนเป็นเลขฐานสอง คือ 00000101 , ส่วน B ก็เป็น 00010100 แต่เวลาจะลบนั้น Z80 จะแปลง ตัวที่นำไปลบ ให้เป็นค่าแบบ 2's complement ก่อน แล้วนำผลที่ได้ไปบวกกับตัวตั้ง ดังนั้น 00010100 แปลงเป็น 2's complement ได้เป็น 11101100 แล้วนำไปบวก ก็จะได้เป็น 00000101 + 11101100 = 11110001 หรือเขียนเป็นฐานสิบหก คือ F1h ใช่ไหมครับ ... (ถ้าไม่เข้าใจ ลองอ่านดูอีกรอบนึง ถ้าไม่เข้าใจก็อ่านรอบต่อๆ ไปอีกนะครับ :D) ... มาดู ตัวอย่างการบวกค่าแบบ 16 บิตกันครับ

ตัวอย่างส่วนที่ 3 : ทดสอบ บวกแบบ 16 บิต

ในการบวกแบบ 16 บิตต้องอาศัย HL ครับ ที่ AF ใช้ไม่ได้ น่าจะเป็นเพราะรีจิสเตอร์ F ใช้เก็บ สถานะการทำงานน่ะครับ ... เริ่มด้วยการกำหนดให้ HL เก็บค่า FFh

เวลาบวกก็ต้องใช้ค่า 16 บิต หรือ ผ่านกับรีจิสเตอร์ 16 บิต ด้วยนะ ... งั้นใช้ BC ล่ะกันครับ ... กำหนดให้ BC เป็น 1 ล่ะกัน

มาบวกค่าดู ... ผลที่ได้คือ HL เป็น 0100h มี Sign flag เป็น 1 ทั้งนี้เพราะค้างมากจาก ตัวอย่างส่วนที่ 2 นะครับ ไม่เกี่ยวกับคำสั่งนี้

ตัวอย่างส่วนที่ 4 : ทดสอบ บวกค่าแบบ BCD

คราวนี้มาลองบวกค่าแบบ BCD กันนะครับ ... เริ่มต้นกำหนดให้ A เก็บค่า 16h

กำหนดให้ C เก็บค่า 55h

บวก A ด้วย C ผลที่ได้คือ 6B ... เอ ... ไม่ใช่การบวกแบบ BCD นี่นา ... เพราะถ้าบวกแบบ BCD เราเอา 16h บวกกับ 55h น่าจะเป็น 71h สิ ... อ้อ ... ลืมไปคำสั่งนึง

เอาล่ะเรียก DAA เพื่อแปลงผลลัพธ์ให้เป็นแบบ BCD ซะเลย ... เห็นไหม สำเร็จ A กลายเป็น 71h แล้ว

ตัวอย่างส่วนที่ 5 : ทดสอบ บวกค่าด้วย carry flag

บวกแบบธรรมดาไปแล้ว ... เรามาลองแบบ ใช้ carry flag กัน ... ว่าด้วยกำหนดให้ A เก็บ FFh

กำหนดให้ B เป็น 1

ทำการบวก A กับ B ได้ออกมาเป็น 00h ... ผลทำให้ CY เป็น 1 มีการทดข้ามจากบิต 3 ทำให้ H เป็น 1 และผลลัพธ์ที่ได้เป้น 0 ทำให้ Z เป็น 1 ...

เอาล่ะลองมาบวกแบบ มี carry กันครับ ตินนี้เรามี A เก็บค่า 00h , B เก็บค่า 1 และมีสถานะของ Carry flag เป็น 1 ดังผลลัพธ์ที่ได้ น่าจะเป็น 0+1+1 หรือ 02h ... เย้ .. ถูกหต้องใช่ไหม ตอนนี้ A เก็บค่า 2 เอาไว้แล้ว

ตัวอย่างส่วนที่ 6 : ทดสอบ ลบด้วย carry flag

ตวอย่างสุดท้าย เรามาทำการลบแบบสนใจ Carry flag กันบ้าง , ให้ A เก็บ 5

ให้ B เก็บ 7

ทำการลบค่าใน A ด้วย B ผลที่ได้คือ A กลายเป็น FEh ส่วนผลของ flag ลองหาเหตุผลดูนะครับ ว่าเป็นเพราะอะไร

คราวนี้กำหนดให้ A เป็น 9

ลองลบ A ด้วย B แบบ carry flag ดูล่ะนะ ... ตอนนี้ A เป็น 9, B เป็น 7 และ Carry Flag ตอนนี้เป็น 1 ดังนั้น 9h - 7h - 1 ผลลัพธ์ที่ได้ คือ 01h ครับ ... ลองดูนะครับว่ามาได้อย่างไร 9 เขียนเป็นเลขฐานสองคือ 00001001 , 7 เขียนเป็นเลขฐาน 2 คือ 00000111 แปลงเป็น 2's complement ได้เป็น 11111001 บวกกันได้ 00001001 + 11111001 = 00000010, แล้ว 1 (ค่าจาก carry flag ) แปลงเป็น 2's complement ได้เป็น 11111111 ดังนั้น ผลลัพธ์สุดท้ายคือ 00000010 + 11111111 = 00000001 ... เป็นไงครับ คำตอบสุดท้าย คือ 1 (ฟู่ ... อธิบายเหนื่อยเลยครับ)

จบโปรแกรมแล้วครับ

จบล่ะครับ ... บทความตอนนี้ยาวจริงๆ ยาวทั้งตัวอย่าง และการทำงาน ... ซับซ้อนนิดหน่อยเรื่องการ ลบ ... สงสัยไหมครับ ว่าทำไม ต้องเก็บค่าแบบ 2's complement ... ทำไม ไม่กำหนดให้ บิต 7 เป้น 1 หมายถึง ค่าลบ และถ้าบิต 7 เป็น 0 เป็นค่าบวกไปเลย ... เหตุผลข้อนึง คือ 00000000 มีค่าเท่ากับ 0 ใช่ไหม แล้ว 10000000 ล่ะ ... -0 หรือเปล่าครับ ... ถ้าเราสนแค่ ให้บิต 7 เป็นตัวบอกค่า บวก หรือ ลบ ... เราก็ติดตรงที่ว่า ค่า 0 ดันสามารถ เขียนแทนได้ทั้ง 2 แบบ ... ผลที่ได้ ก็ทำให้เกิดแนวคิดของ 2's complement ไงล่ะครับ ... ขอไม่อธิบายมากกว่านี้นะครับ เดี๋ยวมีคนยิ้ม (ฮ่าๆ คำถามนี้ผมใช้ถามนักศึกษาบ่อยๆ น่ะครับ ... ขอไม่อธิบายต่อนะครับ) ... สุดท้ายนี้หวังว่า ผู้อ่านคงสามารถทำการบวก และลบ เลขเป็นแล้วนะครับ (หมายถึงสั่งให้เจ้า Z80 มันบวก/ลบเลขน่ะครับ) ... แต่สงสัยไหมครับ ว่าทำไมผมไม่พูดถึงคำสั่งคูณ และหารเลย ... คำตอบก็คือ Z80 ไม่มีคำสั่ง คูณ และหาร ไง ! อ้าว แล้วจะทำยังไงดีล่ะ ... คำตอบคือ ต้องเขียนเองครับ ... เอาไว้ผ่านคำสั่งกระโดดแบบต่าง ๆ ไปก่อน เราค่อยมาศึกษา วิธีการคูณ และหาร กันอีกครั้งนึงล่ะกันครับ ... คราวหน้าจะเป็นคำสั่งที่เกี่ยวกับการ กระโดด และคำสั่ง CP ครับ ... ต่อไปเราก็จะเขียนโปรแกรม แบบเงื่อนไข หรือ วนรอบแบบต่างๆ กันได้แล้วครับ ... สัปดหาหน้าเจอกันอีกครับ


Download source code


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