part.5 Renesas RA MCU
UARTでシリアル通信

目的

FSPライブラリ(sci_uart)によるシリアル通信の制御方法をサンプルプログラム(sci_uart_ek_ra6m4_ep)を使用して理解する。

全体フロー

  • USBシリアル変換モジュール TXD7/RXD7 (P613/P614)接続
  • サンプルプログラム プロジェクトインポート
  • コンフィギュレーション変更 (SCI7有効) / ビルド / プログラム書き込み
  • 動作確認
  • サンプルプログラム コード解析

準備

本編

基板に何らかの制御プログラムを追加した後にPCから外部コントロールして動作や検証をおこなうことは組み込みの現場ではよくあることである。
UART通信はこれを実現するための一番手っ取り早く、また確実な方法となるので、この場で確認しておきたい。

USBシリアル変換モジュール 接続

EK-RA6M4評価キットにはPCとUART通信するための変換ICが実装されていないので、これを用意する必要がある。

秋月電子(https://akizukidenshi.com/)にFTDI製のFT232RLのチップが乗った小基板が販売されているので、これを入手。
筆者のPCの開発環境はWindows10だが、FTDIのドライバをインストールしなくても自動で認識するので便利である。

以下に接続元と接続先の表を記載した。基板間の接続にはケーブルと一部はんだ付けが必要になるのでケーブルと工具を用意する必要がある。

両端のコンタクトがオスのQIコネクタを数本もっていたので、スマートな方法ではないが今回はAE-UM232R側はオスのコンタクトをそのままはんだ付けし、もう片方はコネクタに接続している。

基板間を接続した後、AE-UM232Rのジャンパポストの設定を確認。

デフォルトでは期待どおりになっているはずだが、J1は1-2間ジャンパ, J2はショートが正しい。
特にJ1の設定をミスすると、通信できないどころか、RA6M4チップの許容電圧(4.0V)を超える5Vの信号がポートに入ってチップを壊す可能性があるため、ここは要確認ポイントである。

以下AE-UM232Rの説明書の抜粋となる。

サンプルプログラム プロジェクトインポート

上記の基板接続が完了した後にサンプルプログラムをe2 studioにインポートする。
検証に使うのは「sci_uart_ek_ra6m4_ep」プロジェクトとなる。

e2studioのメニューから
「ファイル」「インポート」「既存プロジェクトをワークスペースへ」を選択し、
以下のウィンドウから
ルートディレクトリの選択に「{アーカイブ展開先}\ek_ra6m4\sci_uart\sci_uart_ek_ra6m4_ep」を指定し、プロジェクトのメニュー欄に
表示された「sci_uart_ek_ra6m4_ep」をインポートする。

コンフィギュレーション修正 / ビルド / プログラム書き込み

サンプルプログラムのプロジェクトのインポートが完了したら、次にコンフィギュレーションの変更をおこなう。

SCI_UARTのサンプルプログラムはSCI0モジュールがデフォルトで設定されており、今回使用するポートはSCI7モジュールで使用可能なP613,P614ポートとなるため、設定の変更が必要となる。

プロジェクトからコンフィギュレーションを開き、Pinsタブを選択する。
Peripherals→Connectivity:SCI→SCI7を選択し、
Operation Mode」を非同期通信である「Asynchronous UART」、「TXD7」を「P613」、「RXD7」を「P614」に設定する。

SCI0モジュールは不要となるため、Peripherals→Connectivity:SCI→SCI0を選択し「Operation Mode」を「Disabled」にする。



次にSCI0モジュールからSCI7モジュールをスタックに認識させる必要がある。
Stacksタブを選択し、HAL/Common Stacksウィンドウから「g_uart UARRT(r_sci_uart)」を選択する。
SCI[n]のnはChannelに相当するので、プロパティのModule g_uart UART→General→Channelに「7」を入力する。
それ以外の項目はデフォルトのままである。

完了したら、右上のGenerate Project Contentをクリックして自動生成ファイルを生成する。


完了したら、プロジェクトをビルドし、EK-RA6M4評価基板にプログラムを書き込む。
前セクションで何度か手順を説明しているので、ここでは詳細は割愛する。

動作確認

次にTeratermのシリアルポートの設定を確認した後、基板の動作を検証する。

Teratermのメニューより「設定→シリアルポート」を選択肢、ポート設定のウィンドウを立ち上げる。
ポートは自動的にCOM4となっているが、
今回のSCI7モジュールのUARTの設定は通信速度:115200bps, データ長:8bitパリティチェック:なし ストップビット:1bit, フロー制御:なし
となっているので、Teratermの内容が合致しているか確認。



メニューより「設定→端末」を確認。今回のプログラムは入力に対するエコーが帰ってこないので、「ローカルエコー」をONにして
入力値がウィンドウに返信されるようにした。


最後にメニューから「ファイル→新しい接続」にて開放されたポートを設定し、評価基板と接続する。

以下は「1→3→5→10→30→50→100」の順にTeraTermから基板にLEDのDutyの設定値を
入力した例である。LEDがそれぞれの入力値に対して反応していることが確認できる。
また入力のたびに「Set next value」という文字列が基板からTeraTermに返信されているので、
Teraterm(PC)と基板間でシリアル送信と受信の処理が成り立っていることも確認できる。

サンプルプログラム コード解析

サンプルプログラム上において、FSPライブラリでUARTで使用されている基本関数は以下となっている。

初期化: R_SCI_Open(&g_uart_ctrl, &g_uart_cfg)

バイト書き込み: R_SCI_UART_Write(&g_uart_ctrl, p_msg, msg_len)

なおバイト読み込みについては単体で読み出しをするR_SCI_UART_Read()を使用せず、
コールバック関数(user_uart_callback)内で受信割り込みイベントが発生(UART_EVENT_RX_CHAR==p_args->event)時のみ
受信データ(p_args->data)を取得する
処理をおこなっている。

つまり本サンプルプログラムから読み取れるのは
上記の3つの構成(R_SCI_Open()による初期化、R_SCI_UART_Write()によるバイト書き込み、コールバック関数の受信イベント処理)
が理解できればUART通信による基本的なデータの送受信が可能
ということである。

以下file:hal_entry.cのメイン関数であるhal_entry()の中身になる。

hal_entry()内の関数:uart_initialize()では、FSP関数:R_SCI_Open(&g_uart_ctrl, &g_uart_cfg)を呼び出すことでUARTの初期化を
おこなっている。初期化が完了した後、関数:uart_ep_demo()でプログラムのメイン処理をおこなう流れとなっている。

file:hal_entry.c

void hal_entry(void) { fsp_err_t err = FSP_SUCCESS; fsp_pack_version_t version = {RESET_VALUE}; /* Version get API for FLEX pack information */ R_FSP_VersionGet(&version); /* Example Project information printed on the Console */ APP_PRINT(BANNER_1); APP_PRINT(BANNER_2); APP_PRINT(BANNER_3,EP_VERSION); APP_PRINT(BANNER_4,version.version_id_b.major, version.version_id_b.minor, version.version_id_b.patch); APP_PRINT(BANNER_5); APP_PRINT(BANNER_6); APP_PRINT("\r\n\r\nThe project initializes the UART with baud rate of 115200 bps."); APP_PRINT("\r\nOpen Serial Terminal with this baud rate value and"); APP_PRINT("\r\nProvide Input ranging from 1 - 100 to set LED Intensity\r\n"); /* Initializing GPT in PWM mode */ err = gpt_initialize(); if (FSP_SUCCESS != err) { APP_PRINT ("\r\n ** GPT TIMER INIT FAILED ** \r\n"); APP_ERR_TRAP(err); } /* Starting GPT */ err = gpt_start(); if (FSP_SUCCESS != err) { APP_PRINT ("\r\n ** GPT START FAILED ** \r\n"); timer_gpt_deinit(); APP_ERR_TRAP(err); } /* Initializing UART */ err = uart_initialize(); if (FSP_SUCCESS != err) { APP_PRINT ("\r\n ** UART INIT FAILED ** \r\n"); timer_gpt_deinit(); APP_ERR_TRAP(err); } /* User defined function to demonstrate UART functionality */ err = uart_ep_demo(); if (FSP_SUCCESS != err) { APP_PRINT ("\r\n ** UART EP Demo FAILED ** \r\n"); timer_gpt_deinit(); deinit_uart(); APP_ERR_TRAP(err) } }
fsp_err_t uart_initialize(void)
{
    fsp_err_t err = FSP_SUCCESS;

    /* Initialize UART channel with baud rate 115200 */
    err = R_SCI_UART_Open (&g_uart_ctrl, &g_uart_cfg);
    if (FSP_SUCCESS != err)
    {
        APP_ERR_PRINT ("\r\n**  R_SCI_UART_Open API failed  **\r\n");
    }
    return err;
}

次にfile:uart_ep.cからuart_ep_demo()の内容を参照する。

関数内の主要な処理は以下の流れになる。

  • コールバック関数:user_uart_callback()で入力されたg_data_received_flagを参照し、受信割り込みの有無をチェック
  • コールバック関数:user_uart_callback()で入力された一時バッファ:g_temp_bufferから入力文字列の読み出しと数値判定
  • 入力文字列をatoi()で数値に変換し、変数:intensityに代入
  • 数値が1から100の範囲ならset_intensity()を呼び出しLEDのデューティを設定
  • uart_print_user_msg()でUART送信をおこない、ターミナルにメッセージを出力
file:uart_ep.c

fsp_err_t uart_ep_demo(void) { fsp_err_t err = FSP_SUCCESS; volatile bool b_valid_data = true; while (true) { if(g_data_received_flag) { g_data_received_flag = false; uint8_t input_length = RESET_VALUE; volatile uint32_t intensity = RESET_VALUE; /* Calculate g_temp_buffer length */ input_length = ((uint8_t)(strlen((char *) &g_temp_buffer))); /* Check if input data length is in limit */ if (DATA_LENGTH > (uint8_t)input_length) { /* This loop validates input data byte by byte to filter out decimals. (floating point input) * Any such data will be considered as invalid. */ for(int buf_index = RESET_VALUE; buf_index < input_length; buf_index++) { if(ZERO_ASCII <= g_temp_buffer[buf_index] && NINE_ASCII >= g_temp_buffer[buf_index]) { /* Set b_valid_data Flag as data is valid */ b_valid_data = true; } else { /* Clear data_valid flag as data is not valid, Clear the buffer and break the loop */ memset(g_temp_buffer, RESET_VALUE, DATA_LENGTH); b_valid_data = false; break; } } /* All bytes in data are integers, convert input to integer value to set intensity. */ intensity = ((uint32_t)(atoi((char *) &g_temp_buffer))); /* Validation input data is in 1 - 100 range. */ if(MAX_INTENISTY < intensity || RESET_VALUE == intensity) { /* Resetting the g_temp_buffer as data is out of limit */ memset(g_temp_buffer, RESET_VALUE, DATA_LENGTH); b_valid_data = false; /* Application is being run on Serial terminal hence transmitting error message to the same */ err = uart_print_user_msg((uint8_t *)"\r\nInvalid input. Input range is from 1 - 100\r\n"); if (FSP_SUCCESS != err) { APP_ERR_PRINT ("\r\n ** UART WRITE FAILED ** \r\n"); return err; } } } else { /* Clear data_valid flag as data is not valid, Clear the g_temp_buffer */ memset(g_temp_buffer, RESET_VALUE, DATA_LENGTH); b_valid_data = false; err = uart_print_user_msg((uint8_t *)"\r\nInvalid input. Input range is from 1 - 100\r\n"); if (FSP_SUCCESS != err) { APP_ERR_PRINT ("\r\n ** UART WRITE FAILED ** \r\n"); return err; } } /* Set intensity only for valid data */ if(b_valid_data) { /* Change intensity of LED */ err = set_intensity(intensity, TIMER_PIN); if (FSP_SUCCESS != err) { APP_ERR_PRINT ("\r\n** GPT failed while changing intensity ** \r\n"); return err; } /* Resetting the temporary buffer */ memset(g_temp_buffer, RESET_VALUE, DATA_LENGTH); b_valid_data = false; err = uart_print_user_msg((uint8_t *)"\r\nSet next value\r\n"); if (FSP_SUCCESS != err) { return err; } } } } }


文字列のUART送信をおこなっている uart_print_user_msg()の中身をみると、FSP関数:R_SCI_UART_Write()が呼び出されていることがわかる。
第1引数はg_uart_ctrlのポインタ, 第2引数は送信するメッセージの入ったバイトの配列, 第3引数は送信するバイト数となっている。

関数の後半にwhile文にて送信完了待ち(UART_EVENT_TX_COMPLETE!=g_uart_event…)をおこなっている。ここで参照している
変数:g_uart_eventは、コールバック関数: user_uart_callback()で入力しており、受信完了待ちのイベントが発生したら変数に
上記の値(UART_EVENT_TX_COMPLETE)が設定される。

file:uart_ep.c

fsp_err_t uart_print_user_msg(uint8_t *p_msg) { fsp_err_t err = FSP_SUCCESS; uint8_t msg_len = RESET_VALUE; uint32_t local_timeout = (DATA_LENGTH * UINT16_MAX); char *p_temp_ptr = (char *)p_msg; /* Calculate length of message received */ msg_len = ((uint8_t)(strlen(p_temp_ptr))); /* Reset callback capture variable */ g_uart_event = RESET_VALUE; /* Writing to terminal */ err = R_SCI_UART_Write (&g_uart_ctrl, p_msg, msg_len); if (FSP_SUCCESS != err) { APP_ERR_PRINT ("\r\n** R_SCI_UART_Write API Failed **\r\n"); return err; } /* Check for event transfer complete */ while ((UART_EVENT_TX_COMPLETE != g_uart_event) && (--local_timeout)) { /* Check if any error event occurred */ if (UART_ERROR_EVENTS == g_uart_event) { APP_ERR_PRINT ("\r\n** UART Error Event Received **\r\n"); return FSP_ERR_TRANSFER_ABORTED; } } if(RESET_VALUE == local_timeout) { err = FSP_ERR_TIMEOUT; } return err; }


最後にコールバック関数:user_uart_callback()の内容を確認する。

  • 変数:g_uart_eventに割り込みのイベント(p_args->event)を代入
  • イベントが受信割り込み(UART_EVENT_RX_CHAR)であり、バイトの受信データがキャリッジリターン(\n)であれば
    受信フラグ:g_data_received_flagをtrueに設定。データがそうでなければ、データを一時バッファ:g_temp_buffer[]に追加。
void user_uart_callback(uart_callback_args_t *p_args)
{
    /* Logged the event in global variable */
    g_uart_event = (uint8_t)p_args->event;

    /* Reset g_temp_buffer index if it exceeds than buffer size */
    if(DATA_LENGTH == g_counter_var)
    {
        g_counter_var = RESET_VALUE;
    }

    if(UART_EVENT_RX_CHAR == p_args->event)
    {
        switch (p_args->data)
        {
            /* If Enter is pressed by user, set flag to process the data */
            case CARRIAGE_ASCII:
            {
                g_counter_var = RESET_VALUE;
                g_data_received_flag  = true;
                break;
            }
            /* Read all data provided by user until enter button is pressed */
            default:
            {
                g_temp_buffer[g_counter_var++] = (uint8_t ) p_args->data;
                break;
            }
        }
    }
}