ATK2のマイコン依存部処理をルネサス製MCALに置き換えてみた②(Gpt)

本コラムでは、TOPPERS/ATK2(以下、ATK2)のマイコン依存部処理であるハードウェアカウンタの制御処理を、ルネサス製Gptに置き換える事例を紹介します。
ルネサス製MCALに関する基本情報や開発環境については「ルネサスエレクトロニクス社製MCALの使用方法」をご参照ください。

※本コラムは、ルネサスエレクトロニクス社の許可を得て公開しています。

ハードウェアカウンタ

AUTOSAR OSでは、タスク起動等の契機となるタイミングを管理するオブジェクトであるカウンタが規定されており、カウンタには、ソフトウェアカウンタとハードウェアカウンタがあります。ソフトウェアカウンタは、API呼出しによりカウンタが管理する現在値をインクリメントするもので、ソフトウェアによって制御されます。一方、ハードウェアカウンタは、タイマ等のハードウェアによって現在値が更新されます。ですので、ハードウェアカウンタを使用するには、対象マイコンのタイマ等を制御する必要があります。AUTOSAR仕様には、ハードウェアカウンタの実現方法としてOS内部で実装するか、Gptを使用するかの2つの方法があると記載されているが、どちらについても詳細な実現方法については言及されていません。ATK2では、OS内部のハードウェアカウンタのみサポートすることとし、独自のコンフィギュレーションパラメータを設けることで、ハードウェアカウンタとタイマ割込みの関連付け等を行う仕様となっています。ATK2のカウンタ仕様の詳細についてはATK2外部仕様書をご参照ください。

ATK2の実装

ATK2のHSBRH850F1K100向けコードでは、タイマアレイユニットJ(TAUJ)を使用したハードウェアカウンタが予めポーティングされており、ユーザはコンフィギュレーションするのみでハードウェアカウンタを使用可能となっています。ATK2のハードウェアカウンタは、以下のハードウェアを制御する関数をタイマ仕様に応じて実装することにより、ポーティングすることができます。

関数定義仕様
void
init_hwcounter_<counter id>(TickType maxval, TimeType nspertick)
<counter id>が示すハードウェアカウンタで必要な初期化処理を実行する.maxvalで指定された値を最大値としてカウンタが循環するようにハードウェアへ設定する.nspertickは,OsSecondsPerTickで指定した値をナノ秒で表現した値である.本関数は,OSの初期化処理によって実行される.
void
start_hwcounter_<counter id>(void)
<counter id>が示すハードウェアカウンタを開始する.本関数は,OSのハードウェアカウンタ初期化後に実行される.
void
stop_hwcounter_<counter id>(void)
<counter id>が示すハードウェアカウンタを停止する.本関数は,OSの終了処理によって実行される.
void
set_hwcounter_<counter id>(TickType exprtick)
<counter id>が示すハードウェアカウンタに対して満了点を設定する.exprtickは次の満了点の絶対時刻を示す値である.本関数は,アラーム,スケジュールテーブルの次回満了点を設定するOS内部処理から実行される.
TickType
get_hwcounter_<counter id>(void)
<counter id>が示すハードウェアカウンタの現在カウント値を取得して返す.ダウンカウンタの場合,アップカウンタとしての値を返す.本関数は,GetCounterValue,GetElapsedValueが呼び出された際に,OS内部処理から実行される.
void
cancel_hwcounter_<counter id>(void)
<counter id>が示すハードウェアカウンタに設定されている満了点を削除する.本関数は,アラーム,スケジュールテーブルの次回満了点をキャンセルする際に,OS内部処理から実行される.
void
trigger_hwcounter_<counter id>(void)
<counter id>が示すハードウェアカウンタに対応するC2ISRを起動する割込みを発生させる.本関数は,満了点の間隔が小さい場合などに,1つ目の満了点に対するキャンセル処理を実行中に,次の満了時刻が経過してしまった場合に,強制的に満了処理を実行するために,OS内部処理から実行される.
void
int_clear_hwcounter_<counter id>(void)
<counter id>が示すハードウェアカウンタに対応するC2ISRが起動した際に,割込み要求のクリアなどのハードウェアに対して行う処理を実行する.処理の必要がないハードウェアは空の関数とする.本関数は,ハードウェアカウンタに対応するC2ISRが起動した直後に実行される.
void
int_cancel_hwcounter_<counter id>(void)
<counter id>が示すハードウェアカウンタに対応するC2ISRに対する割込み要求がペンディングされている場合に,割込み要求をキャンセルする.一度発生した割込み要求をキャンセルできないハードウェアの場合は,空の関数とする.本関数は,満了点の間隔が小さい場合などに,1つ目の満了処理実行中に,次の満了時刻が経過してしまった場合に,不要なC2ISRの起動を抑止するために,OS内部処理から実行される.前述の例で,空の関数とした場合,何も処理を実行しないC2ISRが起動する.

