機能の追加: C 言語から書く
はじめに
高級言語であるところの mruby/c は,下図(右)のように,C 言語の上に作られている. 言い換えると,mruby/c で書かれたプログラムは C で書かれたプログラムのラッパーとなっている. その特徴ゆえに,mruby/c でクラスを定義したり,機能を追加する際には, C 言語側も編集が必要になることに注意されたい.

クラス作成の概要
mruby/c のクラスを作成する場合には,以下の手順を踏むことになる.
- main/Kconfig.projbuild を編集し,make menuconfig に項目を追加する.
- main/ 以下に機能を追加するために C 言語 (ESP-IDF) で書かれたプログラムを追加する.
- main/main.c において,後述の作法に従って mruby/c プログラムを include する.
- mrblib/loops/ 以下に mruby/c で書かれたプログラムを置く.ここで新たなクラスを定義する.
- mrblib/loops/ 以下に追加したクラスを用いたメインプログラム (master.rb) を書く.
本演習で用いている iotex-mrubyc-esp32 は以下のようなディレクトリ構造をしており,上記の手順に従って修正を行うことになる.
$ tree -L 4 iotex-mrubyc-esp32/
mrubyc-template-esp32/
├── Makefile
├── components
│ └── mrubyc mruby/c の VM のソースなど
..........(中略)...............
├── main
│ ├ Kconfig.projbuild make menuconfig に出すメニューの定義 ← このファイルを編集
│ └ mrbc_esp32_led.c 機能追加のためプログラム (C 言語) ← このファイルを追加
│ └ mrbc_esp32_led.h 機能追加のためのプログラム (C のヘッダファイル) ← このファイルを追加
..........(中略)...............
│ └── main.c メインプログラム (C 言語) ← このファイルを編集
└── mrblib
├── loops メインプログラム置き場
└── models クラス置き場 ← ここにプログラムを置く.
手順
以下では,しまねソフト研究開発センターのチュートリアル資料 のハンズ・オン - 3(Lチカ:発光ダイオード点滅) に基づいて,LED を点灯させる機能を 1 から実装することにする.
手順 1: main/Kconfig.projbuild の修正
図に示すように,main/Kconfig.projbuild に項目を増やすと,make menuconfig した時のメニューが増える. 新たにクラスを定義するときは,main/Kconfig.projbuild を編集し,そのクラスを使うか否かのメニューを出すようにしておくと良い.
ここで,LED を点灯させるための "config USE_ESP32_LED" を設定する.
$ vi main/Kconfig.projbuild
config USE_ESP32_LED
bool "USR ESP32 LED"
default y
help
use LED function?
.....(略).....
手順 2: C 言語のプログラムの作成
ここでは簡単のために,LED を光らせるためのプログラム (C 言語) を追加する. ファイル名は main/mrbc_esp32_led.c, main/mrbc_esp32_led.h とする.
なお,main/mrbc_esp32_led.c は,C 言語 (ESP-IDF) のサンプル の内容を関数化したものであることを確認しておくこと.
main/mrbc_esp32_led.h
#include "mrubyc.h" void mrbc_mruby_esp32_led_gem_init(struct VM*);
main/mrbc_esp32_led.c
#include "mrbc_esp32_led.h" //自分のヘッダファイルを読み込む
#include "driver/gpio.h"
static void c_gpio_init_output(mrb_vm *vm, mrb_value *v, int argc) {
int pin = GET_INT_ARG(1);
console_printf("init pin %d\n", pin);
gpio_pad_select_gpio(pin);
gpio_set_direction(pin, GPIO_MODE_OUTPUT);
}
static void c_gpio_set_level(mrb_vm *vm, mrb_value *v, int argc){
int pin = GET_INT_ARG(1);
int level = GET_INT_ARG(2);
gpio_set_level(pin, level);
}
void mrbc_mruby_esp32_led_gem_init(struct VM* vm){
//関数を mruby/c から呼べるようにする
mrbc_define_method(0, mrbc_class_object, "gpio_init_output", c_gpio_init_output); // c_gpio_init_output は gpio_init_output という名前で呼べるようにする
mrbc_define_method(0, mrbc_class_object, "gpio_set_level", c_gpio_set_level); // c_gpio_set_level は gpio_set_level という名前で呼べるようにする
}
手順 3: main/main.c の修正
make menuconfig でチェックを入れたクラスを利用可能とするため,C 言語側の メインプログラム (main/main.c) を修正する.
以下では例として,LED クラスを追加する場合に必要となる修正箇所を示す. main/Kconfig.projbuild において "config USE_ESP32_LED" と記述したとすると, make menuconfig でその項目をチェックしたか否かの情報は変数 CONFIG_USE_ESP32_LED に保存されることになる (接頭子として CONFIG_ が付される). ifdef を用いて,チェックが入った場合にのみ読み込むよう設定する.
.........(前略).................
//*********************************************
// ENABLE LIBRARY written by C
//*********************************************
#ifdef CONFIG_USE_ESP32_LED
#include "mrbc_esp32_led.h" C 言語のヘッダファイル (main/mrbc_esp32_led.h) を読み込むための設定.
#endif
.........(中略).................
//*********************************************
// 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_LED
#include "models/led.h" mruby/c の LED クラス (mrblib/models/led.rb) を読み込むための設定. 拡張子は .h に変えておくこと.
#endif
.........(中略).................
/*
!!!! Add your function !!!!
!!!! example: mrbc_mruby_esp32_XXXX_gem_init(0); !!!!
*/
#ifdef CONFIG_USE_ESP32_LED
printf("start LED (C)\n");
mrbc_mruby_esp32_led_gem_init(0); C 言語で書かれた LED 用の初期化関数 (main/mrbc_esp32_led.c に含まれる) を実行
#endif
.........(中略).................
/*
!!!! Add names of your ruby files !!!!
!!!! example: mrbc_create_task( [replace with your task], 0 ); !!!!
*/
#ifdef CONFIG_USE_ESP32_LED
printf("start LED (mruby/c class)\n");
mrbc_create_task( led, 0 ); mruby/c で書かれた LED クラス (mrblib/models/led.rb) を呼び出す設定
#endif
.........(中略).................
}
手順 4: クラスの定義
mrblib/models/ 以下にクラスを定義するための mruby/c プログラムを置く LED を点灯させるための LED クラスは例えば以下のように書ける.
class Led
def initialize(pin)
@pin = pin
gpio_init_output(@pin)
turn_off
end
def turn_on
gpio_set_level(@pin, 1)
puts "turned on"
end
def turn_off
gpio_set_level(@pin, 0)
puts "turned off"
end
end
手順 5: main プログラムの作成
mrblib/loops/master.rb を以下のように作成する.ここでは,LED の点灯と消灯を turn_on, turn_off というメソッドにしている.
led = Led.new(13) while true led.turn_on sleep 1 led.turn_off sleep 1 end
実行
make menuconfig において LED クラスにチェックを入れ,make, make flash すれば LED が点灯することが確かめられるだろう.
$ make menuconfig [*] USR ESP32 LED $ make $ make flash monitor
課題
C 言語 (ESP-IDF) に含まれる内蔵タッチセンサを mruby/c で使えるようにせよ (現在は,内蔵タッチセンサ用のコードは mruby/c for ESP32 ライブラリに含まれていない). 最終的にはタッチセンサに触れた時に LED が点灯するようにせよ.
準備 1: C 言語で書いてみる
まず,C 言語で内蔵タッチセンサを動かしてみる.
$ cd ~ $ cd ~/esp $ cp -r esp-idf/examples/get-started/hello_world ./touchsensor $ cd touchsensor/
メインプログラムを修正.これは ESP-IDF のサンプル (esp-idf/examples/peripherals/touch_pad_read/main/esp32/tp_read_main.c) を元にしている
$ rm main/hello_world_main.c
$ vi main/main.c
// Touch Pad Read Example
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/touch_pad.h"
#define TOUCH_THRESH_NO_USE (0)
#define tp0 0
void app_main(void)
{
uint16_t touch_value;
// Initialize touch pad peripheral.
// The default fsm mode is software trigger mode.
touch_pad_init();
// Set reference voltage for charging/discharging
// In this case, the high reference valtage will be 2.7V - 1V = 1.7V
// The low reference voltage will be 0.5
// The larger the range, the larger the pulse count value.
touch_pad_set_voltage(TOUCH_HVOLT_2V7, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
touch_pad_config(tp0, TOUCH_THRESH_NO_USE);
// Start task to read values sensed by pads
while (1) {
touch_pad_read(tp0, &touch_value);
printf("T%d:[%4d] \n", tp0, touch_value);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
コンパイルと実行.なお,動作確認をするためには, 実習ボードの GPIO 4 に,ジャンパーケーブルをさしておく必要がある (ESP32 マイコン のピン配置を見ると,GPIO4 が T0 (タッチセンサー 0) が割り当てられていることがわかる). 実行しながらジャンパーケーブルを触ると値が変化する.
$ make $ make flash monitor ....(中略)..... I (287) cpu_start: Starting scheduler on PRO CPU. I (0) cpu_start: Starting scheduler on APP CPU. T0:[1095] T0:[1095] T0:[1094] T0:[ 197] 触れる T0:[ 185] 触れる T0:[ 166] 触れる T0:[1082] T0:[1095]
準備 2: mruby/c のライブラリ作成
プロジェクトの準備
$ git clone https://github.com/gfd-dennou-club/iotex-esp32-mrubyc.git touch_sensor $ cd touch_sensor
あとは,前述の手順 1-5 にしたがって,mruby/c から内蔵タッチセンサーを使えるようにすれば良い.
なお,main/mrbc_esp32_tp.c はおおよそ以下のように書けるはずである ("....." の部分は自分で埋めること).
#include "mrbc_esp32_tp.h"
#include "driver/touch_pad.h"
#define TOUCH_THRESH_NO_USE (0)
static void c_tp_init(mrb_vm *vm, mrb_value *v, int argc) {
int pin = GET_INT_ARG(1); //ピン番号を引数に
...... //初期化
......
......
}
static void c_tp_read(mrb_vm *vm, mrb_value *v, int argc) {
uint16_t touch_value;
int pin = GET_INT_ARG(1); //ピン番号を引数に
...... //touch_pad_read 関数を使って値を読む
SET_INT_RETURN(touch_value); //戻り値
}
void mrbc_mruby_esp32_tp_gem_init(struct VM* vm){
//関数を mruby/c から呼べるようにする
// c_tp_init を tp_init, c_tp_read を tp_read として mruby/c から呼べるようにする
mrbc_define_method( ....... );
mrbc_define_method( ....... );
}
参考
- しまねソフト研究開発センターのチュートリアル資料 のハンズ・オン - 3(Lチカ:発光ダイオード点滅)
- 上記資料では gpio_pad_select_gpio(pin); が抜けており,最近の ESP-IDF 環境では LED が点灯しない.
