การใช้งานซอฟต์แวร์ MPLAB-X IDE สำหรับ AVR ในเบื้องต้น#

Keywords: Atmel/Microchip AVR MCUs, ATmega328P, MPLAB-X IDE


MPLAB-X IDE vs. MPLAB Xpress IDE#

บทความนี้นำเสนอการทดลองใช้งานซอฟต์แวร์ MPLAB-X IDE v6.0.0 ร่วมกับ XC8 C Compiler v2.31 ของบริษัท Microchip ในเบื้องต้น และสาธิตการเขียนโปรแกรมภาษา C สำหรับไมโครคอนโทรลเลอร์ AVR MCU เช่น ATMega328P

หลังจากที่บริษัท Microchip ได้เข้าควบรวมกิจกรรมของบริษัท Atmel ในปีค.ศ. 2016 ก็ได้พัฒนาให้ซอฟต์แวร์ MPLAB-X IDE (ตั้งแต่เวอร์ชัน v5.05 และเปิดให้ดาวน์โหลดไปใช้งานในเดือนตุลาคม ค.ศ. 2018) และ MPLAB Xpress IDE รองรับการใช้งานไมโครคอนโทรลเลอร์ของบริษัท Atmel สำหรับ AVR (8 บิต) และ SAM (32 บิต)

ในช่วงต่อมาได้มีการปรับปรุงซอฟต์แวร์ Atmel Studio 7 IDE ภายใต้ชื่อใหม่คือ Microchip Studio IDE แต่ผู้ใช้คงจะต้องพิจารณาเลือกเองว่า จะใช้ซอฟต์แวร์ใดเป็นหลักในการทำงานต่อไปอนาคต หากว่า ยังใช้ชิปไมโครคอนโทรลเลอร์ของ Atmel

จุดเด่นข้อหนึ่งของ MPLAB-X IDE คือ เป็นซอฟต์แวร์ประเภท Cross-platform มีให้เลือกใช้ได้สำหรับระบบปฏิบัติการ Windows, Linux และ Mac OS X ในขณะที่ Microchip Studio IDE จะใช้ได้เฉพาะกับระบบปฏิบัติการ Windows เท่านั้น

ซอฟต์แวร์รองรับไมโครคอนโทรเลอร์ในตระกูลต่าง ๆ ของบริษัท Atmel / Microchip ที่มีให้เลือกทั้งตัวประมวลผลแบบ 8 บิต ไปจนถึง 32 บิต ผู้ใช้สามารถทำขั้นตอนดีบักโค้ดโดยใช้ตัวจำลองการทำงาน (Simulator) เช่น สำหรับ AVR, SAMC/SAMD และ PIC32/PIC32C เป็นต้น

รูป: MPLAB-X Desktop IDE (v6.0.0) สาธิตการเขียนโค้ดภาษา C สำหรับ AVR MCU

 


ขั้นตอนการใช้งาน#

หากยังไม่เคยใช้งานซอฟต์แวร์มาก่อน ให้ผู้ใช้ดาวน์โหลดและติดตั้งซอฟต์แวร์ MPLAB-X IDE และ XC8 C Compiler ในเครื่องคอมพิวเตอร์ของผู้ใช้ก่อน (สำหรับบทความนี้ ได้ทดลองใช้ซอฟต์แวร์สำหรับระบบปฏิบัติการ Windows 10)

ขั้นตอนการทดลองใช้งานเบื้องต้น มีดังนี้

  1. สร้างโปรเจกต์ใหม่ พร้อมระบุชื่อโปรเจกต์และเลือกชิปไมโครคอนโทรลเลอร์ AVR
  2. สร้างไฟล์ main.c ในโปรเจกต์ใหม่ สำหรับเขียนโค้ด
  3. ลองเขียนโค้ดตามตัวอย่างและทำขั้นตอน Build Project เพื่อคอมไพล์โค้ด
  4. ทดลองการทำงานของโปรแกรมโดยวิธีดีบักโค้ด (Source-Level Debugging) โดยใช้ตัวจำลองการทำงาน (Simulator) เพื่อช่วยในการตรวจสอบหรือหาข้อผิดพลาดในการทำงานของโค้ด
  5. เลือกบรรทัดของโค้ดในไฟล์ main.c เพื่อใช้เป็นตำแหน่งของ Breakpoints เมื่อมีการทำคำสั่งมาถึงบรรทัดดังกล่าว จะมีการหยุดทำคำสั่งชั่วคราว หรือจะทำคำสั่งต่อไปก็ได้
  6. ดูการเปลี่ยนแปลงที่เกิดขึ้นกับฮาร์ดแวร์ เช่น รีจิสเตอร์ต่าง ๆ ของซีพียู ตัวแปรที่มีอยู่ภายในโปรแกรม เป็นต้น

 


