Multi-Tasking with Apache MyNewt RTOS: Getting Started

<rawat.s>
10 min readMar 11, 2020

--

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

บทความนี้นำเสนอ การเขียนโค้ดแบบมัลติทาสกิ้ง โดยใช้ Apach MyNewt RTOS และนำมาใช้กับบอร์ดไมโครคอนโทรลเลอร์ 32 บิต ตระกูล ARM Cortex-M Series เป็นตัวอย่าง (เลือกใช้บอร์ด Nucleo L476RG)

เครื่องมือที่จำเป็นต้องติดตั้งใช้งานสำหรับ Apache MyNewt

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

การเริ่มต้นเรียนรู้การเขียนโค้ดสำหรับไมโครคอนโทรลเลอร์โดยใช้ Apache MyNewt จะต้องมีการติดตั้ง Newt Tool ซึ่งทำงานแบบ CLI (Command Line Interface) และมีให้ใช้งานสำหรับระบบปฏิบัติการ Windows, Linux, Mac OS X โปรแกรมนี้ใช้สำหรับการติดตั้ง Software Repositories ที่จะนำมาใช้กับโปรเจกต์ การคอมไพล์และอัปโหลดไฟล์ Image ไปยังบอร์ดเป้าหมาย เป็นต้น

ในขั้นตอนการอัปโหลดไฟล์ Image และ Bootloader จะต้องใช้โปรแกรม OpenOCD สำหรับเชื่อมต่อแบบ SWD (Serial Wire Debug) เช่น สำหรับ Arduino Zero Compatible Board (ATSAM21G18 MCU) แต่ถ้าใช้บอร์ดของ STMicroelectroncs เช่น STM32 Nucleo หรือ Discovery Boards ก็จะใช้โปรแกรมชื่อ stlink ซึ่งเป็น Open Source Tool เป็นต้น

Mynewt Manager (“newtmgr”) เป็นอีกโปรแกรมหนึ่งที่ช่วยในการตรวจสอบสถานะการทำงานของทาส์กต่าง ๆ ผ่านทาง Serial และยังสามารถใช้สำหรับการอัปเดตเฟิร์มแวร์แบบไร้สาย (Over-the-air firmware update) ได้ด้วย

บทความนี้จะไม่นำเสนอ วิธีการและขั้นตอนการติดตั้งโปรแกรมต่าง ๆ สำหรับการพัฒนาโปรแกรมโดยใช้ Apache MyNewt เนื่องจากได้เคยเขียนไว้ในบทความอื่นก่อนหน้านี้แล้ว

การสร้างโปรเจกต์ใหม่และโค้ดตัวอย่างแรก

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

เริ่มต้นด้วยการสร้างโปรเจกต์ใหม่ พร้อมระบุบอร์ดไมโครคอนโทรลเลอร์ (Target Platform) ที่จะนำมาใช้งาน ในตัวอย่างนี้ เราจะเลือกใช้บอร์ด STM32 Nucleo L476RG (nucleo-l476rg) และตั้งชื่อโปรเจกต์ตามชื่อไดเรกทอรีใหม่ nucleo-mynewt-demo และสร้าง Application โดยใช้ชื่อว่า demo-1

  1. สร้างโปรเจกต์ใหม่ โดยใช้คำสั่ง newt ดังนี้
$ newt new nucleo-mynewt-demo
$ cd nucleo-mynewt-demo

2. แก้ไขไฟล์ project.yml ตามตัวอย่างต่อไปนี้

project.name: "nucleo-mynewt-demo"project.repositories:
- apache-mynewt-core
repository.apache-mynewt-core:
type: github
vers: 0-dev
user: apache
repo: mynewt-core

3. ในโปรเจกต์ nucleo-mynewt-demo ให้สร้าง apps/demo-1 โดยสำเนาจาก apps/blinky

$ cp -R apps/blinky apps/demo-1

4. แก้ไขไฟล์ apps/demo-1/pkg.yml เพื่อแก้ไขชื่อ Application ให้เป็น demo-1 และระบุว่า จะใช้ Packages ใดบ้าง

คำแนะนำ: คำอธิบายเกี่ยวกับไฟล์และโครงสร้างของ MyNewt Project Structure สามารถศึกษาเพิ่มเติมได้จากเอกสารออนไลน์

