Microchip Studio: Simulating Arduino FreeRTOS Code (Part 1)

<rawat.s>
6 min readJan 15, 2021

--

.

ซอฟต์แวร์ Microchip Studio เป็น IDE และใช้ในการพัฒนาโปรแกรมเฟิร์มแวร์เพื่อนำไปใช้กับไมโครคอนโทรลเลอร์ตระกูล AVR (8 บิต) และ SAM (32 บิต) ซอฟต์แวร์นี้ได้รับการพัฒนาต่อจาก Atmel Studio 7 IDE และสามารถใช้ได้กับระบบปฏิบัติการ Windows เช่น Windows 10

ในบทความนี้ จะมาแนะนำการใช้งาน Microchip Studio สำหรับเขียนโค้ดภาษา C และใช้ไลบรารี AVR gcc-libc ในเบื้องต้น และจำลองการทำงานเนื่องจากมี AVR Simulator มาให้ด้วย รวมถึงการสาธิตการนำเข้าโค้ด Arduino Sketch มาใช้ใน Microchip Studio

ถ้ายังไม่ได้ติดตั้งใช้งานมาก่อนให้ดาวน์โหลดและติดตั้งในคอมพิวเตอร์ของผู้ใช้ก่อน

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

.

เริ่มต้นเปิดใช้งาน Microchip Studio IDE แล้วไปที่เมนู File > New > Project … เพื่อสร้างโปรเจกต์ใหม่

จากนั้นจะปรากฎหน้าต่างเพื่อตั้งค่าการใช้งาน ให้เลือก “GCC C Executable Project” แล้วตั้งชื่อโปรเจกต์ และเลือกไดเรกทอรีสำหรับโปรเจกต์ดังกล่าว

.

จากนั้นจะต้องเลือกอุปกรณ์ MCU เป้าหมาย ในตัวอย่างนี้ ให้เลือก ATmega328P

.

กลับสู่หน้าต่างหลักของ IDE และมีการเปิดไฟล์ main.c ที่มีการสร้างไว้โดยอัตโนมัติ เราสามาถเขียนโค้ดลงในไฟล์ดังกล่าว ในเบื้องต้นมีเพียงไฟล์ main.c เท่านั้น ให้ลองใส่โค้ดตามตัวอย่างที่ให้ไว้

ขั้นตอนที่ 2: ลองโค้ดตัวอย่างและทำขั้นตอน Build

.

โค้ดตัวอย่างมีดังต่อไปนี้

#define F_CPU 1000000UL // 1MHz, necessary when using _delay_ms()#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/wdt.h>
#include <util/delay.h> // for _delay_ms()
//initialize watchdog
void wdt_init(void) {
cli(); // Disable interrupts
WDTCSR = (1<<WDCE )|(1<<WDE); // Enable configuration change.
WDTCSR = (1<<WDIE)| // Enable Watchdog Interrupt Mode.
(1<<WDCE )|(0<<WDE )| // Enable WDT interrupt mode (no reset).
(0<<WDP3 )|(0<<WDP2)|(0<<WDP1)|(0<<WDP0); // Set WDT timeout period
sei(); // Enable global interrupts
}
volatile uint8_t state = 0;//Watchdog timeout ISR
ISR(WDT_vect) {
state = !state;
if (state) {
PORTB |= (1 << PORTB5); // Set PORTB5 On
} else {
PORTB &= ~(1 << PORTB5); // Set PORTB5 Low
}
}
int main(void) {
DDRB |= (1 << PORTB5); // Set PORTB5 as output
MCUSR &= ~(1<<WDRF); // Clear WDT flag
wdt_init(); // Initialize WDT
while(1){}
return 0;
}

.

โค้ดนี้สาธิตการทำให้ ATmega328P มีการเปิดใช้งานวงจรภายในที่เรียกว่า Watchdog Timer (WDT) และอนุญาตให้เกิดอินเทอร์รัพท์โดย WDT ดังกล่าว (WDT Interrupt) การเกิดอินเทอร์รัพท์จาก WDT จะไม่ทำให้เกิดการรีเซตระบบ แต่จะมีการเรียกฟังก์ชัน WDT interrupt handler ISR(WDT_vect){…} โดยอัตโนมัติ

หน้าที่ของ WDT interrupt handler ในโค้ดตัวอย่างคือ เมื่อเกิดอินเทอร์รัพท์ในแต่ละครั้ง จะทำให้สถานะที่ขา PORTB5 สลับสถานะลอจิกหนึ่งครั้ง (I/O Toggle) ดังนั้น อัตราการเกิดอินเทอร์รัพท์ จะเป็นตัวกำหนดอัตราการเปลี่ยนสถานะของขา I/O ดังกล่าว (I/O Toggle Rate)

