Arduino互換機+mrubyでラジコンを自動走行してみた

2014/2/20追記
下記はRSTRING_PTR()を誤った使い方をしています。mrb_valueにRSTRING_PTR()マクロを使った場合に取り出した文字列は終端がNULLとは限りませんのでご注意



前回までで、chipKIT Max32上でmrubyプログラムをさくさく開発できるようになりました。何かやることは無いかと考えて、昔遊んでいたラジコンのモータとサーボが制御できることがわかったので、自動走行してみました。

最初に円を描く走行、その次は8の字運転です。

・・・残雪多すぎ+シャフトが外れて8の字は散々(笑)
どちらもmrubyのコードで制御しています。雪がない時に調整すればもう少しまともに走るはず。

円走行(circle.rb)

Serial.println("loading circle.rb....")

class Circle
	include Arduino

	def initialize
		@servo = Servo.new
		@servo.attach(5)
	end

	def idleloop

	end

	def run
		Serial.println("circle started")
		delay(5000)

		@servo.write(60)
		analogWrite(3, 220)
		delay(10 * 1000)

		analogWrite(3, 128)
		@servo.write(90)
		delay(1000)

	end
end

Circle.new

八の字(eight.rb)

Serial.println("loading eight.rb...")

class Eight
	include Arduino

	def initialize
		@servo = Servo.new
		@servo.attach(5)
	end

	def idleloop
	end

	def angle(val)
		@servo.write(val)
	end

	def speed(val)
     #モータは3番PINに接続
		analogWrite(3, val)
	end

	#八の字運転
	def draw_8
		
		#straight
		angle 90
		speed  240
		delay 1000

		#turn left
		angle  0
		speed  210
		delay 1500

		#straight
		angle  90
		speed  240
		delay 1000

		#turn right
		angle  180
		speed  210
		delay 1500
	end		

	def run
		Serial.println("eight started")
		delay(5000)

		3.times do
			draw_8
		end

		angle 90
		speed 128

	end
end

Eight.new

angle()は舵角の調整です。0で左いっぱい、90で中央(直進)、180で右いっぱい。
speed()はモータ回転の調整です。最大255で、コーナリング時は少し遅くしています。

digitalWrite()、pinMode()、Servoクラス等のArduinoのAPIのうち、使うものだけmrubyから呼び出せるようにC側で関数を用意してます(mrbgem化したい→mruby-arduinoとしてmrbgem化しました。)。また、今回はスイッチが押されたらrun()がC側から呼ばれます。

かなり長いですが、C側のコードも一応。

#include <Servo.h>


#define CHANGE_HEAP_SIZE(size) __asm__ volatile ("\t.globl _min_heap_size\n\t.equ _min_heap_size, " #size "\n")



#include <mrbconf.h>
#include <mruby.h>

#include <mruby/irep.h>
#include <mruby/string.h>  
#include <mruby/value.h>
#include <mruby/dump.h>
#include <mruby/data.h>
#include <mruby/class.h>



//extern const char initialcode[];

mrb_state *mrb;
char code[4048];
mrb_value sketchObject;

mrb_value mrb_serial_begin(mrb_state *mrb, mrb_value self){
  mrb_int speed = 0;
  int n = mrb_get_args(mrb,"i",&speed);
  Serial.begin(speed);
  return mrb_nil_value();
}

mrb_value mrb_serial_println(mrb_state *mrb, mrb_value self){
  mrb_value s;
  mrb_get_args(mrb,"S", &s);
  Serial.println(RSTRING_PTR(s));
  return mrb_nil_value();
}

void mrb_servo_free(mrb_state *mrb, void *ptr){
  delete (Servo *)ptr;
}

struct mrb_data_type mrb_servo_type = {"Servo", mrb_servo_free};

mrb_value mrb_servo_initialize(mrb_state *mrb, mrb_value self){
  Servo *newServo = new Servo();
  Serial.println("newServo");
  Serial.println((int)newServo);
  DATA_PTR(self) = newServo;  
  DATA_TYPE(self) = &mrb_servo_type;  
  return self;
}