pkg.name: apps/demo-1pkg.type: app
pkg.description: MyNewt Multitasking Demo 1
pkg.author:
pkg.homepage:
pkg.keywords:
pkg.deps:
- "@apache-mynewt-core/kernel/os"
- "@apache-mynewt-core/hw/hal"
- "@apache-mynewt-core/sys/console/minimal"
- "@apache-mynewt-core/sys/log/stub"

5. ทำคำสั่งเพื่อติดตั้ง Packages ต่าง ๆ ที่เกี่ยวข้อง

$ newt install -v -f

6. แก้ไขไฟล์ซอร์สโค้ด apps/demo-1/src/main.c

#include <string.h>
#include "sysinit/sysinit.h"
#include "os/os.h"
#include "bsp/bsp.h"
#include "hal/hal_gpio.h"
int main( int argc, char **argv ) {
struct os_task *t;
sysinit(); // system initialization
t = os_sched_get_current_task();
printf( "Task '%s': priority %d\r\n", t->t_name, t->t_prio );
printf( "OS_MAIN_TASK_PRIO = %d\r\n", OS_MAIN_TASK_PRIO );
printf( "OS_MAIN_STACK_SIZE = %d\r\n", OS_MAIN_STACK_SIZE );
printf( "OS_IDLE_PRIO = %d\r\n", OS_IDLE_PRIO );
hal_gpio_init_out( LED_BLINK_PIN, 0); // LED output 0
while (1) {
hal_gpio_toggle( LED_BLINK_PIN ); // toggle LED
printf( "LED Blink: %d\r\n", hal_gpio_read(LED_BLINK_PIN) );
os_time_delay( OS_TICKS_PER_SEC/2 ); // delay 0.5 sec
}
return 0;
}

6. ทำขั้นตอน Build Bootloader และอัปโหลดไฟล์ .img ที่ได้ ไปยังบอร์ดเป้าหมาย

$ TARGET_NAME=nucleo-l476rg 
$ BOOT_NAME=${TARGET_NAME}_boot
$ newt target create $BOOT_NAME
$ newt target set $BOOT_NAME app=@mcuboot/boot/mynewt bsp=@apache-mynewt-core/hw/bsp/$TARGET_NAME build_profile=optimized
$ newt build $BOOT_NAME
$ newt load $BOOT_NAME

7. ทำขั้นตอน Build Application และอัปโหลดไฟล์ .img ที่ได้ ไปยังบอร์ดเป้าหมาย

$ APP_NAME=${TARGET_NAME}_demo-1
$ newt target create $APP_NAME
$ newt target set $APP_NAME app=apps/demo-1 bsp=@apache-mynewt-core/hw/bsp/$TARGET_NAME build_profile=debug
$ newt build $APP_NAME
$ newt create-image $APP_NAME 1.0.0 && newt load $APP_NAME

ถ้าทำขั้นตอนได้ถูกต้องครบถ้วนแล้ว จะสังเกตเห็นว่า LED บนบอร์ดไมโครคอนโทรลเลอร์ กระพริบ ทุก ๆ 1 วินาที และถ้าใช้โปรแกรมเปิดพอร์ต Serial ที่เกี่ยวข้องกับบอร์ดไมโครคอนโทรลเลอร์ (ใช้ Baudrate 115200) จะได้รับข้อความที่ถูกส่งมาจากฮาร์ดแวร์

การสร้างทาส์กที่ทำให้ LED กระพริบ

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ในตัวอย่างที่แล้ว พฤติกรรมการทำงานของโปรแกรมคือ การทำให้ LED กระพริบซึ่งเป็นการทำงานโดยทาส์กหลัก (Main Task) ที่สลับสถานะลอจิกที่ขาเอาต์พุต LED และหน่วงเวลา 0.5 วินาที ก่อนทำซ้ำ

ตัวอย่างนี้สาธิตการสร้างทาสก์ใหม่ และให้ทาสก์ดังกล่าวทำหน้าที่สลับสถานะของเอาต์พุต LED และหน่วงเวลาก่อนทำซ้ำ

#include <string.h>
#include "sysinit/sysinit.h"
#include "os/os.h"
#include "bsp/bsp.h"
#include "hal/hal_gpio.h"
#define TASK_PRI (1)
#define STACK_SIZE OS_STACK_ALIGN(128)
// global variables
struct os_task task;
os_stack_t task_stack[ STACK_SIZE ];
void task_func( void *arg ) {
struct os_task *t = os_sched_get_current_task();
hal_gpio_init_out( LED_BLINK_PIN, 0 );
while (1) {
hal_gpio_toggle( LED_BLINK_PIN );
printf( "Task '%s' called, LED %d\r\n",
t->t_name, hal_gpio_read( LED_BLINK_PIN) );
os_time_delay( os_time_ms_to_ticks32(500) );
}
}
int main(int argc, char **argv) {
sysinit(); // system initialization
os_task_init( // create a new task
&task, // task handle
"LED Blink", // task name
task_func, // task function
NULL, // task argument (none)
TASK_PRI, // task priority
OS_WAIT_FOREVER, // no check for sanity task
task_stack, // task stack
STACK_SIZE // task stack size
);
while (1) { // endless loop
os_sched(NULL); // switch to the next task
}
return 0;
}

