Serial I/O - Programming for Musicians and Digital Artists: Creating music with ChucK (2015)

Programming for Musicians and Digital Artists: Creating music with ChucK (2015)

Appendix F. Serial I/O

In times past, serial port communication (the colloquial term for the RS-232 computer interface) enjoyed a wide-ranging prevalence not unlike USB in today’s computing world. Formerly used for devices such as mice, dial-up modems, PC–PC networking, and many others, serial I/O is now largely obsolete in the presence of USB. But the basic mechanism of serial I/O, a bidirectional stream of data between two connected computers, has found new life in the realm of physical computing, such as the Arduino platform. In this appendix we’ll discuss functions and methods ChucK provides for interacting with serial devices. When necessary, we’ll refer to Arduino programs as an example of a serial device that ChucK might be interacting with, although ChucK’s serial capabilities extend to any serial hardware.

F.1. Serial I/O reading

To start, we’ll assume that you’ve connected and set up a serial device and are generally familiar with how to connect to it and program for it. If this isn’t the case, we recommend checking out the Arduino platform at http://arduino.cc/.

The first step to programming with serial devices in ChucK is to verify that ChucK can find your device. The following code retrieves a list of all serial devices ChucK can communicate with on your system and then prints each item in the list.

SerialIO.list() @=> string list[];

for(int i; i < list.size(); i++)

{

<<< i, ":", list[i] >>>;

}

First, you see that there’s a new class, SerialIO, whose static .list() function returns a list of available serial devices. The SerialIO class will mediate all of your interactions with serial hardware.

Now, let’s open a serial device and read from it. First, let’s load up your device with code, which will give you some serial data to read. Assuming you’re using Arduino, open the Graph example (File > Examples > 04.Communication > Graph) and upload it to your device. If you’re using a different hardware platform, no problem—this example simply reads an analog pin, writes its value as an ASCII integer, and then writes a newline. Once the program has finished uploading to the Arduino, you can continue.

On the ChucK side, the code should look familiar at this point, with a few new twists:

SerialIO cereal;

cereal.open(0, SerialIO.B9600, SerialIO.ASCII);

while(true)

{

cereal.onLine() => now;

cereal.getLine() => string line;

chout <= "line:" <= line;

}

Once you run this code, you should see a printout like the following, continuing indefinitely (feel free to remove the shred after a few seconds). Note that the exact number values will vary depending on how your Arduino is configured.

line: 276

line: 277

line: 276

Take a closer look at this ChucK code. On the first line you create an instance of serial I/O, which you’ll use to communicate with a specific serial device. You initiate a connection to this device by calling .open(). The first argument of .open() selects which device to connect with—this refers to the list of devices SerialIO.list() gave you, so it can be any integer between 0 and SerialIO.list().size()-1. Thus, you may need to change it from 0 in the previous code depending on where your Arduino appears in the list. The next argument specifies the baud, which should match the baud used by the serial device, for example, the parameter given to Serial.begin() on the Arduino. The last argument indicates whether to use ASCII (text) mode (SerialIO.ASCII) or binary mode (SerialIO.BINARY). The code proceeds with the venerable while(true) loop, followed by something being chucked to now. Just what is chucked to now is different here. cereal.onLine() => now can be interpreted as “wait until a line of text has been received,” where a line is formally a string of text, with any number of characters, ending in a newline character (\n). Once the line has been received, cereal.getLine() will retrieve it. So, your while loop continuously reads lines from the serial device, printing them as they’re received.

F.1.1 Bytes, ints, floats, strings

The SerialIO class isn’t limited to reading lines of text. In ASCII-mode, SerialIO can read ints, floats, and full lines of text. In binary mode, 8-bit bytes, 32-bit ints, and 32-bit floats can be read. Each read type has corresponding .on() and .get() functions, as summarized by the following table.

Table F.1. Functions for reading from SerialIO

.on() function

.get() function

ASCII-mode description

Binary-mode description

