mrubyc_arduinoにUART


mruby/cの標準的なAPIで、mrubyc_arduinoにまだ組み込んでいないのは、UARTだけになっていた。 標準入力を使えるようにしたので、UARTの必要性が感じられなくて、なかなかやる気にならなかったが、ようやくUARTも作ってみた。 今回もC言語とC++とArduinoとrubyの仕様の違いに苦しめられたが、なんとか完成した。

まずmrbc_uart.hでは、getsのときの最大の文字数を定義した。

#ifndef _MRBC_UART_H
#define _MRBC_UART_H

#include <Arduino.h>

#ifdef __cplusplus
extern "C" {
#endif

#include "mrubyc.h"

#define UART_GETS_MAX 256

void mrbc_init_class_uart(void);

#ifdef __cplusplus
}
#endif

#endif

ArduinoではSerialも通常はC++で書かれているので、メインのコードのファイル名はmrbc_uart.cppとして、様々なメソッドを定義した。 unit番号に応じてSerialやSerial1などを何らかの変数に入れて、それを使ってコマンドを実行したかったが、C++のオブジェクトのポインタを使うと何が起こるか分からなかったので、まずはswitch-caseで分岐するようにして書いたので、冗長になってしまった。 その際、モードの設定のswitch-caseでbreakを忘れたために、パリティ付に設定されて二文字目以降が文字化けして、その原因を見付けるのに苦労した。 また、ArduinoではSerialのモードはSERIAL_8N1などで設定するのだが、この型がマイコンによって異なっていたので、それを格納する変数を定義するときに、decltypeを用いた。 getsはreadBytesUntilを使えば簡単に書けそうに思ったけど、はまってしまった。 ArduinoのreadBytesUntilではsizeを指定しないといけなくて、改行コードが取り除かれるという点が、rubyのgetsとは異なる。 一文字以上が入力されるのを待って、timeoutを指定して、readBytesUntilで読み取ることにしたらうまくいった。 しかし、やはり冗長なのが気に入らないので、なんとかクラスのポインタを使って、それをrubyのUARTオブジェクトに保存して、以下のように比較的短くすることができた。

#include "mrbc_uart.h"

