MacOS Xで、RS232C型シリアルポートを使うためのメモ書きです。 実際には、すでにMacintoshには何年も前からシリアルポートが付いていません。 レガシー(遺産)な技術、すなわち失われた太古の技術と言ってもよいです。 とはいえ、RS232C風のシリアル通信は、簡単で便利なので、世の中ではあちこちで使われています。 それをMacintoshで読む場合には、USBシリアル変換器のようなものを使います。 また、現行のArduinoでは、USBシリアル変換器を内蔵していて、USB経由で、シリアル信号を送ってきます。
USBシリアル変換器やArduinoでは、FTDI社(http://www.ftdichip.com/) の チップが使われていることが多く、ここのVirtual COM port (VCP)ドライバというのをインストールすると、 /dev以下に旧来のシリアルポートと互換のスペシャルファイルが出来上がります。 また、一部のUSBシリアル変換器 ( http://www.sun-denshi.co.jp/scc/products/mobile/vs60r/vs60r.htm ) や、最新のArduino (Arduino Uno, Arduino MEGA 2560)は、USBの標準のCDCクラス (Communications Device Class) として動作するので、 ドライバーをインストールしなくても、/dev/cu.usbmodemXXXXというようなスペシャルファイルが現れます。
ここでは、このようにUSBを経由して送られてきたシリアル信号を、Mac OS Xで読んでプログラムする方法を紹介します。
まずは、ネットでググった一般的なシリアルポートの情報をメモしておきます。
Arduinoで以下のプログラムを作ったとします。
int outpin=13; int inpin=2; void setup() { // initialize the digital pin as an output. // Pin 13 has an LED connected on most Arduino boards: pinMode(outpin, OUTPUT); pinMode(inpin, INPUT); Serial.begin(9600); } void loop() { if ( digitalRead(inpin) == HIGH) { digitalWrite(outpin, HIGH); // set the LED on Serial.print("H"); } else { digitalWrite(outpin, LOW); // set the LED off Serial.print("L"); } delay(1000); // wait for a second }
写真のように、2番ピンに押しボタンスイッチを付けると、 これを押すことで13番のLEDが消えます。
また、このプログラムは、9600baudでボタンの状態を知らせます。 ボタンが押されていないと'H'の文字が、押されると'L'の文字がシリアルポートに流れます。
これをC言語で読むには、以下のようにします。 (逆スラッシュが化けているかもしれないです。コピペするときはお気をつけください)
// #include <sys/types.h> // #include <sys/stat.h> #include <unistd.h> //for open() close() read() #include <fcntl.h> #include <termios.h> #include <stdio.h> #include <strings.h> #include <signal.h> //to handle ctrl-c #define BAUDRATE B9600 #define MODEMDEVICE "/dev/cu.usbmodem621" #define _POSIX_SOURCE 1 /* POSIX compliant source */ #define FALSE 0 #define TRUE 1 int fd; struct termios oldtio, newtio; int isrunning = TRUE; int serialOpen() { fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); if (fd <0) { printf("open error: %s\n",MODEMDEVICE); return -1; } tcgetattr(fd,&oldtio); /* save current port settings */ bzero(&newtio, sizeof(newtio)); newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD; newtio.c_iflag = IGNPAR; newtio.c_oflag = 0; /* set input mode (non-canonical, no echo,...) */ newtio.c_lflag = 0; newtio.c_cc[VTIME] = 0; /* inter-character timer unused */ newtio.c_cc[VMIN] = 1; /* blocking read until 1 chars received */ tcflush(fd, TCIFLUSH); tcsetattr(fd,TCSANOW,&newtio); return 0; } void serialClose() { tcsetattr(fd,TCSANOW,&oldtio); //restore previous port setting close(fd); } unsigned char serialReadChar() { unsigned char result; int length=0; tcflush(fd, TCIFLUSH); //clear the receiving buffer length = read(fd,&result,1); //blocked until reading 1 byte. return result; } void interrupt(int sig) { isrunning = FALSE; } //ctrl-c handler int main() { int c, res; unsigned char result; if(serialOpen() != 0) return -1; signal(SIGINT, interrupt); //set ctrl-c handler while (isrunning) { /*endless loop until ctrl-c is typed */ printf("value is %c\n", serialReadChar()); } serialClose(); return 0; }
これを、gcc test.cなどしてコンパイルし、実行すると、 以下のように、読めていることがわかります。 ArduinoのIDEで /dev/tty.usb..を使っている場合は、上記のように、 /dev/cu.usbl... で動きます.
ちなみに、このプログラムはLinuxなどでも動くと思われます。
上記のプログラムに下記の関数を追加する事でC言語でArduinoへ数字を1文字送信出来るようになります。
void serialWriteChar(int num){ unsigned char result; int length = 0; write(fd,&num,1); }
簡単です。
sudo python setup.py installとして動かします。
つぎに、適当にこんなプログラムを書きます。
import serial ser=serial.Serial('/dev/cu.usbmodem3a21',9600,timeout=2) data = ser.read(10) if len(data) != 0 : print data ser.close()
10個のデータを取得して、約10秒後に表示してくれます(Arduinoから毎秒1個のデータが来るため)。
こちらも簡単です。解説はこちらをみてください。 http://www.processing.org/reference/libraries/serial/
import processing.serial.*; Serial myPort; void setup(){ myPort = new Serial(this,"/dev/tty.usbmodem3a11",9600); } void draw(){ background(0); while(myPort.available() > 0 ) println("value is " + myPort.readChar()); }
Windowsなどではシリアル通信するクラスが配布されていたと思います。 Macなどには無かったように思います(最近はあるのかもしれない)。 ここでは面倒ですが、 Cでダイナミックライブラリを作って、JNAで読む方法を紹介します。 これができればCで作ったもののすべてがJavaから使えるので嬉しい場面があるかと思います。
ダイナミックライブラリとJNAについては以下をみてください。
上記のCプログラムをダイナミックライブラリにします。 このプログラムの名前が、libserread.cだったとしたら、以下のようにコンパイルします。
cc -dynamiclib -o libserread.dylib libserread.c
このあと、JNAをインストールしたJavaの環境で、以下のプログラムをjavacでコンパイルしてjavaで動かせばokです。(確実にクローズするために60個データを表示してcloseするようにしました。)
import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.Platform; public class TestSerial { public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.loadLibrary("serread", CLibrary.class); int serialOpen(); void serialClose(); byte serialReadChar(); } public static void main(String[] args) { CLibrary arduino = CLibrary.INSTANCE; arduino.serialOpen(); for(int i=0;i<60;i++) System.out.println((char)arduino.serialReadChar()); arduino.serialClose(); } }
上記の方法でCのダイナミックライブラリを作り、JavaとArduinoでシリアル通信(読み書き)できるようにするためのクラスです。 これは2014年に研究室有志メンバーで学祭に出店したリジョワーツ魔法魔術学校のアトラクション(薬草学・マンドラゴラ)の制御の為に作ったので、Classの名前がそれ仕様になっています。他で使う際には クラス名とコンストラクタの部分の「SerialFromMNDR」を任意の名称に変更して使って下さい。 また、Arduinoから情報を読み込むread()はChar型1文字分を読み込みますが、Arduinoへ書き込むwrite()の方は1桁の数字を送るようになっています。それぞれ需要にあわせて、Char型やint型はカスタマイズ氏て下さい。
import java.io.UnsupportedEncodingException; import com.sun.jna.Library; import com.sun.jna.Native; /* * JavaでArduinoとシリアル通信をする */ public class SerialFromMNDR { public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.loadLibrary("serread", CLibrary.class); int serialOpen(); void serialClose(); byte serialReadChar(); void serialWriteChar(int num); } CLibrary arduino; public SerialFromMNDR(){ arduino = CLibrary.INSTANCE; } /* シリアル通信開始 */ void open(){ arduino.serialOpen(); } /* シリアル通信終了 */ void close(){ arduino.serialClose(); } /* Arduinoからの信号を読み取る */ String read(){ byte[] data= new byte[1]; data[0] = arduino.serialReadChar(); String strAscii = ""; try { strAscii = new String(data, "US-ASCII"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return strAscii; } /* Arduinoに一文字送る */ void write(int i){ arduino.serialWriteChar(i); } }
このクラスを呼び出して使う際には、まず
SerialFromMNDR.open();
でシリアル通信を開始します。その後はread()やwrite()で自由にArduinoと通信できるようになります。