.onByte()

.getByte() Returns int in range 0–255

Not available

Single byte

.onBytes(int n)

.getBytes() Returns int[] of length n, each in range 0–255

Not available

Series of n bytes

.onInts(int n)

.getInts() Returns int[] of length n

Series of n ASCII-formatted int s

Series of n 32-bit ints

.onFloats(int n)

.getFloats() Returns float[] of length n

Series of n ASCII-formatted floats

Series of n 32-bit floats

.onLine()

.getLine() Returns string

Series of ASCII characters terminated by newline

Not available

As an example, you could modify the previous code to read bytes instead of lines, like this:

SerialIO

cereal; cereal.open(0, SerialIO.B9600, SerialIO.BINARY);

while(true)

{

cereal.onByte() => now;

cereal.getByte() => int bite;

chout <= "byte:" <= bite <= IO.nl();

}

Note the change to SerialIO.BINARY on the second line; the program won’t work without this, because .onByte() is available only in binary mode. If you still have the Graph program running on your Arduino, the output should look something like the following (as before, the exact values may vary):

byte: 50

byte: 50

byte: 54

byte: 13

byte: 10

byte: 50

byte: 50

byte: 54

byte: 13

byte: 10

Each byte in this case corresponds to an ASCII character; for example, 50 = ‘2’, 54 = ‘6’, 13 = ‘\r', the carriage return character, and 10 = ‘\n', the newline character.

Furthermore, reads from a serial device can be chained, in case you want to read more than one item at a time. This is accomplished by adding more .on() function calls to the end of the initial .on() function call and ensuring there are a matching number of .get()s. You can even mix and match different types, as long as you stick to purely ASCII types or purely binary types.

SerialIO cereal;

cereal.open(0, SerialIO.B9600, SerialIO.BINARY);

while(true)

{

// read 1 byte, 1 int, and then 2 more bytes

cereal.onByte().onInts(1).onBytes(2) => now;

cereal.getByte() => int firstByte;

cereal.getInts() @=> int theInts[];

cereal.getBytes() @=> int moreBytes[];

<<< "first byte:", firstByte, "int:", theInts[0], "more bytes:", moreBytes[0], moreBytes[1] >>>;

}

F.2. Serial I/O writing

Writing to a serial device in ChucK is almost the same as writing to a file, which is no coincidence. Most OSes treat serial devices just like normal files, and similarly SerialIO is a subclass of IO, as is FileIO. Thus, SerialIO supports most of the same operations that FileIO provides.

Let’s look at an example. First, you should load up a different Arduino program. Although you we can write to the serial port freely, the Graph program won’t respond to it in any way. Instead, load PhysicalPixel (File > Example > 04.Communication > PhysicalPixel) to your Arduino. This program reads one character of input, turning on the built-in LED if the character is H (HIGH) and turning it off if the character is L (LOW).

The ChucK side of this interaction looks like the following code, which should cause the built-in LED to toggle on or off every second:

SerialIO cereal;

cereal.open(0, SerialIO.B9600, SerialIO.ASCII);

// loop forever

while(true)

{

cereal <= "H"; // turn LED on

1::second => now; // wait

cereal <= "L"; // turn LED off

1::second => now; // wait

}

Declaring and opening the SerialIO object proceeds the same as before. This time, in your while loop, you backchuck the string “H” to the SerialIO instance (turning the Arduino’s LED on), let that persist for 1 second, and then backchuck “L” to the device (turning the LED off), letting that continue for a second. This is repeated ad infinitum, or until you stop the shred.

Anything you can backchuck to a file can also be backchucked to a SerialIO instance. This includes ints, floats, and strings, in either ASCII or binary mode. As with FileIO, you can chain sequences of backchucks:

cereal <= "the value at position" <= 1 <= " is " <= 0.62 <= IO.nl();

Unlike FileIO, SerialIO has no notion of read-only or write-only modes. You can both read from and write to any SerialIO object.