การสร้างโปรเจกต์ใหม่#

เริ่มต้นด้วยการเปิดใช้งานซอฟต์แวร์ MPLAB-X IDE แล้วสร้างโปรเจกต์ใหม่

รูป: เริ่มต้นขั้นตอนการสร้างโปรเจกต์ใหม่

รูป: เลือกรูปแบบของโปรเจกต์ใหม่ เป็นแบบ Standalone Project

รูป: เลือกชิปไมโครคอนโทรลเลอร์ ในตัวอย่างนี้คือ ATmega - ATmega328P และเลือกเครื่องมือ Tool - Simulator สำหรับการตรวจสอบการทำงานด้วยตัวจำลองการทำงาน (ไม่ได้ใช้อุปกรณ์สำหรับการดีบักโปรแกรมด้วยฮาร์ดแวร์จริง)

รูป: เลือกซอฟต์แวร์ Compiler Toolchains ที่จะใช้สำหรับการคอมไพล์โค้ดในโปรเจกต์ ในตัวอย่างนี้ได้เลือกใช้ Microchip XC8 C Compiler

รูป: ระบุชื่อของโปรเจกต์ใหม่และไดเรกทอรีสำหรับเก็บไฟล์ต่าง ๆ ของโปรเจกต์

เมื่อได้สร้างโปรเจกต์ใหม่สำหรับ ATmega328P แล้ว ถัดไปให้สร้างไฟล์ main.c ให้เป็นส่วนหนึ่งของโปรเจกต์ สำหรับทดลองเขียนโค้ด

รูป: สร้างไฟล์เพิ่มในโปรเจกต์สำหรับเขียนโค้ด (File > New > main.c)

รูป: ตั้งชื่อไฟล์เป็น main.c

โค้ดตัวอย่างต่อไปนี้ สาธิตการเขียนคำสั่งเพื่อทำให้ขา GPIO หมายเลข PB5 ของไมโครคอนโทรลเลอร์ ATmega328P สลับสถานะลอจิกทุก ๆ 100 มิลลิวินาที โดยใช้การรอเวลาและเรียกใช้ฟังก์ชัน _delay_ms(...) และ หากใช้บอร์ด Arduino Uno หรือ Nano จะพบว่า มีวงจร LED อยู่บนบอร์ดแล้วที่ขา PB5 หรือ Arduino D13 Pin

ให้ลองคอมไพล์โค้ดตัวอย่าง เพื่อตรวจสอบความถูกต้องในเบื้องต้น โดยทำคำสั่งจากเมนู Production > Build Project

#define F_CPU   16000000UL  // CPU clock speed

// This header file is included when using the XC8 compiler.
#include <xc.h>
#include <util/delay.h> // for _delay_ms()

int main(void) {
   DDRB |= _BV(DDB5); // set PB5 pin as output
   while(1) {
      PORTB |= _BV(PB5);  // output high to PB5 pin
      _delay_ms( 100 );   // busy-wait delay for 100ms
      PORTB &= ~_BV(PB5); // output low to PB5 pin
      _delay_ms( 100 );   // busy-wait delay for 100ms
   }
   return 0;
}

รีจิสเตอร์ DDRB เกี่ยวข้องกับการกำหนดทิศทางอินพุตหรือเอาต์พุตที่ขาของพอร์ต B ดังนั้นจะต้องกำหนดให้บิตในตำแหน่งที่ 5 ของรีจิสเตอร์ มีค่าเป็น 1 เพื่อใช้งานขา PB5 เป็นเอาต์พุต (1 หมายถึง เอาต์พุต และ 0 หมายถึง อินพุต)