mrb_value mrb_servo_attach(mrb_state *mrb, mrb_value self){
  Servo *servo = (Servo *)mrb_get_datatype(mrb, self, &mrb_servo_type);
  
  mrb_int pin = 0;
  int n = mrb_get_args(mrb, "i", &pin);
  servo->attach(pin);
  return mrb_nil_value();
}

mrb_value mrb_servo_write(mrb_state *mrb, mrb_value self){
  Servo *servo = (Servo *)mrb_get_datatype(mrb, self, &mrb_servo_type);
  mrb_int angle = 0;
  int n = mrb_get_args(mrb, "i", &angle);
  servo->write(angle);
  return mrb_nil_value();
}

mrb_value mrb_arduino_pinMode(mrb_state *mrb, mrb_value self){
  mrb_int pin, mode;
  int n = mrb_get_args(mrb, "ii", &pin, &mode);
  pinMode(pin, mode);
  return mrb_nil_value();
}

mrb_value mrb_arduino_digitalWrite(mrb_state *mrb, mrb_value self){
  mrb_int pin, value;
  int n = mrb_get_args(mrb, "ii", &pin, &value);
  digitalWrite(pin, value);
  return mrb_nil_value();
}

mrb_value mrb_arduino_digitalRead(mrb_state *mrb, mrb_value self){
  mrb_int pin;
  int n = mrb_get_args(mrb, "i", &pin);
  int val = digitalRead(pin);
  return mrb_fixnum_value(val);
}

mrb_value mrb_arduino_analogWrite(mrb_state *mrb, mrb_value self){
  mrb_int pin, value;
  int n = mrb_get_args(mrb, "ii", &pin, &value);
  analogWrite(pin, value);
  return mrb_nil_value();
}

mrb_value mrb_arduino_analogRead(mrb_state *mrb, mrb_value self){
  mrb_int pin;
  int n = mrb_get_args(mrb, "i", &pin);
  int val = analogRead(pin);
  return mrb_fixnum_value(val);
}
  
mrb_value mrb_arduino_delay(mrb_state *mrb, mrb_value self){
  mrb_int ms;
  int n = mrb_get_args(mrb, "i", &ms);
  delay(ms);
  return mrb_nil_value();
}

mrb_value mrb_arduino_millis(mrb_state *mrb, mrb_value self){
  return mrb_fixnum_value(millis());
}

mrb_value mrb_arduino_map(mrb_state *mrb, mrb_value self){
  mrb_int value, fromLow, fromHigh, toLow, toHigh;
  int n = mrb_get_args(mrb, "iiiii", &value, &fromLow, &fromHigh, &toLow, &toHigh);
  mrb_int ret = map(value, fromLow, fromHigh, toLow, toHigh);
  return mrb_fixnum_value(ret);
}


void init_arduino_library(mrb_state *mrb){
  RClass *serialClass = mrb_define_class(mrb, "Serial", mrb->object_class);
  mrb_define_class_method(mrb, serialClass,"begin",mrb_serial_begin, ARGS_REQ(1));
  mrb_define_class_method(mrb, serialClass,"println", mrb_serial_println, ARGS_REQ(1));


  RClass *servoClass = mrb_define_class(mrb, "Servo", mrb->object_class);
  MRB_SET_INSTANCE_TT(servoClass, MRB_TT_DATA);
  mrb_define_method(mrb, servoClass,"initialize", mrb_servo_initialize, ARGS_NONE());
  mrb_define_method(mrb, servoClass,"attach", mrb_servo_attach, ARGS_REQ(1));
  mrb_define_method(mrb, servoClass,"write", mrb_servo_write, ARGS_REQ(1));
  
  RClass *arduinoModule = mrb_define_module(mrb, "Arduino");
  mrb_define_module_function(mrb, arduinoModule, "pinMode", mrb_arduino_pinMode, ARGS_REQ(2));
  mrb_define_module_function(mrb, arduinoModule, "digitalWrite", mrb_arduino_digitalWrite, ARGS_REQ(2));
  mrb_define_module_function(mrb, arduinoModule, "digitalRead", mrb_arduino_digitalRead, ARGS_REQ(1));
  mrb_define_module_function(mrb, arduinoModule, "analogWrite", mrb_arduino_analogWrite, ARGS_REQ(2));
  mrb_define_module_function(mrb, arduinoModule, "analogRead", mrb_arduino_analogRead, ARGS_REQ(1));
  mrb_define_module_function(mrb, arduinoModule, "delay", mrb_arduino_delay, ARGS_REQ(1));
  mrb_define_module_function(mrb, arduinoModule, "millis", mrb_arduino_millis, ARGS_NONE());
  mrb_define_module_function(mrb, arduinoModule, "map", mrb_arduino_map, ARGS_REQ(5));
  mrb_define_const(mrb, arduinoModule, "HIGH", mrb_fixnum_value(1));
  mrb_define_const(mrb, arduinoModule, "LOW", mrb_fixnum_value(0));
  mrb_define_const(mrb, arduinoModule, "INPUT", mrb_fixnum_value(0));
  mrb_define_const(mrb, arduinoModule, "OUTPUT", mrb_fixnum_value(1));
  mrb_define_const(mrb, arduinoModule, "INPUT_PULLUP", mrb_fixnum_value(2));
}  

