ルネサスRAマイコンのソフト開発【コードの生成と使い方】

評価ボードの写真ソフト

ルネサスのARMコアマイコンであるRAシリーズは、RXやRL78と比べるとまだまだマイナーな印象ですが、昨今の半導体不足のなかでも多少入手性がマシのようで、採用が増えてきているようです。

RAマイコンの開発環境としてe2studioを採用し、コード生成とその使い方をまとめました。長い記事なので、使う予定の機能の項目を抜粋して読んでいただければと思います。

今どき珍しくも無いですが、RAマイコン開発環境にもGUIでCLKやI/Oの設定をしてHALコードを生成する機能があります。Flexible Software Package (FSP)と呼ばれているものがそれになります。

環境とデバイス

この記事では、下記を前提条件として説明します。

  • 評価ボード: FPB-RA2E1
  • デバイス: R7FA2E1A92DFM(Arm Cortex-M23 64pin)
  • 開発環境: e2studio Version: 2022-04 (22.4.0)
  • コンパイラ: GNU ARM Embedded

使っている評価ボードはデバッガ(E2Lite)機能が実装されていて3,000円くらいで購入できるものなので、RAマイコンを試すのに最適です。

FSP Configuration

FSP Configurationパースペクティブにすると、下のような画面構成になります。ここでFSPの各種設定を行ってHALコードの生成を行います。設定項目は、FSP Configuration Window 下のタブにリストされています。

FSP Configuration パースペクティブ
FSP Configuration パースペクティブ

FSP Configuration Windowが表示されていな場合は、プロジェクト・エクスプローラーの “configuration.xml” をダブルクリックすれば表示されるはずです。

Summary

Summaryタブには、プロジェクトの概要情報がまとめられています。特に設定する項目は有りません。

BSP

BSPタブは、FSPのバージョンやボードやマイコンの指定を行うところです。プロジェクト作成時に指定する内容ではありますので、改めてここで設定しなおすことはあまり無いと思います。

Clocks

Clocksタブでは、外部CLK周波数や分周率を設定して各機能ブロックに供給するCLK周波数を決定します。

評価ボードのFPB-RA2E1には、RTCクリスタルのみ実装でメインクロックは未実装されていないのでデフォルトのままでOKでした。メインクロックで外部クリスタルを使う場合は最適な動作周波数になるように調整することになります。

ClocksConfiguration
ClocksConfiguration

Pins

Pins Configurationタブでは、各ピンの機能割り付けを行います。通常は、こことStacksタブがFSPの主要設定項目になるのではないでしょうか。

端子機能と端子番号の子タブが付いていて、機能別にまとめられたリストから設定したり、端子番号や端子名で整列させられる表から設定したりと、お好みのUIで設定が出来るようになっています。

設定内容は右側にある”FSP Visualization”というマイコンパッケージ図にも反映されます。

PinConfiguration
PinConfiguration & FSP Visualization

私は製品の回路設計時に、このような統合開発環境のピン設定機能を使ってマイコンのピン割り付けを検討するようにしています。

基板上でパターンが交差するのを最小限にするように検討しやすいですし、割り付けミスのチョンボも減るのでお勧めです。

Interrupts

Interruptsタブには、有効になっている割り込みのリストが表示されています。

ここで割り込みを新規に追加していくことも可能ではありますが、あまりそのような事を行う機会は無さそうです。基本的には後に記載のStacksタブでの作業によって追加されていくことになります。

Event Links

Event Linksタブには、有効になっているイベントのリストが表示されています。

ここでイベントを新規追加することも可能ではありますが、Interrupts同様あまりそのような事を行う機会は無さそうです。基本的には後に記載のStacksタブでの作業によって追加されていくことになります。

Stacks

StacksタブではUARTなどのペリフェラル機能のHALドライバや、RTOSなどをプロジェクトに追加する作業を行います。ここで追加した機能が、最終的にコードとして生成されることとなります。通常は、こことPinsタブがFSPの主要設定項目になるのではないでしょうか。

