บทความก่อนหน้านี้ได้นำเสนอวิธีการใช้งาน VSCode ร่วมกับ AliOS Studio เพื่อนำมาใช้ในการเขียนโค้ดแบบมัลติทาสกิ้ง (Multi-Tasking) โดยอาศัย AliOS Things RTOS และเลือกทดสอบการทำงานกับบอร์ด STM32 Nucleo L476RG …ในบทความนี้ เราจะมาลองใช้คำสั่งของ AOS Kernel API เพิ่มเติม
การสร้างทาส์กให้ทำงานตามคาบเวลา
ตัวอย่างนี้สาธิตการสร้างทาส์ก เพื่อทำให้เกิดการสลับสถานะลอจิกที่ขาเอาต์พุตซึ่งต่อกับวงจร LED บนบอร์ดไมโครคอนโทรลเลอร์ เมื่อทาส์กถูกสร้างขึ้นมาและเริ่มทำงาน ก็จะทำให้เกิดการกระพริบที่ LED ด้วยอัตราคงที่ (ให้สลับสถานะของเอาต์พุตทุก ๆ 50 msec) ทาส์กจะทำงานซ้ำตามจำนวนครั้งที่กำหนดไว้แล้วจบการทำงาน
#include <stdio.h>
#include <stdlib.h>#include <aos/kernel.h>
#include <aos/hal/gpio.h>// see: board/stm32l476rg-nucleo/hal/hal_gpio_stm32l4.h
#define GPIO_LED_PIN 5 // PA5// global variables
gpio_dev_t led; // GPIO: LED output
aos_task_t task; // task handlevoid led_gpio_init( void ) {
led.port = GPIO_LED_PIN; // set GPIO port
led.config = OUTPUT_PUSH_PULL; // use output mode
hal_gpio_init( &led ) ; // configure GPIO for LED
}void led_task_func( void *arg ) {
printf( "Task '%s' started..\r\n", aos_task_name() );
for ( int i=0; i < 20; i++ ) {
hal_gpio_output_toggle( &led );
aos_msleep(50);
}
printf( "Task '%s' finished..\r\n", aos_task_name() );
aos_task_exit(0);
}// The entry function for AOS application
int application_start(int argc, char *argv[]) {
led_gpio_init();
aos_msleep( 1000 ); // create a new task
aos_task_new_ext( &task, "LED_Toggle",
led_task_func, NULL,
1024, AOS_DEFAULT_APP_PRI );
while (1) {
aos_msleep( 10 );
}
aos_task_free(&task);
return 0;
}
การสร้างทาส์กที่มีระดับความสำคัญแตกต่างกัน
ตัวอย่างถัดไปเป็นการสร้างทาส์ก “LPT” (Lower Priority Task) และ “HPT” (Higher Priority Task) ซึ่งมีระดับความสำคัญไม่เท่ากัน ทาสก์ LPT มีระดับความสำคัญต่ำกว่า และจะทำให้ LED กระพริบด้วยอัตราที่ต่ำกว่า (เว้นช่วงทุก ๆ 50 msec) และทาส์ก HPT ที่มีระดับความสำคัญสูงกว่า จะทำให้ LED กระพริบด้วยอัตราที่สูงกว่า (เว้นช่วงทุก ๆ 5 msec)
ถ้าทาส์ก HPT ยังไม่พร้อมที่จะทำงาน เช่น หยุดรอให้เวลาผ่านไปตามที่กำหนดไว้ และทาส์ก LPT พร้อม ก็จะได้ทำงานไปตามปรกติ แต่เมื่อทาส์ก HPT พร้อมที่จะทำงานแล้ว การทำงานของ LPT จะถูกแทรกกลาง โดยจะต้องหยุดทำงานชั่วคราว ให้ทาส์ก HPT ได้ทำงาน ซึ่งเป็นไปตามวิธีการจัดลำดับการทำงานของทาส์กแบบ Priority-based, Preemptive Scheduling
#include <stdio.h>
#include <stdlib.h>#include <aos/kernel.h>
#include <aos/hal/gpio.h>// see: board/stm32l476rg-nucleo/hal/hal_gpio_stm32l4.h
#define GPIO_LED_PIN 5 // PA5
#define GPIO_BTN_PIN 45 // PC13// global variables
gpio_dev_t led;
aos_task_t hp_task, lp_task;void led_gpio_init( void ) {
led.port = GPIO_LED_PIN; // set GPIO port
led.config = OUTPUT_PUSH_PULL; // use output mode
hal_gpio_init( &led ); // configure GPIO for LED
}void led_hp_task( void *arg ) {
aos_msleep( 200 );
for ( int i=0; i < 20; i++ ) {
hal_gpio_output_toggle( &led );
aos_msleep(5);
}
aos_msleep( 100 );
for ( int i=0; i < 30; i++ ) {
hal_gpio_output_toggle( &led );
aos_msleep(5);
}
aos_task_exit(0);
}void led_lp_task( void *arg ) {
for ( int i=0; i < 20; i++ ) {
hal_gpio_output_toggle( &led );
aos_msleep(50);
}
aos_task_exit(0);
}int application_start(int argc, char *argv[]) {
led_gpio_init();
aos_task_new_ext( &lp_task, "LPT",
led_lp_task, NULL, 1024, AOS_DEFAULT_APP_PRI );
aos_task_new_ext( &hp_task, "HPT",
led_hp_task, NULL, 1024, AOS_DEFAULT_APP_PRI+1 );
while (1) {
aos_msleep( 10 );
}
return 0;
}
จากรูปคลื่นสัญญาณดิจิทัลที่วัดได้โดยใช้เครื่องออสซิลสโคป จะเห็นได้ว่า ช่วงแรกเกิดการเปลี่ยนสถานะของ LED ในอัตราที่ต่ำ ซึ่งเกิดจากการทำงานของทาส์ก LPT แต่ในช่วงเวลาต่อมา ทาส์ก HPT ได้ทำงาน (แบ่งเป็น 2 ช่วง) จึงเห็นได้ว่า สัญญาณเอาต์พุตมีการเปลี่ยนสถานะในอัตราที่สูงขึ้น เมื่อเวลาผ่านไป ทาส์ก LPT และ HPT ทำงานจบแล้ว ก็จะไม่เห็นการเปลี่ยนสถานะที่ LED อีกต่อไป
การใช้ทรัพยากรร่วมและจัดการโดย Mutex
ตัวอย่างนี้สาธิตการสร้างทาส์ก (จำนวนมากกว่าหนึ่ง) ที่พยายามเข้าถึงทรัพยากรที่ต้องใช้ร่วมกัน (Shared Resource) เช่น การใช้คำสั่ง printf()
เพื่อส่งข้อความออกทาง Serial (USART) ไปยังคอมพิวเตอร์ ในกรณีเราจะใช้ AOS Mutex เพื่อป้องกันการแทรกกลางในขณะที่ทาส์กใดทาส์กหนึ่งกำลังเรียกใช้คำสั่ง printf()
คำสั่งที่เกี่ยวข้องกับการใช้ AOS Mutex ได้แก่
aos_mutex_new()
สร้าง Mutex สำหรับใช้งานaos_mutex_is_valid()
ตรวจสอบดูว่า Mutex สามารถใช้งานได้หรือไม่aoa_mutex_lock()
พยายามเข้าใช้ Mutex หรือรอจนกว่าจะได้ใช้ (สามารถกำหนดระยะเวลาในการรอได้ หรือ Timeout)aos_mutex_unlock()
ยกเลิกการเข้าใช้ Mutexaos_mutex_free()
ทำลาย Mutex เมื่อไม่ใช้งานแล้ว และคืนหน่วยความจำ
#include <stdio.h>
#include <stdlib.h>#include <aos/kernel.h>
#include <aos/hal/gpio.h>// see: board/stm32l476rg-nucleo/hal/hal_gpio_stm32l4.h
#define GPIO_LED_PIN 5 // PA5
#define NUM_TASKS 4#define USE_MUTEX// global variables
gpio_dev_t led;
aos_task_t tasks[ NUM_TASKS ];
aos_mutex_t mutex;void led_gpio_init( void ) {
led.port = GPIO_LED_PIN; // set GPIO port
led.config = OUTPUT_PUSH_PULL; // use output mode
hal_gpio_init( &led ); // configure GPIO for LED
}void task_func( void *arg ) {
uint32_t id = (uint32_t)arg;
uint32_t cnt = 0;
while (1) {
#ifdef USE_MUTEX
aos_mutex_lock( &mutex, -1 );
#endif printf( "Task ID=%lu, ", id );
aos_msleep(1);
printf( " cnt=%lu\r\n", ++cnt );#ifdef USE_MUTEX
aos_mutex_unlock( &mutex );
#endif
aos_msleep( 100 + (abs(aos_rand()))%1000 );
}
aos_task_exit(0);
}int application_start(int argc, char *argv[]) {
char task_name[10] = {0};
led_gpio_init();
aos_srand( 0 );
aos_mutex_new( &mutex ); // create a mutex
printf("\r\n\r\n");
aos_msleep(100);
for ( uint32_t i=0; i < NUM_TASKS; i++ ) {
sprintf( task_name, "Task_%d", i );
aos_task_new_ext( &tasks[i], task_name,
task_func, (void *)i, 1024, AOS_DEFAULT_APP_PRI );
}
while (1) {
hal_gpio_output_toggle(&led);
aos_msleep( 50 );
}
aos_mutex_free(&mutex);
return 0;
}
ถ้าทดสอบการทำงานของโค้ดตัวอย่างนี้โดยใช้ฮาร์ดแวร์ ให้แบ่งเป็น 2 กรณี คือ กรณีแรกไม่มีการคำสั่งนี้ในโค้ด (ไม่ใช้ Mutex) และอีกกรณีคือ มีคำสั่งดังกล่าว (ใช้ Mutex) และเปรียบเทียบความแตกต่าง โดยใช้โปรแกรม เช่น Arduino Serial Monitor เปิดรับข้อความที่ถูกส่งมาจากบอร์ดไมโครคอนโทรลเลอร์
การสร้างทาส์กใหม่เมื่อเกิดเหตุการณ์ภายนอก
ตัวอย่างนี้สาธิตการสร้างทาส์กใหม่เมื่อมีการกดปุ่มแล้วปล่อยหนึ่งครั้ง (ปุ่มกดทำงานแบบ Active-Low) เมื่อทาสก์ทำงานจะทำให้ LED กระพริบตามจำนวนครั้งที่กำหนดไว้แล้วจบการทำงาน นอกจากนั้นยังมีการสื่อสารระหว่างทาส์กโดยใช้ AOS Semaphore เช่น เพื่อรอให้จบการทำงานของทาส์กก่อนสร้างทาส์กถัดไป
คำสั่งที่ใช้เกี่ยวกับ AOS Semaphore ได้แก่
aos_sem_create()
สร้าง Semaphore สำหรับใช้งานaos_sem_is_valid()
ตรวจสอบดูว่า Semaphore สามารถใช้งานได้หรือไม่aos_sem_wait()
รอจนกว่าจะได้เข้าใช้ Semaphore (กำหนดช่วงเวลารอ หรือ Timeout ได้) ให้ผลเหมือน “Take”aos_sem_signal()
ส่งสัญญาณไปยัง Semaphore ให้ผลเหมือน “Give”aos_sem_free()
ทำลาย Semaphore เมื่อไม่ใช้งานแล้ว และคืนหน่วยความจำ
#include <stdio.h>
#include <stdlib.h>#include <aos/kernel.h>
#include <aos/hal/gpio.h>// see: board/stm32l476rg-nucleo/hal/hal_gpio_stm32l4.h
#define GPIO_LED_PIN 5 // PA5
#define GPIO_BTN_PIN 45 // PC13// global variables
gpio_dev_t led; // GPIO: LED output
gpio_dev_t btn; // GPIO: input button (active-low)
aos_sem_t sem; // semaphorevoid led_gpio_init( void ) {
led.port = GPIO_LED_PIN; // set GPIO port
led.config = OUTPUT_PUSH_PULL; // use output mode
hal_gpio_init( &led ); // configure GPIO for LED
}void btn_gpio_init( void ) {
btn.port = GPIO_BTN_PIN; // set GPIO port for push button
btn.config = INPUT_PULL_UP; // use input pull-up
hal_gpio_init(&btn); // configure GPIO for button
}void led_task( void *arg ) {
for ( int i=0; i < 6; i++) {
hal_gpio_output_toggle( &led );
aos_msleep(50);
}
if ( aos_sem_is_valid(&sem) ) {
aos_sem_signal( &sem );
}
printf( "Task '%s' done..\r\n", aos_task_name() );
aos_task_exit(0);
}// The entry function for AOS application
int application_start(int argc, char *argv[]) {
int value = -1;
int pressed = 0;
led_gpio_init();
btn_gpio_init(); if ( aos_sem_new( &sem, 1 )!=0 ) {
printf("aos_sem_new() failed..\r\n" );
while(1) {
aos_msleep( AOS_WAIT_FOREVER );
}
} while(1){
pressed = 0;
hal_gpio_input_get( &btn, &value );
while ( value==0 ) { // button is pressed
pressed = 1;
aos_msleep(100);
hal_gpio_input_get( &btn, &value );
}
if ( pressed==1 ) { // button was pressed
while ( aos_sem_wait( &sem, 100 ) ){}
printf( "create a new task\r\n" );
aos_task_new( "LED_Toggle",
led_task, NULL, 1024 );
}
aos_msleep(10);
}
aos_sem_free(&sem);
return 0;
}
การสื่อสารข้อมูลระหว่างทาส์กโดยใช้ Queue
ตัวอย่างถัดไปเป็นการสร้างทาส์ก “Producer” และ “Consumer” ซึ่งมีระดับความสำคัญเท่ากัน และใช้ AOS Queue เป็นตัวกลางในการส่งและรับข้อมูล โดยใช้คำสั่ง เช่น
aos_queue_new()
สร้าง Queue เพื่อนำมาใช้กับชนิดของข้อมูลตามที่ระบุaos_queue_is_valid()
ตรวจสอบดูว่า สามารถใช้ Queue ได้หรือไม่aos_queue_send()
ส่งข้อมูลไปยัง Queueaos_queue_recv()
รับข้อมูลจาก Queue หรือ รอจนกว่าจะอ่านข้อมูลได้ (กำหนดระยะเวลาสำหรับ Timeout)aos_queue_free()
ทำลาย Queue เมื่อไม่ใช้งานแล้ว และคืนหน่วยความจำ
#include <stdio.h>
#include <stdlib.h>#include <aos/kernel.h>
#include <aos/hal/gpio.h>// see: board/stm32l476rg-nucleo/hal/hal_gpio_stm32l4.h
#define GPIO_LED_PIN 5 // PA5// global variables
gpio_dev_t led;
aos_task_t producer_task, consumer_task;
aos_queue_t queue;typedef struct {
uint8_t id;
uint8_t len;
char buf[32];
} message_t;#define QUEUE_CAPACITY 4
message_t message_queue_buffer[ QUEUE_CAPACITY ];void led_gpio_init( void ) {
led.port = GPIO_LED_PIN; // set GPIO port
led.config = OUTPUT_PUSH_PULL; // use output mode
hal_gpio_init( &led ); // configure GPIO for LED
}void producer_task_func( void *arg ) {
int ret;
uint16_t cnt = 0;
message_t msg;
aos_msleep(2000);
while (1) {
if ( aos_queue_is_valid(&queue) ) {
snprintf( msg.buf, 32, "Hello #%04d", cnt );
msg.id = cnt % 256;
msg.len = strlen( msg.buf );
cnt = (cnt+1) % 10000;
do {
ret = aos_queue_send( &queue,
&msg, sizeof(message_t) );
aos_msleep(100);
} while (ret != 0);
if (cnt % 16 == 0) {
aos_msleep(1000);
}
}
}
}void consumer_task_func( void *arg ) {
int ret;
message_t msg;
unsigned int recv_size;
uint8_t cnt = 0;
while (1) {
if ( aos_queue_is_valid(&queue) ) {
recv_size = 0;
ret = aos_queue_recv( &queue,
AOS_WAIT_FOREVER, &msg, &recv_size );
if ( ret == 0 ) {
printf( "recv: [%02Xh] '%s'\r\n",
msg.id, msg.buf );
}
}
}
}int application_start(int argc, char *argv[]) {
int ret;
led_gpio_init();
ret = aos_queue_new( &queue,
&message_queue_buffer,
sizeof(message_t),
QUEUE_CAPACITY * sizeof(message_t) );
if ( ret != 0 ) {
printf( "aos_queue_new() failed..\r\n" );
} aos_task_new_ext( &consumer_task, "Consumer",
consumer_task_func, NULL,
1024, AOS_DEFAULT_APP_PRI ); aos_task_new_ext( &producer_task, "Producer",
producer_task_func, NULL,
1024, AOS_DEFAULT_APP_PRI ); while (1) {
hal_gpio_output_toggle(&led);
aos_msleep( 50 );
}
aos_queue_free(&queue);
return 0;
}
การทำงานตามคาบเวลาโดยใช้ตัวนับเวลา (Timer)
ตัวอย่างถัดไปสาธิตการใช้ฟังก์ชันที่เกี่ยวข้องกับ AOS Timer ของ AOS Kernel API เพื่อเรียกฟังก์ชันในลักษณะ Callback ตามช่วงเวลาที่กำหนด เช่น การใช้คำสั่ง
aos_timer_new()
สร้าง Timer ไว้ใช้งาน กำหนดระยะเวลา (หน่วยเป็น มิลลิวินาที) หลังจากเริ่มต้นและเวลาผ่านไปตามที่กำหนด จะเรียกฟังก์ชัน Callback ที่เกี่ยวข้อง และสามารถกำหนดได้ว่า จะทำให้เกิดซ้ำ (Repeated) หรือครั้งเดียว (One-Shot)aos_timer_start()
ทำให้ Timer เริ่มทำงานใหม่อีกครั้ง ถ้าเคยถูกหยุดการทำงานไว้ชั่วคราวaos_timer_change()
เปลี่ยนระยะเวลาในการทำงานของ Timeraos_timer_stop()
หยุดการทำงานของ Timeraos_timer_free()
เลิกใช้ Timer เมื่อได้หยุดทำงานแล้ว และคืนหน่วยความจำ
ถ้าทดสอบการทำงานโค้ดนี้กับบอร์ด STM32 Nucleo จะเห็นว่า เริ่มต้น LED บนบอร์ดจะกระพริบ และจะกระพริบช้าลงเรื่อย ๆ เพราะมีการเปลี่ยนช่วงเวลาหรือคาบเวลาการทำงานของ Timer และสุดท้าย จะหยุดการทำงาน เมื่อมีการเปลี่ยนสถานะลอจิก (Toggle) เช่น ครบ 100 ครั้ง
#include <stdio.h>
#include <stdlib.h>#include <aos/kernel.h>
#include <aos/hal/gpio.h>
// see: board/stm32l476rg-nucleo/hal/hal_gpio_stm32l4.h
#define GPIO_LED_PIN 5 // PA5// global variables
gpio_dev_t led;
aos_timer_t timer;void led_gpio_init( void ) {
led.port = GPIO_LED_PIN; // set GPIO port
led.config = OUTPUT_PUSH_PULL; // use output mode
hal_gpio_init( &led ); // configure GPIO for LED
}void timer_handler(void *arg1, void *arg2) {
static uint32_t cnt = 0;
uint32_t msec = (uint32_t)arg2;
hal_gpio_output_toggle( &led );
cnt++; printf( "Count: %lu\n", cnt );
if ( cnt >= 100 ) { // stop the timer
aos_timer_stop( &timer );
aos_timer_free( &timer );
}
else if ( cnt % 10 == 0 ) { // change the timer period
uint32_t period = (cnt/10 + 1)*msec;
aos_timer_stop( &timer ); // stop timer
aos_timer_change( &timer, period ); // change period
aos_timer_start( &timer ); // restart timer
}
}// The entry function for AOS application
int application_start(int argc, char *argv[]) {
int ret;
led_gpio_init();
// create a timer
ret = aos_timer_new( &timer, timer_handler,
(void *)100, 100 /*msec*/, 1 /*repeated*/ );
if (ret != 0) {
printf( "aos_timer_new() failed...\r\n" );
}
while(1) {
aos_msleep( 10 );
} // forever loop
return 0;
}
ในบทความนี้เราได้เห็นตัวอย่างการใช้ AOS Kernel API เช่น การสร้างทาส์กและวิธีการสื่อสารหรือประสานจังหวะการทำงานระหว่างทาส์ก (Inter-Task Communication & Synchronization) โดยใช้ Semaphore / Mutex และ Queue เป็นต้น
ถ้าท่านใดได้เคยใช้งาน RTOS อย่างเช่น FreeRTOS ก็ลองเปรียบเทียบความเหมือนและความแตกต่างกับ AOS Kernel API ของ AliOS Things ดูนะครับ