part.3 Renesas RA MCU
GPTタイマーでLED点滅

目的

  • 汎用PWMタイマー(GPT)の基本的な使い方を理解する。
  • GPTと割り込み処理を使いLEDを点滅させるプログラムを作成する。
  • プロジェクトをスクラッチビルドする手順を理解する。

全体フロー

  • 検討(1) 汎用タイマー(GPT) LED点滅
  • 検討(2) One-Shot Mode LED点灯

準備

以下のハード、ソフトを用意。詳細はpart.1: EK-RA6M4評価キット インストールを参照のこと。

  • EK-RA6M4評価キット
  • e2studio
  • J-Flash Lite

本編

 検討(1) 汎用タイマー(GPT) LED点滅

GPTを使った簡単な例として、カウンタのオーバフロー割り込みによってコールバック関数を呼び出しLEDを点滅させる処理を追加する。

主な処理として以下があげられるが、e2studioの開発環境ではConfiguration.xmlの設定とFSPのコール関数で実現できるので、これらを使った
サンプルコードを用意する。
・カウンタの周期、初期値設定
・カウンタオーバーフロー(カウンタクリア)時の割り込み設定
・メイン関数中でのタイマー開始処理
・コールバック関数内のLED点滅処理

また前回のpart.2の検討ではRA-Bookのサンプルプログラムを使用したが、今回はプロジェクトをスクラッチからビルドするまでの工程を記載する。

プロジェクト作成  (bare-metal)

e2 stduioを起動し、メニューから「ファイル→新規→Renesas C/C++Project→ Renesas RA」を選択する。

以下のウィンドウが表示するので、「Renesas RA C/C++ Project」を選択して次へ。

次の以下のウィンドウが立ち上がるので、プロジェクトの名前を入力する欄に適当なプロジェクト名を入力して、次へ進む。
ここでは「TestTimerIntProject」とした。

FSPのバージョンやボード情報、デバイス、使用言語、ツールチェイン、使用するデバッガを確認するウィンドウが立ち上がる。
FSPは最新バージョンの「4.0.0」、ボードは「EK-RA6M4」、言語は「C」、デバッガーは「J-Link ARM」を選択する。

TrustZoneを使用有無についてのウィンドウが立ち上がる。使用しないため、「Flat(Non-TrustZone) Project」を選択して次へ進む。

次にビルドの生成ファイルとRTOSの有無の問い合わせるウィンドウが立ち上がる。
Build Artifact Selectionは「Executable」、RTOS Selectionには「No RTOS」を入力する。

次にプロジェクトのテンプレートを選択するウィンドウとなる。「Bare Metal」を選択する。

ここまで完了すると、プロジェクトエクスプローラに新規のプロジェクトが作成されるのが確認できる。
configuration.xml」をダブルクリックしてconfigurationの画面を表示させる。

新規のプロジェクト作成直前のConfigurationの画面は以下となる。r_ioportのスタックのみが存在する形となる。ここにGPTのスタックである
r_gptを追加する。スタックを追加するには「New Stack」をクリックする。

New Stack」をクリックするとメニューが表示されるので、「Timers → Timer, General PWM (r_gpt)」を選択する。

上記の操作で汎用PWMタイマーを使用するためのr_gptスタックが追加される。
r_pgtのプロパティを表示するため、HAL/Common Stacks内のr_pgtのスタックを選択し、さらにe2studioのパースペクティブ内から
プロパティのタブを選択する。

プロパティから「Module g_timer0 Timer General PWM(r_gpt)→General」を選択すると、タイマーのモード、またタイマーの周期が
設定できる。
サンプルプログラムでは400ms周期でLEDを点滅させるため、200ms間隔でLEDのON/OFFを切り替える。
Period, Period Unitのデフォルトは「0x100000000」、「Raw Counts」となっているが、それぞれ「200」、「Milliseconds」を変更する。
またModeは「Periodic」となっているが、そのままにする。

次に「Module g_timer0 Timer General PWM(r_gpt)→Interrupts」を開くと、割り込み時のコールバック関数と割り込みの優先度が
設定できる。
Callback」には、割り込み発生時に呼び出されるコールバック関数を登録するが、ここでは「callback_g_timer0」と設定した。
また「Overflow/Crest Interrupt Priority」にはDisabled(割り込み無効)、またはPriority 15(優先度低) から Priority 0(優先度高)まで選択できるが、ここでは最も優先度の低い「Priority 15」とした。

設定が完了してconfiguration.xmlを保存した後、「Generate Project Content」を実行して自動生成ファイルを生成する。

ソースコード作成 (hal_entry.c)

file:{プロジェクト}\src\hal_entry.cのコードは以下になる。

