mrubyc_arduinoにI2C


mrubyc_arduinoには、 GPIO, ADC, PWMはすでに組み込んだので、 mruby, mruby/cのI/O APIガイドラインに規定されているもので、 残っているのはI2C, SPI, UARTの三つである。 その内から、I2Cを組み込んでみた。 Arduinoでは、I2Cは通常はWire.hとWire.cppに書かれており、 C++を使っているので、C言語で書かれているmruby/cと組み合せるのに少し苦労したが、 なんとか動くようになった。

まず、i2c.hだが、これは以下のようにした。

#ifndef _I2C_H
#define _I2C_H

#include <Arduino.h>

#ifdef __cplusplus
extern "C" {
#endif

#include "mrubyc.h"

void mrbc_init_class_i2c(void);

#ifdef __cplusplus
}
#endif

#endif

mruby/cの他のマイコンへの実装では、 writeのときに数値や配列などの様々な形で指定されたものを出力するために、 make_output_bufferという関数を使っている。 この関数はSPIなどでも使うので、outputbuffer.cという別のファイルを作って、cppからも呼び出せるように以下のおまじないで囲んでおいた。

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
}
#endif

mrbc_allocは(void *)なのだが、エラーが出ることがあったので、(uint8_t *)に型変換をして用いるという一箇所だけ変更を加えた。

i2c.cppは、ArduinoのWireの使い方を使って、以下のように書いた。

#include "i2c.h"
#include <Wire.h>

extern "C" {

extern uint8_t * make_output_buffer(mrb_vm *vm, mrb_value v[], int argc,
                             int start_idx, int *ret_bufsiz);

void c_i2c_new(mrb_vm *vm, mrb_value *v, int argc){
  Wire.begin();
}

void c_i2c_write(mrb_vm *vm, mrb_value *v, int argc){
  uint8_t *buf = 0;
  int bufsiz = 0;
  uint8_t adrs = GET_INT_ARG(1);
  Wire.beginTransmission(adrs);
  if(argc>2){
    buf = make_output_buffer( vm, v, argc, 2, &bufsiz );
    Wire.write(buf,bufsiz);
  }
  Wire.endTransmission();
  if( buf ) mrbc_free( vm, buf );
  SET_RETURN( mrbc_integer_value(bufsiz) );
}

void c_i2c_read(mrb_vm *vm, mrb_value *v, int argc){
  uint8_t *buf = 0;
  int bufsiz = 0;
  mrbc_value ret = mrbc_nil_value();
  int adrs = GET_INT_ARG(1);
  int size = GET_INT_ARG(2);
  if( argc > 2 ) {
    buf = make_output_buffer( vm, v, argc, 3, &bufsiz );
    Wire.beginTransmission(adrs);
    Wire.write(buf,bufsiz);
    Wire.endTransmission(false);
  }
  if( buf ) mrbc_free( vm, buf );
  ret = mrbc_string_new(vm, 0, size);
  uint8_t *p = (uint8_t *)mrbc_string_cstr(&ret);
  Wire.requestFrom(adrs, size);
  int i;
  for(i=0;i<size;i++){
    while(Wire.available()==0){}
    p[i] = Wire.read();
  }
  SET_RETURN(ret);
}

void mrbc_init_class_i2c(void){
  mrb_class *i2c = mrbc_define_class(0, "I2C",  mrbc_class_object);
  mrbc_define_method(0, i2c, "new", c_i2c_new);
  mrbc_define_method(0, i2c, "read", c_i2c_read);
  mrbc_define_method(0, i2c, "write", c_i2c_write);
}

} // extern

これで、残るはSPI, UARTの二つになった。 SPIのAPIは、I2Cと似た仕様なので、それを真似すれば、それほど作るのは難しく無いだろう。 UARTは、設定項目やmethodが沢山あるので、少し面倒そうな気がする。