mrubyc_ardinoのdigitalをAPI準拠


2026/4/13のブログで書いたmrubyc_arduino用のGPIOのプログラムは、APIガイドラインに完全には準拠しておらず、またマイコンの種類によっては定数が異なるという欠点があった。 折角なので、書き直してみた。

digital.hは、定数などが無くなったので、すっきりした。

#ifndef _MRBC_GPIO_H
#define _MRBC_GPIO_H

#include <Arduino.h>

#ifdef __cplusplus
extern "C" {
#endif

#include "mrubyc.h"

typedef struct GPIO_HANDLE {
  uint8_t pin_num;
} GPIO_HANDLE;

void mrbc_init_class_digital(void);

#ifdef __cplusplus
}
#endif

#endif

digital.cは、以前よりはmethodが増えた分だけ長くなった。

#include "mrbc_gpio.h"

static void c_gpio_new(mrb_vm *vm, mrb_value *v, int argc){
  v[0] = mrbc_instance_new(vm, v[0].cls, sizeof(GPIO_HANDLE));
  GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
  if(argc>0) handle->pin_num = GET_INT_ARG(1);
  if(argc>1) pinMode( handle->pin_num, GET_INT_ARG(2) );
}

static void c_gpio_setmode(mrb_vm *vm, mrb_value *v, int argc){
  if( v[0].tt == MRBC_TT_OBJECT ){ //Instance method mode.
    GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
    if(argc>0) pinMode( handle->pin_num, GET_INT_ARG(1) );
  }else{ // Class method mode.
    if(argc>1) pinMode( GET_INT_ARG(1), GET_INT_ARG(2) );
  }
}

static void c_gpio_write(mrb_vm *vm, mrb_value *v, int argc){
  GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
  if(argc>0) digitalWrite( handle->pin_num, GET_INT_ARG(1) );
}
static void c_gpio_write_at(mrb_vm *vm, mrb_value *v, int argc){
  if(argc>1) digitalWrite( GET_INT_ARG(1), GET_INT_ARG(2) );
}

static void c_gpio_read(mrb_vm *vm, mrb_value *v, int argc){
  GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
  SET_INT_RETURN( digitalRead(handle->pin_num) );
}
static void c_gpio_high(mrb_vm *vm, mrb_value *v, int argc){
  GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
  SET_BOOL_RETURN( digitalRead(handle->pin_num) >0 );
}
static void c_gpio_low(mrb_vm *vm, mrb_value *v, int argc){
  GPIO_HANDLE *handle = (GPIO_HANDLE *)v[0].instance->data;
  SET_BOOL_RETURN( digitalRead(handle->pin_num) ==0 );
}
static void c_gpio_read_at(mrb_vm *vm, mrb_value *v, int argc){
  if(argc>0) SET_INT_RETURN( digitalRead( GET_INT_ARG(1) ) );
}
static void c_gpio_high_at(mrb_vm *vm, mrb_value *v, int argc) {
  if(argc>0) SET_BOOL_RETURN( digitalRead( GET_INT_ARG(1) ) >0 );
}
static void c_gpio_low_at(mrb_vm *vm, mrb_value *v, int argc) {
  if(argc>0) SET_BOOL_RETURN( digitalRead( GET_INT_ARG(1) ) ==0 );
}

void mrbc_init_class_digital(void){
  mrb_class *gpio = mrbc_define_class(0, "GPIO",  mrbc_class_object);
  mrbc_define_method(0, gpio, "new", c_gpio_new);
  mrbc_define_method(0, gpio, "setmode", c_gpio_setmode);
  mrbc_define_method(0, gpio, "write", c_gpio_write);
  mrbc_define_method(0, gpio, "write_at", c_gpio_write_at);
  mrbc_define_method(0, gpio, "read", c_gpio_read);
  mrbc_define_method(0, gpio, "high?", c_gpio_high);
  mrbc_define_method(0, gpio, "low?", c_gpio_low);
  mrbc_define_method(0, gpio, "read_at", c_gpio_read_at);
  mrbc_define_method(0, gpio, "high_at?", c_gpio_high_at);
  mrbc_define_method(0, gpio, "low_at?", c_gpio_low_at);
  mrbc_set_class_const(gpio, mrbc_str_to_symid("IN"),     &mrbc_integer_value(INPUT));
  mrbc_set_class_const(gpio, mrbc_str_to_symid("OUT"),    &mrbc_integer_value(OUTPUT));
  mrbc_set_class_const(gpio, mrbc_str_to_symid("IN_PU"),  &mrbc_integer_value(INPUT_PULLUP));
#if defined(INPUT_PULLDOWN)
  mrbc_set_class_const(gpio, mrbc_str_to_symid("IN_PD"),  &mrbc_integer_value(INPUT_PULLDOWN));
#endif
#if defined(OUTPUT_OD)
  mrbc_set_class_const(gpio, mrbc_str_to_symid("OUT_OD"), &mrbc_integer_value(OUTPUT_OD));
#elif defined(OUTPUT_OPEN_DRAIN)
  mrbc_set_class_const(gpio, mrbc_str_to_symid("OUT_OD"), &mrbc_integer_value(OUTPUT_OPEN_DRAIN));
#endif
}

以前のプログラムでは、GPIOオブジェクトを定義して、そのmethodでピンを以下のように操作していた。

pin=GPIO.new(2,GPIO::OUTPUT)
pin.write(1)

ガイドラインに準拠したことで、毎回ピン番号を指定して以下のように使うこともできるようになったはずである。

GPIO.setmode(2,GPIO::OUTPUT)
GPIO.write_at(2,1)

前者の方がrubyっぽい気がするが。

setmodeとread_atとwrite_atだけをc言語で定義しておいて、 その他は、rubyで書いてmrbとして組み込むことも出来ると思うが、 どちらの方が良いのだろう。 性能があまり変らないのなら、後者の方が書くのが簡単だとは思うけど。