I use Pro­cess­ing to con­trol my Arduino — an easy and work­ing solu­tion. How­ever if you need to send alot data to the Arduino, you’ll notice a 20ms delay between send­ing (120 bytes) and receiev­ing (16 bytes) a ser­ial mes­sage. After play­ing around with the rxtx Library (changed some time­outs, JNI ver­sion…) I guess the bot­tle­neck is the Java JNI call.

While the ser­ial inter­face is not a part of the JRE (any­more), the net­work stack is. This means we can send net­work pack­ets at full speed. And there should be some Net­work to Ser­ial tools avail­able for my Mac OSX. Nope! I found plenty of such tools, but not a sin­gle one was working:

First I checked the Arduino Play­ground about Seri­alIP. A Ser­ial IP library for the Arduino exist — but I couldn’t find a SLIP util­ity for Mac OSX 10.6.6. There was once slat­tach — but only for pre OSX 10.6. I com­piled the source myself with­out success.

The next step was another Arduino Play­ground wiki entry, about Seri­al­Net. To make the long story short, here are the tools I tried with­out success:

  • nc –l –p 4443 –D < /dev/tty.usbmodem621 > /dev/tty.usbmodem621
  • ./ser2net –C “3001:raw:0:/dev/tty.usbmodem621:115200 NONE 1STOPBIT 8DATABITSXONXOFFLOCALRTSCTS
  • socat –d –d –d –x –s –t 30 TCP-LISTEN:4441 GOPEN:/dev/tty.usbmodem621,raw,ispeed=115200,ospeed=115200,ignoreeof
  • Tin­ker­proxy v2.0

Socat v1.7.1.3 hints: the “non­block” option for the ser­ial device resulted in a “tcsetattr(3, TCSADRAIN, 0x7fff5fbfefd0): Invalid argu­ment” error, I guess this oper­a­tion is not sup­ported by the OS. Ger­hard gave me another hint, that I should use the GOPEN address type (I was using the PTY address type — this *should* cre­ate a new PTY and not open an exist­ing one. This is a bug in the v1.7.1.3 version).

Dis­claimer: Maybe I was just too stu­pid to use those tools correct…

The solu­tion was a bit funky — I cre­ated a Pure­Data patch. Whats PureData?

Pure­Data is a real-time graph­i­cal pro­gram­ming envi­ron­ment for audio, video, and graph­i­cal processing.

But you can use it as network-to-serial relay too. Here is my patch as image:

You may down­load the patch here. Fea­tures of the v0.1 release:

  • Auto­con­nect Ser­ial Port
  • Auto­con­nect back to your lis­ten­ing Server
  • TCP and UDP sup­ported (replace tcpre­ceive with udpre­ceive and tcpsend with udpsend)

Here is the pro­cess­ing sketch I used to test my solution:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import processing.net.*;

Client c;
Server myServer;

void setup() {
  fram­eR­ate(200);
  myServer = new Server(this, 7777);
  c = new Client(this, “127.0.0.1″, 7778); // Con­nect to server on port 80
}

void draw() {
  long l1 = Sys­tem.cur­rent­TimeMil­lis();
  byte buf[] = new byte[70];
  buf[0]=0x01;
  buf[1]=0;
  buf[2]=64;
  buf[3]=0x03;
  buf[4]=0x10;
  for (int j=5; j<70; j++) buf[j]=0x20;
  c.write(buf);
  print(SEND);
 
  Client thisClient=null;
  boolean got­Data=false;
  int i=0;
  while (!got­Data) {
    thisClient = myServer.avail­able();
    if (thisClient!=null) {
      if (thisClient.avail­able()>2) {
        got­Data = true;
      }
    }
    else {
      i++;
      delay(2);
      if (i==30) {
        println(“nore­ply!”);
        return;
      }
    }
  }

  println(“incom­ming ser­ial data “+thisClient.avail­able()+” bytes after “+(Sys­tem.cur­rent­TimeMil­lis()-l1)+“ms”);
  while (thisClient!=null && thisClient.avail­able()>0) {
    int b = thisClient.read();
    //print(“0x”+Integer.toHexString(b)+”, ”);
  }
  //  println();
}

And this is the Arduino Sketch:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#include <TimerOne.h>