ในตัวอย่างนี้มีการเรียกใช้คำสั่งของ MyNewt API ดังนี้

  • os_task_init() เป็นคำสั่งเพื่อสร้างทาสก์ใหม่
  • os_time_delay() เป็นคำสั่งเพื่อหน่วงเวลาการทำงานของทาส์ก ระบุค่าเป็นจำนวน OS Ticks (ชนิดข้อมูลเป็นเลขจำนวนเต็ม 32 บิต) โดยทั่วไปจะตั้งค่าไว้ 1000 Ticks = 1 วินาที
  • os_sched_get_current_task() เป็นคำสั่งเพื่อเข้าถึงทาส์กที่กำลังทำงานในขณะนั้น (อยู่ในสถานะ Running) ซึ่งเป็นพอยน์เตอร์ (struct os_task *)
  • os_time_ms_to_ticks32() เป็นคำสั่งสำหรับแปลงระยะเวลา (มิลลิวินาที) ให้เป็นจำนวน OS Ticks
  • os_sched() เป็นคำสั่งเพื่อระบุว่า จะส่งต่อให้ทาสก์ใดทำงานถัดไป แต่ถ้าระบุเป็น NULL จะเป็นทาสก์ที่มีความสำคัญสูงสุดและพร้อมจะทำงาน
  • hal_gpio_init_out() เป็นคำสั่งเพื่อเริ่มต้นใช้งานขา GPIO เป็นเอาต์พุต พร้อมระบุค่าเริ่มต้น (ลอจิก) ให้เอาต์พุต
  • hal_gpio_read() เป็นคำสั่งสำหรับอ่านสถานะลอจิกของขา GPIO
  • hal_gpio_toggle() เป็นคำสั่งเพื่อสลับสถานะลอจิกของขา GPIO ที่เป็นเอาต์พุต

การสร้างทาสก์โดยใช้คำสั่ง os_task_init() จะต้องระบุสิ่งต่อไปนี้ตามลำดับ

  • พอยน์เตอร์สำหรับอ้างอิงทาส์ก (Task Handle) ที่มีชนิดข้อมูลเป็น struct os_task
  • ชื่อของทาสก์ (Task Name)
  • ฟังก์ชัของทาสก์ (Task Function) ตามรูปแบบที่กำหนด
  • อาร์กิวเมนต์สำหรับฟังก์ชันของทาสก์ (Task Function Argument)
  • ระดับความสำคัญของทาสก์ (Task Priority) ซึ่งจะต้องมีค่าอยู่ระหว่าง 0 และ 255 (ในขณะที่ Main Task มีความสำคัญเท่ากับ 127 และ Idle Task มีความสำคัญ 255) และแต่ละทาสก์จะต้องมีค่าความสำคัญต่างกัน
  • ระยะเวลาในการตรวจสอบการทำงานของทาส์กเป็นระยะ ๆ โดยทาสก์ที่เรียกว่า Sanity Task (Software Watchdog Task) แต่ถ้าระบุเป็น OS_WAIT_FOREVER ก็หมายความว่า ไม่ต้องมีการตรวจสอบ Sanity Check
  • สแตกที่จะใช้สำหรับการทำงานของทาสก์ (Task Stack)
  • ขนาดของสแตกสำหรับทาสก์ (Stack Size)

การสร้างทาสก์ที่ทำให้ LED กระพริบ 2 ดวง

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ตัวอย่างถัดไปเป็นการสร้างทาสก์ จำนวน 2 ชุด (“T0” และ “T1”) เพื่อทำให้ LED จำนวน 2 ดวง กระพริบได้ (เลือกใช้ขาเอาต์พุต Arduino Pins D4 และ D5) และทำงานได้อิสระจากกัน และมีข้อสังเกตว่า ทาสก์ทั้งสองจะต้องมีระดับความสำคัญไม่เท่ากัน (เท่ากับ 1 และ 2 ตามลำดับ)