extern "C" {

typedef struct UART_HANDLE {
  decltype(Serial) *unit_serial;
} UART_HANDLE;

void c_uart_setmode(mrbc_vm *vm, mrbc_value v[], int argc){
  UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data;  
  decltype(SERIAL_8N1) myUARTSettings=SERIAL_8N1;
  MRBC_KW_ARG( baudrate, baud, data_bits, stop_bits, parity, flow_control, txd_pin, rxd_pin, rts_pin, cts_pin );
  int32_t baud_rate = 9600;
  int datb = 8;
  int stpb = 1;
  int prty = 0;
  if( MRBC_KW_ISVALID(baudrate) ) baud_rate = mrbc_integer(baudrate);
  if( MRBC_KW_ISVALID(baud) ) baud_rate = mrbc_integer(baud);
  if( MRBC_KW_ISVALID(data_bits) ) datb = mrbc_integer(data_bits);
  if( MRBC_KW_ISVALID(stop_bits) ) stpb = mrbc_integer(stop_bits);
  if( MRBC_KW_ISVALID(parity) ) prty = mrbc_integer(parity);
  switch(prty){
  case 0:
    myUARTSettings=(stpb<2)? SERIAL_8N1 : SERIAL_8N2; break;
  case 1:
    myUARTSettings=(stpb<2)? SERIAL_8O1 : SERIAL_8O2; break;
  case 2:
    myUARTSettings=(stpb<2)? SERIAL_8E1 : SERIAL_8E2; break;
  }
  decltype(Serial) *unit_serial = handle->unit_serial;
  unit_serial->end();
  unit_serial->begin(baud_rate,myUARTSettings);
  while (!(*unit_serial));
  unit_serial->setTimeout(1000);
  MRBC_KW_DELETE( baudrate, baud, data_bits, stop_bits, parity, flow_control, txd_pin, rxd_pin, rts_pin, cts_pin );
}

void c_uart_new(mrb_vm *vm, mrb_value *v, int argc){
  v[0] = mrbc_instance_new(vm, v[0].cls, 0);
  int unit_num = 0;
  if( argc > 0 ) unit_num = GET_INT_ARG(1);
  MRBC_KW_ARG( unit );
  if( MRBC_KW_ISVALID(unit) ) unit_num = mrbc_integer(unit);
  MRBC_KW_DELETE( unit );
  UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data;
  decltype(Serial) *unit_serial;
  unit_serial=&Serial;
  switch(unit_num){
  case 0: unit_serial=&Serial; break;
  case 1: unit_serial=&Serial1; break;
#if defined(Serial2)
  case 2: unit_serial=&Serial2; break;
#endif
#if defined(Serial3)
  case 3: unit_serial=&Serial3; break;
#endif
#if defined(Serial4)
  case 4: unit_serial=&Serial4; break;
#endif
  }
  handle->unit_serial = unit_serial;
  c_uart_setmode( vm, v, argc );
}
  
void c_uart_write(mrb_vm *vm, mrb_value *v, int argc){
  UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data;
  int bufsiz = 0;
  if(argc>0){
    const char *s = mrbc_string_cstr(&v[1]);
    int len = mrbc_string_size(&v[1]);
    bufsiz=(int) handle->unit_serial->write(s,len);
  }
  SET_RETURN( mrbc_integer_value(bufsiz) );
}

static void c_uart_puts(mrbc_vm *vm, mrbc_value v[], int argc){
  UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data;
  char zero[]="\n";
  char *s=zero;
  int len=1;
  if(argc>0){
    s = mrbc_string_cstr(&v[1]);
    len = mrbc_string_size(&v[1]);
    if( len == 0 || s[len-1] != '\n' ) s[len++]='\n';
  }
  handle->unit_serial->write(s,len);
  SET_NIL_RETURN();
}

void c_uart_read(mrb_vm *vm, mrb_value *v, int argc){
  UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data;
  mrbc_value ret = mrbc_nil_value();
  if(argc>0){
    int size = GET_INT_ARG(1);
    ret = mrbc_string_new(vm, 0, size);
    uint8_t *buf = (uint8_t *)mrbc_string_cstr(&ret);
    size=(int) handle->unit_serial->readBytes(buf,size);
    buf[size] = '\0';
  }
  SET_RETURN(ret);
}

static void c_uart_gets(mrbc_vm *vm, mrbc_value v[], int argc){
  UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data;
  int size=0;
  mrbc_value ret = mrbc_string_new(vm, 0, UART_GETS_MAX);
  uint8_t *buf = (uint8_t *)mrbc_string_cstr(&ret);
  decltype(Serial) *unit_serial = handle->unit_serial;
  while(size==0) size=(int) unit_serial->available();
  size=(int) unit_serial->readBytesUntil('\n', buf, UART_GETS_MAX);
  buf[size] = '\0';
  SET_RETURN(ret);
}

static void c_uart_bytes_available(mrbc_vm *vm, mrbc_value v[], int argc){
  UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data;
  int n=0;
  n=handle->unit_serial->available();
  SET_INT_RETURN(n);
}

static void c_uart_bytes_to_write(mrbc_vm *vm, mrbc_value v[], int argc){
  SET_INT_RETURN( 0 );
}

static void c_uart_can_read_line(mrbc_vm *vm, mrbc_value v[], int argc){
  SET_INT_RETURN( 0 );
}

static void c_uart_flush(mrbc_vm *vm, mrbc_value v[], int argc){
  UART_HANDLE *handle = (UART_HANDLE *)v[0].instance->data;
  handle->unit_serial->flush();
}

static void c_uart_nop(mrbc_vm *vm, mrbc_value v[], int argc){
}

void mrbc_init_class_uart(void){
  mrb_class *uart = mrbc_define_class(0, "UART",  mrbc_class_object);
  mrbc_define_method(0, uart, "new", c_uart_new);
  mrbc_define_method(0, uart, "setmode", c_uart_setmode);
  mrbc_define_method(0, uart, "read", c_uart_read);
  mrbc_define_method(0, uart, "write", c_uart_write);
  mrbc_define_method(0, uart, "gets", c_uart_gets);
  mrbc_define_method(0, uart, "puts", c_uart_puts);
  mrbc_define_method(0, uart, "bytes_available", c_uart_bytes_available);
  mrbc_define_method(0, uart, "bytes_to_write", c_uart_bytes_to_write);
  mrbc_define_method(0, uart, "can_read_line", c_uart_can_read_line);
  mrbc_define_method(0, uart, "flush", c_uart_flush);
  mrbc_define_method(0, uart, "clear_rx_buffer", c_uart_nop);
  mrbc_define_method(0, uart, "clear_tx_buffer", c_uart_nop);
  mrbc_define_method(0, uart, "send_break", c_uart_nop);
  mrb_value none=mrbc_integer_value(0);
  mrb_value odd=mrbc_integer_value(1);
  mrb_value even=mrbc_integer_value(2);
  mrbc_set_class_const(uart, mrbc_str_to_symid("NONE"), &none);
  mrbc_set_class_const(uart, mrbc_str_to_symid("ODD"), &odd);
  mrbc_set_class_const(uart, mrbc_str_to_symid("EVEN"), &even);
}

} //extern

これで、mruby/cの標準的なAPIはコンプリートしたことになり、mrubyc_arduinoが一応の完成をみたと考えて良いだろう。 ESP8266やCH32V203などでの動作確認はしたが、他の種類のマイコンのArduino環境でも動くかを確かめる必要もあるだろう。 しかし、すべてを組み込むと、flashが64kB以上必要になるので、使用できるマイコンの種類がかなり限定されてしまう。 いくつかの機能を制限して、flashの容量を節約する方法も見出してはいるが、やはり64kB程度は必要で、今のところは32kBでは無理である。 まずは余裕をもって組込めるだろうESP8266,ATSAMD21G18A,STM32F103CBT6,CH32V305など用として、次にギリギリ押し込めるだろうSTM32F103C8T6,CH32V203という感じで使っていこうかな。 ESP8266とCH32V203以外は持っていないので、どうしようかな。 STM32F103CBT6としてはBluePillを持っていたはずだけど、引っ越しでどこかに行ってしまったので、見付かるかな。 CH559も試してみたいけど、C++が使えないし仕様が少し特殊なので、かなり工夫しないと動かないだろう。