なお, GPTによるタイマーの割り込み処理をより認識できるようにするため、
前編で検討したソフトウェイト(R_BSP_Software_Delay)によるLED点滅処理をLED1,LED2に適用し、
GPTによるタイマー割り込みはLED3の点滅処理としている。
また、LEDの点灯間隔は、LED1,LED2は1s, LED3を400msとした。

ソースコードの内容を処理別に分類すると、

(1) LED1,2,3のポート情報のある外部変数g_bsp_ledの参照宣言
(2) コールバック関数 callback_g_timer0()による400ms間隔でのLED3点滅処理
(3) メイン関数 hal_entry()内でのGPTタイマー開始処理(g_timer0)、FSP関数: R_GPT_Open(), R_GPT_Start()をコール
(4) メイン関数 hal_entry()内での1s間隔によるLED2, LED3点滅処理

となっている。

#include "hal_data.h"

FSP_CPP_HEADER
void R_BSP_WarmStart(bsp_warm_start_event_t event);
FSP_CPP_FOOTER

// extern g_bsp_led (1)
extern bsp_leds_t g_bsp_leds;

// declare GPT0 callback (2)
void callback_g_timer0(timer_callback_args_t *p_args){
    static LED3_level = BSP_IO_LEVEL_LOW;
    if(p_args->event == TIMER_EVENT_CYCLE_END){
        /* LED3 turn on or off */
        LED3_level = (LED3_level == BSP_IO_LEVEL_HIGH) ?  BSP_IO_LEVEL_LOW: BSP_IO_LEVEL_HIGH;
        g_ioport.p_api->pinWrite(&g_ioport_ctrl, g_bsp_leds.p_leds[BSP_LED_LED3], LED3_level);
    }

}

/*******************************************************************************************************************//**
 * main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used.  This function
 * is called by main() when no RTOS is used.
 **********************************************************************************************************************/
void hal_entry(void)
{
    unsigned char sel = 0;
    bsp_io_level_t led_level;

    // GPT0 open, start (3)
    (void)R_GPT_Open(&g_timer0_ctrl, &g_timer0_cfg);
    (void)R_GPT_Start(&g_timer0_ctrl);

    // LED1, LED2 turn on or off and 1s wait (4)
    while(1){
        for(sel=0; sel<4; sel++){ 
            /* LED1 turn on or off */ 
            led_level = (sel & 0x01) ? BSP_IO_LEVEL_HIGH : BSP_IO_LEVEL_LOW;
            g_ioport.p_api->pinWrite(&g_ioport_ctrl, g_bsp_leds.p_leds[BSP_LED_LED1], led_level);

            /* LED2 turn on or off */
            led_level = (sel & 0x02) ? BSP_IO_LEVEL_HIGH : BSP_IO_LEVEL_LOW;
            g_ioport.p_api->pinWrite(&g_ioport_ctrl, g_bsp_leds.p_leds[BSP_LED_LED2], led_level);

            /* wait 1s by software delay */
            R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_SECONDS);
        }
    }

#if BSP_TZ_SECURE_BUILD
    /* Enter non-secure code */
    R_BSP_NonSecureEnter();
#endif
}

/*******************************************************************************************************************//**
 * This function is called at various points during the startup process.  This implementation uses the event that is
 * called right before main() to set up the pins.
 *
 * @param[in]  event    Where at in the start up process the code is currently at
 **********************************************************************************************************************/
void R_BSP_WarmStart(bsp_warm_start_event_t event)
{
    if (BSP_WARM_START_RESET == event)
    {
#if BSP_FEATURE_FLASH_LP_VERSION != 0

        /* Enable reading from data flash. */
        R_FACI_LP->DFLCTL = 1U;

        /* Would normally have to wait tDSTOP(6us) for data flash recovery. Placing the enable here, before clock and
         * C runtime initialization, should negate the need for a delay since the initialization will typically take more than 6us. */
#endif
    }

    if (BSP_WARM_START_POST_C == event)
    {
        /* C runtime environment and system clocks are setup. */

        /* Configure pins. */
        R_IOPORT_Open (&g_ioport_ctrl, g_ioport.p_cfg);
    }
}

#if BSP_TZ_SECURE_BUILD

BSP_CMSE_NONSECURE_ENTRY void template_nonsecure_callable ();

/* Trustzone Secure Projects require at least one nonsecure callable function in order to build (Remove this if it is not required to build). */
BSP_CMSE_NONSECURE_ENTRY void template_nonsecure_callable ()
{

}
#endif