ข้อสังเกต: ทาสก์ทั้งสองจะเรียกใช้ฟังก์ชันเดียวกันคือ task_func(){…} แต่ทาสก์จะมี “Context” ในการทำงานแยกกัน จึงใช้ฟังก์ชันเดียวกัน

#include <stdio.h>
#include <string.h>
#include <sysinit/sysinit.h>
#include <os/os.h>
#include <bsp/bsp.h>
#include <hal/hal_gpio.h>
#define TASK_PRI (1)
#define STACK_SIZE OS_STACK_ALIGN(128)
#define NUM_TASKS (2)
// constants
const int LED_PINS[] = { ARDUINO_PIN_D4, ARDUINO_PIN_D5 };
const char *TASK_NAMES[] = { "T0","T1" };
// global variables
struct os_task tasks[ NUM_TASKS ];
os_stack_t task_stacks[ NUM_TASKS ][ STACK_SIZE ];
void task_func( void *arg ) {
int id = (int)arg;
os_time_t ticks;
struct os_task *t = os_sched_get_current_task();
hal_gpio_init_out( LED_PINS[id], 0 );
ticks = os_time_ms_to_ticks32( (id*500)/NUM_TASKS );
os_time_delay( ticks );
while (1) {
printf( "Task '%s' (id=%d) called\r\n", t->t_name, id );
hal_gpio_toggle( LED_PINS[id] );
ticks = os_time_ms_to_ticks32( 500 );
os_time_delay( ticks );
}
}

int main(int argc, char **argv) {
// system initialization
sysinit();
// Create two tasks: T0 and T1
for ( int id=0; id < NUM_TASKS; id++ ) {
os_task_init( &tasks[id],
TASK_NAMES[id], task_func,
(void*)id, (TASK_PRI+id),
OS_WAIT_FOREVER,
task_stacks[id], STACK_SIZE
);
}
while (1) { // endless loop
os_time_delay( OS_WAIT_FOREVER );
}
return 0;
}
ตัวอย่างรูปคลื่นสัญญาณเอาต์พุต (Time/Div = 500msec): 2 LEDs

การเพิ่มจำนวนทาสก์สำหรับ LED 4 ดวง

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ถ้าเราต้องการเพิ่มจำนวนทาสก์และจำนวน LED เช่น จำนวน 4 ชุด ก็สามารถใช้โค้ดตัวอย่างที่แล้วและแก้ไขเพียงเล็กน้อยดังนี้

#include <stdio.h>
#include <string.h>
#include <sysinit/sysinit.h>
#include <os/os.h>
#include <bsp/bsp.h>
#include <hal/hal_gpio.h>
#define TASK_PRI (1)
#define STACK_SIZE OS_STACK_ALIGN(128)
#define NUM_TASKS (4)
// constants
static const int LED_PINS[] = {
ARDUINO_PIN_D4, ARDUINO_PIN_D5,
ARDUINO_PIN_D6, ARDUINO_PIN_D7
};
static const char *TASK_NAMES[] = {
"T0", "T1", "T2", "T3"
};
// global variables
struct os_task tasks[ NUM_TASKS ];
os_stack_t task_stacks[ NUM_TASKS ][ STACK_SIZE ];
void task_func( void *arg ) { // task function
int id = (int)arg;
os_time_t ticks;
struct os_task *t = os_sched_get_current_task();
hal_gpio_init_out( LED_PINS[id], 0 );
ticks = os_time_ms_to_ticks32( (id*500)/NUM_TASKS );
os_time_delay( ticks );
while (1) {
printf( "Task '%s' (id=%d) called\r\n", t->t_name, id );
hal_gpio_toggle( LED_PINS[id] );
ticks = os_time_ms_to_ticks32(500);
os_time_delay( ticks );
}
}
int main(int argc, char **argv) {
// system initialization
sysinit();
// Create four tasks
for ( int id=0; id < NUM_TASKS; id++ ) {
os_task_init( &tasks[id],
TASK_NAMES[id], task_func,
(void*)id, (TASK_PRI+id),
OS_WAIT_FOREVER,
task_stacks[id], STACK_SIZE
);
}
while (1) { // endless loop
os_time_delay( OS_WAIT_FOREVER );
}
return 0;
}
ตัวอย่างรูปคลื่นสัญญาณเอาต์พุต (Time/Div = 500msec): 4 LEDs
ตัวอย่างข้อความที่ได้รับจากบอร์ดผ่านทาง Serial