New Stackから追加したい機能を指定、プロパティ―Windowで細かな設定を行うのが基本的な作業フローになります。

Stacks Configurration
Stacks Configurration

Components

Componentsタブでは生成出来るコンポーネント(コード)のリストと、現在の設定で生成されるコンポーネントの確認が出来ます。チェックBOXにチェック入れることでコンポーネントの追加も出来るようですが、ここで追加設定する必要は無いかと思います。素直にStacksタブで設定をおこないましょう。

IOポート

IOポート設定画面
IOポート設定画面

IOポートはデフォルトで最初からg_ioportというStackが用意されています。よって、Pinsタブの上画面の赤枠のところで各ポート機能を設定すれば使用する事ができます。

設定項目自体は、入出力方向や内部プルアップの有無など基本的なものばかりなので特に躓くことは無いかと思います。Symbolic NameCommentは記述しておくと”bsp_pin_cfg.h”ファイルに記述されます。特にSymbolicNameは、この名前でポートにアクセスできるようになり、見やすいコードが書けるのでお勧めです。

ポート制御のAPI関数はいろいろありますが、基本的な下記の4つを覚えておけばとりあえずはOKです。その他のAPIはポートをまとめて制御したり、イベントリンクコントローラ(ELC)による制御だったりします。

  • R_BSP_PinRead
  • R_BSP_PinWrite
  • R_BSP_PinAccessEnable
  • R_BSP_PinAccessDisable

下がスイッチを押したらLEDが点灯するサンプルコードです。

if(R_BSP_PinRead(SW1) == BSP_IO_LEVEL_HIGH){
    R_BSP_PinAccessEnable();
    R_BSP_PinWrite (LED1, BSP_IO_LEVEL_LOW);
    R_BSP_PinAccessDisable();
}
else{
    R_BSP_PinAccessEnable();
    R_BSP_PinWrite (LED1, BSP_IO_LEVEL_HIGH);
    R_BSP_PinAccessDisable();
}

デフォルトではポート制御が不可になっていて、R_BSP_PinAccessEnable()関数を使わないと状態変更できないことに注意してください。

UART

UART設定画面
UART設定画面

UARTを使うには、g_uart* Stackを追加/設定します。NewStack→Connectivity→UARTと設定するとg_uart*が生成されます。

ボーレートやパリティといったような基本的な設定は”プロパティ―”Windowにリスト化されています。

UARTスタックを生成すると、子供Stackとして送信/受信のDTCが表示されます。受信DTCは非推奨となっているので、送信DTCのg_transfer* のみを有効化しておきます。

こちらは特に追加で設定する内容はありませんし、UARTのAPIもDTC無しと変わらないので余計な手間は発生しません。DTCの初期化や使用もUARTの生成コードの中でおこなわれていているので、DTCのAPIを学習する必要もありません。

UARTのAPI関数は、下記を覚えておけば、とりあえずはOKです。

  • R_SCI_UART_Open
  • R_SCI_UART_CallbackSet
  • R_SCI_UART_Read
  • R_SCI_UART_Write

OpenはUART機能を使う前にコールすべき準備関数、CallbackSetは、受信送信の完了などの各種イベント発生時にコールされる関数を割り当てます。

CallbackSet関数を使わなくても、g_uart* のプロパティでコールバック関数を指定することも可能です。プロパティのModule→Interrupts→Callbackに関数名を記載すれです。

実際のUART通信はWriteやReadを使って行います。これらの関数は、実際の送受信が完了していなくても関数から抜けてしまいます。送信中に再度Write関数をコールされると、前回の送信が中断されてしまうようですので、状態管理用のフラグを使って管理するのが良さそうです。

下のコードは、UARTで受信したデータをそのまま送信するサンプルコードになります。

static volatile bool uart_sending = false;
static volatile bool uart_receiving = false;

