mruby/c のクラスの作成方法
はじめに
高級言語であるところの mruby/c は,下図(右)のように,C 言語の上に作られている. 言い換えると,mruby/c で書かれたプログラムは C で書かれたプログラムのラッパーとなっている. その特徴ゆえに,mruby/c でクラスを定義するためには, C 言語側も若干の編集が必要になることに注意されたい.
クラス作成の概要
mruby/c のクラス定義を別ファイルで行う場合には,以下の手順を踏むことになる.
- main/Kconfig.projbuild を編集し,make menuconfig に項目を追加する.
- main/main.c において,後述の作法に従って mruby/c プログラムを include する.
- mrblib/models/ 以下に mruby/c で書かれたプログラムを置く.ここで新たなクラスを定義する.
- mrblib/loops/ 以下に追加したクラスを用いたメインプログラム (master.rb) を書く.
本演習で用いている iotex-mrubyc-esp32 は以下のようなディレクトリ構造をしており,上記の手順に従って 4 箇所を修正することになる.
$ tree -L 4 iotex-mrubyc-esp32/ mrubyc-template-esp32/ ├── Makefile ├── components │ └── mrubyc mruby/c の VM のソースなど ..........(中略)............... ├── main │ ├ Kconfig.projbuild make menuconfig に出すメニューの定義 ← このファイルを編集 │ └ mrbc_esp32_gpio.c ラッパー │ └ mrbc_esp32_gpih ..........(中略)............... │ └── main.c メインプログラム (C 言語) ← このファイルを編集 └── mrblib ├── loops メインプログラム置き場 └── models クラス置き場 ← ここにプログラムを置く.
練習問題
mruby/c のI2C の課題 で作成した LCD と RTC を用いたプログラムから,LCD クラスを作成する. なお,リアルタイム用のクラスは課題 (後述) とする.
$ git clone https://github.com/gfd-dennou-club/iotex-esp32-mrubyc.git mrubyc-11-class $ cd mrubyc-11-class
手順 0: メインファイル内でクラス定義する
いきなりクラスの定義を別ファイルで行うのは大変なので,まずはメインファイル内で LCD クラスを定義してみる. mruby/c のI2C の課題 で作成した LCD と RTC を用いたプログラムから LCD 部分を抜き出し, LCD クラスを作ってみると,以下のようになる.主な修正点は以下である.
- コンストラクタの定義と利用 (7-9 行目, 65 行目)
- 各メソッドの引数の変更.引数から i2c を削る (コンストラクタを定義したので不要に)
メソッド名から lcd_ を削除.利用の際は, lcd_init でなく,lcd.init となる.
$ vi mrblib/loops/master.rb 1 # coding: utf-8 2 3 class LCD 4 5 ADDRESS = 0x3e 6 7 def initialize(i2c) 8 @i2c = i2c 9 end 10 11 def cmd(cmd) 12 @i2c.write(LCD::ADDRESS, [0x00, cmd]) 13 end 14 15 def data(data) 16 @i2c.write(LCD::ADDRESS, [0x40, data]) 17 end 18 19 def clear() 20 cmd( 0x01) 21 sleep 0.1 22 end 23 24 def home0() 25 cmd( 0x02) 26 sleep 0.1 27 end 28 29 def home1() 30 cmd( 0x40|0x80) 31 sleep 0.1 32 end 33 34 def cursor(x, y) 35 pos = (x + y * 0x40) | 0x80 36 cmd( pos) 37 sleep 0.1 38 end 39 40 def init() 41 sleep 0.2 42 [0x38, 0x39, 0x14, 0x70, 0x56, 0x6c].each do |cmd| 43 cmd( cmd) 44 end 45 sleep(0.3) 46 [0x38, 0x0c, 0x01].each do |cmd| 47 cmd( cmd) 48 end 49 sleep(0.1) 50 end 51 52 def print(data) 53 data.length.times do |n| 54 data( data[n].ord) 55 end 56 end 57 58 end 59 60 61 #I2C 初期化 62 i2c = I2C.new(22, 21) 63 64 # LCD 初期化 65 lcd = LCD.new(i2c) 66 lcd.init 67 68 # LCD に "Hello World" 表示 69 lcd.cursor(1, 0) 70 lcd.print( "Hello!x")
実行して,LCD に文字が表示されるか確認する.
$ make menuconfig [*] USR ESP32 I2C $ make $ make flash monitor
手順 1: main/Kconfig.projbuild の修正
図に示すように,main/Kconfig.projbuild に項目を増やすと,make menuconfig した時のメニューが増える. 新たにクラスを定義するときは,main/Kconfig.projbuild を編集し,そのクラスを使うか否かのメニューを出すようにしておくと良い.
今回は LCD 用に USE_ESP32_I2C_LCD というエントリを新たに追加することにする. これは I2C 接続なので,depends on で USE_ESP32_I2C に依存させる.
$ vi main/Kconfig.projbuild ...(中略)... config USE_ESP32_I2C bool "USR ESP32 I2C" default y help use I2C function? config USE_ESP32_I2C_LCD bool "PERIPHERAL: LCD Display" depends on USE_ESP32_I2C default n help use My LCD Display? ...(後略)...
手順 2: main/main.c の修正
make menuconfig でチェックを入れたクラスを利用可能とするため,C 言語側の メインプログラム (main/main.c) を修正する.なお, main/Kconfig.projbuild において "config USE_ESP32_I2C_LCD" と記述したので,make menuconfig でその項目をチェックしたか否かの情報は, 変数 CONFIG_USE_ESP32_I2C_LCD に保存される (接頭子として CONFIG_ が付される). ifdef を用いて,チェックが入った場合にのみ読み込むよう設定する.
$ vi main/main.c ...........(前略)........... //********************************************* // ENABLE CLASSES and MASTER files written by mruby/c // // #include "models/[replace with your file].h" // #include "loops/[replace with your file].h" //********************************************* ... #ifdef CONFIG_USE_ESP32_I2C_LCD #include "models/lcd.h" // システムが自動生成する mruby/c の LCD クラス (mrblib/models/lcd.rb) のヘッダファイルを読み込むための設定. 拡張子は .h に変えておくこと #endif ...........(中略)........... /* !!!! Add names of your ruby files !!!! !!!! example: mrbc_create_task( [replace with your task], 0 ); !!!! */ ... #ifdef CONFIG_USE_ESP32_I2C_LCD printf("start My LCD (mruby/c class)\n"); mrbc_create_task( lcd, 0 ); // mruby/c で書かれた LCD クラス (mrblib/models/lcd.rb) を呼び出す設定 #endif ...........(後略)...........
手順 3: クラスの定義ファイル (mruby/c) の作成
「手順 0」で作ったメインプログラムからクラス部分を別ファイル (mrblib/models/lcd.rb) に保存する.
$ vi mrblib/models/lcd.rb 1 # coding: utf-8 2 class LCD 3 4 ADDRESS = 0x3e 5 6 def initialize(i2c) 7 @i2c = i2c 8 end 9 10 def cmd(cmd) 11 @i2c.write(LCD::ADDRESS, [0x00, cmd]) 12 end 13 14 def data(data) 15 @i2c.write(LCD::ADDRESS, [0x40, data]) 16 end 17 18 def clear() 19 cmd(0x01) 20 sleep 0.1 21 end 22 23 def home0() 24 cmd(0x02) 25 sleep 0.1 26 end 27 28 def home1() 29 cmd(0x40|0x80) 30 sleep 0.1 31 end 32 33 def cursor(x, y) 34 pos = (x + y * 0x40) | 0x80 35 cmd(pos) 36 sleep 0.1 37 end 38 39 def init() 40 sleep 0.2 41 [0x38, 0x39, 0x14, 0x70, 0x56, 0x6c].each do |cmd| 42 cmd(cmd) 43 end 44 sleep(0.3) 45 [0x38, 0x0c, 0x01].each do |cmd| 46 cmd(cmd) 47 end 48 sleep(0.1) 49 end 50 51 def print(data) 52 data.length.times do |n| 53 data(data[n].ord) 54 end 55 end 56 57 end
手順 4: メインプログラム (mruby/c) の作成
「手順 0」で作ったメインプログラムからクラス部分を削除する.
$ vi mrblib/loops/master.rb 1 # coding: utf-8 2 3 #I2C 初期化 4 i2c = I2C.new(22, 21) 5 6 # LCD 初期化 7 lcd = LCD.new(i2c) 8 lcd.init 9 10 # LCD に "Hello World" 表示 11 lcd.cursor(1, 0) 12 lcd.print( "Hello!") 13 14 lcd.home1() 15 lcd.print( "from ESP")
実行
make menuconfig で作成した LCD クラスを include し, マイコンにプログラムを書き込む. LCD に文字が表示されることを確認すること.
$ make menuconfig [*] USR ESP32 I2C [*] My LCD $ make $ make flash monitor
課題
mruby/c のI2C の課題 で作成した LCD と RTC を用いたプログラム を元にして, RTC2 クラス (mrblib/models/rtc2.rb) を定義し,動作させよ.