機能の追加: 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 が点灯しない.