読者です 読者をやめる 読者になる 読者になる

読んだ本の内容

読んだ本の内容を忘れてしまうのはよくあること。 実践的な技術書だと即実践してコードとして残る場合があるので、これが反復効果となり身につくように思う。 概念的な内容の場合はどうだろうか? 即実践することが難しいものは既に思い出せない時点で忘れてしまっているのだろう。。。 ブログに書評を書く人がいるのは反復のためなのだろうか。

時間待ち

プログラムで時間待ちをする場合、プラットフォームにもよりますが様々な方法があります。 小規模な組み込みシステムでも組み込みOSを採用する場合にはsleepやwait関数等が用意されていると思いますが、 組み込みOSを採用できず、ベンダーからライブラリ等も用意されない場合は自作しなければならないこともあります。

簡単に待ちを実現するには、以下のように最適化を抑制してカウントすることで待ちを実現することができますが、 count指定なのでどのくらいの時間待ちになるのかがわからないですよね。

void wait(const unsigned int count)
{
    volatile unsigned int vcount = count;
    while (vcount--);
}

周辺回路としてtimerが用意されている場合は、供給するクロックとタイマカウント値で時間がわかりますので、 100MHzで動作するtimerなら100,000,000で1sec、100,000で1msec、100で1usecです。

void usleep(const unsigned int usec)
{
    /* ダウンカウンタの場合 */
    const unsigned int initialCount = (100 * usec);
    /* timerのcountレジスタに初期値としてinitialCountを設定 */
    /* timerのカウントを開始 */
    while (/* timerのcountレジスタを読み出し */);
}

timerもない場合は時間がわからないか? というと、CPUの動作周波数と命令のサイクル数によって時間待ちを代用することができます。 以下は最初のwait関数のようなものをMicroBlazeアセンブリコードで書いたものです。 ※他のCPUでも似たようなコードになると思います

    .align  4
    .global mb_asm_wait
    .ent    mb_asm_wait

mb_asm_wait:
    add     r3, r5, r0      /* 第一引数はr5に入っている r3 = r5の意味 */
1:  /* ここから */
    add     r4, r3, r0      /* r0は常に0のレジスタなのでr4 = r3の意味 */
    bneid   r4, 1b          /* 分岐遅延スロットを使用、r4が非0なら次のダウカウントを実行して1:へ */
    addi    r3, r4, -1      /* 遅延スロットでダウンカウント r3 = r4 - 1 */
    /* ここまで */
    rtsd    r15, 8
    or      r0, r0, r0

    .end    mb_asm_wait

ここで重要なのはダウンカウントループが1回まわるのに何サイクルかかるかですが、 addで1、bneidで2、addiで1の計4サイクルかかることになります。 CPUが100MHzで動作しているとしたら、1secは100,000,000サイクルなので25,000,000ループで1secとみなすことができます。 同じように25,000ループで1msec、25ループで1usecです。

これを利用することで単純な実装でも以下のようにusleepを実現することができました。

enum { kCPU_FREQ_HZ = 100000000 };
enum { kCYCLES_PER_LOOP = 4 };
enum { kCYCLES_PER_1USEC = (kCYCLES_PER_LOOP * 1000000) };
static const unsigned int kLOOP_PER_USEC = (kCPU_FREQ_HZ + (kCYCLES_PER_1USEC - 1)) / kCYCLES_PER_1USEC; /* 端数切り上げ */

void usleep(const unsigned int usec)
{
    mb_asm_wait(kLOOP_PER_USEC * usec);
}

さて、この実装はCPUの動作周波数によっては端数がでてしまいます。 確実に指定時間待つようにするため、kLOOP_PER_USECを算出する際の割り算では端数を切り上げていますが、 良い悪いは別にして世の中には端数を切り捨てる実装も存在していますので認識はしておく必要があります。

ALTERA社のNios2のusleep関数は端数切り捨てになっており、 実装例でいうところのkCYCLES_PER_1USECが(3 * 1000000)なので、 正確に時間待ちをしたい場合は3,000,000で割り切れる動作周波数にするとよいです。

※例えば100MHz動作の場合は99MHz動作扱いになってしまうということです

■この実装のまとめ

  • CPUの動作周波数に合わせてアセンブリでサイクル数の調整が可能です
  • 命令実行サイクル数を時間に換算しているため、そもそも命令キャッシュが無い等の理由で命令フェッチに時間がかかる環境では無駄に待ってしまうことになります
  • 時間待ち中に割り込みが入ると割り込み処理分遅れることになります
    • timerによる待ちの場合はtimerがカウントしてくれるので割り込み処理による遅れは最大1回分程度に抑えることができます

組み込みソフトエンジニア

「組み込みソフトエンジニアです。」 なんて言うとたいていポカーんとされると思っていたのですが、、、 最近はAndroid開発も組み込みと認識されているようで、「スマホとかの?」と言われることもあり、 「そ、そんな感じかな。。。」と説明を諦めてしまう(汗

そんな私は組み込みソフトエンジニアとして、 マイコンMPUはH8/SH系, ソフトマクロCPUとしてはNios, MicroBlaze等、いろいろ扱ってきました(数MHz~200MHz程度までの動作クロック)。 小規模なシステムではほとんどOSレス(single-thread)ですが、 TCP/IPスタック等のミドルウェアが必要な場合やthread(task)が欲しい等の理由から、 中・大規模システムではuITRON系やWindowsCE系を採用してきました。

私の担当する開発では大規模FPGAを採用したものがほとんどですが、 流行のCortex-A系を内蔵したSoCを採用するほどのソフトウェア規模にはならないため、 総じて大規模FPGAにソフトマクロCPUをインスタンスして使用することになります。

そういうわけでここ数年はソフトマクロCPU専門ですが、 主に組み込みソフトエンジニア向けの記事を書いていこうかなと思っています。

■免責事項

掲載した情報に誤りがあった場合、または当サイトのご利用に際して生じたトラブルについては、一切責任を問わないものとします。 また、理由の如何に関わらず、情報の変更及び当サイトの運営の中断または中止によって生じるいかなる損害についても責任を負うものではありません。 当サイトのご利用によって生じたソフトウェア、ハードウェア上のトラブルその他の損害についても、一切責任を問わないものとします。 なお、当サイトに掲載されている情報等につきましては、予告なく変更、又は削除されることがありますので、あらかじめご了承ください。