タスク機能を実際に NonOS SDK で使う方法を解説します。
タスク登録のための準備
タスク1つにつき以下をすべてのタスクに行ってください。
タスク用の変数等を準備
タスク機能を使う時に SDK および呼ばれたユーザー関数側で使う変数を準備します。API リファレンス上では確か「キュー」と言われています。連続的に配列としてメモリを確保して使います。
まず、ポインタを宣言。多分どこでも良いとは思いますが、取り敢えずグローバル変数として於いたほうが良さげ。
os_event_t *queue;
タスク実行用の関数も用意しておきましょう。取り敢えず空で。
static void task(os_event_t *e) {
}
初期化用の記述
その上で、以下を user_init
関数内に記述します。#include <stdlib.h>
をお忘れなく。
queue = (os_event_t *)malloc(sizeof(os_event_t) * 4);
system_os_task(task, USER_TASK_PRIO_0, queue, 4);
この解説です。まず malloc
は領域確保なのでわかりますよね。取り敢えず4つ確保しておきました。
2行目では引数として、タスク実行用関数のポインタ(型は void (*)(os_event_t *)
)、優先順位、キューへのポインタ、キューの数を記述します。
この内優先順位ですが、マクロとして USER_TASK_PRIO_0
, USER_TASK_PRIO_1
, USER_TASK_PRIO_2
が用意されていて、最大3つまでタスクを登録できます。0〜2の各優先順位に、タスクを割り振るというイメージです。なので 優先順位0のタスクを複数登録するということは出来ません。
なお、数字が大きいほど優先順位は上なようです。
以上で準備完了です。
タスク実行のリクエスト
実際にリクエストを送る処理は、user_init
関数を抜けた後に呼んだほうが良いでしょう。例えば定期的に実行したければタイマーのコールバック関数内で呼んでも良いかも。僕は OS タイマーで呼んでいます。
例えばこうやって user_init
内に記述することで OS タイマーをセットして...
os_timer_disarm(&os_timer);
os_timer_setfn(&os_timer, (os_timer_func_t *)os_timer_cb, NULL);
os_timer_arm(&os_timer, 1000, 1); // 1000msec
以下のように呼ぶことで、OS タイマーのコールバック関数が実行されるごとにリクエストを送れます。
static void os_timer_cb(void *arg) {
system_os_post(USER_TASK_PRIO_0, 0, 'x');
}
上の例では、先程登録した優先順位0のタスクを実行せよ、と言っています。当然ここではタスクを実行をリクエストする(正確には先程用意したキューにデータを追加する)だけですので、OS タイマーのコールバック関数はすぐに終了します。身軽ですね。
この system_os_post
ですが、以下のようなプロトタイプ宣言になっています。
bool system_os_post (
uint8 prio,
os_signal_t sig,
os_param_t par
);
uint8 prio
は優先順位です。残りに関してですが、os_signal_t
, os_param_t
に関して、これは uint32_t と同等です。ですのであまり身構えないでくださいね。
これらは呼び出されたタスク関数内で、引数としてユーザーが自由に利用できます。
実践編
では実際にLチカをするタスクを作ってみます。これだけだとタスク機能の利便性が伝わらないかもですが、とにかく動かしてみます。
コードはこんな感じ。
static os_timer_t os_timer;
static void os_timer_cb(void *arg) {
system_os_post(USER_TASK_PRIO_0, 0, 'x');
os_printf("\r\nTask Registered!\r\n");
}
os_event_t *queue;
static void task(os_event_t *e) {
switch(e->sig) {
case SIG_OFF:
gpio_output_set(0, BIT4, 0, 0);
os_printf("SIG_OFF...");
break;
case SIG_ON:
gpio_output_set(BIT4, 0, 0, 0);
os_printf("SIG_ON...");
break;
default:
break;
}
os_printf("Wait for 200msec...");
os_delay_us(1000 * 200); // wait for 200msec
os_printf("\r\nTask Done!\r\n");
}
void user_init(void) {
uart_init(BIT_RATE_115200, BIT_RATE_115200); // For debug output
os_printf("\r\n");
queue = (os_event_t *)malloc(sizeof(os_event_t) * 4);
system_os_task(task, USER_TASK_PRIO_0, queue, 4);
gpio_output_set(0, 0, BIT4, 0);
os_timer_disarm(&os_timer);
os_timer_setfn(&os_timer, (os_timer_func_t *)os_timer_cb, NULL);
os_timer_arm(&os_timer, 1000, 1); // 1000msec
}
これを書き込んで実行すると、
LED ON→200msec 待ち→LED OFF→200msec 待ち→残りの600msec待ち
と言う処理がなされます。シリアル通信データは以下のようです。
...
Task Registered!
SIG_ON...Waiting for 200msec...
Task Done!
SIG_OFF...Waiting for 200msec...
Task Done!
Task Registered!
SIG_ON...Waiting for 200msec...
Task Done!
SIG_OFF...Waiting for 200msec...
Task Done!
...
〆
ある一定間隔の間に処理を行わせたいだとか、重い処理なので割り込みコールバック関数内に記述したくないとか、そういう場面で上手く使えそうな機能です。
比較低レイヤの SDK ですので、登録タスク数が少ないのが若干残念かなあと思いました。まあ沢山のタスクを並列で回したいとかの場合は RTOS SDK を使えってことなんですかね。いずれにしろ、今回は逐次処理でしたがタスク機能を利用することが出来ました。