GPIO ภาษาแอสเซมบลี (Assembly) กับ Arduino Uno
GPIO (General Purpose Input / Output) คืออินเทอร์เฟซที่ควบคุมด้วยซอฟต์แวร์ซึ่งมักพบในไมโครคอนโทรลเลอร์และไมโครโปรเซสเซอร์ ICs หรือชิปเซ็ตอินเทอร์เฟซบางตัว โดยทั่วไปแล้ว GPIO จะเป็นขาหนึ่งตัวขึ้นไปบน IC ซึ่งไม่มีจุดประสงค์พิเศษในตัวมันเอง แต่ช่วยอำนวยความสะดวกให้กับนักออกแบบอุปกรณ์ในการสร้างอินเทอร์เฟซ / การเชื่อมต่อระหว่าง IC และส่วนประกอบอุปกรณ์ต่อพ่วงโดยการเขียนโปรแกรมการลงทะเบียนฮาร์ดแวร์บางตัว ด้วยเหตุนี้ภายในข้อ จำกัด ขา GPIO สามารถปรับแต่งเพื่อใช้เพื่อให้มีฟังก์ชันหรือวัตถุประสงค์เฉพาะบางอย่างภายในการออกแบบอุปกรณ์ฮาร์ดแวร์
ในบทความนี้เราจะเห็นเทคนิคการเขียนโปรแกรม ภาษาแอสเซมบลี (Assembly) เพื่อตั้งค่า GPIO เป็น Digital แบบ Input และ Output โดยจะครอบคลุมโหมดทั้งหมดที่สามารถตั้งค่า GPIO ได้และเราจะเห็นการใช้งานจริงสำหรับแต่ละโหมด
- Output – Push Pull
- Input
- Internal Pull Up
- External Pull Up
- External Pull Down
GPIO เป็นเอาต์พุต – Push Pull
โปรแกรมแรกของระบบสมองกลฝังตัวมักจะเป็นโปรแกรมกะพริบ เริ่มต้นด้วย LED ใน บอร์ด Arduino Uno ภาษา Assembly ที่เราเคยทดสอบมา สิ่งนี้จะทำให้เริ่มต้นได้ง่ายเนื่องจากไม่ต้องใช้ฮาร์ดแวร์เพิ่มเติม
DDxn bit ใน DDRx Register จะเลือกทิศทางของขานี้ ถ้า DDxn เขียนลอจิกเป็น 1 จะมีการกำหนดค่า pxn เป็นขาเอาต์พุต ถ้า DDxn เขียนลอจิกเป็น 0 จะกำหนดค่า pxn เป็นขาอินพุต
ถ้า PORTxn ถูกเขียนลอจิกเมื่อกำหนดค่าขาเป็นขาเอาต์พุต ขาพอร์ตจะถูกขับเคลื่อนให้เป็น HIGH (1) ถ้า PORTxn เขียนลอจิกเป็น 0 เมื่อกำหนดค่าขาเป็นขาเอาต์พุตขาพอร์ตจะถูกขับเคลื่อนให้เป็น LOW (0)
เรามีพอร์ต 3 พอร์ตในไมโครคอนโทรลเลอร์ Atmega328P คือ พอร์ต B [PB], พอร์ต C [PC], พอร์ต D [PD]
จริงๆแล้ว พอร์ต B มี 8 ขา คือขา PB0 ถึง PB7 แต่ในการทำงานของบอร์ด Arduino UNO ขาของ PB6 และ PB7 ใช้สำหรับ Crystal Oscillator คือ PB6 หรือที่เป็นขาตัวเลข 9 เป็น XTAL1 และ PB7 หรือที่เป็นขาตัวเลข 10 เป็น XTAL2 ดังนั้นจึงไม่สามารถใช้ได้ เราจึงเหลือ 6 ขา ในการใช้งานเป็น GPIO คือ PB0 ถึง PB5
พอร์ต C มี 7 ขา คือขา PC0 ถึง PC6 แต่ในการใช้งานของบอร์ด Arduino UNO นั้น ขา PC6 หรือที่เป็นขาตัวเลข 1 จะใช้สำหรับ ~ RESET (ขารีเซ็ต) เราจึงสามารถใช้ได้เพียง 6 ขา ที่ เป็น GPIO คือ PC0 ถึง PC5
พอร์ต D มี 8 ขา คือ PD0 ถึง PD7 ในการใช้งานของบอร์ด Arduino UNO เราสามารถใช้ขาทั้ง 8 เป็น GPIO ได้อย่างมีประสิทธิภาพ
ให้เราดูแผนผังของการใช้งานแบบบอร์ด Arduino UNO และดูว่าความสามารถการใช้งานของขาต่างๆ
1: การเขียนโปรแกรม ภาษา Assembly กำหนดค่าเป็นเอาต์พุต
ตัวอย่างที่ 1: การกำหนดค่าขา PB0 ถึง PB5 ของพอร์ต B ของ Atmega328P ให้เป็นเอาต์พุตทั้งหมด
ldi r24, 0x3F ; // Load Immediate to Register r24 [ 1 CPU Cycle ]
out 0x04, r24 ; // Store Register to I/O Location 0x04 [ 1 CPU Cycle ]
ตัวอย่างที่ 2: การกำหนดค่า PB4 ขาเดียวให้เป็นเอาต์พุตโดยไม่ทำให้ขาอื่นๆเปลี่ยนแปลง
sbi 0x04, 4 ; // Set Bit in I/O Register 0x04 [ 2 CPU Cycle ]
ตัวอย่างที่ 3: การกำหนดค่าขาหลายๆขาเช่น PB2, PB3, PB4 เป็นเอาต์พุต
in r24, 0x04 ; // Load an I/O Location to Register r24 [ 1 CPU Cycle ]
ori r24, 0x1C ; // Logical OR r24 with Immediate value 0x1C [ 1 CPU Cycle ]
out 0x04, r24 ; // Store Register r24 to I/O Location 0x04 [ 1 CPU Cycle ]
2: การเขียนโปรแกรม ภาษา Assembly ขับเคลื่อนผลลัพธ์
ตัวอย่างที่ 1: การกำหนดขาของพอร์ต B ทั้งหมด คือขา PB0 ถึง PB5 ของ Atmega328P ให้เป็น HIGH
ldi r24, 0x3F ; // Load Immediate to Register r24 [ 1 CPU Cycle ] out 0x05, r24 ; // Store Register to I/O Location 0x04 [ 1 CPU Cycle ]
ตัวอย่างที่ 2: การกำหนดขา PB4 ขาเดียวให้เป็น HIGH โดยที่ขาอื่นๆไม่เปลี่ยนแปลง
sbi 0x05, 4 ; // Set Bit in I/O Register 0x05 [ 2 CPU Cycle ]
ตัวอย่างที่ 3: การกำหนดหลายๆขา เช่น PB2, PB3, PB4 ให้เป็น HIGH
in r24, 0x05 ; // Load an I/O Location to Register r24 [ 1 CPU Cycle ]
ori r24, 0x1C ; // Logical OR r24 with Immediate value 0x1C [ 1 CPU Cycle ]
out 0x05, r24 ; // Store Register r24 to I/O Location 0x05 [ 1 CPU Cycle ]
ตัวอย่างที่ 4: การกำหนดหลายๆขา เช่น PB1, PB2, PB5 ให้เป็น LOW
in r24, 0x05 ; // Load an I/O Location to Register r24 [ 1 CPU Cycle ]
andi r24, 0xD9 ; // Logical AND r24 with Immediate value 0xD9 [ 1 CPU Cycle ]
out 0x05, r24 ; // Store Register r24 to I/O Location 0x05 [ 1 CPU Cycle ]
ตัวอย่างที่ 5: การกำหนดขา PB5 ขาเดียวให้เป็น LOW โดยไม่ทำให้ขาอื่นๆเปลี่ยนแปลง
cbi 0x05, 5 ; // Clear Bit in I/O Register 0x05 [ 2 CPU Cycle ]
3: การเขียนโปรแกรม ภาษา Assembly Binky LED
โค้ดตัวอย่างเป็นการทำให้ LED ที่อยู่เชื่อมต่ออยู่ที่ ขา PB5 ของ ATmega328P กระพริบได้ ด้วยการหน่วงเวลา หรือเว้นระยะ จะใช้วิธีที่เรียกว่า Software Delay Loop
main:
sbi 0x04, 5 ; PORTB5 output
loop: ; main loop begin
sbi 0x05, 5 ; PORTB5 high
call delay_1000ms ; delay 1s
cbi 0x05, 5 ; 5 PORTB5 low
call delay_1000ms ; delay 1s
rjmp loop ; main loop
delay_1000ms: ; subroutine for 1s delay
; initialize counters
ldi r18, 0xFF ; 255
ldi r24, 0xD3 ; 211
ldi r25, 0x30 ; 48
inner_loop:
subi r18, 0x01 ; 1
sbci r24, 0x00 ; 0
sbci r25, 0x00 ; 0
brne inner_loop
ret
GPIO as Input – Internal Pull Up
ถ้า DDxn เขียนลอจิกเป็น 0 จะกำหนดค่า pxn เป็นขาอินพุต
ถ้า PORTxn ถูกเขียนลอจิกเมื่อกำหนดค่าขาเป็นพินอินพุตตัวต้านทานแบบ Pull Up จะเปิดใช้งาน ในการปิดตัวต้านทานแบบ Pull Up นั้น PORTxn จะต้องเขียนลอจิกเป็น 0 หรือต้องกำหนดค่าขาเป็นขาเอาต์พุต port pins are tri-stated when reset จะใช้งานได้แม้ว่าจะไม่มีนาฬิกาทำงานอยู่ก็ตาม
4: การเขียนโปรแกรม ภาษา Assembly กำหนดค่าเป็นอินพุต
ตัวอย่างที่ 1: การกำหนดค่าขาทั้งหมดของพอร์ต B คือ ขา PB0 ถึง PB5 ของ ATmega328P เป็นอินพุต
ldi r24, 0xC0 ; // Load Immediate to Register r24 [ 1 CPU Cycle ]
out 0x04, r24 ; // Store Register to I/O Location 0x04 [ 1 CPU Cycle ]
ตัวอย่างที่ 2: การกำหนดค่า PB4 ขาเดียวให้เป็นอินพุต โดยไม่ทำให้ขาอื่นๆเปลี่ยนแปลง
cbi 0x04, 4 ; // Clear Bit in I/O Register 0x04 [ 2 CPU Cycle ]
ตัวอย่างที่ 3: การกำหนดค่าหลายๆขา เช่นขา PB2, PB3, PB4 ให้เป็นอินพุต
in r24, 0x04 ; // Load an I/O Location to Register r24 [ 1 CPU Cycle ]
andi r24, 0xE3 ; // Logical AND r24 with Immediate value 0xE3 [ 1 CPU Cycle ]
out 0x04, r24 ; // Store Register r24 to I/O Location 0x04 [ 1 CPU Cycle ]
5: การเขียนโปรแกรม ภาษา Assembly เปิดใช้งาน Pull Up ภายใน
ตัวอย่างที่ 1: การเปิดใช้งาน Internal Pull Up ขาที่ใช้งานทั้งหมดของพอร์ต B คือขา PB0 ถึง PB5
ldi r24, 0xC0 ; // Load Immediate to Register r24 [ 1 CPU Cycle ]
out 0x05, r24 ; // Store Register to I/O Location 0x05 [ 1 CPU Cycle ]
ตัวอย่างที่ 2: การเปิดใช้งาน Internal Pull Up ขา PB4 ขาเดียว
sbi 0x05, 4 ; // Set Bit in I/O Register 0x05 [ 2 CPU Cycle ]
6: การเขียนโปรแกรม ภาษา Assembly จับอินพุต
ตัวอย่างที่ 1: การจับอินพุตของขาทั้งหมดของพอร์ต B คือขา PB0 ถึง PB5
in r24, 0x03 ; // Store I/O Location to Register r24 [ 1 CPU Cycle ]