mrubyc_arduinoにSPI
I2Cに続いて、mrubyc_arduinoにSPIを組み込んでみた。 Arduinoでは、SPIは通常はSPI.hとSPI.cppに書かれており、 これをmruby/cから呼び出す。 ArduinoでのSPIの設定の仕方が、以前とは変っていたので、少し手間取ったが、おそらく実装できただろう。
ヘッダファイルは、spi.hにしようと思ったが、ArduinoのSPI.hなどと干渉する可能性があるので、mbc_spi.hとした。 その内容は以下の通り、初期化関数だけを宣言した。
#ifndef _MRBC_SPI_H
#define _MRBC_SPI_H
#include <Arduino.h>
#ifdef __cplusplus
extern "C" {
#endif
#include "mrubyc.h"
void mrbc_init_class_spi(void);
#ifdef __cplusplus
}
#endif
#endif
SPIでは、周波数、ビットの順番、モードなどを設定する必要がある。 また、通信時にreadとwriteが同時に行われるという特徴をもつ。 そのため、I2Cよりは少し複雑になったが、 SPIクラス実装編 を参考にして、試行錯誤の末に完成した mrbc_spi.cppは、以下のとおりである。
#include "mrbc_spi.h"
#include <SPI.h>
extern "C" {
static SPISettings mySPISettings = SPISettings(1000000, MSBFIRST, SPI_MODE0);
extern uint8_t * make_output_buffer(mrb_vm *vm, mrb_value v[], int argc,
int start_idx, int *ret_bufsiz);
void c_spi_setmode(mrbc_vm *vm, mrbc_value v[], int argc){
MRBC_KW_ARG( unit, frequency, mode, first_bit );
int32_t spi_freq = 1000000;
int spi_mode = SPI_MODE0;
int spi_first_bit = MSBFIRST;
if( MRBC_KW_ISVALID(frequency) ) spi_freq = mrbc_integer(frequency);
if( MRBC_KW_ISVALID(first_bit) ) spi_first_bit = mrbc_integer(first_bit);
if( MRBC_KW_ISVALID(mode) ){
switch(mrbc_integer(mode)){
case 1:
spi_mode = SPI_MODE1; break;
case 2:
spi_mode = SPI_MODE2; break;
case 3:
spi_mode = SPI_MODE3; break;
}
}
mySPISettings = SPISettings(spi_freq, spi_first_bit, spi_mode);
MRBC_KW_DELETE( unit, frequency, mode, first_bit );
}
void c_spi_new(mrb_vm *vm, mrb_value *v, int argc){
v[0] = mrbc_instance_new(vm, v[0].cls, 0);
c_spi_setmode( vm, v, argc );
SPI.begin();
}
void c_spi_write(mrb_vm *vm, mrb_value *v, int argc){
uint8_t *buf = 0;
int bufsiz = 0;
if(argc>1){
buf = make_output_buffer( vm, v, argc, 1, &bufsiz );
SPI.beginTransaction(mySPISettings);
SPI.transfer(buf, bufsiz);
SPI.endTransaction();
mrbc_free( vm, buf );
}
SET_RETURN( mrbc_integer_value(bufsiz) );
}
void c_spi_read(mrb_vm *vm, mrb_value *v, int argc){
mrbc_value ret = mrbc_nil_value();
int size = GET_INT_ARG(1);
ret = mrbc_string_new(vm, 0, size);
uint8_t *buf = (uint8_t *)mrbc_string_cstr(&ret);
memset(buf, 0, size);
SPI.beginTransaction(mySPISettings);
SPI.transfer(buf, size);
SPI.endTransaction();
SET_RETURN(ret);
}
void c_spi_transfer(mrb_vm *vm, mrb_value *v, int argc){
mrbc_value ret = mrbc_nil_value();
uint8_t *buf = 0;
int bufsiz = 0;
if(argc>0){
buf = make_output_buffer( vm, v, 1, 1, &bufsiz );
if(argc>1){
int additional_read_bytes = GET_INT_ARG(2);
uint8_t *buf2 = (uint8_t *) mrbc_realloc(vm, buf, bufsiz + additional_read_bytes);
buf = buf2;
memset(buf + bufsiz, 0, additional_read_bytes);
bufsiz += additional_read_bytes;
}
ret = mrbc_string_new_alloc(vm, buf, bufsiz);
SPI.beginTransaction(mySPISettings);
SPI.transfer(buf, bufsiz);
SPI.endTransaction();
}
SET_RETURN(ret);
}
void mrbc_init_class_spi(void){
mrb_class *spi = mrbc_define_class(0, "SPI", mrbc_class_object);
mrbc_define_method(0, spi, "new", c_spi_new);
mrbc_define_method(0, spi, "setmode", c_spi_setmode);
mrbc_define_method(0, spi, "read", c_spi_read);
mrbc_define_method(0, spi, "write", c_spi_write);
mrbc_define_method(0, spi, "transfer", c_spi_transfer);
mrb_value lsb=mrbc_integer_value(LSBFIRST);
mrb_value msb=mrbc_integer_value(MSBFIRST);
mrbc_set_class_const(spi, mrbc_str_to_symid("LSB_FIRST"), &lsb);
mrbc_set_class_const(spi, mrbc_str_to_symid("MSB_FIRST"), &msb);
}
} //extern
writeではI2Cと同じ、make_output_bufferを使った。 定数を定義するときに、ポインターを使っているが、c++だと変数に代入しないとエラーが出た。 やはり、c言語とc++の違いは難しいな。
昔使っていたSPIで動くICを見付けて、それで動作チェックをしようとしたが、ICが壊れてしまっているようで、以前動いていたはずのArduinoのプログラムでも正しい値を返さなかった。 しかし、間違った設定では零しか返さなかったので、それっぽい値を返したら良しということにして、動作チェックを行った。 いつか、きちんとしたSPIのICで動作を確認したいところである。
今回はファイル名をmrbc_spiとしたが、これまでに組み込んだものも、頭にmrbc_を付けた方が良いかな。 しかし、これで残るはUARTだけになったけども、週末にでも書こうかな。 他にもやりたいことはいろいろとあるので、そっちをやってたら時間が無いかな。