#define BAUD_RATE 115200

//— pro­to­col data start
#define CMD_START_BYTE  0x01
#define CMD_SENDFRAME 0x03

#define START_OF_DATA 0x10
#define END_OF_DATA 0x20

//frame size for spe­cific color res­o­lu­tion
#define COLOR_5BIT_FRAME_SIZE 64
#define SERIAL_HEADER_SIZE 5
//— pro­to­col data end

#define SERIAL_DELAY_LOOP 3
#define SERIAL_WAIT_DELAY 3

//this should match RX_BUFFER_SIZE from HardwareSerial.cpp
byte serIn­Str[COLOR_5BIT_FRAME_SIZE+SERIAL_HEADER_SIZE];                     // array that will hold the ser­ial input string

#define SERIALBUFFERSIZE 16
byte seri­al­Resonse[SERIALBUFFERSIZE];

byte g_errorCounter;

//send sta­tus back to library
sta­tic void sendAck() {
  seri­al­Resonse[0] = ‘A’;
  seri­al­Resonse[1] = ‘K’;
  seri­al­Resonse[2] = Ser­ial.avail­able();
  seri­al­Resonse[3] = g_errorCounter;
  Ser­ial.write(seri­al­Resonse, SERIALBUFFERSIZE);
}

void setup() {
  pin­Mode(13, OUTPUT);
 
  mem­set(seri­al­Resonse, 0, SERIALBUFFERSIZE);

  Ser­ial.begin(BAUD_RATE); //Setup high speed Ser­ial
  Ser­ial.flush();
}

void loop() {
  //read the ser­ial port and cre­ate a string out of what you read
  g_errorCounter=0;

  // digitalWrite(13, LOW);
  // see if we got a proper com­mand string yet
  if (read­Com­mand(serIn­Str) == 0) {
    return;
  }

  dig­i­tal­Write(13, HIGH);
 
    //i2c addres of device
  byte addr    = serIn­Str[1];
  //how many bytes we’re send­ing
  byte sendlen = serIn­Str[2];
  //what kind of com­mand we send
  byte type = serIn­Str[3];
  //parameter
  byte* cmd    = serIn­Str+5;
 
   switch (type) {
   case CMD_SENDFRAME:
        if (sendlen == COLOR_5BIT_FRAME_SIZE) {    
        } else {
      g_errorCounter=100;
        }
        break;

    case CMD_PING:
        //just send the ack!
        break;
    default:
        //invalid com­mand
        g_errorCounter=130;
        break;
  }
  sendAck();
  dig­i­tal­Write(13, LOW);
}

byte read­Com­mand(byte *str) {
  byte b,i,sendlen;

  //wait until we get a CMD_START_BYTE or queue is empty
  i=0;
  while (Ser­ial.avail­able()>0 && i==0) {
    b = Ser­ial.read();
    if (b == CMD_START_BYTE) {
      i=1;
    }
  }

  if (i==0) {
    //failed to get data ignore it
    return 0;    
  }

  //read header  
  i = SERIAL_DELAY_LOOP;
  while (Ser­ial.avail­able() < SERIAL_HEADER_SIZE-1) {   // wait for the rest
    delay(SERIAL_WAIT_DELAY);
    if (i == 0) {
      return 0;        //no data avail­able!
    }
  }
  for (i=1; i<SERIAL_HEADER_SIZE; i++) {
    str[i] = Ser­ial.read();       // fill it up
  }

  // — START HEADER CHECK    
  //check if data is cor­rect, 0x10 = START_OF_DATA
  if (str[4] != START_OF_DATA) {
    return 0;
  }

  //check sendlen, its pos­si­ble that sendlen is 0!
  sendlen = str[2];  
  // — END HEADER CHECK

  //read data  
  i = SERIAL_DELAY_LOOP;
  // wait for the final part, +1 for END_OF_DATA
  while (Ser­ial.avail­able() < sendlen+1) {
    delay(SERIAL_WAIT_DELAY);
    if( i == 0 ) {
      return 0;
    }
  }

  for (i=SERIAL_HEADER_SIZE; i<SERIAL_HEADER_SIZE+sendlen+1; i++) {
    str[i] = Ser­ial.read();       // fill it up
  }

  //check if data is cor­rect, 0x20 = END_OF_DATA
  if (str[SERIAL_HEADER_SIZE+sendlen] != END_OF_DATA) {
    return 0;
  }

  //return data size (with­out meta data)
  return sendlen;
}

