1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【チュートリアル】mruby/cをSTM32マイコンで動かす Chapter05: GPIOの拡張と、複数プログラムの動作

Last updated at Posted at 2024-06-14

しまねソフト研究開発センター(略称 ITOC)にいます、東です。

mruby/cをSTマイクロエレクトロニクス社製の32bitマイコンSTM32で動かす記事
今回はその第5回、GPIOの拡張と、複数プログラムの動作を行います。

目標

  • オンボードのプッシュスイッチ入力を、mruby/c で読み取りができるようにする
  • 外部に追加したLEDを、mruby/c から点滅できるようにする
  • 複数のプログラムを、協調して動かすことができるようにする

準備するもの

  • LED
  • 固定抵抗器 (330Ω程度)
  • ブレッドボードなど

LEDと固定抵抗器は、いずれもリードタイプを用意します。

オンボードのプッシュスイッチ入力を、mruby/c で読み取りができるようにする

ピンアサインの確認

Nucleoボードには、ユーザー入力用のプッシュスイッチが用意してあります。IDEのピンアサイン画面、もしくは回路図でピンアサイン(接続先)を確認すると、PC13 に接続されていることが分かります。

STM32Tuto05-スクリーンショット 2024-06-04 13.42.53.png

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 ファイル全体はこちら
Core/mrubyc/start_mrubyc.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 をダブルクリックして開きます。

以下の通り変更します。

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 であることを見つけます。

STM32Tuto05-スクリーンショット 2024-06-04 15.06.23.png

外部LEDの取付け

以下の写真と回路図を参考に、LEDを取り付けます。

STM32Tuto05-スクリーンショット 2024-06-04 16.02.06.jpg
STM32Tuto05-schema1.png

PA10ピンをGPIO出力に設定

Project Explorer から、(プロジェクト名).ioc (ここでは、tuto01.ioc) をダブルクリックし、コンフィグレーション画面を開きます。

Pinout view 画面でICのPA10端子をクリックするとコンテキストメニューが開くので、GPIO_Output を選びます。
STM32Tuto05-スクリーンショット 2024-06-04 15.10.31.png

メニューから、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 をダブルクリックして開きます。

以下の通り変更します。

Core/mrubyc/task1.rb
while true
  gpio_pa10_write( 1 )
  sleep 1
  gpio_pa10_write( 0 )
  sleep 1
end

実行

メニューから、Run > Run を選んでターゲットへ書き込みます。

STM32Tuto05-Demo1.gif

無事に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 をダブルクリックして開きます。

以下の通り変更します。

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] をクリックし、ファイルを作ります。

以下の内容を入力します。

Core/mrubyc/task2.rb
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 に登録してください。

実行

STM32Tuto05-Demo2.gif

おわりに

ファイル全体は、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回に分けて書いたポーティング記事も、今回で最後です。おつきあい、ありがとうございました。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?