การกำหนดสถานะลอจิกสำหรับเอาต์พุตของพอร์ต B เกี่ยวข้องกับการทำงานของรีจิสเตอร์ PORTB ดังนั้นการเซตหรือเคลียร์บิตที่ 5 ของรีจิสเตอร์ดังกล่าว จะเป็นตัวกำหนดสถานะลอจิก HIGH หรือ LOW ตามลำดับ

ในไลบรารี avr-libc มีฟังก์ชัน _delay_ms(...) ซึ่งทำหน้าที่หน่วงเวลาแบบ busy-wait delay และได้ถูกการประกาศเอาไว้ในไฟล์ util/delay.h เป็นแบบ inline function ดังนั้นเมื่อเรียกใช้ฟังก์ชันนี้ (Function Call) คอมไพเลอร์จะแทนที่คำสั่งเรียกฟังก์ชันด้วยโค้ดที่อยู่ภายในฟังก์ชันดังกล่าว

static inline void _delay_ms(double __ms) __attribute__((always_inline));

นอกจากนั้น จะต้องมีการกำหนดค่าของสัญลักษณ์ F_CPU เพื่อระบุความถี่ที่ใช้ในการทำงานของซีพียู AVR ในฟังก์ชัน _delay_ms(...) จะมีการเรียกใช้ฟังก์ชันอีกฟังก์ชันหนึ่งชื่อ _delay_loop_2(...) ที่ทำคำสั่งแบบวนลูป (Delay loop) เพื่อนับนับถอยหลังทีละหนึ่งจากค่าเริ่มต้น หนึ่งรอบของการนับใช้เวลา 4 ไซเคิล เช่น ถ้าความถี่ F_CPU เท่ากับ 1MHz จะนับถอยหลังโดยใช้เวลาสูงสุดไม่เกิน 262.14 มิลลิวินาที ( วินาที) ต่อการเรียกฟังก์ชันนี้หนึ่งครั้ง

void _delay_loop_2(uint16_t __count);

 


การดีบักด้วย Software Simulator#

การดีบักโปรแกรมที่ได้จากการคอมไพล์โค้ด ทำได้สองวิธีคือ การจำลองการทำงาน (ใช้ซอฟต์แวร์ที่เรียกว่า Simulator) และวิธีที่สองคือ การใช้ฮาร์ดแวร์ซึ่งประกอบด้วยบอร์ดไมโครคอนโทรลเลอร์ และอุปกรณ์สำหรับการดีบักในวงจร (In-Circuit Debugger)

ในบทความนี้ เราจะใช้วิธีการทดสอบการทำงานของโค้ดตัวอย่างในขั้นตอนดีบัก (เมนูคำสั่ง Debug > Debug Project) โดยใช้ตัวจำลองการทำงาน ซึ่งเป็นวิธีที่สะดวก และไม่จำเป็นต้องมีอุปกรณ์จริง

การเข้าสู่ Debug Session จะมีสองขั้นตอนคือ การคอมไพล์โค้ดสำหรับดีบัก (Build for Debugging) และเริ่มต้นการทำงานของดีบักเกอร์ (Launch Debugger)

รูป: เมนูคำสั่งที่เกี่ยวข้องกับการดีบัก

ก่อนเริ่มขั้นตอนดีบัก ให้กำหนดตำแหน่งหยุดชั่วคราวของดีบักเกอร์ในโค้ด main.c เช่น อาจจะกำหนดให้บรรทัดแรกภายใน ฟังก์ชัน main(){...} เป็นตำแหน่งหยุดแรก ตำแหน่งหยุดในลักษณะนี้เรียกว่า Breakpoint

รูป: การตั้งค่าในส่วนที่เกี่ยวข้องกับการทำงานของ Simulator เช่น การกำหนดความถี่หรือความเร็วในการทำคำสั่ง (Instruction Frequency ซึ่งมีค่า Default เท่ากับ 1MHz) เป็นต้น

