ステップバイステップ！リアルタイムOSを評価ボードへ移植 ～ TOPPERS/ASP3をNXP FRDM-MCXN947へ～ 第5回

## 本記事について 

リアルタイムOSを市販のマイコン評価ボードに移植する手順をステップバイステップでポイント毎に動作を確認しながら、説明します。移植するリアルタイムOSはTOPPERS/ASP3です。 

下記構成で複数回に分けての投稿を予定しています。 

:::note info
本記事の投稿予定

[第0回 本記事の趣旨、TOPPERS/ASP3と評価ボードの説明](https://qiita.com/Debuggy_24/items/0b42d50c4744238bb3a1) 

[第1回 準備　ハードウェアの入手、ツールのインストールなど](https://qiita.com/Debuggy_24/items/e6a8c788979df97b4b67) 

[第2回 とりあえず動くものを移植](https://qiita.com/Debuggy_24/items/25e21b8b8d02fe614e4b) 

第3回 細部の作り込み1	クロックの設定 

第4回 細部の作り込み2	UARTドライバ 

第5回 細部の作り込み3 タイマドライバ (ティックレス対応) 
:::
 
今回(第5回)はティックレスに対応させます。 

 

## ティックレスとは

ティックレスの前に、ティックレスに対応していないOSの時間管理について説明します。

ティックレスでない場合、定周期(例えば、1ミリ秒毎)のタイマ割り込みを発生させ、OSにティックを供給します。OSは時間をカウント、時間処理などを行います。


一方、ティックレスでは、定周期の割り込みではなく、直近に発生するイベントの時間(タスク待ち時間満了など)が経過した後にタイマ割り込みを発生させ、イベントを処理します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817840/2ef90bae-d188-f54e-1572-8adf5a58cd36.png)



## ティックレスの利点

### 利点1 省電力
ティックレスでない場合は定周期で頻繁にタイマ割り込みが発生しますが、ティックレスでは必要最小現のタイマ割り込みで処理できます。

タスクの処理、割り込み処理がない時間は省電力モードなどに入り、電力を抑えたいケースにはティックレスは有効です。
![tickless2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817840/2993f443-baff-01d4-8429-32e83337c1d7.png)

Arm Cortex-MのTOPPERS/ASP3の実装ではCPUが動作しなくてもよい期間はWFI (Wait For Intterupt)を実行することで低消費電力モードになっています。


asp3/arch/arm_m_gcc/common/core_support.S
``` 
ALABEL(_idle_loop_1)
	dsb /* スリープ前に全てのメモリアクセスが完了していることを保証する */
	wfi
	b     _idle_loop_1
	nop
```

### 利点2 時間分解能を上げやすい
ティックレスでない場合、短い時間で割り込みを発生させることで、時間分解能を上げることができますが、タイマー割り込みの発生回数が増え、割り込みを処理する時間が増え、その分CPUが占有されます。また、利点1で説明した電力についても割り込み処理を多く行うことで大きくなります。

例えば1ミリ秒毎のタイマー割り込みを1μ秒に変更することで、分解能は1μ秒に変更できますが、割り込みの回数も1000倍となります。
![tickless3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817840/8f372796-83b2-033b-10a7-8ffca6cff155.png)

ティックレスの場合、イベント発生時にしかタイマー割り込みが発生しないため、分解能を上げても割り込みの回数は変りません。

## TOPPERS/ASP3のタイマドライバ実装についてのドキュメント
asp3/doc/porting.txtの「6.13 高分解能タイマドライバ」に説明されています。
![timer_doc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817840/8a1b06ca-e87a-adc3-08b6-f3bc84d7e73a.png)


## TOPPERS/ASP3のタイマドライバ実装のポイント

- 1μ秒に1カウントするように設定する。(1MHz)
- 現在のカウント値を取得する関数target_hrt_initializeを実装する
- 初期関数target_hrt_initializeを実装する
- タイマ割り込みでsignal_time()を実行する
- 引数で指定された時間経過後にタイマ割り込みを発生させる関数target_hrt_set_eventを実装する

## ファイル
target_timer.c, target_timer.h, target_timer.cfgにタイマドライバは実装されています。
![edit_file_list.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817840/99c0f8eb-e7dd-2173-c4ea-1f60eef88bc5.png)

## MCXN947での実装
### 使用するタイマ
CTIMER0を使用します。

LPC55S69にも同じIPが実装されています。LPC55S69-EVK向けのTOPPERS/ASP3でもCTIMER0が使用されているので同じコードを流用できます。(細かいところでMCXN947向けのアレンジが必要となります。)

### 割り込み番号の設定
CTIMER0の割り込み番号をtarget_timer.hのINTNO_TIMERに定義します。
```
/*
 * タイマ割込みハンドラ登録のための定数
 */
#define INTNO_TIMER  (31 + 16) /* 割込み番号 */

```
割り込み番号はMCXN947のリファレンスマニュアルに書かれています。

MCX Nx4x Reference Manualの記述
![CTIMER_interrupt_doc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817840/6c20b36a-e5be-a640-dbe2-ee6b8398e88d.png)

### クロックとカウントの設定

CTIMERの入力クロックをfr0_12m(12MHzのクロック)に設定します。
12MHzのクロックをCTIMERのPrescale(PR)レジスタの設定で12回に一回カウントアップするようにして、
1MHzのカウンターとしています。

target_timer.c
```
/*
 * タイマの起動処理
 */
void target_hrt_initialize(intptr_t exinf)
{
    sil_wrw_mem(MCXNx4x_SYSCON_CTIMER0CLKDIV, 0x03 << 29);
    sil_wrw_mem(MCXNx4x_SYSCON_CTIMER0CLKDIV, 0x00);
    sil_wrw_mem(MCXNx4x_SYSCON_CTIMERCLKSEL0, 0x04);       // FRO 12MHz clock
    sil_wrw_mem(MCXNx4x_SYSCON_AHBCLKCTRLSET1, (1 << 26)); // bit26 TIMER0 - Enables the clock for CTIMER0
    
    sil_wrw_mem((uint32_t *)(MCXNx4x_CTIMER0_BASE + CTIMER_PR), 12 - 1);/* 12MHz / 12 = 1MHz */
    sil_wrw_mem((uint32_t *)(MCXNx4x_CTIMER0_BASE + CTIMER_TCR), 0x01); /* bit0 - Counter Enable */
}
```
### 現在のカウント値を取得
CTIMERのTimer Counter（TC）レジスタを読み出しています。

target_timer.h
```
/*
 * 高分解能タイマの現在のカウント値の読出し
 */
Inline HRTCNT target_hrt_get_current(void)
{
    return sil_rew_mem((uint32_t *)(MCXNx4x_CTIMER0_BASE + CTIMER_TC));
}
```
### タイマへの割り込みタイミングの設定
CTIMERのMatch0(MR0)レジスタへ現在のカウント値+指定されたカウント値を設定後、MR0の割込みを有効に設定することで、カウンターがMR0と一致したときに割り込みが発生させるようにします。

target_timer.h
```
/*
 * 高分解能タイマへの割込みタイミングの設定
 *
 * 高分解能タイマを，hrtcntで指定した値カウントアップしたら割込みを発
 * 生させるように設定する．
 */
Inline void target_hrt_set_event(HRTCNT hrtcnt)
{
    /*
     * 現在のカウント値を読み，hrtcnt後に割込みが発生するように設定する．
     */
    const uint32_t current = target_hrt_get_current();
    sil_wrw_mem((uint32_t *)(MCXNx4x_CTIMER0_BASE + CTIMER_MR0), current + hrtcnt);
    
    /* Match Control (MCR) */
    /*  bit0 MR0I Interrupt on MR0 */
    /*    1b - Generates           */
    sil_wrw_mem((uint32_t *)(MCXNx4x_CTIMER0_BASE + CTIMER_MCR), 0x01); 

    /*
     * 上で現在のカウント値を読んで以降に，hrtcnt以上カウントアップしてい
     * た場合には，割込みを発生させる．
     */
    if (target_hrt_get_current() - current >= hrtcnt) {
        target_hrt_raise_event();
    }
}
```

### 割り込みハンドラ
signal_time()を実行することでカーネルへタイマ割り込みが発生したことを通知します。

target_timer.c
```
/*
 *  タイマ割込みハンドラ
 */
void target_hrt_handler(void)
{
    /* Clear Interrupt Flag (MR0INT)*/
    sil_wrw_mem((uint32_t *)(MCXNx4x_CTIMER0_BASE + CTIMER_IR), (0x01));
    /* Disable interrupt */
    sil_wrw_mem((uint32_t *)(MCXNx4x_CTIMER0_BASE + CTIMER_MCR), 0x00);
    
    signal_time();
}
```

### Makefileの編集
Systickの使用を止め今回実装したタイマへ変更するために、KERNEL_TIMERの定義をSYSTICKからTIMへ変更します。

asp3/target/frdm_mcxn947_gcc/Makefile.target
```
#
# 使用するタイマ
#
#KERNEL_TIMER = SYSTICK
KERNEL_TIMER = TIM
```

## ソースコード公開

本記事向けのソースコード一式は[こちら](https://github.com/jun1-hirata/toppers_asp3_frdm_mcxn947_step_by_step_porting/releases/tag/part5_package)からダウンロードできます。

![source_release_github_part5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3817840/29ace183-e73f-9ec0-f68f-04c92caaf1a3.png)

ビルド、実行の手順は[第2回を参照](https://qiita.com/Debuggy_24/items/25e21b8b8d02fe614e4b)してください。
正常に動作していれば、1秒間隔でLED(ボードのD2)が点滅します。


「第5回 細部の作り込み3 タイマドライバ (ティックレス対応)」は以上です。