การเขียนโปรแกรม GPIO เบื้องต้น
GPIO (General Purpose Input / Output) คืออินเทอร์เฟซที่ควบคุมด้วยซอฟต์แวร์ซึ่งมักพบในไมโครคอนโทรลเลอร์และไมโครโปรเซสเซอร์ ICs หรือชิปเซ็ตอินเทอร์เฟซบางตัว โดยทั่วไปแล้ว GPIO จะเป็นขาหนึ่งตัวขึ้นไปบน IC ซึ่งไม่มีจุดประสงค์พิเศษในตัวมันเอง แต่ช่วยอำนวยความสะดวกให้กับนักออกแบบอุปกรณ์ในการสร้างอินเทอร์เฟซ / การเชื่อมต่อระหว่าง IC และส่วนประกอบอุปกรณ์ต่อพ่วงโดยการเขียนโปรแกรมการลงทะเบียนฮาร์ดแวร์บางตัว ด้วยเหตุนี้ภายในข้อ จำกัด ขา GPIO สามารถปรับแต่งเพื่อใช้เพื่อให้มีฟังก์ชันหรือวัตถุประสงค์เฉพาะบางอย่างภายในการออกแบบอุปกรณ์ฮาร์ดแวร์
ในบทความนี้เราจะเห็นเทคนิคการเขียนโปรแกรมเพื่อตั้งค่า AVR GPIO เป็น Digital แบบ Input และ Output โดยจะครอบคลุมโหมดทั้งหมดที่สามารถตั้งค่า GPIO ได้และเราจะเห็นการใช้งานจริงสำหรับแต่ละโหมด
- Output – Push Pull
- Input
- Internal Pull Up
- External Pull Up
- External Pull Down
GPIO เป็นเอาต์พุต – Push Pull
โปรแกรมแรกของระบบสมองกลฝังตัวมักจะเป็นโปรแกรมกะพริบ เริ่มต้นด้วย LED ใน วงจร ATmega328P ที่เราเคยทดสอบมา สิ่งนี้จะทำให้เริ่มต้นได้ง่ายเนื่องจากไม่ต้องใช้ฮาร์ดแวร์เพิ่มเติม
Registers (รีจิสเตอร์)
Atmel AVR เป็นไมโครคอนโทรลเลอร์ 8 บิต พอร์ตทั้งหมดกว้าง 8 บิต โดยมีพอร์ต 3 พอร์ต ทุกพอร์ตมีรีจิสเตอร์ ที่เชื่อมโยงกัน และแต่ละพอร์ตมี 8 บิต
รีจิสเตอร์ DDxn bit ใน DDRx จะเลือกทิศทางของขานี้ ถ้า 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 และดูว่าความสามารถการใช้งานของขาต่างๆ
BIT OPERANDS:
ฟังก์ชั่น BIT SHIFT (<< และ >>)
ฟังก์ชั่น Bit Shift ทำตามที่มันบอกมันจะเลื่อนข้อมูลไปทางซ้ายหรือทางขวาทีละนิด
ฟังก์ชัน Bit Shift เป็นฟังก์ชันคณิตศาสตร์บิตที่ใช้มากที่สุดเมื่อจัดการกับ AVR
ดังนั้นลองดูการดำเนินการนี้ในรูปแบบ mathematical โดยจะเว้นวรรคทุกๆอักขระที่ 4 เพื่อให้ตัวเลขอ่านง่ายดังนั้น 00011000 จะอ่านเป็น 0001 1000
เลื่อนบิตไปทางซ้าย:
ตอนนี้เราได้เห็นการขยับเล็กน้อยในแบบ mathematical มาดูในโค้ดกัน
i = 1;// i = 0000 0001
i = i << 2; // i is now = 0000 0100
i = i >> 2;// i is now = 0000 0001
NOT (~)
โดยพื้นฐานแล้วมันจะพลิกบิตไปตรงข้าม
~ 0 = 1
~ 1 = 0
หากใช้ใน big register จะเปลี่ยนแต่ละบิตให้อยู่ในสถานะตรงกันข้าม
~ 0000 1111 = 1111 0000
i = 0b10101010;
i = ~i;// i is now 01010101
i = 0b11111111;
i = ~i;// i is now 0
AND ( & )
ในทางคณิตศาสตร์และค่อนข้างง่ายคุณเปรียบเทียบตัวเลข 2 ตัวเข้าด้วยกันและถ้าทั้งคู่เป็น TRUE (1) คำตอบของคุณก็คือ TRUE (1)
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
OR and XOR ( | and ^ )
อาจทำให้สับสนเล็กน้อยมีฟังก์ชัน OR มี 2 ประเภทคือ INCLUSIVE หรือ (|) และ EXCLUSIVE OR (^)
OR (INCLUSIVE):
Inclusive หรือมักเรียกง่ายๆว่า OR
ฟังก์ชัน OR จะตรวจสอบว่า ether bit เป็น 1 หรือไม่
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
1: การเขียนโปรแกรม กำหนดค่าเป็นเอาต์พุต
ตัวอย่างที่ 1: การกำหนดค่าขา PB0 ถึง PB5 ของพอร์ต B ของ Atmega328P ให้เป็นเอาต์พุตทั้งหมด
DDRB = 0x3F; // ใช้ระบบเลขฐานสิบหก
หรือ
DDRB = 0b00111111; // ใช้ระบบเลขฐานสอง
ตัวอย่างที่ 2: การกำหนดค่า PB4 ขาเดียวให้เป็นเอาต์พุตโดยไม่ทำให้ขาอื่นๆเปลี่ยนแปลง โดยการ Bit Shift
DDRB |= 1 << 4; // เลื่อนบิตไปทางซ้าย 4 ตำแหน่ง
หรือ
DDRB |= 0b00010000; // ใช้ระบบเลขฐานสอง
หรือ
DDRB |= 0x10; // ใช้ระบบเลขฐานสิบหก
ตัวอย่างที่ 3: การกำหนดค่าขาหลายขาเช่น PB2, PB3, PB4 เป็นเอาต์พุตด้วยการ Bit Shift
DDRB |= 1<<4 | 1<<3 | 1<<2;
หรือ
DDRB |= 0b00011100;
หรือ
DDRB |= 0x1C;
2: การเขียนโปรแกรม ขับเคลื่อนผลลัพธ์
ตัวอย่างที่ 1: การกำหนดขาของพอร์ต B ทั้งหมด คือขา PB0 ถึง PB5 ของ Atmega328P ให้เป็น HIGH
PORTB = 0x3F; // ใช้ระบบเลขฐานสิบหก
หรือ
PORTB = 0b00111111; // ใช้ระบบเลขฐานสอง
ตัวอย่างที่ 2: การกำหนดขา PB4 ขาเดียวให้เป็น HIGH โดยที่ขาอื่นๆไม่เปลี่ยนแปลง
PORTB | = 1 << 4; // เลื่อนบิตไปทางซ้าย 4 ตำแหน่ง
หรือ
PORTB | = 0b00010000; // ใช้ระบบเลขฐานสอง
หรือ
PORTB | = 0x10; // ใช้ระบบเลขฐานสิบหก
ตัวอย่างที่ 3: การกำหนดหลายๆขา เช่น PB2, PB3, PB4 ให้เป็น HIGH
PORTB |= 1<<4 | 1<<3 | 1<<2;
หรือ
PORTB |= 0b00011100;
หรือ
PORTB |= 0x1C;
ตัวอย่างที่ 4: การกำหนดหลายๆขา เช่น PB1, PB2, PB5 ให้เป็น LOW
PORTB &= ~(1<<5 | 1<<2 | 1<<1);
หรือ
PORTB &= 0b11011001;
หรือ
PORTB &= 0xD9;
ตัวอย่างที่ 5: การกำหนดขา PB5 ขาเดียวให้เป็น LOW โดยไม่ทำให้ขาอื่นๆเปลี่ยนแปลง
PORTB &= ~(1 << 5); // เลื่อนบิตไปทางซ้าย 5 ตำแหน่ง
หรือ
PORTB &= 0b11011111; // ใช้ระบบเลขฐานสอง
หรือ
PORTB &= 0xDF; // ใช้ระบบเลขฐานสิบหก
3: การเขียนโปรแกรม Binky LED
โค้ดตัวอย่างเป็นการทำให้ LED ที่อยู่เชื่อมต่ออยู่ที่ ขา PB5 ของ ATmega328P กระพริบได้ ด้วยการหน่วงเวลา หรือเว้นระยะ จะใช้วิธีที่เรียกว่า Software Delay Loop
ตัวอย่าง : ใช้ระบบเลขฐานสอง
#define F_CPU 16000000UL // ให้นำค่า 16MHz ไปไปคำนวณร่วมกับฟังก์ชั่น delay.h ในการหน่วงเวลา
#include <avr/io.h> // ใช้งานเกี่ยวกับควบคุมอินพุท/เอาท์พุท (เช่น PORTB, DDRB)
#include <util/delay.h> // ใช้งานเกี่ยวกับการหน่วงเวลา
int main(void) // เป็นฟังก์ชั่นแรกที่โปแกรมเริ่มทำงาน
{ // เริ่มต้นบล็อกฟังก์ชั่น main()
DDRB |= 0B100000; // กำหนดให้ขา PB5 ทำงานแบบเอาท์พุท
while (1) // วนลูปไปเรื่อยๆ
{ // เริ่มต้นบล็อกในส่วนของ while (1)
PORTB |= 0B100000; // ให้ที่ขา PB5 ทำงานเป็น HIGH
_delay_ms(1000); // หน่วงเวลารอ 1 วินาที
PORTB &= ~ 0B100000; // ให้ที่ขา PB5 ทำงานเป็น LOW
_delay_ms(1000); // หน่วงเวลารอ 1 วินาที
} // สิ้นสุดบล็อกในส่วนของ while (1)
} // สิ้นสุดบล็อกฟังก์ชั่น main()
หรือ
ตัวอย่าง : เลื่อนบิตไปทางซ้าย Bit Shift
#define F_CPU 16000000UL // ให้นำค่า 16MHz ไปไปคำนวณร่วมกับฟังก์ชั่น delay.h ในการหน่วงเวลา
#include <avr/io.h> // ใช้งานเกี่ยวกับควบคุมอินพุท/เอาท์พุท (เช่น PORTB, DDRB)
#include <util/delay.h> // ใช้งานเกี่ยวกับการหน่วงเวลา
int main(void) // เป็นฟังก์ชั่นแรกที่โปแกรมเริ่มทำงาน
{ // เริ่มต้นบล็อกฟังก์ชั่น main()
DDRB |= 1<<5; // กำหนดให้ขา PB5 ทำงานแบบเอาท์พุท
while (1) // วนลูปไปเรื่อยๆ
{ // เริ่มต้นบล็อกในส่วนของ while (1)
PORTB |= 1<<5; // ให้ที่ขา PB5 ทำงานเป็น HIGH
_delay_ms(1000); // หน่วงเวลารอ 1 วินาที
PORTB &= ~(1<<5); // ให้ที่ขา PB5 ทำงานเป็น LOW
_delay_ms(1000); // หน่วงเวลารอ 1 วินาที
} // สิ้นสุดบล็อกในส่วนของ while (1)
} // สิ้นสุดบล็อกฟังก์ชั่น main()
GPIO as Input – Internal Pull Up
ถ้า DDxn เขียนลอจิกเป็น 0 จะกำหนดค่า pxn เป็นขาอินพุต
ถ้า PORTxn ถูกเขียนลอจิกเมื่อกำหนดค่าขาเป็นพินอินพุตตัวต้านทานแบบ Pull Up จะเปิดใช้งาน ในการปิดตัวต้านทานแบบ Pull Up นั้น PORTxn จะต้องเขียนลอจิกเป็น 0 หรือต้องกำหนดค่าขาเป็นขาเอาต์พุต port pins are tri-stated when reset จะใช้งานได้แม้ว่าจะไม่มีนาฬิกาทำงานอยู่ก็ตาม
4: การเขียนโปรแกรม กำหนดค่าเป็นอินพุต
ตัวอย่างที่ 1: การกำหนดค่าขาทั้งหมดของพอร์ต B คือ ขา PB0 ถึง PB5 ของ ATmega328P เป็นอินพุต
DDRB = 0xC0; // ใช้ระบบเลขฐานสิบหก
หรือ
DDRB = 0b11000000; // ใช้ระบบเลขฐานสอง
ตัวอย่างที่ 2: การกำหนดค่า PB4 ขาเดียวให้เป็นอินพุต โดยไม่ทำให้ขาอื่นๆเปลี่ยนแปลง
PINB |= (1 << 4); // เลื่อนบิตไปทางซ้าย 4 ตำแหน่ง
หรือ
DDRB &= ~(1 << 4); // เลื่อนบิตไปทางซ้าย 4 ตำแหน่ง
หรือ
DDRB &= 0b11101111; // ใช้ระบบเลขฐานสอง
หรือ
DDRB &= 0xEF; // ใช้ระบบเลขฐานสิบหก
ตัวอย่างที่ 3: การกำหนดค่าหลายๆขา เช่นขา PB2, PB3, PB4 ให้เป็นอินพุต
PINB |= (1<<4 | 1<<3 | 1<<2);
หรือ
DDRB &= ~(1<<4 | 1<<3 | 1<<2);
หรือ
DDRB &= 0b11100011;
หรือ
DDRB &= 0xE3;
5: การเขียนโปรแกรม เปิดใช้งาน Pull Up ภายใน
ตัวอย่างที่ 1: การเปิดใช้งาน Internal Pull Up ขาที่ใช้งานทั้งหมดของพอร์ต B คือขา PB0 ถึง PB5
PORTB = 0xC0; // ใช้ระบบเลขฐานสิบหก
หรือ
PORTB = 0b11000000; // ใช้ระบบเลขฐานสอง
ตัวอย่างที่ 2: การเปิดใช้งาน Internal Pull Up ขา PB4 ขาเดียว
PORTB |= 1 << 4;
หรือ
PORTB |= 0b00010000;
หรือ
PORTB |= 0x10;
6: การเขียนโปรแกรม รับค่าจากสวิตช์
ตัวอย่างที่ 1: การการรับค่าจากอินพุตของขาทั้งหมดของพอร์ต B คือขา PB0 ถึง PB5
uint8_t port_value = 0;
port_value = PINB; // ใช้ระบบเลขฐานสิบหก
<<< C2: อัพโหลดโค้ด ด้วย External Tools บทความก่อนหน้า | บทความต่อไป C4: Push Button กดติดปล่อยดับ >>>