ちなみに file:{project}\ra_gen\hal_data.hにFSPのAPI関数で使用している変数:g_timer0_cfgやg_timer0_ctrl, またコールバック関数 calback_g_timer0()が外部宣言されているので、file:hal_data.hをインクルードしているfile:hal_entry.cはそのままこれらの変数、関数を
呼び出すことができる。

/* generated HAL header file - do not edit */
#ifndef HAL_DATA_H_
#define HAL_DATA_H_
#include <stdint.h>
#include "bsp_api.h"
#include "common_data.h"
#include "r_gpt.h"
#include "r_timer_api.h"
FSP_HEADER
/** Timer on GPT Instance. */
extern const timer_instance_t g_timer0;

/** Access the GPT instance using these structures when calling API functions directly (::p_api is not used). */
extern gpt_instance_ctrl_t g_timer0_ctrl;
extern const timer_cfg_t g_timer0_cfg;

#ifndef callback_g_timer0
void callback_g_timer0(timer_callback_args_t *p_args);
#endif
void hal_entry(void);
void g_hal_init(void);
FSP_FOOTER
#endif /* HAL_DATA_H_ */

file:hal_entry.cを修正した後、前編と同様にプロジェクトの設定を変更し、Flash用のフォーマット設定をIntel HEXに変更しておき、ビルドを実行。

生成されたhexファイルをJ-Flash LiteでEK-RA6M4に書き込む。

書き込みが完了し、基板リセット後の各LEDの動作は以下の通りとなる。LED1,2は1s間隔でBCD点滅を繰り返すが、
LED3はタイマー割り込み処理により400ms間隔で点滅するのがわかる。

 

検討(2) One-Shot Mode LED点灯

検討(1)ではカウンタのモードがPeriodicに設定しており、カウンタがPeriodで指定したカウント値に到達すると
カウンタクリアとカウントアップが継続するので周期的に割り込み処理が実行される。
それに対しカウンタのモードがOne-Shotの場合、カウンタを開始してからPeriodで指定したカウント値に到達するとカウンタが停止し、
一回のみ割り込み処理が発生する。

タイマーによる周期動作と単発動作は組み込み処理で非常によく利用されるため、Periodicに加え本検討ではOne-Shotモードでの動作を確認する。

configuration変更

コンフィギュレーションのr_gptモジュールで、プロパティからGeneral->Periodの内容を「Periodic」から「One-Shot」に変更する。
またGeneral->Periodに「4」、General->Period Unitに「Seconds」を指定し、
評価基板の電源ON後(関数:hal_entry()のルーチンから関数:R_GPT_Start()がコール)から4秒後にLED3が点灯する処理とした。

設定が完了したら、Generate Project Contentで自動ファイルを生成し、プロジェクトのビルド、JLink-Liteでプログラム
の書き込みをおこなう。

実行結果は以下の通りである。電源ON(真ん中の白色LED4が点灯)してから4秒後にLED3が点灯するのがわかる。

なお、一度実行が完了したOne-Shotモードを再度起動するには、FSP関数:R_GPT_Start()を再度コールすればよい。
file:hal_entry.c割り込み関数: callback_g_timer0()の一部を修正しその確認をおこなった。
設定は上記と同等なため、正常に起動すれば4秒間隔でLED3が点灯と消灯を繰り返す動作となる。

void callback_g_timer0(timer_callback_args_t *p_args){
static bsp_io_level_t LED3_level = BSP_IO_LEVEL_LOW;

    if(p_args->event == TIMER_EVENT_CYCLE_END){
        /* LED3 turn on or off */
        LED3_level = (LED3_level == BSP_IO_LEVEL_HIGH) ? BSP_IO_LEVEL_LOW: BSP_IO_LEVEL_HIGH;
        g_ioport.p_api->pinWrite(&g_ioport_ctrl, g_bsp_leds.p_leds[BSP_LED_LED3], LED3_level);
        (void)R_GPT_Start(&g_timer0_ctrl);
    }
}

結果は以下となる。4秒間隔でLED3が点灯,消灯しているのがわかる。

検討(3) PWM Mode

検討(1), (2)でPeriodic ModeとOne Shot Modeの動作確認をおこなったが、PWM Modeも組み込みの現場での用途が多数あるので
こちらの動きを見ておきたい。
評価基板のLED1/LED2/LED3は各々P415,P404,P400ポートと接続するのと同時に、PWM Modeで出力可能なGTIOC0A, GTIOC3B, GTIOC6A端子となっている。
本検討ではコンフィギュレーションでLED3のGTIOC6AをPWM Modeに変更し、動作を検証することにする。

注1: 汎用PWMタイマによるPWMの動作原理や応用については本編では取り扱わないので、詳細については
RA6M4ハードウェア ユーザーズマニュアル
p.539 – p.655 汎用PWMタイマの章を参照のこと。