รูป: เข้าสู่ Debug Session สำหรับโปรเจกต์ และมีการหยุดชั่วคราวในตำแหน่งแรกของ Breakpoint

จากรูปตัวอย่างข้างล่าง จะเห็นได้ว่า มีการกำหนดตำแหน่งหยุดไว้ที่บรรทัด 10 และ 12 ตามลำดับ ซึ่งอยู่ภายในคำสั่งวนซ้ำแบบ while

รูป: เมื่อดีบักเกอร์ทำงานต่อไป (Continue) จะไปหยุดที่ตำแหน่งของ Breakpoint ในลำดับการทำงานถัดไป และให้สังเกตข้อความในหน้าต่าง Stopwatch

ในระหว่างการดีบัก ผู้ใช้สามารถเปิดหน้าต่างย่อยเพื่อดูสถานะการทำงานของไมโครคอนโทรลเลอร์ได้ เช่น

  • Stopwatch แสดงค่าของ Cycle Count ที่มีการนับจำนวนไซเคิลของซีพียูและบันทึกเอาไว้ และแสดงค่าตัวเลขล่าสุด เมื่อมีการหยุดการทำงานของดีบักเกอร์ชั่วคราวในแต่ละครั้ง
  • Window > Debugging > Variables แสดงค่าของตัวแปรที่มีการประกาศใช้ในโค้ด
  • Window > Debugging > Breakpoints แสดงตำแหน่งหยุดการทำงานชั่วคราวที่ได้มีการกำหนดไว้โดยผู้ใช้
  • Window > Debugging > Watches แสดงค่าของตัวแปรหรือรีจิสเตอร์เฉพาะรายการที่ได้เลือกมาโดยผู้ใช้
  • Window > Debugging > Call Stack แสดงลำดับชั้นของการเรียกฟังก์ชันต่าง ๆ ในโค้ด เช่น เริ่มต้นจากการทำงานของ Startup Code เมื่อหลังจากรีเซต เช่น gcrt1.S ของ avr-libc แล้วเรียกฟังก์ชัน main() ซึ่งอาจมีการเรียกฟังก์ชันอื่นต่อไปอีกในขณะทำงาน
  • Window > Debugging > IO View แสดงค่าในรีจิสเตอร์ที่เกี่ยวข้องกับวงจรภายในของชิป (Peripheral Registers)
  • Window > Debugging > Disassembly แสดงคำสั่งในภาษาแอสเซมบลีในแต่ละฟังก์ชันที่ได้จากการแปลงโค้ดภาษา C
  • Simulator > IO Pins แสดงสถานะลอจิกของขา I/O ที่ผู้ใช้เจาะจงเลือกมา

รูป: หน้าต่าง IO View แสดงค่าในรีจิสเตอร์ที่เกี่ยวข้องกับวงจรภายในของชิป เช่น การดูค่าของรีจิสเตอร์ที่เกี่ยวข้องกับการทำงานของพอร์ต B

รูป: หน้าต่าง I/O Memory (SFRs) แสดงค่าในรีจิสเตอร์ทั้งหมดที่เกี่ยวข้องกับวงจรภายใน

รูป: แสดงโค้ดในภาษา AVR Assembly ที่ได้จากการแปลงโค้ดภาษา C ในไฟล์ main.c

 


การแสดงสัญญาณ I/O เมื่อจำลองการทำงาน#

ถัดไปเป็นการสาธิตการจำลองการทำงานและแสดงรูปคลื่นสัญญาณที่เกิดขึ้นกับขา PB5 ของไมโครคอนโทรลเลอร์ ซึ่งมีการทำคำสั่งในลูป while(1){...} เขียนค่าลงในรีจิสเตอร์ PINB เพื่อทำให้เกิดการสลับสถานะลอจิก ที่ขา PB5 โดยไม่มีการหน่วงเวลา

#define F_CPU   16000000UL  // CPU clock speed 16MHz
// This header file is included when using the XC8 compiler.
#include <xc.h>

int main(void) {
  DDRB |= _BV(DDB5); // output pin for LED
  while (1) {
     PINB |= _BV(PINB5); // toggle LED
  }
  return 0;
}