การใช้งาน Mutex

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ตัวอย่างถัดไปสาธิตการใช้คำสั่งเกี่ยวกับ Mutex (struct os_mutex) เช่น

  • os_mutex_init() เริ่มต้นใช้งาน Mutex ที่อ้างอิงโดยพอยน์เตอร์ (struct os_mutex *)
  • os_mutex_pend() เข้าใช้งาน Mutex และอาจมีการรอ ถ้ามีทาสก์อื่นเข้าใช้งานอยู่ก่อนแล้ว สามารถระบุระยะเวลารอสูงสุดได้ (Timeout) หรือรอไปจนกว่าจะสามารถเข้าใช้งานได้ (OS_WAIT_FOREVER)
  • os_mutex_release() เลิกใช้ Mutex

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

#include <string.h>
#include "sysinit/sysinit.h"
#include "os/os.h"
#include "bsp/bsp.h"
#include "hal/hal_gpio.h"
#define TASK_PRI (1)
#define STACK_SIZE OS_STACK_ALIGN(128)
#define NUM_TASKS (4)
#define DELAY_TICKS os_time_ms_to_ticks32(100)
#define USE_MUTEX (1)const int LED_PINS[] = {
ARDUINO_PIN_D4, ARDUINO_PIN_D5,
ARDUINO_PIN_D6, ARDUINO_PIN_D7
};
const char *TASK_NAMES[] = {"T0","T1","T2","T3"};
// global variable
struct os_task tasks[ NUM_TASKS ];
os_stack_t task_stacks[ NUM_TASKS ][ STACK_SIZE ];
struct os_mutex mutex;
void task_func(void *arg) {
int id = (int)arg;
hal_gpio_init_out( LED_PINS[id], 0 );

while (1) {
#if (USE_MUTEX==1)
if ( os_mutex_pend( &mutex, OS_TIMEOUT_NEVER)==OS_OK ) {
hal_gpio_write( LED_PINS[id], 1 );
os_time_delay( DELAY_TICKS );
hal_gpio_write( LED_PINS[id], 0 );
os_mutex_release( &mutex );
os_time_delay( (NUM_TASKS-1)*DELAY_TICKS );
}
#else
hal_gpio_write( LED_PINS[id], 1 );
os_time_delay( DELAY_TICKS );
hal_gpio_write( LED_PINS[id], 0 );
os_time_delay( (NUM_TASKS-1)*DELAY_TICKS );
#endif
}
}
int main(int argc, char **argv) {
sysinit(); // system initialization
printf( "Apache MyNewt Demo...\r\n" );
// Create a mutex
if ( os_mutex_init(&mutex) != OS_OK ) {
printf( "Cannot create a mutex!\r\n" );
os_time_delay( OS_WAIT_FOREVER );
}
// Create tasks
for ( int id=0; id < NUM_TASKS; id++ ) {
os_task_init(
&tasks[id], TASK_NAMES[id],
task_func, (void*)id,
(TASK_PRI+id), OS_WAIT_FOREVER,
task_stacks[id], STACK_SIZE
);
}
while (1) { // endless loop
os_time_delay( OS_WAIT_FOREVER );
}
return 0;
}
ตัวอย่างสัญญาณเอาต์พุต (Time/Div=200ms): NUM_TASKS=4, USE_MUTEX=0
ตัวอย่างสัญญาณเอาต์พุต (Time/Div=200ms): NUM_TASKS=4, USE_MUTEX=1

การใช้งาน Semaphore เพื่อใช้ในการสื่อสารกันระหว่างทาสก์

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ตัวอย่างถัดไปสาธิตการใช้คำสั่งเกี่ยวกับ Semaphore (struct os_sem) เช่น

  • os_sem_init() เริ่มต้นใช้งาน Semaphore ที่อ้างอิงโดยพอยน์เตอร์ (struct os_sem *) และสามารถระบุค่าเริ่มต้นเป็นเลขจำนวนเต็มตั้งแต่ 0 ขึ้นไป
  • os_sem_pend() นำโทเคน (Token) ออกจาก Semaphore และให้ผลเหมือน Take โดยอาจจะต้องรอถ้าจำนวนโทเคนเหลือเป็น 0 แล้ว สามารถระบุระยะเวลาในการรอได้สูงสุดได้ (Timeout)
  • os_sem_release() นำโทเคนไปใส่คืนให้ Semaphore ให้ผลเหมือน Give