unsigned short receiveNewCode(){
  unsigned short lengthH = Serial.read();
  while(Serial.available() == 0);
  unsigned short lengthL = Serial.read();
  unsigned short codeLength = (lengthH << 8) | lengthL;
  
  //send first ack("!")
  Serial.write(33);

  unsigned short readedLen = 0;
  while(readedLen < codeLength){
    for (int i = 0 ; i < 100; i++){
      while(Serial.available() == 0);
      code[readedLen] = Serial.read();
      readedLen++;
      if (readedLen == codeLength)
        break;
    }
    Serial.write(35);
  }
  Serial.println("uploaded(from mruby_rc_runner)");   
  
  return readedLen;
}

const int MOTOR_PIN = 3;
const int START_PIN = 2;
const int SERVO_PIN = 5;

const int SPEED_CONTROL_PIN = 0;
const int ANGLE_CONTROL_PIN = 1;

Servo servo;

void setup(){
  
  //the magic code to increase heap size to 100kb
  // see http://www.chipkit.org/forum/viewtopic.php?f=19&t=1565
  CHANGE_HEAP_SIZE(100*1024);
  
  Serial.begin(9600);
  
  pinMode(START_PIN, INPUT);
  pinMode(MOTOR_PIN, OUTPUT);
  analogWrite(MOTOR_PIN, 128);

  
  mrb = NULL;
  
}



void loop(){
  
  if (Serial.available() > 0 ){
    /*  upload mode  */
    
    if (unsigned short codeLen = receiveNewCode()){
      if (mrb) mrb_close(mrb);
      mrb = mrb_open();
      init_arduino_library(mrb);
      FILE *fp = fmemopen(code, codeLen, "rb");  //open buffer as FILE  stream. platform dependent.
      sketchObject = mrb_load_irep_file(mrb, fp);
     
      //mrb_value result = mrb_funcall(mrb, sketchObject, "setup", 0);
      if (mrb->exc){
        Serial.println("exception occured in setup on new code");
      }
    }
    
  }else if (digitalRead(START_PIN)){
    if (!mrb) return;
    mrb_value result = mrb_funcall(mrb, sketchObject, "run", 0);
    if (mrb->exc){
      Serial.println("exception occured in run");
    }
  }else{
    
    if (mrb) {
      mrb_value result = mrb_funcall(mrb, sketchObject, "idleloop", 0);
      if (mrb->exc){
        Serial.println("exception occured in idle loop");
      }
    }
    
    Serial.println("idle loop");
    int speed = analogRead(SPEED_CONTROL_PIN);
    speed /= 4;
    if (speed > 127){
      if (speed > 250){
        speed = 250;
      }
    } else if (speed > 64){
      //backward slow
      speed = 2;
    }else{
      //backward fast
      speed = 1;
    }
    analogWrite(MOTOR_PIN, speed);

    int angle = analogRead(ANGLE_CONTROL_PIN);
    angle = map(angle, 0, 1024, 180, 0);
    
    /*servo.attach(SERVO_PIN);
    servo.write(angle);  
    delay(500);
    servo.detach();
    */
    delay(100);
    
  }
}