LPC1114FN28/102 (12) -- IRQLATENCY レジスタ (5)
STMicroelectronics 製の Cortex-M3 マイコン STM32F10x シリーズでは、フラッシュからの読み出しに際して挿入する必要のあるウェイト・ステート数 (WS) について、システム・クロック (SYSCLK) との間に次のような関係があります。
- 0 MHz <= SYSCLK <= 24 MHz : 0 WS
- 24 MHz <= SYSCLK <= 48 MHz : 1 WS
- 48 MHz <= STSCLK <= 72 MHz : 2 WS
「STM32 Value Line Discovery」ボードに搭載されている STM32F100RBT の最高クロックは 24 MHz であり、デフォルトの設定のままでウェイト・ステートなしで動作できます。
割り込みレイテンシの測定のためには、特にクロックが高速である必要はないので、STM32VL-Discovery ボードを使うことにしました。
開発環境は Atollic TrueSTUDIO for STMicroelectonics STM32 V2.3.0 Lite (コンパイラは gcc ベース) を使いました。
SysTick 割り込みハンドラ部分は LPC1114FN28/102 用のプログラムと同じ物です。
冒頭のタイマ・カウント値を読み込む部分の
tc_save = SysTick->VAL;
08000324 <SysTick_Handler>: 8000324: f24e 0310 movw r3, #57360 ; 0xe010 8000328: f2ce 0300 movt r3, #57344 ; 0xe000 800032c: 6898 ldr r0, [r3, #8]
Cortex-M0 の場合とは違って、32-bit Thumb-2 命令 (movw, movt) を使うようにコンパイルされています。
movw 命令で 32 ビット・レジスタの下位ハーフワード (16 ビット)、movt 命令で上位ハーフワード (16 ビット) に対象のアドレスをロードしています。
32 ビット命令ですが、実行はそれぞれ 1 クロックで行われ、ldr 命令の 2 クロックと合わせて、上に示した部分は合計で 4 クロックで実行されます。
この SysTick タイマによる測定では、16 クロックという結果が得られました。
上記の部分の寄与 4 クロックを差し引き、割り込みハンドラ開始までのレイテンシを求めると 12 クロックとなり、Cortex-M3 のテクニカル・リファレンス・マニュアルに記述されている値とぴったりです。
ただし、「地」、つまり「割り込まれる側」のプログラムによっては、レイテンシにジッタが生じ、17 クロックとか 18 クロックの測定結果になる場合もありました。
そこで、さらに詳しく調べるために、通常は hist_end フラグをポーリングして単純にループするだけの地のプログラムを、下に示す dummy_func() 関数をループの中で毎回呼び出すようにしてみました。
volatile uint32_t dummy_var = 1; void __attribute__(( noinline )) dummy_func( void ) { dummy_var; // SRAM SysTick->LOAD; // SysTick timer RCC->CR; // AHB peripheral GPIOA->CRH; // APB peripheral __asm__ __volatile__ ( "push {r0,r1,r2,r3,r4,r5,r6,r7} \n\t" "pop {r0,r1,r2,r3,r4,r5,r6,r7} \n\t" : : ); } // void dummy_func()
アトリビュートで「noinline」を指定しているのは、コンパイラの最適化でインライン展開されるのを防止して、独立した関数として bl 命令で呼び出されるを保証するためです。
また、実行に多くのクロック数を要する命令の代表として、複数レジスタの push/pop 命令をインライン・アセンブラで記述しています。
関数の冒頭では、AHB のみを経由するデータ、
の読み出しを行っています。
また、 APB に接続されているペリフェラルとして、GPIOA のレジスタを読み出しています。
この dummy_func() のコンパイル結果の逆アセンブリ・リストを下に示します。
08000388 <dummy_func>: 8000388: f240 0300 movw r3, #0 ; 0x0 800038c: f24e 0010 movw r0, #57360 ; 0xe010 8000390: f44f 5180 mov.w r1, #4096 ; 0x1000 8000394: f44f 6200 mov.w r2, #2048 ; 0x800 8000398: f2ce 0000 movt r0, #57344 ; 0xe000 800039c: f2c4 0102 movt r1, #16386 ; 0x4002 80003a0: f2c4 0201 movt r2, #16385 ; 0x4001 80003a4: f2c2 0300 movt r3, #8192 ; 0x2000 80003a8: 681b ldr r3, [r3, #0] 80003aa: 6843 ldr r3, [r0, #4] 80003ac: 680b ldr r3, [r1, #0] 80003ae: 6853 ldr r3, [r2, #4] 80003b0: b4ff push {r0,r1,r2,r3,r4,r5,r6,r7} 80003b2: bcff pop {r0,r1,r2,r3,r4,r5,r6,r7} 80003b4: 4770 bx lr 80003b6: bf00 nop
4 つのレジスタへのアクセスは、SysTick ハンドラの場合と同様に、それぞれ
(movw または mov.w) + (movt) + (ldr)
の形にコンパイルされています。
これをもとに、適宜コメント・アウトして RCC へのアクセスだけとか、GPIOA へのアクセスだけとかに限定して実行させてみると、GPIOA へのアクセス、つまり APB 経由のアクセスの場合に限りジッタが発生することが分かりました。
アクセスするのが AHB に接続されているペリフェラルだけならジッタは発生せず、push/pop 命令、bl 命令などのクロック数を要する命令の実行中に割り込まれてもジッタは生じません。
前回の LPC1114FN28/102 用のプログラムでは、
- ペリフェラルの初期化
- SysTickConfig() 関数の実行 (レイテンシ・ヒストグラム収集開始)
- UART 経由でバナー・メッセージ出力
- hist_end フラグ待ちの単純ループ
- UART 経由でレイテンシ・ヒストグラム・データ出力
- 終了
という構成になっていました。
IRQLATENCY の値が小さい場合に観測されたジッタは、SysTick タイマが動作を開始した直後に実行されるバナー・メッセージを出力するためのルーチンで、 APB につながっている UART のレジスタのフラグ・チェックを行っているのが原因と思われます。
Cortex-M0 の場合には、IRQLATENCY レジスタの値を大きくすれば、レイテンシが大きくなる代わりにジッタが吸収されますが、Cortex-M3 に同様の機構があるかどうかは不明です。
FM 音源プログラムでは割り込み要因はシリアル DAC に出力する LRCK を作成しているタイマ割り込みひとつだけで、しかも一定周期での割り込みですから、APB につながっているペリフェラルへのアクセスの時期をうまく調整すれば、ジッタのない割り込み応答が実現できるかも知れません。