ในตัวอย่างนี้ จะมีการสร้างทาสก์ใหม่ (เรียกว่า Input Button Task) นอกเหนือจากทาสก์หลัก (Main Task) โดยใช้ทาสก์นี้ ทำหน้าที่คอยตรวจสอบการกดปุ่มที่ทำงานแบบ Active-Low (ใช้วิธีวนซ้ำเพื่อคอยตรวจสอบสถานะอินพุต หรือ Polling) เพื่อดูว่า มีการกดปุ่มแล้วปล่อยหนึ่งครั้งหรือไม่ ถ้าเกิดเหตุการณ์ดังนั้น แล้วให้แจ้งเหตุการณ์นี้ไปยังทาสก์หลักโดยใช้ (Binary) Semaphore

ทาสก์หลักจะรอเหตุการณ์หรือการแจ้งเตือนจากอีกทาสก์ที่คอยตรวจสอบสถานะของปุ่มกด และสื่อสารกันด้วย Semaphore เมื่อเกิดเหตุการณ์ดังกล่าว ก็จะทำให้เกิดการสลับสถานะของ LED หนึ่งครั้ง

#include <string.h>
#include "sysinit/sysinit.h"
#include "os/os.h"
#include "bsp/bsp.h"
#include "hal/hal_gpio.h"
#define USER_BTN_PIN MCU_GPIO_PORTC(13) // PC13
#define LED_PIN MCU_GPIO_PORTA(5) // PA5
#define TASK_PRI (1)
#define STACK_SIZE OS_STACK_ALIGN(128)
// global variables
struct os_task task;
os_stack_t task_stack[ STACK_SIZE ];
struct os_sem sem;
volatile uint32_t clicks = 0;
void task_func(void *arg) {
while (1) {
if ( hal_gpio_read( USER_BTN_PIN ) == 0 ) {
printf( "Button pressed! #%lu\r\n", ++clicks );
// Wait until the button is released.
while ( hal_gpio_read( USER_BTN_PIN )==0 ) {
os_time_delay( os_time_ms_to_ticks32(10) );
}
os_sem_release( &sem );
}
os_time_delay( os_time_ms_to_ticks32(10) );
}
}
int main(int argc, char **argv) {
sysinit(); // system initialization
printf( "Apache MyNewt Demo...\r\n" );
// Initialize GPIOs for LED and input button
hal_gpio_init_in( USER_BTN_PIN, HAL_GPIO_PULL_UP );
hal_gpio_init_out( LED_PIN, 0 );
// Create a semaphore
if ( os_sem_init(&sem,0) != OS_OK ) {
printf( "Cannot create a semaphore!\r\n" );
os_time_delay( OS_WAIT_FOREVER );
}
// Create a new task
os_task_init(
&task, "Input Button Task",
task_func, NULL,
TASK_PRI, OS_WAIT_FOREVER,
task_stack, STACK_SIZE
);

while (1) { // endless loop
if ( os_sem_pend(&sem, OS_TIMEOUT_NEVER)==OS_OK ) {
hal_gpio_toggle( LED_PIN );
}
}
return 0;
}

การใช้อินเทอร์รัพท์เพื่อตรวจสอบสถานะอินพุตจากปุ่มกด

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ถัดไปเป็นการสาธิตการตรวจสอบการเปลี่ยนแปลงลอจิกของขา GPIO ที่ใช้เป็นอินพุต เช่น เมื่อนำไปต่อกับวงจรปุ่มกด (ตัวอย่างนี้ใช้วงจรปุ่มกด USER BUTTON ที่มีอยู่แล้วบนบอร์ด Nucleo)

เมื่อมีการกดปุ่ม จะทำให้เกิดการเปลี่ยนจาก High เป็น Low หรือ Falling Edge เหตุการณ์ดังกล่าวจะทำให้เกิดอินเทอร์รัพท์จากภายนอก และจะมีการเรียกใช้ฟังก์ชันที่ทำหน้าที่เป็น ISR (Interrupt Service Routine) หรือ Interrupt Handler ซึ่งจะไปเรียกฟังก์ชันที่กำหนดโดยผู้ใช้อีกต่อหนึ่ง

ในตัวอย่างนี้ จะสาธิตการใช้ Semaphore เพื่อสื่อสารกันระหว่าง Callback Function และการทำงานของทาสก์หลัก