ATK2外部仕様書 表2-9 ハードウェアカウンタ制御関数仕様から抜粋。

TAUJ向けの上記関数は、tauj_hw_counter.cに実装されています。実装を見れば分かるように、TAUJユニット0の、チャネル0を差分タイマ、チャネル1を現在値タイマとして使用しています。現在値タイマは、現在値を管理するだけの目的で、常に動作させ続けるタイマで、差分タイマはタスク起動等の要求に応じて指定したタイミングで割込みを発生させるためのタイマとなります。本コラムでは、上記の各関数の実装を、可能な限りGptのAPIを呼び出すことで実現してみます。

Gpt

コンフィギュレーション

GptのEcucDefsは、<インストールフォルダ>\X1X\F1x\modules\gpt\definition\4.2.2\F1K\R422_GPT_F1K.arxmlに定義されています。ルネサス製GptのEcucDefsでは、/Renesas/EcucDefs_Gpt/Gpt/GptChannelConfigSet/GptTaUnitというタイマ毎の設定を行うコンテナが追加されている以外は、ほとんどAUTOSAR仕様通りの構成となっています。TAUJユニット0の、チャネル0およびチャネル1を使用するためのコンフィギュレーションをYAML形式(※1)で記載すると以下のようになります。(該当パラメータの抜粋となります)

Gpt:
  GptChannelConfigSet:
    # 差分タイマ
    DiffTimer:
      DefinitionRef: GptChannelConfiguration
      GptChannelId: 0
      GptChannelMode: GPT_CH_MODE_ONESHOT     # 差分タイマのためONESHOT
      GptChannelClk: CK0                      # クロック供給:CK0
      GptTimerInputSelection: TAUJ0I0         # TAUJユニット0のチャネル0
    # 現在値タイマ
    CurTimer:
      DefinitionRef: GptChannelConfiguration
      GptChannelId: 1
      GptChannelMode: GPT_CH_MODE_CONTINUOUS  # 現在値タイマのためCONTINUOUS
      GptChannelClk: CK0                      # クロック供給:CK0
      GptTimerInputSelection: TAUJ0I1         # TAUJユニット0のチャネル1
    GptTaUnit:
      GptPrescalerCk0: PCLK_DIVBY_2_POWOF_00  # CK0を分周しない(16MHz)
      GptTaUnitSelection: TAUJ0               # TAUJユニット0

※1 YAML形式によるEcucValsの表記については「ABREXの使い方」をご参照ください。

TAUJへの供給クロック設定に関しては「ATK2のマイコン依存部処理をルネサス製MCALに置き換えてみた①(Mcu/Port)」をご参照ください。

ジェネレータ実行

Port同様、Gptのジェネレータ実行時も、入力情報として以下の4つが必要となります。
※入力情報の詳細な説明はPortの説明をご参照ください。

  • [1] ECU Configuration Description File
    • Gpt向けのEcucValsを指定します。
    • 複数モジュール向けのEcucValsを1つのarxmlで定義しても問題ありません。
  • [2] Translation XML File
    • ジェネレータを実行するフォルダからの相対パスが同じであれば、Portのジェネレータで使用したものと同じファイルを使用できます。
  • [3] BSWMDT File
    • Gpt用のBSWMDT Fileは、以下に用意されています。
      • <インストールフォルダ>\X1X\F1x\modules\gpt\generator\R422_GPT_F1x_BSWMDT.arxml
  • [4] Configuration XML File
    • Port同様、省略可能です。

実装

まず、Gpt_Initにより初期化を行います。Gpt_Initの引数は、ハードウェアやMCAL実装に依存して定義される構造体型であるGpt_ConfigTypeへのポインタとなります。ルネサス製Gptでは、ジェネレータで生成したGpt_PBcfg.cに対象の構造体が生成されます。また、Gpt_Cfg.hに以下のマクロが定義されますので、こちらをGpt_Initの引数として使用することができます。

