Hello world I am AVR part II
Hello world I am AVR part II
จากตอนที่ 1 เราได้ทำกาต่อวงจรไฟฟ้าขั้นพื้นฐานเพื่อให้ไมโครคอนโทรลเลอร์ Atmega48 ของเราทำงานได้อย่างที่เราต้อง ซึ่งถ้าเพื่อนๆ ได้ใช้บอร์ดอื่นๆ หรือไอซีเบอร์อื่นๆ ที่ไม่ใช่เบอร์ Atmega48 ก็สามารถทำตามได้ โดยหลักการของไอซีค่าย Atmel แล้ว ก็ไม่ต่างกันมาก
ในตอนที่ 2 นี้ เราจะมาเริ่มลงมือเขียนส่วนที่เป็น Firmware ของไอซีสมองกลฝังตัว อย่างที่ผมเคยยกตัวอย่างไปแล้วเจ้า Firmware ที่เราจะเขียนนี้ ก็เปรียบเสมือนระบบปฏิบัติการเช่น Windows หรือ Linux ที่เราคุ้นเคยกันนั่นเอง ต่างกันตรงที่ความซับซ้อน และขนาดหน่วยความจำที่ค่อนข้างจำกัดจำเขียด จะประกาศตัวแปรหรือทำอะไร ก็ต้องคอยระมัดระวังรอบคอบ อย่าให้ขนาดโค๊ด (Code size) เกินหน่วยความจำที่ใช้เก็บโค๊ด (Flash memory) ที่อยู่อย่างจำกัด ไม่เหมือนกับคอมพิวเตอร์ที่มีมากมาย ดังนั้นแล้ว การเขียนโค๊ดให้กับไมโครคอนโทรลเลอร์ เป็นเรื่องที่ค่อนข้างจะลำบากสำหรับมือใหม่ ที่ต้องใช้ความระมัดระวังอย่างมาก
แต่ในเบื่องต้น เป้าหมายของเราคือต้องการให้หลอด LED ที่ต่ออยู่กับขา digital output ของไมโครคอนโทรลเลอร์ทำการสั่งให้หลอด LED ติดดับเป็นจังหวะได้ ซึ่งขนาดของโค๊ดกับงานแค่นี้ ไม่น่าจะเกินหน่วยความจำ ฉะนั้นในเบื้องต้น ตัดความกังวลเรื่องขนาดโค๊ดเกินหน่วยความจำไปได้เลย
เอาหล่ะ เรามาเริ่มจากเปิดโปรแกรม Atmel Studio ที่เราได้ติดตั้งกันไปก่อนหน้านี้ขึ้นมาครับ จะปรากฏหน้าต่างดังรูป
เราจะมาเริ่มสร้างโปรเจคกันก่อน หากใครที่เคยใช้โปรแกรมอย่างเช่นพวก Visual Studio จะพบว่า โปรแกรม Atmel Studio มีลักษณะ IDE ที่คล้ายกันมาก (จริงๆ พื้นฐานก็มาจากที่เดียวกัน)
ก่อนที่เราจะเริ่มสร้างโปรเจคเพื่อเขียนโปรแกรม เราจะต้องทำการออกแบบโค๊ดโปรแกรมของเราให้ทำงานสอดคล้องกับฮาร์ดแวร์ของเราเสียก่อน เราอาจจะออกแบบในกระดาษ โดยเขียนเป็น flow chart หรืออะไรก็ตามแต่ ที่เราถนัด ในที่นี้โปรแกรมของเราไม่ได้ซับซ้อนอะไร ผมเลยขออนุญาตที่จะเขียนบนหน้าเว็บอธิบายหลักการทำงานคร่าวๆ ก็แล้วกัน
เป้าหมายของเราก็คิือ เราต้องการให้เกิดการติดดับของหลอด LED ที่ต่ออยู่กับขาของไอซี Atmega48 ขา digital output ขาใดขาหนึ่ง ดังนั้น ก่อนที่เราจะลงมือเขียนโปรแกรมนั้น เราก็ต้องเลือกขาของไอซีที่จะมาทำงานเสียก่อน เนื่องจากงานของเราต้องการให้มีสัญญาณดิจิตอลออกที่ขาใดขาหนึ่ง ผมขอเลือกที่จะใช้ PORTC ก็แล้วกัน (จริงๆ เลือก PORTD หรือ PORTB ก็ไม่ผิด ทำงานได้เหมือนกัน) ให้ย้อนกลับไปที่ดาต้าชีทอีกครั้ง ในหน้าที่เกี่ยวกับ 14.2.1 Configuring the pin อธิบายไว้ดังนี้
หากต้องการควบคุมการทำงานของ Digital I/O แล้ว จะต้องเกี่ยวข้องกับ Register 3 ตัว (ย้ำนะครับว่า เรากำลังทำงานกับ Digital I/O มันก็เลยมีแค่ 3 Register ที่เกี่ยวข้อง) คือ DDRxn , PORTxn , PINxn โดยที่
x คือชื่อ PORT เช่น PORTC ค่า x ก็เท่ากับ C
n คือตำแหน่งบิต นั้นๆ เช่น bit ที่ 0 ของ PORTC0 ค่า n ก็เืท่ากับ 0
ที่นี้ ถ้าผมเขียนแบบนี้ PORTC ผมกำลังอ้างถึงค่าที่เก็บได้ 8 บิต (ค่าตั้งแต่ 0-255)
และถ้าผมเขียน PORTC0 ผมกำลังอ้างถึงค่าที่เก็บได้แค่ 1 บิต (มีแค่ 0 กับ 1)
ศึกษาเรื่องบิตข้อมูลกับ PORT ได้ที่นี่ การทำงานข้อมูลระดับบิตในภาษาซี
กลับมาที่เรื่องที่เราจะกำหนดการทำงานของขา Digigtal I/O ก่อน เนื่องจากขา Digital I/O Port C สามารถทำงานได้แบบ Bi-Directional นั่นแปลว่า มันสามารถทำหน้าที่เป็นได้ทั้ง Input และ Output digital ขึ้นอยู่กับว่าเราจะกำหนดให้มันเป็นอะไร โดยมันจะถูกควบคุมจากรีจิสเตอร์ที่ชื่อ DDRC นั่นเอง
ในการเซตให้ขาใดขาหนึ่งทำงานเป็น output เราจะกำหนดให้มีค่าเป็น 1 ในทางตรงกันข้าม หากต้องการให้เป็น input เราจะให้เป็น 0 ดังนั้น หากผมกำหนด DDRC |= 0x02; ก็จะมีความหมายดังรูปด้านล่าง
ต่อมาเมื่อเราได้กำหนดหน้าที่ขา Digital ที่จะเป็น output แล้ว หน้าที่ต่อไปก็คือ สั่งให้มี output ออกที่ขานั้นๆ การที่จะสั่งให้มี output ที่เป็นสัญญาณดิจิตอลออกไปที่ขานั้นๆ ได้นั้น ต้องอาศัยรีจิสเตอร์ที่ชื่อ PORTC หากเรากำหนดให้ค่าเป็น 1 ที่บิตใดๆ ในพอร์ตนั้นๆ จะทำให้เกิดสัญญาณดิจิตอล Logic High ออกที่ขานั้น ในทางตรงกันข้ามหากให้บิตใดๆ มีค่าเป็น 0 จะทำให้เกิดสัญญาณดิจิตอลที่เป็น Logic Low ออกที่ขานั้นๆ ซึ่งมีค่าเที่ยบเท่ากับกราวด์ของวงจร
เมื่อพิจารณาดูจากรูปภาพ ผมกำหนดให้ PORTC = 0x02; นั่นแปลว่าผมให้ตำแหน่งบิตที่ 2 คือ PORTC1 มีค่าเป็น 1 นั่นเอง ที่เหลือเป็น 0
หากไม่เข้าใจเรื่อง 0x02 ทำไมได้ตำแหน่งบิตที่ 2 มีค่าเป็น 1 ให้ศึกษาเรื่องการแปลงเลขฐานสิบหก ไปเป็น ฐานสอง เพิ่มเติมในวิชาดิจิตอล ครับ
ส่วนอีกรีจิสเตอร์ที่ยังไม่พูดถึงตอนนี้คือ PINx ครับ เพราะตอนนี้เราไม่ได้ใช้ความสามารถในการทำให้ขาดิิจิตอลเป็น input เลยขอข้ามไปก่อน เดี๋ยวจะงงไปกันใหญ่
และในที่นี้สัญญาณดิจิตอลของเราจะมีอยู่ 2 สถานะ คือ Logic High กับ Logic Low ถ้ามองเป็นแรงดันไฟฟ้า ก็มีค่าคือ 5V (เท่ากับแหล่งจ่าย) กับ 0V ซึ่งเกินพอที่จะทำให้หลอด LED ที่ต่ออยู่กับขาดิจิตอลเกิดการติดดับได้ ที่ผมบอกว่า "เกินพอ" นั้นหมายความว่า จริงๆ แล้ว แรงดันแค่ 1.8 - 3.3V ก็ทำให้หลอด LED ทั่วๆไป ติดได้แล้วครับ (อ่านเพิ่มเติม) แต่ที่นี้ หากแรงดันเมื่อเป็น Logic High ออกมาจากขาไมโครแล้ว จะทำให้หลอด LED เสียหายได้ ดังนั้นเราจึงมักใช้ตัวต้านทาน (Resistor) ต่ออนุกรมไว้กับหลอด LED ด้วย เพื่อปัองกันความเสียหาย ค่าที่ใส่มักอยู่ในช่วง 220 - 560 โอห์ม ครับ หากเรามีตัวต้านทานที่อยู่ในช่วงนี้ หาค่าได้ค่าไหน ก็เอาค่านั้นครับ พออนุโลมได้
ว่าจะเขียนแต่เรื่องการพัฒนาซอร์ฟแวร์อย่างเดียว แต่ก็อดเข้าไปเกี่ยวข้องกับทางฮาร์ดแวร์ไม่ได้ มันเป็นอย่างนี้แหละครับ งานทางด้าน Embedded มันต้องใช้ความรู้ประสานกันทั้งสองอย่าง
พอเราได้ชื่อรีจิสเตอร์ที่เกี่ยวข้อง และได้กำหนดขาดิจิตอลที่จะใช้งานแล้ว เราก็มาจัดการลงมือเขียนโปรแกรมกันเลยครับ รายละเอียดหากเขียนเป็นตัวหนังสือ จะค่อนข้างยาวครับ ผมก็เลยรวบรัดจัดทำำเป็นคลิปวีดีโอเลย หลักๆ ก็มีดังนี้ครับ
สร้างโปรเจค กำหนดชื่อโปรเจค
เลือกเบอร์ไมโครคอนโทรลเลอร์
เขียนโค๊ด
คอมไพล์
ดูวิดีโอประกอบครับ
ซอร์โค๊ดหลัก
#define F_CPU 10000000UL
#include <util/delay.h>
#include <avr/io.h>
int main(void)
{
PORTC = 0x02;
DDRC |= 0x02;
while(1)
{
PORTC ^= 0x02;
_delay_ms(500);
//TODO:: Please write your application code
}
}
เมื่อจบขั้นตอนที่ 4 หากไม่มีข้อผิดพลาดใดๆ จะปรากฏข้อความ
Build succeeded.
========== Build: 1 succeeded or up-to-date, 0 failed, 0 skipped ==========
ผมอธิบายโค๊ดคร่าวๆ ดังนี้ครับ
#define F_CPU 10000000UL ผมกำหนดให้คำว่า F_CPU มีค่าเท่ากับ 10 ล้าน เพื่อเป็นตัวอ้างอิงบอกให้ฟังก์ชั่น _delay_ms นำไปคำนวนให้เกิดการ delay time เป็นเวลา 1 ms ศึกษาเพิ่มเติมได้ในไฟล์ delay.h ที่อยู่ในโฟวเดอร์ util ซึ่งค่า 10 ล้านได้มาจากค่า Crystal Oscillator ที่เราได้เลือกเป็นตัวสร้างสัญญาณนาฬิกาให้กับระบบนั่นเอง
#include <util/delay.h>
#include <avr/io.h> สองบรรทัดนี้ เป็นการดึงไฟล์ delay.h และ io.h เข้ามาร่วมในขั้นตอน compile ด้วย หากมีการอ้างถึงสิ่งที่อยู่ในไฟล์ทีั้งสอง แล้วเราไม่ได้ #include เข้ามา จะเกิด Compile error ครับ
int main(void) ใครเคยเขียนภาษาซีมา ไม่น่าจะมีปัญหา
PORTC = 0x02;
DDRC |= 0x02; อย่างที่ได้อธิบายไว้ตอนต้นแล้ว ที่ผมต้องกำหนดก่อน ก็เพราะเป็นค่าเริ่มต้นการทำงานหน่ะครับ
เมื่อโปรแกรมเดินทางมาจนถึง while(1){ ..... }
while(1)
{
PORTC ^= 0x02;
_delay_ms(500);
//TODO:: Please write your application code
}
ซึ่ง เป็นการสั่งให้กระบวนการทำงานเกิดการทำซ้ำเช่นนี้ไปเรื่อยๆ ผลก็คือ หลอดไฟ LED ที่ต่ออยู่ที่ PORTC บิตที่ 2 เกิดการ Toggle หรือติดดับ สลับไปมา ห่างกันเป็นระยะเวลา 500ms หรือ ครึ่งวินาที
เมื่อเสร็จขั้นตอนที่ 4 เราจะได้ไฟล์ hex (อ่านเพิ่มเติม) ซึ่งเป็นไฟล์ที่สามารถนำไปบรรจุลงในหน่วยความจำ Flash memory ได้ และเมื่อเราป้อนไฟแหล่งจ่ายให้กับตัวไอซีไมโครคอนโทรลเลอร์ กระบวนการโหลดคำสั่งไปทำงานจะดำเนินไปจนจบคำสั่ง
ในตอนต่อไป ก็มาถึงขั้นตอนการนำ hex้ file ไปบรรจุลงในหน่วยความจำ Flash memory ลงไปบนตัวไอซีแล้วครับ เดี๋ยวเรามาต่อกันใน ตอนที่ 3 ครับ