しまねソフト研究開発センター(略称 ITOC)にいます、東です。
mruby/cをSTマイクロエレクトロニクス社製の32bitマイコンSTM32で動かす記事、
今回はその第5回、GPIOの拡張と、複数プログラムの動作を行います。
目標
- オンボードのプッシュスイッチ入力を、mruby/c で読み取りができるようにする
- 外部に追加したLEDを、mruby/c から点滅できるようにする
- 複数のプログラムを、協調して動かすことができるようにする
準備するもの
- LED
- 固定抵抗器 (330Ω程度)
- ブレッドボードなど
LEDと固定抵抗器は、いずれもリードタイプを用意します。
オンボードのプッシュスイッチ入力を、mruby/c で読み取りができるようにする
ピンアサインの確認
Nucleoボードには、ユーザー入力用のプッシュスイッチが用意してあります。IDEのピンアサイン画面、もしくは回路図でピンアサイン(接続先)を確認すると、PC13 に接続されていることが分かります。
mruby/c メソッドの作成
スイッチの入力を読み取るための、ユーザー拡張メソッドを作成します。これは、C言語で作成します。
仕様は、以下の通りとします。
- sw_read() という名前にする
- スイッチが押されていると1を、押されていないと0を返す
手順
CubeIDEの左ペイン Project Explorer から、Core > mrubyc とたどり、start_mrubyc.c
をダブルクリックして開きます。
start_mrubyc() 関数の、コメント「ユーザ定義メソッドの登録」 あたりへ、以下通り muby/cから使うメソッド名と、実行するC関数を対応づける宣言を書きます。
mrbc_define_method(0, 0, "sw_read", c_sw_read);
次に、実際にそのメソッドを実行するC関数を書きます。
/*! オンボードSW 読み取りメソッドの実装
*/
static void c_sw_read(mrbc_vm *vm, mrbc_value v[], int argc)
{
switch( HAL_GPIO_ReadPin( GPIOC, GPIO_PIN_13 ) ) {
case GPIO_PIN_SET:
SET_INT_RETURN( 0 );
break;
case GPIO_PIN_RESET:
SET_INT_RETURN( 1 );
break;
}
}
ファイルの先頭付近に、プロトタイプ宣言も必要です。
static void c_sw_read(mrbc_vm *vm, mrbc_value v[], int argc);
start_mruby.c ファイル全体はこちら
#include "main.h"
#include "../mrubyc_src/mrubyc.h"
static void c_led_write(mrbc_vm *vm, mrbc_value v[], int argc);
static void c_sw_read(mrbc_vm *vm, mrbc_value v[], int argc);
/* mruby/c プログラムが使うワークメモリの確保 */
#define MRBC_MEMORY_SIZE (1024*30)
static uint8_t memory_pool[MRBC_MEMORY_SIZE];
/*! mruby/c プログラムの実行開始
*/
void start_mrubyc( void )
{
mrbc_init(memory_pool, MRBC_MEMORY_SIZE);
// ユーザ定義メソッドの登録
mrbc_define_method(0, 0, "led_write", c_led_write);
mrbc_define_method(0, 0, "sw_read", c_sw_read);
// タスクの登録
extern const uint8_t task1[];
mrbc_create_task( task1, 0 );
// 実行開始
mrbc_run();
}
/*! オンボードLED ON/OFF メソッドの実装
*/
static void c_led_write(mrbc_vm *vm, mrbc_value v[], int argc)
{
int on_off = GET_INT_ARG(1);
HAL_GPIO_WritePin( GPIOA, GPIO_PIN_5, on_off );
}
/*! オンボードSW 読み取りメソッドの実装
*/
static void c_sw_read(mrbc_vm *vm, mrbc_value v[], int argc)
{
switch( HAL_GPIO_ReadPin( GPIOC, GPIO_PIN_13 ) ) {
case GPIO_PIN_SET:
SET_INT_RETURN( 0 );
break;
case GPIO_PIN_RESET:
SET_INT_RETURN( 1 );
break;
}
}
/*! HAL
*/
int hal_write(int fd, const void *buf, int nbytes)
{
extern UART_HandleTypeDef huart2;
HAL_UART_Transmit( &huart2, buf, nbytes, HAL_MAX_DELAY );
return nbytes;
}
int _write(int file, char *ptr, int len)
{
return hal_write(file, ptr, len);
}
int hal_flush(int fd)
{
return 0;
}
void hal_abort(const char *s)
{
}
テストプログラムの作成
テスト用 mruby/c プログラムを作ります。簡単に、スイッチを押したらオンボードLEDが点灯するだけのプログラムとします。
手順
Project Explorer から、Core > mrubyc とたどり、task1.rb
をダブルクリックして開きます。
以下の通り変更します。
while true
sw = sw_read()
led_write( sw )
end
実行
メニューから、Run > Run を選んでターゲットへ書き込みます。
正常に動作していれば、青色のプッシュスイッチを押すと LED が点灯し、はなすと消えるのが確認できます。
外部に追加したLEDを、mruby/c から点滅できるようにする
次に、追加部品を使って、外部拡張端子に接続したLEDを制御してみます。
ピンアサインの決定
まずは、接続するピンを決めます。ボードを眺めると、右下のコネクタ下から3番目に D2 と印刷された端子が、PWM等の他の機能が被ってなさそうなので、これに決めます。
回路図を見て、D2 はマイコンのGPIO番号 PA10 であることを見つけます。
外部LEDの取付け
以下の写真と回路図を参考に、LEDを取り付けます。
PA10ピンをGPIO出力に設定
Project Explorer から、(プロジェクト名).ioc (ここでは、tuto01.ioc) をダブルクリックし、コンフィグレーション画面を開きます。
Pinout view 画面でICのPA10端子をクリックするとコンテキストメニューが開くので、GPIO_Output
を選びます。
メニューから、File > Save で保存すると、コード生成するかというダイアログが開きますので、[Yes] をクリックします。
mruby/c メソッドの作成
PA10ピンを制御するための、ユーザー拡張メソッドを作成します。
今回はLEDをつなぎますが、実際は汎用出力なので、gpio_pa10_write() という名前のメソッドにします。
手順
Project Explorer から、Core > mrubyc とたどり、start_mruby.c
をダブルクリックして開きます。
start_mrubyc() 関数の、コメント「ユーザ定義メソッドの登録」 あたりへ、以下通り muby/cから使うメソッド名と、実行するC関数を対応づける宣言を書きます。
mrbc_define_method(0, 0, "gpio_pa10_write", c_gpio_pa10_write);
次に、実際にそのメソッドを実行するC関数を書きます。
/*! GPIO PA10 write メソッドの実装
*/
static void c_gpio_pa10_write(mrbc_vm *vm, mrbc_value v[], int argc)
{
int on_off = GET_INT_ARG(1);
HAL_GPIO_WritePin( GPIOA, GPIO_PIN_10, on_off );
}
ファイルの先頭付近に、プロトタイプ宣言も必要です。
static void c_gpio_pa10_write(mrbc_vm *vm, mrbc_value v[], int argc);
テストプログラムの作成
テスト用 mruby/c プログラムを作ります。やっぱり「えるちか」ですね。
手順
Project Explorer から、Core > mrubyc とたどり、task1.rb
をダブルクリックして開きます。
以下の通り変更します。
while true
gpio_pa10_write( 1 )
sleep 1
gpio_pa10_write( 0 )
sleep 1
end
実行
メニューから、Run > Run を選んでターゲットへ書き込みます。
無事にLEDが点滅したでしょうか。
複数のプログラムを、協調して動かすことができるようにする
mruby/c の特徴の一つとして、OSなしで複数のプログラムを同時に(プリエンプティブに)動作させる機能があります。これを試してみましょう。
仕様
- 通常時、外部LEDはゆっくり(1秒ごと)点滅し、オンボードLEDは消灯している
- プッシュスイッチが押されている間、外部LEDは早く(0.2秒ごと)点滅し、オンボードLEDは点灯する
これを、2つのプログラムに分けます。
プログラム1: スイッチ監視とオンボードLED担当
プログラム2: 外部LEDのエルチカ担当
mruby/c では、グローバル変数がプログラム間で共有されるので、今回はグローバル変数をプロセス間通信に使います。
手順
task1.rb の変更
Project Explorer から、Core > mrubyc とたどり、task1.rb
をダブルクリックして開きます。
以下の通り変更します。
$cycle_delay = 1
while true
sw = sw_read()
led_write( sw )
if sw == 1
$cycle_delay = 0.2
else
$cycle_delay = 1
end
end
task2.rb の新規作成
Project Explorer から、Core > mrubyc とたどり、mrubyc の上で右クリックし、New > File を選びます。
Create New File ダイアログで、File name 欄へ、task2.rb と入力し、[Finish] をクリックし、ファイルを作ります。
以下の内容を入力します。
sleep 1 # task1で、$cycle_delay へ代入される事を確実にするため
while true
gpio_pa10_write( 1 )
sleep $cycle_delay
gpio_pa10_write( 0 )
sleep $cycle_delay
end
task2 の実行を登録
Project Explorer から、Core > mrubyc とたどり、start_mrubyc.c をダブルクリックして開きます。
start_mrubyc() 関数の「タスクの登録」 コメント付近へ、task2 の実行を登録します。
// タスクの登録
extern const uint8_t task1[];
mrbc_create_task( task1, 0 );
extern const uint8_t task2[]; // 追加
mrbc_create_task( task2, 0 ); // 追加
ビルド
いつも通り、メニューから、Run > Run もしくは、Project > Build Project を選んでビルドしてください。今回は恐らく以下のようにtask2のシンボルが見つからないと、ビルドエラーが出ると思います。
undefined reference to `task2'
そのまま、もう一度ビルドしてみてください。今度は正常に終了すると思います。
これは、task2.c が作られるタイミングの問題で、1度目のビルド時はまだ、task2.c が新規作成されておらず、ビルド終了後に IDE が、task2.c ができていることを認識するので、2度目は成功するという仕組みです。
もし、2度目以降もtask2のシンボルが見つからないとエラーが出るようなら、mruby フォルダに task2.c ができていることを確認し、それを手動で IDE に登録してください。
実行
おわりに
ファイル全体は、github リポジトリにありますので、そちらをご覧ください。
今回は、オンボード以外のデバイスの追加と、それを mruby/c から使うための拡張関数をつくりました。また、複数のmruby/cプログラムを動かして、それらを協調動作させました。
このチュートリアルで説明した一通りの方法は、最初に説明したとおり、mruby/c を C言語の既存ソースコード、もしくは自動生成コードとともにビルドし、ターゲットに書き込む方法です。見方を変えれば、すでにC言語だけで書かれた既存機器のファームウェアを mruby/c で機能拡張するという場合に、ここで説明した方法は適しているとも言えます。
多機能、高機能、もしくはコンフィギャラブルな機器が求められる場合において、DSLとして mruby/c を採用するのは結構良いアイデアだと思います。
今回はGPIOだけを扱いましたが、その他のペリフェラルをクラス化するためのガイドラインをmruby, mruby/c 共通で定めています。
https://github.com/mruby/microcontroller-peripheral-interface-guide
別記事で、これらの API の移植を行う記事も書きますので、ご期待ください。
5回に分けて書いたポーティング記事も、今回で最後です。おつきあい、ありがとうございました。