ในตัวอย่างนี้ ได้กำหนดให้ WDT สร้างอินเทอร์รัพท์ทุก ๆ 16 msec โดยประมาณ โดย WDT ของ AVR จะทำงานโดยใช้วงจร Oscillator ที่มีความถี่ 128kHz และถ้าตั้งตัวหารความถี่หรือ Prescaler ไว้เท่ากับ 2048 (Cycles) หรือ 2K ก็จะได้อัตราการเกิดอินเทอร์รัพท์เท่ากับ 128KHz/2048 = 62.5 Hz หรือคิดเป็นช่วงเวลาเท่ากับ 1/62.5Hz = 16 msec

จากนั้นให้ลองทำขั้นตอน Build Solution ซึ่งจะมีการคอมไพล์โค้ดต่าง ๆ ของโปรเจกต์ ถ้าลองเปิดหน้าต่างในส่วนที่เรียกว่า Solution Explorer เราจะเห็นโครงสร้างไฟล์ของโปรเจกต์

หรือลองดูไฟล์ต่าง ๆ ของ FreeRTOS Library

รูป: ตัวอย่างไฟล์ Source Code ของ FreeRTOS Library (Arduino AVR Port)

ขั้นตอนที่ 3: ตรวจสอบการทำงานของโค้ดใน Debug Mode

.

โดยทั่วไปแล้วการตรวจสอบการทำงานของโค้ดหรือที่เรียกว่า Debugging นั้น มีสองวิธีคือ การใช้ฮาร์ดแวร์ซึ่งก็คือบอร์ดไมโครคอนโทรลเลอร์ที่รองรับการทำงานร่วมกับซอฟต์แวร์ที่เป็น Debugger และอีกวิธีคือ การจำลองการทำงานโดยใช้ Instruction Set / Peripheral Simulator

ในตัวอย่างนี้ เนื่องจากเราได้เลือกใช้ AVR และ Microchip Studio มี AVR Simulator มาให้ใช้งาน เราจึงสามารถตรวจสอบการทำงานของโค้ด โดยยังไม่ต้องใช้ฮาร์ดแวร์

เริ่มต้นขั้นตอน Debug Session โดยไปที่เมนู Debug > Start Debugging and Break

.

เมื่อเริ่มต้น Debug ครั้งแรกสำหรับโปรเจกต์ จะต้องมีการตั้งค่าก่อน

.

ในส่วนของ Tools ให้เลือกเป็น Simulator แล้วบันทึกการเปลี่ยนแปลง (Ctrl+S) จากนั้นเริ่มต้นขั้นตอน Debug อีกครั้ง

เมื่ออยู่ใน Debug Mode ถ้าเปิดดูรายการเมนูในกลุ่มคำสั่ง Debug จะมีคำสั่งตามรูปภาพ เช่น จบการดีบัก (Stop Debugging) เริ่มต้นดีบักใหม่อีกครั้ง (Restart) หรือ คำสั่งที่เกี่ยวกับการทำคำสั่งในโค้ด เช่น Continue / Step Into / Step Over / Step Out / Run To Cursor เป็นต้น

.

หรือจะกดปุ่มไอคอนคำสั่งก็ได้เช่นกัน

.

การดีบักโค้ดนั้น สิ่งที่ควรทำคือ การกำหนดตำแหน่งของ Breakpoint ในโค้ด เพื่อให้หยุดการทำงานชั่วคราวเมื่อมีการทำงานมาถึงคำสั่งดังกล่าวในโค้ด เมื่อหยุดชั่วคราว เราก็สามารถดูสถานะการทำงานของไมโครคอนโทรลเลอร์ได้ เช่น ดูค่าในรีจิสเตอร์ของซีพียู (Registers) สถานะของโปรเซสเซอร์ (Processor Status) หรือรีจิสเตอร์ที่เกี่ยวข้องกับวงจรรอบข้าง (I/O) เป็นต้น

รูป: การแสดงค่าในส่วนของ I/O เช่น ดูค่ารีจิสเตอร์สำหรับพอร์ต PB

.

ในส่วนแสดงผล Processor Status เราสามารถดูค่า เช่น

  • รีจิสเตอร์ที่ระบุแอดเดรสของคำสั่งที่กำลังทำอยู่ หรือ Program Counter (PC)
  • รีจิสเตอร์ที่เกี่ยวข้องกับการทำงานของหน่วยความจำแบบ Stack หรือ Stack Pointer (SP)
  • รีจิสเตอร์สถานะ (Status Register)
  • ตัวนับไซเคิล (Cycle Counter) ซึ่งเป็นตัวระบุจำนวนไซเคิลที่ได้ทำไปแล้ว
  • ความถี่ของตัวประมวลผล (Frequency) ที่ใช้ในการจำลองการทำงาน (เช่น ในรูปตัวอย่าง มีการตั้งค่าไว้เท่ากับ 1 MHz) และ
  • ตัวจับเวลาการทำงาน (Stop Watch) เป็นต้น

