ガレスタさんのDIY日記

電子回路、Web、組み込み、自作エフェクターを語るblog

IMXRT1050 General Purpose Timer (GPT)

General Purpose Timer (GPT)

前回はPITでしたが今回はもう少し機能があるGPTの話です。

言葉でつらつら書くのが得意ではないので概要をまとめてみた

  • 32bit アップカウンタタイマー
  • 入力キャプチャに対応
  • キャプチャはrising edge,falling edgeに対応
  • outputイベントの生成が可能
  • 12bit prescalerを搭載
  • 複数のクロックソースを持つ

こんな感じですかね。


モジュールの構成は以下の画像の通り
f:id:gsmcustomeffects:20180210195428p:plain

クロックソースは5種類
[f:id:gsmcustomeffects:20180210195814p:plain

この手のやつを使って行くにあたってポイントを示しておく

  • カウンタレジスタはどれか?
  • クロックプリスケーラはあるか?
  • クロックソースはどこ?
  • 割り込みリクエストの種類は?
  • 動作モードはどうなのか?(オートリフレッシュなど

以上の点を考えながらリファレンスだったりSDKのマニュアルを読んで行くと良いだろう。
Twitterの方では酔漢さんがドキュメントの重要性について語っておられるが読み手の方も内容のポイントを抑えながら取り組むという姿勢も大事なのでペリフェラルごとに重要な点を片隅に置いておきましょう。(そうすることでプラットフォームが変わってもだいたい対応できます。)

入力キャプチャ

最近のタイマーにはよくある機能なんですが自分の理解が違ってた困るというのと初めての方もいるかもしれないので一応書きます。

リファレンスをざっとまとめると

モジュールごとに2つのインプットキャプチャがある。そいつらはそれぞれキャプチャレジスタとエッジ検出ロジックを持っている。
各種インプットキャプチャはステータスフラグを持ちプロセッサに対して割り込み要求を生成できる。
あらかじめ設定されたエッジ遷移(HIGH->LOW or LOW -> HIGH)が発生するとGPT_CNT(GPT Counter Register)の値が対応するキャプチャレジスタにキャプチャされ、割り込みステータスフラグがセットされる。
キャプチャは立ち上がりエッジ検出,立ち下がりエッジ検出および両エッジ検出に対応しています。またキャプチャ機能を無効にすることも可能です。
キャプチャイベントはGPT_CNT動作用クロックに同期しています。

入力トランジションのラッチには、最大で1クロックサイクルの不確実性があります。(図を参照

f:id:gsmcustomeffects:20180210225949p:plain

インプットキャプチャレジスタはいつでも読み出し可能です。

ざっとこんな感じだが注意すべきなのは検出が1クロックズレるってことぐらいだと思う。
パルス幅なんかを計測するときはキャプチャする信号よりも十分に早いクロックでカウンタを回していれば問題ないと思われる。

出力比較

出力比較も同様にGPT_CNT(GPT Counter Register)を利用します。
GPT1_OCR(GPT Output Compare Register )のプログラムされた内容がGPT_CNT(GPT Counter Register)の値と一致すると、GPTx_SR[2:0](GPT Status RegisterのOFビット)がセットされ、割り込みが発生する。(対応するビットが割り込みレジスタにて設定されている場合)。
クロックピリオドに応じてタイマー出力ピンはセット、クリア、トグル、アクティブローパルス生成ができる(GPIOの最大速度に依存)
あとはMODEビット部分を読めって書いてあるw。

次の項ではこうある

There is also a "forced-compare" feature that allows the software to generate a compare event when required, without the condition of the counter value that is equal to the compare value.

The action taken as a result of a forced compare is the same as when an output compare match occurs, except that the status flags are not set and no interrupt can be generated.

ソフトウエアによる強制比較機能のことですね。
この操作では出力マッチ動作とほぼ同じだがステータスフラグと割り込み生成がされない点が異なる。

んでもって

Forced channels take programmed action immediately after the write to the force-compare bits. These bits are self-negating and always read as zeros.

強制イベントはあらかじめ設定されたマッチイベントを強制実行する的な意味合いですかね・・・・
要は通常ならばカウンタ一致イベントの次にピンが動くわけですがその動作をマッチイベント関係なく強制的に実行するってことです。

次にやっとMODEビットについてです。

GPT_CRの対応ビットを設定することで各種設定可能です。

f:id:gsmcustomeffects:20180211002754p:plain

これにしたがって比較カウンタ(GPT Output Compare Register )が4の時の図を示す。

f:id:gsmcustomeffects:20180211003630p:plain

これも1クロック遅れるのがデフォなのね・・・・・・
矢印みるにキャプチャ自体はカウンタが4になる立ち上がりでされててGPIOに反映されるのが次のクロックということなんだろう。

割り込みについて

GPTには6つの割り込みがある。

  1. Rollover Interrupt
  2. Input Capture Interrupt 1, 2
  3. Output Compare Interrupt 1, 2, 3

Rollover Interrupt

カウンタが0xffffffffに達したら起こる割り込み
そのあとは0x00000000に戻って継続できるらしい。
Rollover割り込みはGPT_IRのROVIEビットを設定すると有効にできるらしい
割り込みステータスはGPT_SRのROVビットで確認できる。

Input Capture Interrupt 1, 2

キャプチャイベントが発生すると、関連するインプットキャプチャチャネルが割り込みリクエストする。
GPT_IR registerのIF2IE、 IF1IEビットで有効にできる。

Output Compare Interrupt 1, 2, 3

コンペアイベントが発生すると、関連するアウトプットコンペアチャンネルが割り込みリクエストする。
GPT_IR registerのOF3IE, OF2IE, OF1IEビットで有効にできる 。

強制比較は割り込みを生成しないので関係ない

タイマーモジュール初期化手順

まずGPT_CR registerのCLKSRCビットにてクロックソースを決定する。
この操作はGPTモジュールがdisableになっていることが好ましいのでGPT_CR registerのENビットを0にセットする。

上記の操作を含め手順をリストにした。

  1. GPT_CR registerのENビットを0にセット
  2. GPT interrupt registerを無効にする(GPT -> GPT_IR = 0x00)
  3. 出力比較機能の設定(使わないならGPT_CRのOMビットは0、FOも0
  4. 入力キャプチャ機能の無効(IMビットを0にする
  5. CLKSRCをセットしクロック源を選択
  6. GPT_CRにてSWRビットでソフトウエアリセットをアサート
  7. GPT_SRにてステータスフラグのクリア
  8. GPT_CRにてENMOD=1にする。(こうすることでモジュール無効の時にカウンタが初期化される)
  9. GPT_CRにてEN=1にしてモジュールを有効にする。
  10. GPT_IRにて割り込みを有効にする。

SDKを使った設定

ここでやっと実用的なコラムに入る
ものとしてはSDKに同梱されているサンプルの解説になる。(gpt_timer.cがそれに当たる)

まずは使う構造体の整理でもしていこう

typedef struct _gpt_init_config
{
    gpt_clock_source_t clockSource; /*!< clock source for GPT module. */
    uint32_t divider;               /*!< clock divider (prescaler+1) from clock source to counter. */
    bool enableFreeRun;             /*!< true: FreeRun mode, false: Restart mode. */
    bool enableRunInWait;           /*!< GPT enabled in wait mode. */
    bool enableRunInStop;           /*!< GPT enabled in stop mode. */
    bool enableRunInDoze;           /*!< GPT enabled in doze mode. */
    bool enableRunInDbg;            /*!< GPT enabled in debug mode. */
    bool enableMode;                /*!< true:  counter reset to 0 when enabled;
                                    false: counter retain its value when enabled. */
} gpt_config_t;

gpt_config_tでは主にGPT Control Registerの下位ビットのmode部分のメンバが多い
gpt_clock_source_tという構造体(まあ列挙型)を中に含んでいるのでそいつも見て行く

typedef enum _gpt_clock_source
{
    kGPT_ClockSource_Off = 0U,      /*!< GPT Clock Source Off.*/
    kGPT_ClockSource_Periph = 1U,   /*!< GPT Clock Source from Peripheral Clock.*/
    kGPT_ClockSource_HighFreq = 2U, /*!< GPT Clock Source from High Frequency Reference Clock.*/
    kGPT_ClockSource_Ext = 3U,      /*!< GPT Clock Source from external pin.*/
    kGPT_ClockSource_LowFreq = 4U,  /*!< GPT Clock Source from Low Frequency Reference Clock.*/
    kGPT_ClockSource_Osc = 5U,      /*!< GPT Clock Source from Crystal oscillator.*/
} gpt_clock_source_t;

gpt_clock_source_tはクロックソースの列挙型のようだ
0〜5まであるのでそれがGPT_CRレジスタのここに対応している。
f:id:gsmcustomeffects:20180211163606p:plain

ペリフェラルクロック

次にペリフェラルクロックの供給をする。
ここに関してはいつものようにこのAPIを使う

    /*Clock setting for GPT*/
    CLOCK_SetMux(kCLOCK_PerclkMux, 0U);
    CLOCK_SetDiv(kCLOCK_PerclkDiv, 0U);

構造体に初期値をセット

クロック供給ができたらGPT_GetDefaultConfigで先ほどの構造体にデフォルトの値をセットする。個人的にこの設計いいなぁと思ってます。

GPT_GetDefaultConfig(&gptConfig);

GPT_GetDefaultConfigの中身はこのような感じ

void GPT_GetDefaultConfig(gpt_config_t *config)
{
    assert(config);

    config->clockSource = kGPT_ClockSource_Periph;
    config->divider = 1U;
    config->enableRunInStop = true;
    config->enableRunInWait = true;
    config->enableRunInDoze = false;
    config->enableRunInDbg = false;
    config->enableFreeRun = false;
    config->enableMode = true;
}

この感じを見るとクロックはペリフェラルクロックになるみたいですね。

init

 GPT_Init(GPT2, &gptConfig);
void GPT_Init(GPT_Type *base, const gpt_config_t *initConfig)
{
    assert(initConfig);

#if !(defined(FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL) && FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL)
    /* Ungate the GPT clock*/
    CLOCK_EnableClock(s_gptClocks[GPT_GetInstance(base)]);
#endif /* FSL_SDK_DISABLE_DRIVER_CLOCK_CONTROL */
    base->CR = 0U;

    GPT_SoftwareReset(base);

    base->CR =
        (initConfig->enableFreeRun ? GPT_CR_FRR_MASK : 0U) | (initConfig->enableRunInWait ? GPT_CR_WAITEN_MASK : 0U) |
        (initConfig->enableRunInStop ? GPT_CR_STOPEN_MASK : 0U) |
        (initConfig->enableRunInDoze ? GPT_CR_DOZEEN_MASK : 0U) |
        (initConfig->enableRunInDbg ? GPT_CR_DBGEN_MASK : 0U) | (initConfig->enableMode ? GPT_CR_ENMOD_MASK : 0U);

    GPT_SetClockSource(base, initConfig->clockSource);
    GPT_SetClockDivider(base, initConfig->divider);
}

基本GPT_CRの設定ですね。
f:id:gsmcustomeffects:20180211183059p:plain

プリスケーラの設定

一応InitAPIのなかでもGPT_SetClockDividerとかGPT_SetClockSourceっての呼んでるけどあくまで初期化なのでここで再設定する。(この時点では両方とも1がセットされている。

GPT_SetClockDivider(GPT2, 3);
||

>|c|
static inline void GPT_SetClockDivider(GPT_Type *base, uint32_t divider)
{
    assert(divider - 1 <= GPT_PR_PRESCALER_MASK);

    base->PR = (base->PR & ~GPT_PR_PRESCALER_MASK) | GPT_PR_PRESCALER(divider - 1);
}

GPT_PRの設定ですね
f:id:gsmcustomeffects:20180211185710p:plain

f:id:gsmcustomeffects:20180211185718p:plain


設定としては3分周ですかね(divider - 1 をしてるため0x02がPRにセットされる。 0x2はDivide by 3になる。
先に実行した時のネタバレですが各種クロックはこのようになっています。

f:id:gsmcustomeffects:20180211192245p:plain

コアが600MHz、それの4分周でipgが150Mhzさらに3分周でgptが50MHzとなるわけだ。

比較カウンタの設定

    /* Get GPT clock frequency */
    gptFreq = EXAMPLE_GPT_CLK_FREQ;//この時点ではこの変数は150Mhz
    ipgclock = CLOCK_GetFreq(kCLOCK_IpgClk);
    /* GPT frequency is divided by 3 inside module */
    gptFreq /= 3;

    /* Set both GPT modules to 1 second duration */
    GPT_SetOutputCompareValue(GPT2, kGPT_OutputCompare_Channel1, gptFreq);

まず各種クロックを変数にもらってくる。
んでgptFreqを3で割る(これで50MHz
GPT_SetOutputCompareValueでOCRレジスタに50000000を代入

すなわちクロックが50000000でカウンタが50000000なので1秒で一致する。

割り込みの有効とタイマースタート

    GPT_EnableInterrupts(GPT2, kGPT_OutputCompare1InterruptEnable);
    EnableIRQ(GPT_IRQ_ID);
    GPT_StartTimer(GPT2);

あとはペリフェラル側でどのフラグで割り込みリクエストを出すのか?とCMSIS側で割り込みの有効をする。
そのあとにタイマースタート

最後にハンドラを適当に定義

void GPT2_IRQHandler(void)
{
    /* Clear interrupt flag.*/
    GPT_ClearStatusFlags(GPT2, kGPT_OutputCompare1Flag);

    gptIsrFlag = true;
    /* Add for ARM errata 838869, affects Cortex-M4, Cortex-M4F, Cortex-M7, Cortex-M7F Store immediate overlapping
      exception return operation might vector to incorrect interrupt */
#if defined __CORTEX_M && (__CORTEX_M == 4U || __CORTEX_M == 7U)
    __DSB();
#endif
}

ハンドラ内部でフラグクリアを忘れないようにしないと永遠に割り込みが発生してしまうw

実際にやって見る


きちんと割り込みハンドラでブレークできる
f:id:gsmcustomeffects:20180211193459p:plain


何秒か待って止めるとそれっぽい回数ハンドラに入ったこと確認できる。
f:id:gsmcustomeffects:20180211193616p:plain


今回はセミホストを有効にしているのでコンソールでも確認可能だ
f:id:gsmcustomeffects:20180211193655p:plain


セミホストに関してはゆーくりっどさんの記事が参考になる。
yuqlid.sakura.ne.jp