void main(void){
    uint8_t tx_d, rx_d;

    R_SCI_UART_Open(&g_uart0_ctrl, &g_uart0_cfg);
    R_SCI_UART_CallbackSet(&g_uart0_ctrl, UartCallback, NULL, NULL);

    uart_receiving = true;
    R_SCI_UART_Read(&g_uart0_ctrl, &rx_d, 1);
    while(1){
        if(!uart_receiving){
            tx_d = rx_d;
            uart_receiving = true;
            R_SCI_UART_Read(&g_uart0_ctrl, &rx_d, 1);

            uart_sending = true;
            R_SCI_UART_Write(&g_uart0_ctrl, &tx_d, 1);
            while(uart_sending == true){;}
        }
    }
}

void UartCallback(uart_callback_args_t * p_args){
    switch(p_args->event){
        case UART_EVENT_TX_COMPLETE:
            uart_sending = false;
            break;
        case UART_EVENT_RX_COMPLETE:
            uart_receiving = false;
            break;
        default:
            break;
    }
}

I2C (master)

I2C設定画面
I2C設定画面

I2Cをマスターとして使うには、g_i2c_master* Stackを追加/設定します。NewStack→Connectivity→I2C Masterと設定するとg_i2c_master*が生成されます。

ルネサス製マイコンにおけるI2Cは、I2C専用機能とSCIのI2Cモードの2通りで使用することができます。Stackも(r_iic_master)と(r_sci_i2c)の2通りがあります。どちらも同じAPI関数で使う事ができるようなのであまり意識する必要は無さそうです。マイコンのどのピンに(周辺機能に)I2Cデバイスが接続されているのかを回路図で確認して選定すればOKです。

また、”I2C Communication Device”というStackもあります。こちらは、g_i2c_master* よりも上位のStackになり、RTOSを使っている時にセマフォでI2C機能の交通整理などをしてくれます。今回の説明では使用していません。

基本的な設定は”プロパティ―”Windowにリスト化されているのでこちらで設定してください。大抵のI2Cデバイスとの通信においては、Rate(通信速度)、スレーブアドレス、Callback関数、Pin割り当てくらいで良いかと思います。

API関数は、とりあえず下記を覚えておきましょう。

  • R_IIC_MASTER_Open
  • R_IIC_MASTER_Read
  • R_IIC_MASTER_Write
  • R_IIC_MASTER_CallbackSet
  • R_IIC_MASTER_SlaveAddressSet

基本的にはUARTのAPI関数と似たような感じで使えるものですが、R_IIC_MASTER_SlaveAddressSetだけがI2C特有のAPI関数になります。

こちらは、I2Cアクセス時のスレーブアドレスを指定する機能になります。FSPでパラメータ設定することも可能な項目ですが、API関数も用意されています。I2Cライン上にアドレスの異なる複数のデバイスが接続されている時に使用することになります。

下のサンプルコードは、I2C接続のADCに対して1ビットずつ大きな値を指定してノコギリ波を生成しています。

static volatile bool i2c_writing = false;

void main(void){
    uint8_t i2c_tx_buf[2] = {0x00, 0x00};
    R_IIC_MASTER_Open(&g_i2c_master0_ctrl, &g_i2c_master0_cfg);
    R_IIC_MASTER_CallbackSet(&g_i2c_master0_ctrl, I2c_Callback, NULL, NULL);
    R_IIC_MASTER_SlaveAddressSet(&g_i2c_master0_ctrl, 0x60, I2C_MASTER_ADDR_MODE_7BIT);

    while(1){
        if(!i2c_writing){
            i2c_tx_buf[1]++;
            i2c_writing = true;
            R_IIC_MASTER_Write(&g_i2c_master0_ctrl, i2c_tx_buf, 2, false);
        }
    }
}

void I2c_Callback(i2c_master_callback_args_t *p_args){
    switch(p_args->event){
        case I2C_MASTER_EVENT_TX_COMPLETE :
            i2c_writing = false;
            break;
        default:
            break;
    }
} 

SPI

SPI設定画面
SPI設定画面