#include <string.h>
#include "sysinit/sysinit.h"
#include "os/os.h"
#include "bsp/bsp.h"
#include "hal/hal_gpio.h"
//#define USER_BTN_PIN MCU_GPIO_PORTC(13) // PC13 (Onboard)
#define USER_BTN_PIN MCU_GPIO_PORTA(10) // PA10 (D2)
#define LED_PIN MCU_GPIO_PORTA(5) // PA5 (D13)
// global variables
struct os_sem sem;
volatile uint32_t clicks = 0;
void btn_irq_handler( void *arg ) {
hal_gpio_irq_disable( USER_BTN_PIN );
os_sem_release( &sem );
}
int main(int argc, char **argv) {
sysinit(); // system initialization
printf( "Apache MyNewt Demo...\r\n" );
// Create a semaphore
if ( os_sem_init(&sem,0) != OS_OK ) {
printf( "Cannot create a binary semaphore!\r\n" );
os_time_delay( OS_WAIT_FOREVER );
}

// Initialize GPIO for LED output
hal_gpio_init_out( LED_PIN, 0 );

// Initialize GPIO for input button
// Enable interrupt for input button pin
hal_gpio_irq_init(
USER_BTN_PIN, btn_irq_handler, NULL,
HAL_GPIO_TRIG_FALLING,
HAL_GPIO_PULL_UP
);
hal_gpio_irq_enable( USER_BTN_PIN );

while (1) { // endless loop
if ( os_sem_pend(&sem, OS_TIMEOUT_NEVER)==OS_OK ) {
hal_gpio_write( LED_PIN, 1 );
os_time_delay( OS_TICKS_PER_SEC/10 );
hal_gpio_write( LED_PIN, 0 );
printf( "Button pressed! #%lu\r\n", ++clicks );
hal_gpio_irq_enable( USER_BTN_PIN );
}
}
return 0;
}

เมื่อเกิดเหตุการณ์ขอบขาลงที่สัญญาณอินพุตจากวงจรปุ่มกด (เช่น ต่อวงจรปุ่มกดเข้าที่ขา PA10 หรือ D2) จะทำให้เกิดสัญญาณพัลส์หนึ่งครั้งที่ขาเอาต์พุต LED (ขา D13 หรือ PA5) ตามมา ถ้าวัดสัญญาณด้วยเครื่องออสซิลโลสโคป ก็สามารถทราบระยะเวลาในการตอบสนองเหตุการณ์ได้

ตัวอย่างรูปคลื่นสัญญาณ 2 ช่อง อินพุตและเอาต์พุต ตามลำดับ (TIME/DIV=10 usec)

การใช้ Event Queue และสร้างเหตุการณ์เมื่อมีการกดปุ่ม

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

จากตัวอย่างที่แล้ว เมื่อมีการกดปุ่ม จะเกิดอินเทอร์รัพท์ในส่วนของ GPIO และทำให้มีการเรียกฟังก์ชันที่ทำหน้าที่เป็น IRQ Handler ให้ทำงานและสื่อสารกับทาส์กหลักโดยใช้ Semaphore แต่ในตัวอย่างนี้ จะลองเปลี่ยนมาใช้ Event Queue และสร้างทาส์กใหม่ในการประมวลเหตุการณ์จาก Event Queue แทนการใช้ Semaphore

#include <string.h>
#include "sysinit/sysinit.h"
#include "os/os.h"
#include "os/os_eventq.h"
#include "bsp/bsp.h"
#include "hal/hal_gpio.h"
//#define USER_BTN_PIN MCU_GPIO_PORTC(13) // PC13 (Onboard)
#define USER_BTN_PIN MCU_GPIO_PORTA(10) // PA10 (D2)
#define LED_PIN MCU_GPIO_PORTA(5) // PA5 (D13)
#define TASK_PRI (1)
#define STACK_SIZE OS_STACK_ALIGN(128)
// function prototypes
void btn_event_callback( struct os_event * );
// global variables
struct os_task event_task; // used to process event queue
os_stack_t event_task_stack[ STACK_SIZE ];
struct os_eventq event_queue;
struct os_event btn_event = {
.ev_cb = btn_event_callback,
};
volatile uint32_t clicks = 0;// function implementations
void btn_event_callback( struct os_event *ev ) {
hal_gpio_write( LED_PIN, 1 );
hal_gpio_write( LED_PIN, 0 );
printf( "Button clicked #%lu\r\n", ++clicks );
hal_gpio_irq_enable( USER_BTN_PIN );
}
void btn_irq_handler( void *arg ) {
hal_gpio_irq_disable( USER_BTN_PIN );
os_eventq_put( &event_queue, &btn_event );
}
void event_task_func( void *arg ) {
while (1) {
// process event queue
os_eventq_run( &event_queue );
}
}
int main(int argc, char **argv) {
sysinit(); // system initialization
printf( "Apache MyNewt Demo...\r\n" );

// Create an event queue for GPIO interrupt events
os_eventq_init( &event_queue );

// Create a task for processing the event queue
os_task_init(
&event_task, "Event Task",
event_task_func, NULL,
TASK_PRI, OS_WAIT_FOREVER,
event_task_stack, STACK_SIZE
);
// Initialize GPIO for LED
hal_gpio_init_out( LED_PIN, 0 );
// Initialize GPIO for input button
// Enable interrupt for input button pin
hal_gpio_irq_init(
USER_BTN_PIN, btn_irq_handler, NULL,
HAL_GPIO_TRIG_FALLING,
HAL_GPIO_PULL_UP
);
hal_gpio_irq_enable( USER_BTN_PIN );
while (1) { // endless loop
// process the system default event queue
os_eventq_run( os_eventq_dflt_get() );
}
return 0;
}