จากรูปตัวอย่าง เมื่อเริ่มต้น Debug จะมีการทำคำสั่งจนมาถึงคำสั่งแรกในฟังก์ชัน main() ซึ่งสังเกตได้จากลูกศร (สีเหลือง) ชี้อยู่ด้านข้าง

รูป: การจำลองการทำงานของโค้ดและหยุดรอที่คำสั่งแรกในฟังก์ชัน main()

ขั้นตอนที่ 4: การกำหนดตำแหน่งของ Breakpoint ในโค้ดและจำลองการทำงาน

.

ถัดไปให้วางตำแหน่งของเมาส์ใน Editor ตรงบรรทัดที่มีคำสั่ง ISR(WDT_vect) แล้วกดดับเบิลคลิก เพื่อกำหนดให้บรรทัดนี้เป็น Breakpoint และจะสังเกตเห็นวงกลมสีแดง

.

จากโค้ดตัวอย่างและการจำลองการทำ ถ้ากดปุ่ม Continue (F5) จะมีการทำคำสั่งต่อไปจนมาถึงตำแหน่ง Breakpoint อีกครั้ง

ถ้าทำคำสั่งต่อไป ก็จะกลับมาหยุดที่ตำแหน่ง Breakpoint อีก ซึ่งมีเพียงอันเดียวในตัวอย่างนี้

.

ถ้าเราจับเวลาในการเกิดอินเทอร์รัพท์และมาหยุดชั่วคราวอยู่ที่ตำแหน่ง Breakpoint เราจะได้ผลต่างซึ่งเป็นระยะเวลาในการเกิดอินเทอร์รัพท์ในแต่ละครั้ง

คำนวณผลต่าง Cycle Count เมื่อเกิดเหตุการณ์ WDT Interrupt สองครั้งถัดกัน จะได้ (32867–16460) = 16407 ซึ่งหมายถึง 16.407 ms เนื่องจาก 1 Clock Cycle = 1 usec (1MHz CPU clock rate)

ถ้าดูการตั้งค่า WDT ได้เลือกตัวหารความถี่เท่ากับ 2K Cycles (2048) และวงจร WDT ใช้ความถี่ 128kHz (internal WDT oscillator) ดังนั้นจะได้เท่ากับ 128kHz/2048 = 62.5Hz หรือคิดเป็นระยะเวลาในการเกิดเหตุการณ์ WDT Interrupt เท่ากับ 1000/62.5 = 16 msec

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

แนะนำให้ศึกษาเพิ่มเติมจาก ATmega328P Datasheet และตัดบางส่วนจากเอกสาร ที่เกี่ยวข้องกับ WDT มาเป็นตัวอย่างดังนี้

รูป: บางส่วนจากเอกสาร Datasheet ที่เกี่ยวข้องกับ WDT ของ ATmega328P
รูป: ตารางแสดงตัวเลือกสำหรับ WDT Prescaler

ถ้าเราเปลี่ยนความถี่ของ CPU ใน Debug Simulator จาก 1MHz เป็น 16MHz (ในหน้าต่าง Processor Status ในช่อง Frequency เราสามารถคลิกเลือก แล้วเปลี่ยนตัวเลขได้)

จากนั้นลองทำจำลองการทำงานใหม่อีกครั้ง จะพบว่า ระยะเวลาในการเกิด WDT Interrupt จะลดลง 16 เท่า ซึ่งไม่ถูกต้อง ถ้าดูจากค่า Stop Watch เปลี่ยนเทียบกับการจำลองก่อนหน้าเมื่อใช้ความถี่ 1 MHz

.

ระยะเวลาในการเกิดอินเทอร์รัพท์ควรจะต้องเหมือนเดิมคือ 16 msec ดังนั้นถ้าเราจะคงเขียนโค้ดเหมือนเดิม ไม่แก้ไขการตั้งค่าในรีจิสเตอร์ WDTCSR เมื่อจำลองการทำงานใน Debug Session ให้เปลี่ยนค่า Watchdog Timer Prescaler Bits (WDTCSR) จาก Oscillator Cycles 2K เป็น Oscillator Cycles 32K

เมื่อตั้งค่าใหม่แล้วและจำลองการทำงานอีกครั้ง แล้วตรวจสอบระยะเวลาในการเกิด WDT Interrupt จะได้ 16 msec เหมือนเดิม

ขั้นตอนที่ 5: การนำเข้าโค้ด Arduino Sketch

.

