BBC micro:bit v2でmruby/cを動かしてみる

最近息子と一緒にBBC micro:bitを触って遊んでいます。

今日はこのmicro:bit上でmruby/cを動かしてみましたのでメモを残しておきます。

(写真撮ったあとにBBCBCCtypoしてるのに気付きました。。。)

はじめに

micro:bitとは

micro:bitBBC(英国放送協会)が主体となって開発されたシングルボードコンピューターです。学校での情報教育(プログラミング)などで利用されているとのことです。

microbit.org

こちらのスイッチエデュケーションさんのサイトに特徴がまとめられています。

switch-education.com

micro:bit の特徴
- LEDやボタン、センサーなどをあらかじめ搭載しています
- パソコンやタブレット、さまざまな環境でプログラミングできます
- 段階的にプログラミングを学ぶことができます
- 拡張パーツをつなげれば、さまざまな作品を作ることができます

値段も2,000円程度(執筆時点)と手ごろな価格になっており、手軽に触り始めることが出来ます。

こちらはRaspberry Piと比べてみた写真です。手のひらサイズですね。

ブロックを用いたビジュアルプログラミングができるMakeCodeやMicroPythonを利用可能なので子供のプログラミング&電子工作入門に良さそうと思い購入しました。

mruby/cとは

mruby/cとは、軽量Rubyであるmrubyをさらに組み込み機器向けに軽量化したmrubyの実装とのことです。

www.s-itoc.jp

こちらの記事でWio LTE上で動かしているのを読んでmicro:bit上でも動かせるんじゃないか?と思ったのが今回のきっかけです。
magazine.rubyist.net

ということで早速micro:bit上でmruby/cを動かしてみます。

準備

先ほどのRubyist Magagineの記事を参考に、開発環境にはArduino IDEを利用します。

必要なもの

  • Arduino IDE
  • mruby v2.1.1 (v2.1.2だとエラーになるため)

Arduino IDEmicro:bit v2を使えるようにする

まずはArduino IDEmicro:bit v2を使えるようにするためにsandeepmistry/arduino-nRF5をインストールします。

github.com

Arduinoの環境設定ダイアログを開き、追加のボードマネージャのURLに以下を入力します。
https://sandeepmistry.github.io/arduino-nRF5/package_nRF5_boards_index.json

注: 既に他のURLが入力されている場合、カンマ区切りで後ろに追記してください。

これでツール -> ボード -> Nordic Semiconductor nRF5 boardsという項目の中からBBC micro:bit V2が選べるようになっているはずです。

mruby/cをmicro:bit v2へ移植

次にmruby/cをmicro:bit v2へ移植します。

  • mruby/cのソースを取得
  • HALを実装
  • Arduinoのライブラリにする

mruby/cのソースを取得

次にmruby/cのソースを取得します。

git clone https://github.com/mrubyc/mrubyc.git

今回は何も考えずmasterを利用しました。エラーが出るようならタグが付けられたバージョンを使おうと思ったのですが、幸い特にエラーは起きなかったのでそのままmasterを使いました。

HAL(Hardware Abstraction Layer)の実装

mruby/cにはHALとして以下のものが用意されていました。

  • hal_esp32
  • hal_pic24
  • hal_posix
  • hal_psoc5lp

これらを参照しつつmicro:bit(nRF52833)向けにHALを実装します。

実装手順
  • 既存のsrc/hal_*ディレクトリを削除
  • 既存のsrc/hal_selector.hの修正
  • src/halディレクトリの作成 & hal.h/hal.c/hal.cppの作成

既存のsrc/hal_*ディレクトリを削除

まずsrc/hal_*ディレクトリを削除してしまいます。
(残しておくとArduino IDEでエラーが出たため。原因を調べるのが面倒だったので削除しちゃいました)

既存のsrc/hal_selector.hの修正

次にsrc/hal_selector.hを以下のように修正します。

/*! @file
  <pre>
  Copyright (C) 2016-2020 Kyushu Institute of Technology.
  Copyright (C) 2016-2020 Shimane IT Open-Innovation Center.
  This file is distributed under BSD 3-Clause License.
  </pre>
*/

// 以下をごっそり削りhal/hal.hのincludeだけにする
#include "hal/hal.h"

本来はどのHALを使うのかの判定が入っているのですが、今回はごっそり削ってhal/hal.hを決め打ちしました。