เมื่อมีการกดปุ่มแล้วสัญญาณอินพุตเกิดขอบขาลง (Falling Edge) จะทำให้มีการเรียกฟังก์ชันชื่อ btn_irq_handler() ซึ่งจะเพิ่มเหตุการณ์ (struct os_event) ลงใน Event Queue เพื่อรอการประมวลผลต่อไปโดยทาสก์ (event_task) จากนั้นเมื่อมีการประมวลผลเหตุการณ์จาก Event Queue ก็จะไปเรียกฟังก์ชันชื่อ btn_event_callback() เพื่อสร้างสัญญาณพัลส์เป็นเอาต์พุตที่ขา LED หนึ่งครั้ง

การใช้งาน Callout เพื่อสร้างเหตุการณ์ที่เกิดซ้ำ

‍‍‍‍‍‍ ‍‍ ‍‍‍‍‍‍ ‍‍‍‍‍‍ ‍‍

ถ้าต้องการให้เกิดเหตุการณ์หรือทำงานซ้ำและมีการเว้นช่วงระยะเวลาก่อนครั้งถัดไป ก็สามารถใช้สิ่งที่เรียกว่า Callout ซึ่งทำหน้าที่เหมือน Software Timer และจะต้องใช้งานร่วมกับ Event Queue ในตัวอย่างนี้ใช้ Default Event Queue ของระบบและประมวลผลโดยใช้ทาสก์หลัก

#include <string.h>
#include "sysinit/sysinit.h"
#include "os/os.h"
#include "bsp/bsp.h"
#include "hal/hal_gpio.h"
#define LED_PIN MCU_GPIO_PORTA(5) // PA5 (D13)// Constant
const uint32_t TIMEOUT = 100; // microseconds
// Global variables
struct os_callout timer;
uint32_t saved_ticks;
void timer_callback( struct os_event *ev ) {
uint32_t now_ticks = os_cputime_get32();
// Reset the callout so that it fires again
os_callout_reset( &timer, TIMEOUT );
hal_gpio_toggle( LED_PIN );
printf( "%lu msec\r\n", (now_ticks-saved_ticks)/1000 );
saved_ticks = now_ticks;
}
int main(int argc, char **argv) {
sysinit(); // system initialization
printf( "Apache MyNewt Demo...\r\n" );
os_cputime_init( 1000000UL ); // 1MHz
saved_ticks = os_cputime_get32();
// Initialize GPIO for LED
hal_gpio_init_out( LED_PIN, 0 );

// Create a callout (timer)
os_callout_init(
&timer, os_eventq_dflt_get(),
timer_callback, NULL
);
// Start the callout (timer)
os_callout_reset( &timer, TIMEOUT );

while (1) { // endless loop
// process the system default event queue
os_eventq_run( os_eventq_dflt_get() );
}
return 0;
}

โดยสรุป บทความนี้ได้นำเสนอตัวอย่างการเขียนโปรแกรมในภาษา C โดยใช้ Apache MyNewt เพื่อสาธิตการทำงานแบบมัลติทาสกิ้งในรูปแบบต่าง ๆ เช่น การสร้างทาสก์ การสื่อสารกันระหว่างทาสก์ โดยใช้ Semaphore, Mutex และ Event Queue เป็นต้น

--

--

<rawat.s>
<rawat.s>

Written by <rawat.s>

I'm Thai and working in Bangkok/Thailand.

No responses yet