本記事の目的
本記事は、日本Androidの会 秋葉原支部ロボット部の2020年アドベント・カレンダーの22日目の記事として書いたものです。
日本Androidの会 秋葉原支部ロボット部とメンバーが何人か重複している勉強会に、低レベル勉強会inTokyoというものがあります。低レベルとはいいましても出席者の技術レベルが低いわけでは決してありませんで、毎回組み込みのコアな話題を扱ったり実際に小型シングル・ボード・コンピューターを使用してのハンズオンを行ったり等のIT分野のハードウェアに近い低レイヤーに関する技術勉強会を開催しています。
本記事は、そんな低レベル勉強会inTokyoが2020年7月から8月にかけた勉強会で中国Sipeed社製の「Lichee Nano」という小型シングル・ボード・コンピューターに関してのハンズオンを開催しましたので、その時に私が行ったことの記録です。
Lichee Nanoについて
Lichee Nanoは、SoCが中国Allwinner社のF1C100sで、CPUがARM926EJ-S、クロック周波数は最大900MHz、32ビットのコンピューターです。SoCのパッケージ内に32MBのDDR SDRAMをマルチ・チップ・モジュール(最近はSIP:System In Packageというらしいですが。)で搭載しています。OSはLinuxなのですが、カーネルのバージョンは2.xと少々古いです。価格が¥1k前後ととにかく安価で、32MBものメモリーを搭載していることから同じSipeed社製のLichee Zeroと合わせてmrubyに適していると言えるでしょう。
クロス・コンパイル環境の作成
さっそく、Lichee Nanoでもmrubyを動かすことにトライしてみることにしました。mrubyはすでにバージョン3.0のプリビュー版がリリースされていますが、本記事ではバージョン2.xを取り扱います。まずはクロス・コンパイル環境の作成です。PCにDebian 10.5 Busterをインストールしました。(注:本記事執筆時点では10.6が最新です。)DebianのPCへのインストール方法については他の方の記事を検索してご参照ください。Windows 10上のWSL2を使ってもよいかもしれません。
DebianのパッケージとしてARM CPUのクロス・コンパイルのためのものがありますのでインストールします:
$ sudo apt install crossbuild-essential-armel crossbuild-essential-armhf
ホーム・ディレクトリー内に適当な名前でクロス・コンパイルのためのディレクトリーを作成します。今回はその名前をlow_level_studyとしました:
$ mkdir ~/low_level_study
$ cd ~/low_level_study
gitをまだインストールしていない場合は、インストールします:
$ sudo apt-get install git
本記事執筆時のmrubyの安定板のバージョンは2.1.2ですので、それを以下のURLよりダウンロードします:
https://github.com/mruby/mruby/archive/2.1.2.zip
ダウンロードしたディレクトリーよりファイル mruby-2.1.2.zip ファイルを~/low_level_sudyへと移し、解凍のためのunzipコマンドをまだインストールしていないようでしたらインストールします:
$ sudo apt-get install unzip
~/low_level_studyディレクトリー中にて mruby-2.1.2.zip ファイルを解凍し、解凍した mruby-2.1.2 ディレクトリー中のmruby-2.1.2ディレクトリーへと移ります:
$ unzip mruby-2.1.2.zip
$ cd ./mruby-2.1.2
mrubyのビルドにはRuby言語をインストールする必要があるため、インストールしす。本記事ではその方法については詳しくは触れませんが、「DebianにRubyを入れる方法」などを参考にしてください。なお、この記事ではcurlとwgetパッケージを事前にインストールしておく必要がありますので、事前にインストールしておいてください:
$ sudo apt-get install curl wget
また、rbenv installコマンドを使用する際は、最新のRubyの安定版のバージョンは現時点で2.7.2ですので、以下の様にしてください:
$ rbenv install 2.7.2
なお、最後に rbenv globalコマンドを打つのを忘れないでください:
$ rbenv global 2.7.2
~/low_level_study/mruby-2.1.2 のディレクトリーに居ることを確認してからmrubyのビルドを行いましょう:
$ cd ~/low_level_study/mruby-2.1.2
~/low_level_study/mruby-2.1.2/build_config ディレクトリー中に以下の様なファイルをlichee_nano.rbという名前で作成してください:
MRuby::Build.new do |conf|
# load specific toolchain settings
# Gets set by the VS command prompts.
if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
toolchain :visualcpp
else
toolchain :gcc
end
# Turn on `enable_debug` for better debugging
# enable_debug
# Use mrbgems
# conf.gem 'examples/mrbgems/ruby_extension_example'
# conf.gem 'examples/mrbgems/c_extension_example' do |g|
# g.cc.flags << '-g' # append cflags in this gem
# end
# conf.gem 'examples/mrbgems/c_and_ruby_extension_example'
# conf.gem :core => 'mruby-eval'
# conf.gem :mgem => 'mruby-onig-regexp'
# conf.gem :github => 'mattn/mruby-onig-regexp'
# conf.gem :git => 'git@github.com:mattn/mruby-onig-regexp.git', :branch => 'master', :options => '-v'
# include the default GEMs
conf.gembox 'default'
# C compiler settings
# conf.cc do |cc|
# cc.command = ENV['CC'] || 'gcc'
# cc.flags = [ENV['CFLAGS'] || %w()]
# cc.include_paths = ["#{root}/include"]
# cc.defines = %w()
# cc.option_include_path = %q[-I"%s"]
# cc.option_define = '-D%s'
# cc.compile_options = %Q[%{flags} -MMD -o "%{outfile}" -c "%{infile}"]
# end
# mrbc settings
# conf.mrbc do |mrbc|
# mrbc.compile_options = "-g -B%{funcname} -o-" # The -g option is required for line numbers
# end
# Linker settings
# conf.linker do |linker|
# linker.command = ENV['LD'] || 'gcc'
# linker.flags = [ENV['LDFLAGS'] || []]
# linker.flags_before_libraries = []
# linker.libraries = %w()
# linker.flags_after_libraries = []
# linker.library_paths = []
# linker.option_library = '-l%s'
# linker.option_library_path = '-L%s'
# linker.link_options = "%{flags} -o "%{outfile}" %{objs} %{libs}"
# end
# Archiver settings
# conf.archiver do |archiver|
# archiver.command = ENV['AR'] || 'ar'
# archiver.archive_options = 'rs "%{outfile}" %{objs}'
# end
# Parser generator settings
# conf.yacc do |yacc|
# yacc.command = ENV['YACC'] || 'bison'
# yacc.compile_options = %q[-o "%{outfile}" "%{infile}"]
# end
# gperf settings
# conf.gperf do |gperf|
# gperf.command = 'gperf'
# gperf.compile_options = %q[-L ANSI-C -C -p -j1 -i 1 -g -o -t -N mrb_reserved_word -k"1,3,$" "%{infile}" > "%{outfile}"]
# end
# file extensions
# conf.exts do |exts|
# exts.object = '.o'
# exts.executable = '' # '.exe' if Windows
# exts.library = '.a'
# end
# file separetor
# conf.file_separator = '/'
# bintest
# conf.enable_bintest
end
MRuby::Build.new('host-debug') do |conf|
# load specific toolchain settings
# Gets set by the VS command prompts.
if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
toolchain :visualcpp
else
toolchain :gcc
end
enable_debug
# include the default GEMs
conf.gembox 'default'
# C compiler settings
conf.cc.defines = %w(MRB_ENABLE_DEBUG_HOOK)
# Generate mruby debugger command (require mruby-eval)
conf.gem :core => "mruby-bin-debugger"
# bintest
# conf.enable_bintest
end
MRuby::Build.new('test') do |conf|
# Gets set by the VS command prompts.
if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
toolchain :visualcpp
else
toolchain :gcc
end
enable_debug
conf.enable_bintest
conf.enable_test
conf.gembox 'default'
end
#MRuby::Build.new('bench') do |conf|
# # Gets set by the VS command prompts.
# if ENV['VisualStudioVersion'] || ENV['VSINSTALLDIR']
# toolchain :visualcpp
# else
# toolchain :gcc
# conf.cc.flags << '-O3'
# end
#
# conf.gembox 'default'
#end
# Define cross build settings
#MRuby::CrossBuild.new('32bit') do |conf|
# toolchain :arm-linux-gnueabi-gcc
#
# conf.cc.flags << "-m32"
# conf.linker.flags << "-m32"
#
# conf.build_mrbtest_lib_only
#
# conf.gem 'examples/mrbgems/c_and_ruby_extension_example'
#
# conf.test_runner.command = 'env'
# end
# mruby クロスビルド for Lichee Pi nano
MRuby::CrossBuild.new('licheepinano') do |conf|
toolchain :gcc
conf.cc {|cc|
cc.command = "/usr/bin/arm-linux-gnueabi-gcc"
cc.flags << %w(
-march=armv5te
-mtune=arm926ej-s
-mthumb
-mfpu=vfpv4-d16
-mfloat-abi=softfp
)
conf.gem :core => 'mruby-print'
}
end
作成が終わりましたら、Rubyのrakeコマンドでmrubyをビルドします:
$ cd ~/low_level_study/mruby-2.1.2
$ rake MRUBY_CONFIG=./build_config/lichee_nano.rb
DebianをインストールしたPC上で動くmrubyの環境およびLichee Nano用のライブラリー
~/low_level_study/mruby-2.1.2/build/licheepinano/lib/libmruby.aがビルドされます。
mrubyのソースコードを書く
次に、~/low_level_study/mruby-2.1.2 ディレクトリー中に以下の様に動かすたった一行のmrubyのプログラムをhello_world.rb という名前で作成してください:
puts "Hello, mruby World!\n"
先ほどビルドしたDebian上で動くmrbcコマンドでこの一行のソースコードをmrubyのバイトコードにコンパイルし、さらにC言語の配列形式に変換します:
$ ./bin/mrbc -B hello_world hello_world.rb
コンパイルされたC言語の配列形式を実際に見てみま
しょう。hello_world.cというファイルの中身を
適当なエディターで見てください。以下の様になって
いるはずです:
#include <stdint.h>
#ifdef __cplusplus
extern const uint8_t hello_world[];
#endif
const uint8_t
#if defined __GNUC__
__attribute__((aligned(4)))
#elif defined _MSC_VER
__declspec(align(4))
#endif
hello_world[] = {
0x52,0x49,0x54,0x45,0x30,0x30,0x30,0x37,0x78,0x46,0x00,0x00,0x00,0x6a,0x4d,0x41,
0x54,0x5a,0x30,0x30,0x30,0x30,0x49,0x52,0x45,0x50,0x00,0x00,0x00,0x4c,0x30,0x30,
0x30,0x32,0x00,0x00,0x00,0x68,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,0x14,0x48,0x65,0x6c,0x6c,0x6f,0x2c,0x20,0x6d,0x72,0x75,0x62,0x79,0x20,
0x57,0x6f,0x72,0x6c,0x64,0x21,0x0a,0x00,0x00,0x00,0x01,0x00,0x04,0x70,0x75,0x74,
0x73,0x00,0x45,0x4e,0x44,0x00,0x00,0x00,0x00,0x08,
};
グルー・ファイルの作成
次に~/low_level_study/mruby-2.1.2 ディレクトリー中に
main.cという名前で以下の様なファイルを作成して
ください:
#include <stdio.h>
#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/irep.h>
extern const uint8_t hello_world[];
int main(int argc, char *argv[]){
mrb_state *mrb = mrb_open();
//mrb_load_string(mrb,"puts 'Hello, mruby World!!\n'");
mrb_load_irep(mrb, hello_world);
mrb_close(mrb);
return 0;
}
mrubyのコンパイル
さあて、いよいとLichee Nano上で動く実行ファイルへとコンパイルします:
$ /usr/bin/arm-linux-gnueabi-gcc -static -I./include -o main main.c hello_world.c ./build/licheepinano/lib/libmruby.a -lm
~/low_level_study/mruby-2.1.2 ディレクトリー中に生成されたmainというファイルがARM CPUで動くELFの実行ファイルです。
PCとLichee Nanoとを接続
ここでは、問題なく実行ファイルへとコンパイルできたものとします。mainファイルをマイクロSDメモリー・カードのルート・ディレクトリー中にPCからコピーして、マイクロSDメモリー・カードをLichee Nanoへ挿し、実行させましょう。マイクロSDメモリー・カードは、キオクシア製をお薦めします。実行させるには、Lichee Nanoに搭載されているSoC(F1C100s)内のUARTにシリアル回線で接続する必要があります。Lichee NanoはUARTのシリアル⇔USBの変換のためのICを搭載していませんので、+3.3Vのシリアル回線とPCとを接続する必要がありますので注意してください。私はFTDI社製の変換ICと搭載したケーブルがトラブルが少ないのでこれを使いました:
https://akizukidenshi.com/catalog/g/gM-05840/
仮想COMポートでの通信を行うためのドライバーをインストールするのを忘れないでください:
https://www.ftdichip.com/Drivers/VCP.htm
また、シリアル⇔USB変換ケーブルとLichee Nano間の接続のためにこれも使いました:
https://www.amazon.co.jp/gp/product/B07MR1SVVR/ref=ppx_yo_dt_b_asin_title_o01_s00?ie=UTF8&psc=1
半田付け作業かワニ口クリップ等も必要になります。必ずシリアル⇔USB変換ケーブルのTXDをLichee NanoのU0:Rxに、RXDをU0:Txに接続してください。シリアル⇔USB変換ケーブルのグラウンド(GND)とLichee NanoのGNDを接続するのを忘れないでください。シリアル通信のボー・レート(通信速度)は115.2kbps(115200bps)です。Lichee Nanoが動作するための電源は、シリアル⇔USB変換ケーブルが+5Vを供給するならばそれはそれをLichee Nanoの5Vに接続すれば動きますし、USBケーブルから電源を供給することもできます。シリアル通信のソフトウェアですが、私はWindwos 10上で動くTeraTermを使用しました。
mrubyの実行
Lichee Nanoにシリアル通信のための環境をすべて設定してLichee Nanoに電源を投入すると、ブートが開始されログインのためのアカウント名とパスワードがシリアル通信ソフト内で要求されます。ここで:
アカウント名: root
パスワード : licheepi
です。
次にマイクロSDメモリー・カードをマウントするためのディレクトリーを作成します:
# mkdir /media/sd
そして、マイクロSDメモリー・カードをマウントします:
# mount -t vfat /dev/mmcblk0p1 /media/sd
マイクロSDメモリー・カードのルート・ディレクトリー(注:Lichee Nanoのルート・ディレクトリーとは違います。)に飛んで、main実行ファイルを動作させてみます:
# cd /media/sd
# ./main
Hello, mruby World!
というメッセージがシリアル通信ソフトに表示されれば成功です。
謝辞
本記事の作成には、以下のWebサイトを参考にさせていただきました。記事の執筆者の方々にお礼を申し上げます。また、本記事中にてリンクを張らせていただきましたWebサイトの作成者の方々にも謝意を表します。さらに @_shuujii さん、誤りのご指摘ありがとうございました。
参考Webサイト
matoken's meme
超小型SBC 『LicheePi Nano』を触る!その1 ~セットアップ編~
WSLでSDカードをマウントする
修正履歴
2020/12/27:本記事のタグに、組み込み IoTを追加しました。
2020/12/27:本記事をmruby バージョン2.1.2対応とするために、mrubyを取得する方法を変更しました。
2020/12/27:ファイル名および関数名のスペリングのミスを修正しました。