src/halディレクトリの作成 & hal.h/hal.c/hal.cppの作成

次にsrc/halディレクトリを作成し、その中にhal.h/hal.c/hal.cppを作成していきます。

hal.h
/*! @file
  https://github.com/mrubyc/mrubyc/blob/master/src/hal_posix/hal.hを参考に実装
  オリジナルのライセンス表記は以下のとおり

  Copyright (C) 2016-2020 Kyushu Institute of Technology.
  Copyright (C) 2016-2020 Shimane IT Open-Innovation Center.
  This file is distributed under BSD 3-Clause License.
*/
#ifndef MRBC_SRC_HAL_H_
#define MRBC_SRC_HAL_H_

#ifdef __cplusplus
extern "C" {
#endif

/***** Macros ***************************************************************/
#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
// You may be able to reduce power consumption if you configure
// MRBC_TICK_UNIT_2_MS or larger.
#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 10
#endif

#if !defined(MRBC_NO_TIMER)    // use hardware 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()    ((void)0)

#endif


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

#ifdef __cplusplus
}
#endif
#endif // ifndef MRBC_SRC_HAL_H_

本来はhal_*()達を実装すべきなのですが、今回はとりあえず動かすことが目的なので((void)0)で済ませてます。
動作確認のためにシリアル出力は行いたいのでhal_write()hal_flush()だけは実装します。

hal.c
/*
  https://github.com/kishima/libmrubycForWioLTEArduino/blob/master/src/hal/hal.cを参考に実装
  オリジナルのライセンス表記: https://github.com/kishima/libmrubycForWioLTEArduino/blob/master/LICENSE
*/
#include "hal.h"

int hal_write(int fd, const void *buf, int nbytes)
{
    char* t = (char*)buf;
    char tbuf[2];
    if(nbytes==1){
        tbuf[0]=*t;
        tbuf[1]='\0';
        hal_write_string(tbuf);
        return nbytes;
    }
    hal_write_string(t);
    return nbytes;
}

int hal_flush(int fd) {
    hal_serial_flush();
}

前述のRubyist Magazineの記事を参考に実装してみました。
hal_write_string()hal_serial_flush()はこのあとhal.cppで実装します。

hal.cpp
/*
  https://github.com/kishima/libmrubycForWioLTEArduino/blob/master/src/hal/hal.cppを参考に実装
  オリジナルのライセンス表記: https://github.com/kishima/libmrubycForWioLTEArduino/blob/master/LICENSE

  Copyright (c) 2018, katsuhiko kageyama All rights reserved.
*/
#include <Arduino.h>

extern "C" void hal_write_string(char* text){
    Serial.write(text);
}

extern "C" void hal_serial_flush(char* text){
    Serial.flush();
}

こちらも最低限の実装となってます。

とりあえずこれで最低限動くはずです。

Arduinoのライブラリにする

次にmruby/cをArduinoから使うために先ほど移植したソースをArduinoのライブラリにします。

ここもほぼ前述のRubyist Magazineの記事の通りに進めます。
Arduinoのライブラリディレクトリ(macの場合~/Documents/Arduino/librariesWindowsの場合はC:¥Users¥ユーザー名¥Documents¥Arduino¥librariesなど)にlibmrubycなどという名前でディレクトリを作成します。 そのディレクトリ内に先ほど修正したソース達とlibrary.propertiesというテキストファイルを格納します。

library.propertiesの作成

以下のような内容にします。

name=mruby/c for Micro:Bit v2
version=0.0.1
author=yamamoto-febc
maintainer=yamamoto-febc
sentence=mruby/c implementation for BBC Micro:Bit v2.
paragraph=
category=Communication
url=https://github.com/yamamoto-febc/libmrubyc
architectures=*
includes=mrubyc.h

修正したmruby/cのsrcディレクトリをコピー

次にmruby/cのsrcディレクトリをコピーします。

最終的に以下のようなファイル構成になっているはずです。

(~/Documents/Arduino/など)/libraries/
    ├── library.properties    // 作成したファイル
    └── src                   // mruby/cのsrcからコピーしたもの

これでArduino IDEを再起動するとライブラリとして認識されているはずです。

mruby/cのコード作成〜バイトコード生成〜Arduinoのスケッチ作成

Arduinoの準備が出来たのでいよいよmruby/cのコードを書いてみます。
今回は以下のようにputsするだけです。

