Renesas CK-RX65Nを使用してmruby/c 3.3.1を動かす

# はじめに
今回はRenesas CK-RX65Nを使用して最新版のmruby/cを動かしてみました。
mruby/cを使用すればRX65Nマイコン上でバイトコード化した「Ruby」プログラムを動かすことができます。
mruby/cはプログラミング言語「Ruby」の高い開発生産性と可読性の特徴を引き継ぎつつ、プログラム実行に必要なメモリ消費量がmruby（組込み向け軽量Ruby）より少ない開発言語であり、小さなワンチップマイコンでも動作するように開発しており、センサーネットワークやウェアラブルデバイスなどの小型端末のソフトウェア開発、既存デバイスへのDSL追加に適しているのが特徴です。

* https://www.s-itoc.jp/support/technical-support/mrubyc/

mruby/cではRX65Nマイコンに適用できるHAL層、既存デバイスドライバを用意して、それらを呼び出すクラス/メソッドを作成することで、VM上で簡単にバイトコード化した「Ruby」プログラムを動作させることができます。
今回はmruby/c上で動作する簡単なシリアルエコーサーバーを作成します。

準備した環境は以下の通りです。
1. CK-RX65N (評価ボード)
2. e2 studio 2024-07 (統合開発環境)
3. GCC for Renesas 8.3.0.202405-GNURX (RXマイコン用コンパイラ)
4. mruby/c 3.3.1 (mruby/c VM)
   * https://github.com/mrubyc/mrubyc/tree/release3.3.1 

# mruby/c用新規プロジェクトの作成
最初にルネサス製統合開発環境であるe<sup>2</sup> studioを起動して、mruby/c用の新規プロジェクトを作成します (e<sup>2</sup> studioとGCCコンパイラをインストールした上でe<sup>2</sup> studioを起動してください)。
e<sup>2</sup> studioの左上の「ファイル」から「Renesas RX」を選択します。
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/28b72a99-fe3f-155d-9631-b1d5f3649012.png) |
|:-:|

次の画面で「GCC用の実行可能プロジェクト」を選択します。
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/f169e182-9ddc-e877-3099-e056884dc370.png) |
|:-:|

今回「RTOS」は使用しないので「None」に設定します。
「Target Board」を「CK-RX65N」を選択して次に進みます。
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/9afbc2ec-788e-ca62-6f33-1e6980e2ed41.png) |
|:-:|

後はすべてデフォルトの選択肢でプロジェクトの作成を完了します。
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/e9056c30-61f0-23f8-e349-e0820fa2e4a9.png) |
|:-:|

# 既存デバイスドライバ(FITモジュール)のインストール
今回は簡単なシリアルエコーサーバーを作成するので、シリアル通信モジュール(r_sci_rx)を作成したプロジェクトにインストールします。
具体的にはプロジェクト選択後に表示される画面で「コンポーネント」タブを開き、「+」ボタンから「r_sci_rx」を追加します。

:::note warn
「重複する機能のコンポーネントを非表示」のチェックを外さないと「r_sci_rx」は表示されません。
:::

| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/30ab6aa3-8a77-24cb-97de-e4e0435fe9cc.png) |
|:-:|

今回シリアル通信で使用するUSB-Serial変換ICに接続されているチャネルはSCI5なので、SCI5を有効にします。
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/3c47b825-6cbb-7358-5a41-1aab01776af9.png) |
|:-:|

また、「r_bsp」で「User stack」、「Interrupt stack」、「Heap」サイズを増やしておきます。
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/18e64b88-4dd4-3dcb-19ea-9af1d96f28ca.png) |
|:-:|

その後、右上の「コードの生成」ボタンをクリックします。
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/e99d03f7-bfd7-4957-69c0-32962294b48b.png) |
|:-:|

# mruby/c VMのインストール
下記サイトからmruby/c 3.3.1をダウンロードして「src」フォルダ内のすべてのファイルを作成したプロジェクトの「src」フォルダにコピーしてください。

:::note info
該当ファイルをe<sup>2</sup> studio上の「src」フォルダにドラッグアンドドロップすればコピーすることができます。
:::

* https://github.com/mrubyc/mrubyc/tree/release3.3.1

# HAL層の作成
作成したプロジェクトの「src」フォルダ以下に「hal.h」を作成します。