เมื่อแก้ไขโค้ดในไฟล์ main.c และคอมไพล์โค้ดแล้ว ให้เริ่มต้นขั้นตอนดีบักอีกครั้ง แล้วเปิดหน้าต่าง Window > Simulator > Logic Analyzer

รูป: เปิดหน้าต่าง Simulator > Logic Analyzer

รูป: คลิกเลือกขาหมายเลข PB5 (Selected Pin)

รูป: ในหน้าต่าง Logic Analyzer Settings จะเห็นว่า บัปเฟอร์สำหรับบันทึกข้อมูลมีขนาดจำกัด (Buffer Size สูงสุด 10,000 records)

รูป: แสดงรูปคลื่นสัญญาณที่ขา PB5 ที่เป็นผลจากการทำงานของโปรแกรม แกนนอนแสดงจำนวนไซเคิลของการทำคำสั่ง (Instruction Cycles)

รูป: แสดงรูปคลื่นสัญญาณที่ขา PB5 และตัวเลขในแกนนอนเป็นเวลา (ในหน่วยนาโนวินาที: nano sec) หากเปลี่ยนไปแสดงค่าตัวเลขเชิงเวลาแทนจำนวนไซเคิล

รูป: แสดงข้อความในหน้าต่าง Stopwatch จะเห็นได้ว่า การทำซ้ำในหนึ่งรอบเพื่อทำคำสั่งสลับสถานะลอจิกที่ขา PB5 จะใช้เวลา 10 ไซเคิล หรือ ซึ่งเป็นความกว้างของพัลส์ (ช่วงที่เป็น HIGH และ LOW) ของสัญญาณเอาต์พุต

รูป: ตัวอย่างโค้ดในภาษา AVR Assembly ของโค้ดตัวอย่างที่ได้จากขั้นตอน Disassembly

 


การกำหนดสัญญาณอินพุตเพื่อทดสอบการทำงานของโปรแกรม#

โค้ดตัวอย่างนี้สาธิตการเปิดใช้งานอินเทอร์รัพท์ภายนอก หมายเลข 0 (External Interrupt 0) ของ AVR ที่ขา PD2 (พอร์ต D บิตที่ 2) ซึ่งตรงกับขา D2 ของบอร์ด Arduino

เมื่อมีการเปลี่ยนแปลงลอจิกที่ขาอินพุต PD2 และเป็นขอบขาลง จะเกิดอินเทอร์รัพท์ และฟังก์ชัน ISR (Interrupt Service Routine) ที่เกี่ยวข้องจะทำงาน และทำให้เกิดการสลับสถานะลอจิกหนึ่งครั้งที่ขา PB5 (ขา D13 ของบอร์ด Arduino)

#define F_CPU   16000000UL  // CPU clock speed 16MHz
// This header file is included when using the XC8 compiler.
#include <xc.h>
#include <avr/interrupt.h>
#include <util/delay.h>

ISR(INT0_vect) { // ISR for external interrupt 0 
  PINB |= _BV(PINB5); // toggle LED <-- set breakpoint #1
}

void init_button_pin() {
  DDRD  &= ~_BV(DDD2);   // clear the PD2/D2 pin
  PORTD |= _BV(PORTD2);  // enable internal pull-up on PD2
  // set INT0 to trigger on both edges
  EICRA &= ~_BV(ISC01); 
  EICRA |= _BV(ISC00); 
  EIMSK |= _BV(INT0);  // turn on INT0 interrupt
}

void init_led_pin() {
  DDRB |= _BV(DDB5); // output pin for LED
}

int main(void) {
  init_led_pin();
  init_button_pin();
  sei(); // enable global interrupts
  while (1) {
     // nop operation
     __asm("nop"); // <-- set breakpoint #2
     _delay_us(100); // delay for 100 usec
  }
  return 0;
}

ให้ไปที่เมนูคำสั่ง Window > Simulator > Stimulus เพื่อกำหนดรูปแบบของสัญญาณอินพุต ซึ่งจะนำไปใช้ในการจำลองการทำงานของโปรแกรม