ถ้าเราเขียนโค้ดโดยใช้ Arduino Sketch เราไม่สามารถจำลองการทำงานของโค้ดโดยการดีบักได้ ดังนั้นถัดไป มาลองนำเข้าไฟล์ Arduino Sketch (.ino) เข้ามาใช้งานใน Microchip Studio

โค้ดตัวอย่าง Arduino Sketch ให้บันทึกลงไฟล์ เช่น uno_freertos_demo.ino

#include <Arduino_FreeRTOS.h>
// see: https://github.com/feilipu/Arduino_FreeRTOS_Library
#define LED_PIN 13 // PORTB5 pinvoid task( void *pvParameters );void setup() {
xTaskCreate(
task /* task function */,
"BlinkTask" /* task name */,
128 /* stack size */,
NULL /* no parameters */,
2 /* priority level */,
NULL /* no task handle */ );
// Note the FreeRTOS task scheduler is started automatically.
}
void loop() {}void task( void *pvParameters ) {
boolean state = false;
pinMode( LED_PIN, OUTPUT );
while(1) { // toggle and update LED output
digitalWrite( LED_PIN, state = !state );
vTaskDelay( 1 );
}
}
//////////////////////////////////////////////////////////////////

.

จากนั้นเริ่มต้นสร้างโปรเจกต์ใหม่ใน Microchip Studio IDE โดยเลือกประเภทของโปรเจกต์เป็น “Create project from Arduino sketch

รูป: เริ่มต้นสร้างโปรเจกต์ใหม่จากไฟล์ Arduino Sketch

.

เลือกไฟล์ .ino และระบุ Arduino IDE Path ในเครื่องของผู้ใช้ (จะต้องมีการติดตั้ง Arduino IDE เอาไว้แล้ว) จากนั้นกดปุ่ม OK

ในขั้นตอนการสร้างโปรเจกต์ใหม่ จะมีการดาวน์โหลดไฟล์ซอร์สโค้ดสำหรับ Arduino Core มาใส่ในไดเรกทอรีของโปรเจกต์ และถ้ามีการใช้ไลบรารี เช่น Arduino FreeRTOS for AVR ในกรณีนี้ ก็จะมีไฟล์ Source Code สำหรับ FreeRTOS ใส่ไว้ให้ด้วย

ไฟล์หลักคือ Sketch.cpp ที่มาแทนที่ไฟล์ .ino ของ Arduino Sketch

รูป: หน้าต่าง IDE ที่มีการสร้างไฟล์สำหรับโปรเจกต์ใหม่ได้สำเร็จแล้ว
รูป: แสดงรายการไฟล์ของโปรเจกต์ใน Solution Explorer

.

ถัดไปให้ทำขั้นตอน Build และเริ่มต้นดีบักโดยใช้ Simulator และเริ่มต้นจะมาหยุดที่ประโยคำสั่งแรกในฟังก์ชัน main() ในไฟล์ main.cpp

รูป: เริ่มต้นการดีบักและหยุดชั่วคราว

.

จากนั้นให้ไปที่ไฟล์ Sketch.cpp แล้วเพิ่มตำแหน่ง Breakpoint ในโค้ดตรงคำสั่ง vTaskDelay(...) แล้วทำคำสั่งต่อไป (Continue)

คำสั่ง vTaskDelay(1) เป็นการทำให้ Task ใน FreeRTOS อยู่ในสถานะ Waiting (และให้ทาสก์อื่นจะได้มีโอกาสทำงาน) รอจนกว่าเวลาจะผ่านไปเท่ากับ 1 Tick ซึ่งตรงกับอัตราการเกิดอินเทอร์รัพท์ของ WDT แล้วจะทำงานต่อจากตำแหน่งที่หยุดค้างไว้

Arduino FreeRTOS เลือกที่จะใช้ WDT แทน Timer (Timer0/Timer1,…) เป็นตัวกำหนดจังหวะการทำงานหรือ System Tick สำหรับการทำงานของ Scheduler เมื่อนำมาใช้กับ AVR

คำถาม: จากการจำลองการทำงานแล้วจับเวลาการหยุดชั่วคราวที่ตำแหน่ง Breakpoint ซึ่งก็คือ vTaskDelay(1) ในกรณีตัวอย่างนี้ ระยะห่างระหว่างสองครั้งจะได้ประมาณ 16 msec หรือไม่ ถ้าไม่ได้ เป็นเพราะเหตุใด ?

ก่อนจบ ขอแนะนำคลิปการใช้งาน Microchip Studio IDE สำหรับ AVR

กล่าวสรุป

.

เราได้เรียนรู้ขั้นตอนการใช้งาน Microchip Studio IDE และจำลองการทำงานของโค้ดที่เขียนด้วยภาษา C และการนำเข้าโค้ด Arduino Sketch (C++) รวมถึงการจำลองการทำงานของโค้ดได้ด้ว

--

--