:::note info
「src」フォルダを右クリックすると「.h」ファイルを作成することができます。
:::

| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/56ee8f3b-88a0-360f-656b-c7188cda096c.png) |
|:-:|

「hal.h」の内容は以下のようにします。

```c:hal.h

/*! @file
  @brief
  Hardware abstraction layer
        for CK-RX65N

  <pre>
  Copyright (C) 2016 Kyushu Institute of Technology.
  Copyright (C) 2016 Shimane IT Open-Innovation Center.

  This file is distributed under BSD 3-Clause License.
  </pre>
*/

#ifndef MRBC_SRC_HAL_H_
#define MRBC_SRC_HAL_H_

#ifdef __cplusplus
extern "C" {
#endif

/***** Feature test switches ************************************************/
/***** System headers *******************************************************/
#include "r_smc_entry.h"
/***** Local headers ********************************************************/
/***** Constant values ******************************************************/
/***** Macros ***************************************************************/
#ifndef MRBC_SCHEDULER_EXIT
#define MRBC_SCHEDULER_EXIT 1
#endif

#if !defined(MRBC_TICK_UNIT)
#define MRBC_TICK_UNIT_1_MS   1
#define MRBC_TICK_UNIT_2_MS   2
#define MRBC_TICK_UNIT_4_MS   4
#define MRBC_TICK_UNIT_10_MS 10
// portTICK_PERIOD_MS is 10 in ESP-IDF by default.
// You can configure MRBC_TICK_UNIT less than 10 ms
// under the understanding of FreeRTOS's tick.
#define MRBC_TICK_UNIT MRBC_TICK_UNIT_1_MS
// Substantial timeslice value (millisecond) will be
// MRBC_TICK_UNIT * MRBC_TIMESLICE_TICK_COUNT (+ Jitter).
// MRBC_TIMESLICE_TICK_COUNT must be natural number
// (recommended value is from 1 to 10).
#define MRBC_TIMESLICE_TICK_COUNT 1
#endif

#ifndef MRBC_NO_TIMER
# define hal_init()        ((void)0)
# define hal_enable_irq()  ((void)0)
# define hal_disable_irq() ((void)0)
# define hal_idle_cpu()    ((void)0)

#else // MRBC_NO_TIMER
# define hal_init()        ((void)0)
# define hal_enable_irq()  ((void)0)
# define hal_disable_irq() ((void)0)
# define hal_idle_cpu()    (R_BSP_SoftwareDelay(MRBC_TICK_UNIT, BSP_DELAY_MILLISECS), mrbc_tick())

#endif


/***** Typedefs *************************************************************/
/***** Global variables *****************************************************/
/***** Function prototypes **************************************************/
int hal_write(int fd, const void *buf, int nbytes);
int hal_flush(int fd);

/***** Inline functions *****************************************************/

#ifdef __cplusplus
}
#endif
#endif // ifndef MRBC_HAL_H_
```

また、作成したプロジェクトの「プロパティ」を開き、「MRBC_NO_TIMER」を定義します。

| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/1a8ea5d2-75cd-f40a-0e6e-aff167d4f606.png) |
|:-:|

# VM起動ルーチンとメソッドの作成
<作成したプロジェクト名>.cにVM起動ルーチンとメソッドを作成します。
「<作成したプロジェクト名>.c」の内容は以下のようにします。