RAマイコンに限らずルネサス製マイコンにおいてSPIは、SPI専用機能とSCIのSPIモードの2つの周辺機能で使用出来ます。SPI専用機能を使うにはg_spi*(r_spi) を、SCIのSPIモードを使うにはg_spi*(r_sci_spi)を追加/設定します。NewStack→Connectivity→SPIにどちらも用意されています。

この2つのSPI機能にはいろいろと差分がありますが、大きなところではSSL(CS)出力機能の有無があります。SPI専用機能ではSSL出力を制御できるのですが、SCIのSPIモードはSSLを出力する機能がありません。IOポートの出力機能でソフト制御してやる必要があります。しかしながら、SPI専用機能でSSLを設定するのも結構面倒ですし、どのSSLを有効にするのかを指定するAPI関数が用意されていないようです。シビアなアクセススピードを要求されるプロダクトでないならば、SPI専用機能においてもIOポートでSSL出力をソフト実装した方が簡単なきがします。

SPI Stackを追加すると、子StaskとしてDTC機能が2つ自動的に追加されます。こちらは、特にプロパティを設定する必要性も薄そうですし、API関数を使う際もDTC機能を意識する必要はありませんのでそのまま有効にしておけば良いと思います。

SPIのプロパティ設定は項目が多いし、どんな意味なのかよく分からないものもあって結構大変です。データシートを読み込んだり、オシロで波形を確認したりしながらターゲットデバイスに適合するように頑張って調整することになると思います。

設定は大変ですが、APIなどその他の通信機能とだいたい同じなのでそんなに苦労はしないと思います。

  • R_SPI_Open
  • R_SPI_Read
  • R_SPI_Write
  • R_SPI_WriteRead
  • R_SPI_CallbackSet

実際のアクセスにはRead、Write、WriteReadの3つの関数がありますが、汎用的に使えるWriteReadだけでも開発は可能です。Open/CallbackSetの使い方もUARTやI2Cと基本的には一緒です。

デバイスアクセスする前にはOpen関数を実行し、CallbackSetでコールバック関数を設定しておきましょう。コールバック関数ではデバイスアクセスの完了などの管理をしておくことになると思います。

下のサンプルコードは、SPI接続のADCに対して1ビットずつ大きな値を指定してノコギリ波を生成しています。


static volatile bool spi_writing = false;

void main(void){
    uint8_t device_reset[3] = {0x00, 0x00, 0x0f};
    uint8_t write_con_reg[3] = {0x21, 0x00, 0x04};
    uint8_t write_data[3] = {0x00, 0x00, 0x03};

    R_SPI_Open(&g_spi0_ctrl, &g_spi0_cfg);


    spi_writing = true;
    R_SPI_Write(&g_spi0_ctrl, device_reset, 1, SPI_BIT_WIDTH_24_BITS);
    while(spi_writing == true){;}

    spi_writing = true;
    R_SPI_Write(&g_spi0_ctrl, write_con_reg, 1, SPI_BIT_WIDTH_24_BITS);
    while(spi_writing == true){;}

    while(1){
        write_data[1]++;
        spi_writing = true;
        R_SPI_Write(&g_spi0_ctrl, write_data, 1, SPI_BIT_WIDTH_24_BITS);
        while(spi_writing == true){;}
        R_BSP_SoftwareDelay(1, BSP_DELAY_UNITS_MILLISECONDS);
    }
}

void spi_callback(spi_callback_args_t *p_args){
    if (SPI_EVENT_TRANSFER_COMPLETE == p_args->event)
    {
        spi_writing = false;
    }
}

ADC

ADC設定画面
ADC設定画面

ADCを使うには、g_adc* Stackを追加/設定します。NewStack→Analog→ADCと選択するとg_adc* が生成されます。

ADC Stackのプロパティを確認してみると、かなりの大量の設定項目がありますマイコンのADCは豊富な機能や動作モードがあるので仕方ないですが、初めて設定するときはかなり戸惑うと思います。必要最低限の設定でADC読み取りを行う場合はModule g_adc ADC → Input → ChannnelScanMask で使用するADCのチャンネルにチェックを入れればOKです。(Pinsタブでのポート設定は忘れずに行ってください。)その他の細かな設定は、少しづつ覚えていけば良いと思います。

