NXP JN516X (TWELITE) をプログラミングする(Multi Task)

JN516xの詳細を知りたくて調べてみたらNXP社の前はJENNIC社がASICで製造していて組み込んだCPUがBeyond Semiconductor社が開発していたOpenRISCの派生物らしい。NXP社が提供している開発ツールがBeyond Studioであることからもほぼ間違いないとは思う。

CPUはOpenRISCと似通ってはいるが異なる点も多くOpenRISCの命令セットの一部に独自命令を追加した命令セットになっているようだ。レジスタはR0 – R31まであって、R0が常にゼロを返すレジスタ、R1がスタックポインタ(SP)、R2は不明(OpenRISCではFP)、R3-R8が引数レジスタ、R9がリンクレジスタ(LR)となっている。またR9以上のレジスタについてはCallee-saveとなっていてSDKがR16-R31を予約使用しているようなのでアプリはR16以上を使ってはいけないようだ。特にR16はペリフェラルへアクセスするためにSDKの初期化で設定された後は変更されないことを前提に各APIで使用しているようなので絶対変更してはいけない。

JN516xでマルチタスクというとContikiというswich構文ベースの疑似マルチタスクぐらいしかないようだ。SDKにはsetjmp/longjmpが実装されていたのでそれを使ってマルチタスクできるだろうと試してみたらまたまたドツボにハマッてしまった。このCPUは本当に落とし穴が多すぎる。(-_-;)

実装は問題なさそうなのだがsetjmp.hを見ると”stubs for future use.”と書かれていたので何か問題あるのかもとは思っていたがsetjmpは正しく動作するもののlongjmpを実行するとCPUが停止してしまう。R1からR31までのレジスタを単純に設定するだけで停止?...試しにと設定するレジスタを一つずつ減らしてみたら特定のレジスタ(R15,R17,R19,R25,R26)の設定でCPUが停止(例外?)するという普通にありえない現象だった。orz

しかも、それらのレジスタはコードで現実に使われているレジスタなので不安は尽きない...

とりあえず一部のレジスタのみを保存復旧するsetjmp/longjmpを試してみたら見事に動作するようになった。(/・ω・)/

タスク・ライブラリは、以前に投稿した”シンプルすぎる汎用C++タスクスケジューラー”を改良しJN516xに対応してみた。CPU非依存にするためスタックポインタの調整はalloca()を利用している。タイムスライスなしのノンプリエンプティブなタスクライブラリである。ちなみに前回のクラス名が長すぎてシンプルでなかったので今回は短くシンプルな名前にしてみた。(笑)

シンプルすぎる汎用C++タスクスケジューラー(CScheduler)を作ってみた。

以前と同じくWindows/Linuxでも使うことが可能なカレントスタックを切り刻んで使う方法に加え、組み込み用途でよくある静的配列変数領域をスタック領域としたりmalloc()で割り当てた領域をスタック領域とすることもできるようにしてみた。但し、スタックとヒープ領域が同じアドレス空間にあることやメモリレイアウトがスタック(下方伸長)>ヒープ(上方伸長)であるという前提である。それ以外のCPUでは動作はしない。

※JN516xのSDKではmalloc()が実装されていないことに注意。

タスクはダブルエンデット・リストで管理されるがその操作はこれ以上無理なレベルまで最適化している。複数行のコードを1行にまとめただけとも言えるがコンパイラの最適化により全く無駄のないコードが生成される。

タスク同期機能はsleep/wakeup, mutex, semaphore, eventに対応している。ないよりはましという程度ではあるが。(-_-;)

JN516xのSDKではクラスのコンストラクターが実行されないのでそのかわりにinit()を定義している。またmalloc()が使えないためタスク同期クラスはstatic変数として定義し最初にinit()を呼び出す必要があることに注意しよう。

【修正履歴】
2022-06-29
昨日のSemクラスの修正の修正。

2022-06-28
Semクラスのタイミングバグ修正とTask::start()に負数のスタックサイズ指定をしたとき登録タスクではなく直前の登録タスクのスタックサイズ指定になっていて混乱しやすいので修正。

2022-06-23
Semクラスのバグ修正とTaskクラスのチューニング。それと2レベルではあるがタスク優先度を追加し起床タスクの優先度を一時的にあげる仕組みを追加してみた。リアルタイムにはスイッチングしないが休止状態から起床されたタスクが最優先で実行されるようになる。可能ならリアルタイム性を追加したいところではあるがリアルタイムやタイムスライスに対応すると標準ライブラリを含む全てのライブラリもマルチスレッド対応にする必要があったりしてとても大変なことになるで対応はしない。