/* Configuration Set Handles */
    :(略)
#define GptChannelConfigSet  (&Gpt_GstConfiguration[0])

Port同様、初期化処理にてGpt_Initを呼び出すことで初期化を行います。

#include "Gpt.h"
    :
void
StartupHook(void) {
        :
    Gpt_Init(GptChannelConfigSet);
        :
}

ATK2では、"MAIN_HW_COUNTER"という名称(<counter id>)で、TAUJ0によるハードウェアカウンタがポーティングされています。実装済みの処理を、Gptの機能に置き換えるわけですが、いくつかの制御処理は、Gptが持つ機能では対応できないため、部分的に既存のATK2の処理を使用する必要があります。具体的に見ていきましょう。

関数実装解説
void
init_hwcounter_MAIN_HW_COUNTER(TickType maxval, TimeType nspertick)
{
    MAIN_HW_COUNTER_maxval = maxval;
}
TAUJ0の初期化は、Gpt_Initで実施済みのため、ここではカウンタの最大値(maxval)を保持しておくのみです。本関数の中でGpt_Initを呼び出す方法もありますが、ハードウェアカウンタが複数存在する場合、本初期化関数は、ハードウェアカウンタの数だけ呼び出されてしまうので2回目以降のGpt_Init呼出しがエラーとなります。
void
start_hwcounter_MAIN_HW_COUNTER(void)
{
    Gpt_StartTimer(GptConf_GptChannelConfiguration_CurTimer,
                                        MAIN_HW_COUNTER_maxval);
}
本関数では現在値タイマを開始すればよいので、Gpt_StartTimerにより、チャネル1のタイマを開始します。第1引数はSymbolicNameValue属性のマクロを使っています。第2引数に、init_hwcounter_MAIN_HW_COUNTERで保持したカウンタの最大値を指定することで、この値を最大値としてラップアラウンドします。
void
stop_hwcounter_MAIN_HW_COUNTER(void)
{
    /* 差分タイマの割込み禁止 */
    HwcounterDisableInterrupt(TAUFJ0I0_INTNO);

    Gpt_StopTimer(GptConf_GptChannelConfiguration_DiffTimer);
    Gpt_StopTimer(GptConf_GptChannelConfiguration_CurTimer);
}
本関数はOS終了時に呼び出されますので、Gpt_StopTimerにより、現在値タイマ、差分タイマの両者を停止します。Gptにはタイマ割込みの禁止/許可を行うAPIが用意されていませんので、差分タイマの割込み禁止処理は、ATK2に実装されている関数HwcounterDisableInterruptを使用します。
void
set_hwcounter_MAIN_HW_COUNTER(TickType exprtick)
{
    TickType curr_time;
    TickType value;

    /* 差分タイマの割込み要求のクリア */
    HwcounterClearInterrupt(TAUFJ0I0_INTNO);

    /* 差分タイマの割込み許可 */
    HwcounterEnableInterrupt(TAUFJ0I0_INTNO);

    /* 現在時刻の取得 */
    curr_time = GetCurrentTimeTAUJ(HWC_CTIM_UNIT,
                                   HWC_CTIM_ID,
                                   MAIN_HW_COUNTER_maxval);

    /* タイマに設定する値を算出 */
    if (exprtick >= curr_time) {
        value = exprtick - curr_time;
    }
    else {
        value = (exprtick - curr_time) + (MAIN_HW_COUNTER_maxval + 1U);
    }

    /*
     *  タイマに0x00を設定し,割込み発生後,再度0を設定した場合,
     *  2回目の0x00設定後の割込みは発生しないので,0x00設定値を
     *  0x01に直して設定
     */
    if (value == 0x00U) {
        value = 0x01U;
    }

    /* 差分タイマの設定と開始 */
    Gpt_StartTimer(GptConf_GptChannelConfiguration_DiffTimer, value);
}
本関数の実装は、Gpt_StartTimerの呼出し以外は、現在値タイマと差分タイマによるタイミング算出処理のため、ATK2の実装と同じです。最後に、差分タイマで割込みを入れるまでのカウンタ値の設定と、差分タイマの開始にGpt_StartTimerを使用しています。
なお、EcucValsにて、/Renesas/EcucDefs_Gpt/Gpt/GptDriverConfiguration/GptClearPendingInterruptsをtrueとしておくことで、Gpt_StartTimerによりペンディングされている割込み要求がクリアされますので、この場合は冒頭のHwcounterClearInterruptによる割込み要求のクリアは不要となります。
TickType
get_hwcounter_MAIN_HW_COUNTER(void)
{
    return((TickType) Gpt_GetTimeElapsed(GptConf_GptChannelConfiguration_CurTimer));
}
本関数は、単純に現在値タイマの値を返すものとなります。Gpt_GetTimeElapsedにより、対象タイマに対してGpt_StartTimerを実行してからの経過時間を取得できますので、これを呼び出すのみとなります。 Gpt_GetTimeElapsedの返り値のデータ型であるGpt_ValueTypeは、具体的なデータ型が実装依存とAUTOSAR仕様で規定されています。ルネサス製Gptではuint32でtypedefされていますが、ATK2で用意しているTickTypeもuint32であるため、そのままキャストして差し支えありません。
void
cancel_hwcounter_MAIN_HW_COUNTER(void)
{
    Gpt_StopTimer(GptConf_GptChannelConfiguration_DiffTimer);
}
ハードウェアカウンタへの要求キャンセル処理ですので、単純にGpt_StopTimerにより差分タイマを停止します。
void
trigger_hwcounter_MAIN_HW_COUNTER(void)
{
    Gpt_StopTimer(GptConf_GptChannelConfiguration_DiffTimer);

    /* 差分タイマの割込み要求のクリア */
    HwcounterClearInterrupt(TAUFJ0I0_INTNO);

    /* 差分タイマの割込み許可 */
    HwcounterEnableInterrupt(TAUFJ0I0_INTNO);

    /* 差分タイマをカウンタ値1で開始してすぐに割込みを発生させる */
    Gpt_StartTimer(GptConf_GptChannelConfiguration_DiffTimer, 1U);
}
一旦、Gpt_StopTimerにより差分タイマを停止後、割込みをクリアおよび許可します。コンペア対象とするカウンタ値を1としてGpt_StartTimerにより差分タイマを開始することで、この直後に割込みを発生させることができます。
なお、set_hwcounter_MAIN_HW_COUNTER同様、EcucValsにて、/Renesas/EcucDefs_Gpt/Gpt/GptDriverConfiguration/GptClearPendingInterruptsをtrueとしておくことで、Gpt_StartTimerによりペンディングされている割込み要求がクリアされますので、この場合はHwcounterClearInterruptによる割込み要求のクリアは不要となります。
void
int_clear_hwcounter_MAIN_HW_COUNTER(void)
{
    /* 差分タイマ停止 */
    Gpt_StopTimer(GptConf_GptChannelConfiguration_DiffTimer);

    /* 割込み要求クリア */
    HwcounterClearInterrupt(TAUFJ0I0_INTNO);
}
Gptでは、モジュール内部でチャネル毎の状態を持っており、Gpt_StopTimerにより停止せずに、連続してGpt_StartTimerによりタイマを開始しようとするとエラーが発生します。ですので、差分タイマの割込みが発生した時点で、一度Gpt_StopTimerを呼び出すことにより、次回のGpt_StartTimer呼出し時にエラーとなることを防ぐことができます。
void
int_cancel_hwcounter_MAIN_HW_COUNTER(void)
{
    /* 割込み要求クリア */
    HwcounterClearInterrupt(TAUFJ0I0_INTNO);
}
Gptではタイマの割込み要求をクリアするAPIが用意されていませんので、ATK2に実装されている関数HwcounterClearInterruptを使用します。

動作確認

ルネサス製Gptを用いて、ATK2付属のサンプルプログラム(sample1)を使って動作確認を行いました。以下の通り、1秒周期でMAIN_HW_COUNTERに接続したアラームが起動すること、GetCounterValueによりMAIN_HW_COUNTERの現在値が取得できること、GetElapsedValueにより経過時間を取得できることを確認できました。



鴫原 一人博士(情報科学)
鴫原 一人(Kazuto Shigihara)

ASI事業部
次長 / エグゼクティブフェロー

この記事を読んだ人はこちらの記事も読んでいます。
技術者の“特許権”について考える
column
この2019年の4月1日からNHKで始まった「逆転人生」という新番組。偶然、初回放送を観ました。ジリ貧の人生の時期があっても諦めずにもがき、...