API関数は、下の5個くらいを覚えておけばほとんどの場合でOKかと思います。OpenとScancfgで設定をしたらScanStartでADC取り込みを開始、StatusGetで読み込み終了をチェックしてReadで結果を取得という流れです。

  • R_ADC_Open
  • R_ADC_ScanCfg
  • R_ADC_ScanStart
  • R_ADC_StatusGet
  • R_ADC_Read

下のサンプルコードは最もシンプルなADC読み取りのコードになります。アナログ電圧値の読み取り頻度がそれほど大きくない場合は、このコードでも十分ではあります。

adc_status_t status;
uint16_t adc_result;

R_ADC_Open(&g_adc0_ctrl, &g_adc0_cfg);
R_ADC_ScanCfg(&g_adc0_ctrl, &g_adc0_channel_cfg);

R_ADC_ScanStart(&g_adc0_ctrl);

do{
    R_ADC_StatusGet(&g_adc0_ctrl, &status);
}while(status.state == ADC_STATE_SCAN_IN_PROGRESS);

    R_ADC_Read(&g_adc0_ctrl, ADC_CHANNEL_0, &adc_result);
}

タイマー割り込み

RAマイコンの(というか、殆どのマイコンの)タイマーは様々な機能をもっています。ここでは、その中で最も基本的な機能で大抵のプロダクトで1個は使用するであろうタイマー割り込みについて記載します。

タイマー割り込み設定画面
タイマー割り込み設定画面

タイマー割り込みを使うには、g_timer* Stackを追加/設定します。NewStack→Timers→Timer, General PWMと選択するとg_timer* が生成されます。

プロパティ設定では、タイマー割り込み以外のタイマー機能(PWM出力は波形取り込みなど)の設定も出来るようになっていて沢山の設定項目がありますが、タイマー割り込みを使う場合は以下を設定すればOKです。

  • Module → General → Channel  適当なChannel数
  • Module → General → Mode   ”Periodic”
  • Module → General → Period  割り込み周期
  • Module → General → Period  割り込み周期の単位
  • Module → Interrupts → Callback  割り込みコールバック関数名
  • Module → Interrupts → Overflow…  Disabled以外の希望する優先度

冒頭の設定画面が設定箇所と設定例になります。コールバック関数が”NULL”となっていて未設定ですが、こちらはAPI関数を使って後で設定する事も可能です。

API関数については、以下の3個だけ覚えておけば基本的な動作は十分行えます。”R_GPT_CallbackSet”についてはFSPのプロパティで設定済なら使う必要もないので、Open→Startだけでタイマー割り込みは開始出来ることになります。

  • R_GPT_Open
  • R_GPT_Start
  • R_GPT_CallbackSet

下がサンプルコードになります。タイマーの開始とコールバック関数内でLEDの点滅をしています。

void timer_callback (timer_callback_args_t * p_args);

void TimerStart(void)
{
    R_GPT_Open(&g_timer0_ctrl, &g_timer0_cfg);
    R_GPT_CallbackSet(&g_timer0_ctrl, timer_callback, NULL, NULL);
    R_GPT_Start(&g_timer0_ctrl);
    while(1){;}
}

void timer_callback (timer_callback_args_t * p_args)
{
    static uint16_t count = 0;

    if (TIMER_EVENT_CYCLE_END == p_args->event)
    {

        //ここにタイマー割り込み発生時の処理を記載する
        if(count >= 1000){
            count = 0;
        }
        else if(count >= 500){
            R_BSP_PinAccessEnable();
            R_BSP_PinWrite (LED1, BSP_IO_LEVEL_LOW);
            R_BSP_PinAccessDisable();
        }
        else{
            R_BSP_PinAccessEnable();
            R_BSP_PinWrite (LED1, BSP_IO_LEVEL_HIGH);
            R_BSP_PinAccessDisable();
        }
        count++;
    }
}

コメント