2022-06-21
Task::start()の最適化を行った。

2022-06-15
Sync/Mutex/Sem/Eventクラスのコード最適化の続きを行った。

2022-06-14
Sync/Mutex/Sem/Eventクラスのコード最適化を行った。

2022-06-13
Eventクラスがバグッてたので修正。

2022-06-12
Task::start()で指定するタスクのスタックサイズが割り当てモード毎に違っていて混乱するのでスタックサイズにはTask::TCB_SIZEを加算したサイズが指定されているものと仮定した仕様変更を行った。

例えば、スタックサイズとして2048バイトを指定する場合は
2048 + Task::TCB_SIZE
を指定しなければならない。

2022-06-11
いつの間にかタスクが動作しなくなっていたのとSem/Mutex/Eventのウェイト処理のタイミングバグを修正。あとメッセージバッファのような機能を作りやすくするためSemクラスのwait()/post()にカウント値引数を追加。

2022-06-10
Sem::init()にカウンター初期値を設定するための引数を追加

2022-05-01
詳細不明であることが気になっていたためsetjmp/longjmpにて可能な限りのレジスタを保存復旧してみた。しかし、どうやってもCPUが不安定になってしまうためR17/R19の対応は無理なようだ。命令の順番を入れ替えたりすると挙動が変わるため投機実行処理(パイプライン)のバグなのかもしれない。R16以降のレジスタがSDKの予約レジスタであるなら問題にはならないはず。
ちなみにアセンブラのニーモニックにJRとかJALがあって日本に詳しい設計者がいたのかな?みたいに感じるところが少し面白い。(笑) でも命令セットに起因して巨大なコードになってしまいがちだし実行速度が特別速いという感じでもないしコンパイラが64bit型の計算時にcarryを使わない非効率的なコードを生成するのも気になる点だ。OpenRISCの初期開発チップにcarryバグがあったらしいのでその回避策なのかもしれないが...
ちなみに最近存在感が目立ってきたRISC-V(OpenRISCの後継?)がどこまで進歩しているのか少し気になってきたかも。

【サンプル】

【ライブラリ】

【関連投稿】
NXP JN516X (TWELITE) をプログラミングする(開発環境の構築)
NXP JN516X (TWELITE) をプログラミングする(メイン・ルーチン)
NXP JN516X (TWELITE) をプログラミングする(TICKTIMER)
NXP JN516X (TWELITE) をプログラミングする(UART)
NXP JN516X (TWELITE) をプログラミングする(SYSTEM)
NXP JN516X (TWELITE) をプログラミングする(GPIO)
NXP JN516X (TWELITE) をプログラミングする(TIMER)
NXP JN516X (TWELITE) をプログラミングする(ALARM)
NXP JN516X (TWELITE) をプログラミングする(WAKETIMER)
NXP JN516X (TWELITE) をプログラミングする(WATCHDOG)
NXP JN516X (TWELITE) をプログラミングする(I2C)
NXP JN516X (TWELITE) をプログラミングする(SPI)
NXP JN516X (TWELITE) をプログラミングする(ADC)
NXP JN516X (TWELITE) をプログラミングする(COMPARATOR)
NXP JN516X (TWELITE) をプログラミングする(CLOCK)
NXP JN516X (TWELITE) をプログラミングする(BROWNOUT)
NXP JN516X (TWELITE) をプログラミングする(PULSCOUNTER)
NXP JN516X (TWELITE) をプログラミングする(INFRARED)
NXP JN516X (TWELITE) をプログラミングする(RANDOM-GENERATOR)
NXP JN516X (TWELITE) をプログラミングする(FLASH)
NXP JN516X (TWELITE) をプログラミングする(EEPROM)
NXP JN516X (TWELITE) をプログラミングする(WPAN)
NXP JN516X (TWELITE) をプログラミングする(Eclipse-CDT+MWSTAGE)
NXP JN516X (TWELITE) をプログラミングする(乗算と除算)
NXP JN516X (TWELITE) をプログラミングする(マルチタスク)
NXP JN516X (TWELITE) をプログラミングする(フラッシュ・プログラマー)
NXP JN516X (TWELITE) をプログラミングする(OTA UPDATE)
NXP JN516X (TWELITE) をプログラミングする(TWELITE CUE/MC3630)
NXP JN516X (TWELITE) をプログラミングする(LED)
NXP JN516X (TWELITE) をプログラミングする(AES)
NXP JN516X (TWELITE) をプログラミングする(Downloads)