2018年3月5日月曜日

Cubeの続き

 FreeRTOSのスタックをCCMに移動する。

 FreeRTOSConfig.hの最後の方にあるUSER CODE BEGIN Definesの中に #define configAPPLICATION_ALLOCATED_HEAP 1 を追加する。

 freertos_heap.cというファイルを作り、以下の4行を書く。
////
#include "FreeRTOS.h"
#if( configAPPLICATION_ALLOCATED_HEAP == 1 )
uint8_t ucHeap[configTOTAL_HEAP_SIZE];
#endif
////

 MakefileのC_SOURCESに Src/freertos_heap.c \ を追加する

 STM32F405RGTx_FLASH.ldの.ccmramブロックの*(.ccmram*)の下に *freertos_heap.o を追加する。

 これでFreeRTOSで使うスタックがCCM領域に配置される。
 コンパイラによってはもっと簡単な指定方法が有るらしいけど、ウチの環境で動かなかったのでこういう方法になった。
 ちなみにCCM領域はDMA転送に使えないので、そういう場合はstaticで通常のRAM領域に配置するようにする。

 FreeRTOSのスタックとかヒープは紛らわしい。今回はスタック領域をCCMに移動したいのだが、そのスタックはヒープ領域から取ってくるので、マクロ名とか変数名はヒープになる。正しくは「FreeRTOSのヒープをCCMに配置する」なんだろうな。

***

 stdoutの転送速度が遅い問題。
 USB_CDC_stdout_funcのforの中身を以下のように書き換える。
////
static uint8_t buffer[32];
uint16_t count = 0;
BaseType_t dequeue_state = pdPASS;

while (dequeue_state == pdPASS && count < sizeof(buffer))
{
  dequeue_state = xQueueReceive(stdout_queueHandle, &buffer[count], 1);

  if(dequeue_state)
  {
    count++;
  }
}

if (count > 0)
{
  CDC_Transmit_FS(buffer, count);
}
osDelay(1);
////

 短いコードなので読めばわかると思うが、簡単に説明。
 そもそも、元の処理でネックになっていたのは、1文字の転送に1msecかかる、という点。例えばデバッグダンプが200文字あれば、表示に200msec必要になる。これは目で見てもゆっくり転送されているのがわかるような速度。

 下位レイヤのCDC_Transmit_FSは複数バイトの転送が可能なので、わざわざ1文字ずつ転送する理由はコードの単純さ以外に無く、デメリットのほうが多い。

 ということで、上記のような、一旦配列に読み込んでまとめて転送してやれば、一気に数十倍の高速化が可能となる。
 バッファサイズが大きければ大きいほど、大量の転送が発生したときの性能向上が見込めるが、実際の運用では数百ミリ秒毎に数十文字程度を超えることはまれなので、これが入る程度の大きさであれば十分。今回は最大32バイトだが、これはちょっと小さいかな、という程度。

 スタック/ヒープのところでも書いたが、CCM領域はDMA転送に使用できない。USBはDMAを使うので、FreeRTOSのヒープに配列が有るとDMA転送でハングアップする。なのでstaticをつけて通常のRAM領域に置くようにしている。

***

 改行コードをCRLFにした時に正常に処理できない問題。
 scanf, getcharの下に以下を追加する。
////
{
  char *p = strchr(str, '\r');

  if (p)
  {
    *p = '\0';
  }
}
////

 scanfではある程度の大きさを上限としてバッファに文字列を読み込むが、この際LFは削除して読み込む。しかしCRは削除しないので、CRLFを改行コードとした場合にCRが残ってしまう。
 ということで、strchrでCRが有るかを確認し、CRがあればそれをヌル文字に置き換える。
 コードとしては簡単だが、役割(行中のCRをを消す)の割に行数が多いので、前回は扱わなかった機能。

 ちなみに、ウチの環境ではLFだけで改行した場合、次のコマンドを入力せずに改行を送ると前回のコマンドが実行されてしまう。
 これは場合によっては利点だが、コマンドがトグル動作だったり、インクリメント動作だった場合、誤ったコマンドを実行してしまうことになる。ということで、CRを送ってコマンドが残らないようにしておいたほうが良い。