puts "hello mruby/c from BBC micro:bit v2!"

これをhello.rbとして作成します。 そしてこのファイルをmrbcコマンドに渡してmruby/cに渡すバイトコードを生成します。

$ mrbc -E -B code hello.rb

これでカレントディレクトリにhello.cが作成されているはずです。
これを後ほどArduinoスケッチに貼り付けます。

TIPS: mrbcコマンドで-e/-E option no longer neededというエラーが出る

おそらくmruby v2.1.2以降を利用しています。
参考: https://github.com/mruby/mruby/commit/48c473a0c4abc67614a00d282d24d18089908449

mruby v2.1.1を利用するようにしてください。

次にArduinoのスケッチを作成します。

/*
  https://github.com/kishima/libmrubycForWioLTEArduino/blob/master/examples/controlLED/controlLED.inoを参考に実装
  オリジナルのライセンス表記: https://github.com/kishima/libmrubycForWioLTEArduino/blob/master/LICENSE
*/
#include <mrubyc.h>

/* ここに先ほど生成したhello.cの内容を貼り付ける */

void setup() {
  delay(100);
  Serial.begin(9600);
  Serial.println("microbit is ready!");

  mrbc_init(mempool, MEMSIZE);
  if(NULL == mrbc_create_task( code, 0 )){
    Serial.println("mrbc_create_task error");
    return;
  }
  Serial.println("--- run mruby script");
  mrbc_run();
}

void loop() {
  delay(1000);

}

スケッチの中に先ほど生成したhello.cの内容をそのまま貼り付けてください。
私の手元の環境では最終的なスケッチは以下のようになりました。

/*
  https://github.com/kishima/libmrubycForWioLTEArduino/blob/master/examples/controlLED/controlLED.inoを参考に実装
  オリジナルのライセンス表記: https://github.com/kishima/libmrubycForWioLTEArduino/blob/master/LICENSE
*/

#include <mrubyc.h>

#include <stdint.h>
const uint8_t code[] = {
0x52,0x49,0x54,0x45,0x30,0x30,0x30,0x36,0xef,0x0a,0x00,0x00,0x00,0x7a,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x5c,0x30,0x30,
0x30,0x32,0x00,0x00,0x00,0x78,0x00,0x01,0x00,0x04,0x00,0x00,0x00,0x00,0x00,0x0c,
0x10,0x01,0x4f,0x02,0x00,0x2e,0x01,0x00,0x01,0x37,0x01,0x67,0x00,0x00,0x00,0x01,
0x00,0x00,0x24,0x68,0x65,0x6c,0x6c,0x6f,0x20,0x6d,0x72,0x75,0x62,0x79,0x2f,0x63,
0x20,0x66,0x72,0x6f,0x6d,0x20,0x42,0x43,0x43,0x20,0x6d,0x69,0x63,0x72,0x6f,0x3a,
0x62,0x69,0x74,0x20,0x76,0x32,0x21,0x00,0x00,0x00,0x01,0x00,0x04,0x70,0x75,0x74,
0x73,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};


#define MEMSIZE (1024*50)
static uint8_t mempool[MEMSIZE];

void setup() {
    // 省略
}

void loop() {
    // 省略
}

実機で動かしてみる

あとはmicro:bitに転送して動かすだけです。 うまくいけばシリアルモニタに文字が表示されるはずです。

f:id:febc_yamamoto:20210220142008p:plain

TIPS: Error: Illegal bytecodeというエラーが出る

hello.cのコピペミスやmrubyのバージョン違いの可能性があります。
最初mrubyの最新版(2.1.2)を使ってたらこのエラーが出ました。
mruby/c側が(記事執筆時点のmasterでは)mruby v2.1.1のバイトコード(RITE0006)を期待してるんですね。
参考: https://github.com/mrubyc/mrubyc/blob/228645971e0005cc743cfa653c99ff2b78ae02a0/src/load.c#L54-L57

終わりに

ということでmicro:bit v2上でmruby/cを動かしてみました。
このままだとシリアル出力しか出来ないのでもうちょっと色々書く必要がありますが、とりあえずmruby/cを使っていくためのスタートラインには立てたんじゃないかなと思います。

参考にした記事/サイト

↓↓この辺はこの記事書いてる時に見つけました。書き始める前に読みたかった。。。

以上です。