Here are some per­for­mance results on Mac OSX v10.6.6, Pure­Data v0.41.4-extended, Arduino v0022, Pro­cess­ing v1.2.1 — using an Arduino UNO:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
SEND incom­ming ser­ial data 16 bytes after 31ms
SEND incom­ming ser­ial data 16 bytes after 30ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 18ms
SEND incom­ming ser­ial data 16 bytes after 19ms
SEND incom­ming ser­ial data 16 bytes after 31ms
SEND incom­ming ser­ial data 14 bytes after 19ms
SEND incom­ming ser­ial data 3 bytes after 20ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 19ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 20ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 19ms
SEND incom­ming ser­ial data 16 bytes after 32ms
SEND incom­ming ser­ial data 16 bytes after 30ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 18ms
SEND incom­ming ser­ial data 3 bytes after 29ms
SEND incom­ming ser­ial data 16 bytes after 31ms
SEND incom­ming ser­ial data 3 bytes after 21ms
SEND incom­ming ser­ial data 13 bytes after 8ms
SEND incom­ming ser­ial data 32 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 30ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 15 bytes after 20ms
SEND incom­ming ser­ial data 6 bytes after 25ms
SEND incom­ming ser­ial data 16 bytes after 30ms
SEND incom­ming ser­ial data 14 bytes after 22ms
SEND incom­ming ser­ial data 3 bytes after 31ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 9 bytes after 19ms
SEND incom­ming ser­ial data 7 bytes after 11ms
SEND incom­ming ser­ial data 16 bytes after 8ms
SEND incom­ming ser­ial data 9 bytes after 12ms
SEND incom­ming ser­ial data 23 bytes after 11ms
SEND incom­ming ser­ial data 16 bytes after 11ms
SEND incom­ming ser­ial data 3 bytes after 17ms
SEND incom­ming ser­ial data 3 bytes after 10ms
SEND incom­ming ser­ial data 25 bytes after 21ms
SEND incom­ming ser­ial data 7 bytes after 9ms
SEND incom­ming ser­ial data 32 bytes after 20ms
SEND incom­ming ser­ial data 16 bytes after 19ms
SEND incom­ming ser­ial data 16 bytes after 32ms
SEND incom­ming ser­ial data 9 bytes after 19ms
SEND incom­ming ser­ial data 7 bytes after 11ms
SEND incom­ming ser­ial data 16 bytes after 9ms
SEND incom­ming ser­ial data 32 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 19ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 19ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 19ms
SEND incom­ming ser­ial data 16 bytes after 21ms
SEND incom­ming ser­ial data 16 bytes after 19ms
SEND incom­ming ser­ial data 16 bytes after 31ms
SEND incom­ming ser­ial data 11 bytes after 19ms
SEND incom­ming ser­ial data 5 bytes after 11ms
SEND incom­ming ser­ial data 16 bytes after 11ms
SEND incom­ming ser­ial data 22 bytes after 20ms
SEND incom­ming ser­ial data 10 bytes after 18ms
SEND incom­ming ser­ial data 16 bytes after 3ms
SEND incom­ming ser­ial data 16 bytes after 17ms
SEND incom­ming ser­ial data 4 bytes after 8ms
SEND incom­ming ser­ial data 16 bytes after 11ms
SEND incom­ming ser­ial data 16 bytes after 11ms
SEND incom­ming ser­ial data 5 bytes after 8ms

Con­clu­sion: Com­pared to using the rxtx JNI libraries I can­not see a huge per­for­mance boost! Sad but true. So my assump­tion was wrong — JNI is NOT the ser­ial bot­tle­neck using Arduino with the rxtx libraries. Again:

JNI is NOT the ser­ial bot­tle­neck using Arduino with the rxtx libraries

Side note: I read on sev­eral forum threads (mainly on the Arduino forum) that the bau­drate should not mat­ter is you use the USB ver­sion. This con­clu­sion is just wrong! You can use my code above and run the tests with 9600 bps and 115200 bps — you’ll notice a HUGE difference!