```c:main.c

/**
 * @file main.c
 * @author nizuki926
 * @brief main program
 * @version 1.0
 * @date 2019-02-03
 *
 * @copyright Copyright (c) 2019, nizuki926 All rights reserved.
 *
 */
#include "r_smc_entry.h"
#include "r_sci_rx_if.h"
#include "mrubyc.h"

#define MEMORY_SIZE (1024*40)
static char memory_pool[MEMORY_SIZE];

extern const uint8_t sample_serial_echo_server[];

sci_hdl_t xSerialSciHandle;

static void c_sci5_init (mrb_vm * vm, mrb_value * v);
static void c_sci5_read (mrb_vm * vm, mrb_value * v);
static void c_sci5_write (mrb_vm * vm, mrb_value * v);

static void c_sci5_init (mrb_vm * vm, mrb_value * v)
{
    sci_cfg_t xSerialSciConfig;
    R_SCI_PinSet_SCI5();

    xSerialSciConfig.async.baud_rate    = BSP_CFG_SCI_UART_TERMINAL_BITRATE;
    xSerialSciConfig.async.clk_src      = SCI_CLK_INT;
    xSerialSciConfig.async.data_size    = SCI_DATA_8BIT;
    xSerialSciConfig.async.parity_en    = SCI_PARITY_OFF;
    xSerialSciConfig.async.parity_type  = SCI_EVEN_PARITY;
    xSerialSciConfig.async.stop_bits    = SCI_STOPBITS_1;
    xSerialSciConfig.async.int_priority = 1; /* lowest at first. */
    R_SCI_Open(SCI_CH5, SCI_MODE_ASYNC, &xSerialSciConfig, NULL, &xSerialSciHandle);
}

static void c_sci5_read(mrb_vm * vm, mrb_value * v)
{
    sci_err_t ret;
    uint8_t ch;

    do
    {
        ret = R_SCI_Receive(xSerialSciHandle, &ch, 1);
    } while (SCI_SUCCESS != ret);

    SET_INT_RETURN(ch);
}

static void c_sci5_write(mrb_vm * vm, mrb_value * v)
{
    sci_err_t ret;
    uint8_t ch = GET_INT_ARG(1);

    do
    {
        ret = R_SCI_Send(xSerialSciHandle, &ch, 1);
    } while (SCI_SUCCESS != ret);
}

int hal_write(int fd, const void * buf, int nbytes)
{
	return 0;
}

int hal_flush(int fd)
{
	return 0;
}

/**
 * @brief mruby/c initialize routine
 *
 */
int main (void)
{
	mrbc_init(memory_pool, MEMORY_SIZE);
	mrbc_define_method(0, mrbc_class_object, "serial_init", (mrbc_func_t)c_sci5_init);
	mrbc_define_method(0, mrbc_class_object, "serial_read", (mrbc_func_t)c_sci5_read);
	mrbc_define_method(0, mrbc_class_object, "serial_write", (mrbc_func_t)c_sci5_write);

	if (NULL == mrbc_create_task(sample_serial_echo_server, 0))
	{
		while(1);
	}

	mrbc_run();

	while(1)
	{
		/* do nothing */
	}

    return 0;
}
```

# バイトコード化した「Ruby」プログラムの作成
最後にバイトコード化した「Ruby」プログラムを作成します。
下記サイトからmrubyコンパイラ(mruby/c 3.xに対応)をダウンロードして任意のフォルダに保存して解凍します。
   * https://www.s-itoc.jp/support/technical-support/mrubyc/mrubyc-download/

解凍したフォルダ内に`sample_serial_echo_server.rb`という名前のファイルを作成してください。
`sample_serial_echo_server.rb`の内容は以下のようにします。

```ruby:sample_serial_echo_server.rb
#
# mrbc -E -Bsample_serial_echo_server sample_serial_echo_server.rb
#

character = 0

serial_init()

while true
    character = serial_read()
    serial_write(character)
end
```

その後、PowerShell上で以下のコマンドを実行します。
同フォルダ内に`sample_serial_echo_server.c`が作成されるので、それを作成したプロジェクトの「src」フォルダ内にコピーします。

:::note info
該当ファイルをe<sup>2</sup> studio上の「src」フォルダにドラッグアンドドロップすればコピーすることができます。
:::

```bash
.\mrbc.exe -E -Bsample_serial_echo_server sample_serial_echo_server.rb
```

e<sup>2</sup> studio上でプロジェクトのビルドを実行します。
ビルドしたプログラムをデバッグ実行して、Tera Term上で送信した文字がそのままエコーバックされることを確認できます。
Tera Termの「シリアルポート」におけるボーレートの設定は「115200」に設定してください(サンプルプログラムのボーレート設定が上記値であるためです)。

:::note info
ビルドはe<sup>2</sup> studio上で「トンカチ」マークをクリックします。
デバッグ実行はe<sup>2</sup> studio上で「虫」マークをクリックします。
:::

| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/354472/ea018393-f200-a5fa-4681-6226092f9c35.png) |
|:-:|

# さいごに
今回はRenesas CK-RX65Nを使用して最新版のmruby/c VM上でシリアルエコーサーバーを動かしてみました。
ルネサスでは既存デバイスドライバとしてFITモジュールが提供されているので、これらを使用すれば様々な周辺機能を使用した「Ruby」プログラムをVM上で動作させることができると考えています。