コンフィギュレーション変更

プロジェクトのコンフィギュレーションを開き、PinsタブからPort->P4->P400を選択、
Modeを「Output Mode(Initial Low)」から「Disabled」に変更する。

前検討ではP400Input/OutputGPIOに設定されており、この状態だとP400にPWM Modeが割り当てられないため、
上記を設定してGPIOをNoneにする必要がある。

次にTimers->GPT->GPT6からInput/Output->GTIOC6AP400を割り当てる。
GTIOC6AのPWM Modeと対応するのはGPT6のタイマとなる。(注1)
設定が終わったら、項目の隣りにあるLinkボタンを押すと先のP400ポートの設定にジャンプすることができる。

注1: GTIOC[n]A端子とGPT[n]がそれぞれ対応していることに注意したい。もしLED1, LED3をPWM ModeにするのならGPT1, GPT3を
設定する必要がある。

P400ポートの設定内容を見ると、Modeが「Peripheral mode」、Input/Output->P400が「GPT6_GTIOCA」に変更されることが確認できる。

次にr_gptモジュールを選択してプロパティから項目(赤枠)に設定した値に変更する。
GPT6を使用するので、General->Channelは「6」とする。必須ではないが合わせてNameも「g_timer6」とした。
また500ms間隔でLED3を点灯と消灯を繰り返す動作をおこなうので、
General->Periodを「1」、Period Unitを「Seconds」、Output->Duty cycle percentを「50」としている。
また、前検討の割り込み処理は不要となるので、Interrupt->Callbackは「NULL」、Overflow/Crest Interrupt Priorityを「Disabled」と
デフォルト値に戻している。
先のPinsタブでの設定は完了しているので、Pins->GTIOC6Aには「P400」が表示される。

ここまで完了したらコンフィギュレーションを保存して、Generate Project Contentを実行し自動生成ファイルを生成する。

ソースコード変更 (hal_entry.c)

プロジェクトのfile:hal_entry.cの内容を変更する。
前検討で使用したコールバック関数:callback_g_timer0()は不要となったので、コメントアウトした。
なお、先のコンフィギュレーションにてGeneral->Nameで設定した「g_timer6」によって
g_timer0_ctrl, g_timer0_cfgが自動的にg_timer6_ctrl, g_timer6_cfgにリネームされることを確認されたい。

 #if 0
 // GPT0 callback
 void callback_g_timer0(timer_callback_args_t *p_args){
     static bsp_io_level_t LED3_level = BSP_IO_LEVEL_LOW;

     if(p_args->event == TIMER_EVENT_CYCLE_END){
         /* LED3 turn on or off */
         LED3_level = (LED3_level == BSP_IO_LEVEL_HIGH) ?  BSP_IO_LEVEL_LOW: BSP_IO_LEVEL_HIGH;
         g_ioport.p_api->pinWrite(&g_ioport_ctrl, g_bsp_leds.p_leds[BSP_LED_LED3], LED3_level);
 //        (void)R_GPT_Start(&g_timer0_ctrl);
     }

 }
 #endif

 /*******************************************************************************************************************//**
  * main() is generated by the RA Configuration editor and is used to generate threads if an RTOS is used.  This function
  * is called by main() when no RTOS is used.
  **********************************************************************************************************************/
 void hal_entry(void)
 {
     unsigned char sel = 0;
     bsp_io_level_t led_level;

     // GPT0 start
     (void)R_GPT_Open(&g_timer6_ctrl, &g_timer6_cfg);
     (void)R_GPT_Start(&g_timer6_ctrl);
...

結果 (Period:1 seconds duty:50%)

コンフィギュレーションとソースコードの修正が完了したらビルド、プログラムをおこなう。
結果は以下となり、LED3が500ms間隔(1s * 50%)で点灯と消灯を繰り返すことが確認できる。

結果 (Period:1 milliseconds duty:50% or 5%)

PWMのDUTY設定の効果をより理解するため、PWMのPeriodをより短周期にしDUTY幅を調整することによりLEDの輝度が変化することを確認する。
コンフィギュレーションに以下の2パターンを用意。
General:
  Period: 1
  Units: milliseconds
Output:
Duty Cycle Percent: ①50 or ②2
Duty Cycle Percent: 5

再プログラムした結果が以下になる。動画の前者、後者がそれぞれ①、②になるが、LED3の輝度がDUTY Cycle Percentの変更で変化
(①輝度高 ②輝度低)したことがわかる。
PWMの基本動作(Periodic, One Shot, PWM Mode)が確認できたところで、本編は終了とする。