***

 タスク一覧を表示する。

 CubeのFREERTOS Configurationを開き、USE_TRACE_FACILITYとUSE_STATS_FORMATTING_FUNCTIONSをEnableにする。

 先に追加したブロックの下に以下のコードを追加する。
///
if (!strcmp(str, "tasklist"))
{
  char buff[10 * 45];
  osThreadList((uint8_t *)buff);
  printf(
      "Name            State   Priorty Stack   Num\n"
      "*******************************************\n"
      "%s"
      "*******************************************\n",
      buff);
}
///

 "tasklist"というコマンドを与えると、実行中のタスク一覧が表示される。

 例えば以下のようになる。
Name            State   Priorty Stack   Num
*******************************************
console         R       1       611     3
IDLE            R       0       115     4
LED_blink       B       2       999     2
USB_CDC_stdout  B       3       84      1
*******************************************
 フォントの関係で綺麗に表示されないと思うが、ターミナルソフト等なら問題ないはず。
 この一覧からわかるのは、USB_CDC_stdout, LED_blink, console, IDLEという4個のタスクがあり、USB_CDCとLED_blinkはブロック状態、それ以外はランニング状態である。ということ。
 ランニングというのはタスクが実行状態であることを示す。ブロックはキューやセマフォ、ウエイトを待っている状態であることを示す。
 USB_CDCはキューを使っているし、LED_blinkはosDelayを使っている。これを待っているため、ブロックになっている。
 consoleは実行中だが、これはtasklistコマンドを処理している最中。
 IDLEは特別なタスクで、常にランニング状態であり、ブロック状態になることは許可されていない。これは、OSのスケジューリングの条件分岐を省略してパフォーマンスの改善を図る目的によるものだが、詳しく説明するのは面倒なので省略。とにかく「IDLEというタスクがあり、止めることはできない」ということだけ知っていれば十分。

 プライオリティという項目(文字数の関係でスペルは不正)はいろいろな数字が有るが、この数字が大きいほど優先度が高いタスク。
 RTOSは優先度が高いタスクが常に実行されるので、高優先のタスクがランニングの時はそれより優先度が低いタスクは実行されない。上記の状態だと、USB_CDCとLED_blinkがブロック状態なので、それより優先度が低いconsoleを処理できる。IDLEもランニングだが、consoleがブロックになるまで、IDLEは実行されない。

 Stackの項目は、残りのスタック数が表示される。残スタックがゼロを下回ると、いわゆるスタックオーバーフローという状態になる(残数がゼロ未満=使える領域を超えている=オーバーフロー)。
 基本的にこの数値が極端に低くならないようにNew TaskやEdit TaskのStack Sizeを調整することになる。ただし、printfのような多機能な関数呼び出しを行うといきなり大量のスタックを消費するし、小規模な処理しか行わないならスタックの使用量も少なく済む。
 LED_blinkとconsoleはStack Sizeを1024にしているが、LED_blinkはosDelayやGPIO操作しか行っていないから、スタック使用量は少ない(=残数が多い)。一方で、consoleはprintf等を使っているから、スタック使用量が多い(=残数が少ない)。
 USB_CDC_stdoutは残数が極端に少ないが、Stack Sizeは128だから、実際には3割程度しか使われていない。ということで、十分とは言えないまでも、まだ余裕がある状態。
 IDLEもStack Size 128で作成されているはずだが、13しか使われていない。これは無限ループといった軽量な計算しか行っていないため。


 ちなみに、この処理(osThreadList)はちょっとした副作用があるので、極力使用しないことが好ましい。あくまで「よしよし、ちゃんと動いてるな」と悦に入るための機能。
 また、バッファサイズの10*45というのは、タスク1個あたり余裕を見て45バイト、それが10個分、という設定。タスクを20個作るなら25*45くらいにする。


******

 ということで、stdoutが遅いのと、stdinでゴミが残る問題を解決し、悦に入るためのコマンドも実装したし、使い道の少ないCCMをFreeRTOSに活用できるようにもした。
 これによりメインメモリ128kBのほとんどをマイコンの処理に使えるようになり、かつFreeRTOSが使えるスタック領域も数十kB確保したので、タスクやキューも相当な数を使えるようになった。

 あとはコレで何を作るか、という問題だが、何を作るかなぁ。。。

0 件のコメント:

コメントを投稿