รูปแบบการสัญญาณกระตุ้นเพื่อใช้เป็นอินพุตมีหลายแบบให้เลือก ในตัวอย่างนี้ให้เลือกแบบ Clock Stimulus คือ การสร้างสัญญาณแบบพัลส์ที่เกิดขึ้นหลายครั้งหรือมองว่าเป็นสัญญาณคลื่นสี่เหลี่ยม (Rectangular Wave)

  • ตั้งชื่อ Label เป็น PulseIn
  • เลือกขาอินพุต Pinเป็น PD2
  • ให้ค่าเริ่มต้น Initial เป็น Low
  • จำนวนไซเคิลสำหรับ Low Cycles เท่ากับ 160 ไซเคิล
  • จำนวนไซเคิลสำหรับ High Cycles เท่ากับ 160 ไซเคิล
  • เริ่มต้นสร้างสัญญาณ Begin ให้เป็น Start
  • หยุดสร้างสัญญาณ End ให้เป็น Never

จากนั้นจะต้องมีการกำหนดตำแหน่งสำหรับ Breakpoint เพื่อให้ดีบักเกอร์หยุดทำงานชั่วคราว

  1. หยุดที่คำสั่งแรกภายในฟังก์ชัน ISR และจะเกิดขึ้นเมื่อเกิดเหตุการณ์ขอบขาขึ้นหรือขาลงที่ขาอินพุต PD2
  2. หยุดที่คำสั่ง __asm("nop"); ในฟังก์ชัน main() ซึ่งจะเกิดขึ้นซ้ำโดยเว้นระยะห่างประมาณ 100 usec

เมื่อเกิดการหยุดโดย Breakpoint ผู้ใช้จะต้องกดปุ่ม F5 (Continue) หลาย ๆ ครั้ง และหากต้องการดูการเปลี่ยนแปลงที่ขา PD2 (อินพุต) และ PB5 (เอาต์พุต) ให้เป็นเพิ่มรายการและสังเกตการเปลี่ยนแปลงในหน้าต่าง Window > Simulator > Logic Analyzer

รูป: การตั้งค่าเพื่อสร้างสัญญาณอินพุตที่ขา PD2 ในหน้าต่าง Stimulus

รูป: การหยุดโดย Breakpoint เมื่อฟังก์ชัน ISR ถูกเรียกให้ทำงานเนื่องจากเกิดอินเทอร์รัพท์ (เกิดขึ้นทุก ๆ 160 ไซเคิล หรือ 10 usec)

รูป: ตัวอย่างข้อความที่แสดงให้เห็นในหน้าต่างของ Stopwatch ซึ่งมีการบันทึกหรือจับเวลาเมื่อหยุดชั่วคราวโดย Breakpoint

รูป: แสดงคลื่นสัญญาณดิจิทัลที่ขา PD2 (อินพุต) และ PB5 (เอาต์พุต) ที่ได้จากการจำลองการทำงาน และเห็นได้ว่า เมื่อมีการเปลี่ยนแปลงเกิดขึ้นที่สัญญาณอินพุต จะมีการเปลี่ยนแปลงที่สัญญาณเอาต์พุตตามมา

รูป: วัดระยะห่างจากขอบขาลงของสัญญาณอินพุต PD2 และขอบขาขึ้นของสัญญาณเอาต์พุต PB5 ซึ่งเป็นผลจากการตอบสนองต่ออินเทอร์รัพท์ที่เกิดขึ้น (มีระยะห่างเชิงเวลาหรือ Latency ประมาณ 1.44 usec)

 


กล่าวสรุป#

บทความนี้ได้สาธิตการใช้งานซอฟต์แวร์ MPLAB-X IDE v6.0.0 สำหรับการเขียนโปรแกรมภาษา C เพื่อนำไปทดลองใช้งานกับไมโครคอนโทรลเลอร์ ATmega328P และทดสอบการทำงานของโค้ดตัวอย่างโดยใช้ตัวจำลองการทำงาน (Simulator) ของซอฟต์แวร์ MPLAB-X IDE

 


This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.

Created: 2022-01-21 | Last Updated: 2022-01-23