17 min read

(For more resources related to this topic, see here.)

Communicating easily with Max 6 – the [serial] object

The easiest way to exchange data between your computer running a Max 6 patch and your Arduino board is via the serial port. The USB connector of our Arduino boards includes the FTDI integrated circuit EEPROM FT-232 that converts the RS-232 plain old serial standard to USB.

We are going to use again our basic USB connection between Arduino and our computer in order to exchange data here.

The [serial] object

We have to remember the [serial] object’s features. It provides a way to send and receive data from a serial port. To do this, there is a basic patch including basic blocks. We are going to improve it progressively all along this article.

The [serial] object is like a buffer we have to poll as much as we need. If messages are sent from Arduino to the serial port of the computer, we have to ask the [serial] object to pop them out. We are going to do this in the following pages.

This article is also a pretext for me to give you some of my tips and tricks in Max 6 itself. Take them and use them; they will make your patching life easier.

Selecting the right serial port

we have used the message (print) sent to [serial] in order to list all the serial ports available on the computer. Then we checked the Max window. That was not the smartest solution. Here, we are going to design a better one.

We have to remember the [loadbang] object. It fires a bang, that is, a (print) message to the following object as soon as the patch is loaded. It is useful to set things up and initialize some values as we could inside our setup() block in our Arduino board’s firmware.

Here, we do that in order to fill the serial port selector menu. When the [serial] object receives the (print) message, it pops out a list of all the serial ports available on the computer from its right outlet prepended by the word port. We then process the result by using [route port] that only parses lists prepended with the word port.

The [t] object is an abbreviation of [trigger]. This object sends the incoming message to many locations, as is written in the documentation, if you assume the use of the following arguments:

  • b means bang
  • f means float number
  • i means integer
  • s means symbol
  • l means list (that is, at least one element)

We can also use constants as arguments and as soon as the input is received, the constant will be sent as it is.

At last, the [trigger] output messages in a particular order: from the rightmost outlet to the leftmost one.

So here we take the list of serial ports being received from the [route] object; we send the clear message to the [umenu] object (the list menu on the left side) in order to clear the whole list. Then the list of serial ports is sent as a list (because of the first argument) to [iter]. [iter] splits a list into its individual elements.

[prepend] adds a message in front of the incoming input message.

That means the global process sends messages to the [umenu] object similar to the following:

  • append xxxxxx
  • append yyyyyy

Here xxxxxx and yyyyyy are the serial ports that are available.

This creates the serial port selector menu by filling the list with the names of the serial ports. This is one of the typical ways to create some helpers, in this case the menu, in our patches using UI elements.

As soon as you load this patch, the menu is filled, and you only have to choose the right serial port you want to use. As soon as you select one element in the menu, the number of the element in the list is fired to its leftmost outlet. We prepend this number by port and send that to [serial], setting it up to the right-hand serial port.

Polling system

One of the most used objects in Max 6 to send regular bangs in order to trigger things or count time is [metro].

We have to use one argument at least; this is the time between two bangs in milliseconds.

Banging the [serial] object makes it pop out the values contained in its buffer.

If we want to send data continuously from Arduino and process them with Max 6, activating the [metro] object is required. We then send a regular bang and can have an update of all the inputs read by Arduino inside our Max 6 patch.

Choosing a value between 15 ms and 150 ms is good but depends on your own needs.

Let’s now see how we can read, parse, and select useful data being received from Arduino.

Parsing and selecting data coming from Arduino

First, I want to introduce you to a helper firmware inspired by the Arduino2Max page on the Arduino website but updated and optimized a bit by me. It provides a way to read all the inputs on your Arduino, to pack all the data read, and to send them to our Max 6 patch through the [serial] object.

The readAll firmware

The following code is the firmware.

int val = 0; void setup() { Serial.begin(9600); pinMode(13,INPUT); } void loop() { // Check serial buffer for characters incoming if (Serial.available() > 0){ // If an 'r' is received then read all the pins if (Serial.read() == 'r') { // Read and send analog pins 0-5 values for (int pin= 0; pin<=5; pin++){ val = analogRead(pin); sendValue (val); } // Read and send digital pins 2-13 values for (int pin= 2; pin<=13; pin++){ val = digitalRead(pin); sendValue (val); } Serial.println();// Carriage return to mark end of data flow. delay (5); // prevent buffer overload } } } void sendValue (int val){ Serial.print(val); Serial.write(32); // add a space character after each value sent }

For starters, we begin the serial communication at 9600 bauds in the setup() block.

As usual with serial communication handling, we check if there is something in the serial buffer of Arduino at first by using the Serial.available() function. If something is available, we check if it is the character r. Of course, we can use any other character. r here stands for read, which is basic. If an r is received, it triggers the read of both analog and digital ports. Each value (the val variable) is passed to the sendValue()function; this basically prints the value into the serial port and adds a space character in order to format things a bit to provide an easier parsing by Max 6. We could easily adapt this code to only read some inputs and not all. We could also remove the sendValue() function and find another way of packing data.

At the end, we push a carriage return to the serial port by using Serial.println(). This creates a separator between each pack of data that is sent.

Now, let’s improve our Max 6 patch to handle this pack of data being received from Arduino.

The ReadAll Max 6 patch

The following screenshot is the ReadAll Max patch that provides a way to communicate with our Arduino:

Requesting data from Arduino

First, we will see a [t b b] object. It is also a trigger, ordering bangs provided by the [metro] object. Each bang received triggers another bang to another [trigger] object, then another one to the [serial] object itself.

The [t 13 r] object can seem tricky. It just triggers a character r and then the integer 13. The character r is sent to [spell] that converts it to ASCII code and then sends the result to [serial]. 13 is the ASCII code for a carriage return.

This structure provides a way to fire the character r to the [serial] object, which means to Arduino, each time that the metro bangs. As we already see in the firmware, it triggers Arduino to read all its inputs, then to pack the data, and then to send the pack to the serial port for the Max 6 patch.

To summarize what the metro triggers at each bang, we can write this sequence:

  1. Send the character r to Arduino.
  2. Send a carriage return to Arduino.
  3. Bang the [serial] object.

This triggers Arduino to send back all its data to the Max patch.

Parsing the received data

Under the [serial] object, we can see a new structure beginning with the [sel 10 13] object. This is an abbreviation for the [select] object. This object selects an incoming message and fires a bang to the specific output if the message equals the argument corresponding to the specific place of that output. Basically, here we select 10 or 13. The last output pops the incoming message out if that one doesn’t equal any argument.

Here, we don’t want to consider a new line feed (ASCII code 10). This is why we put it as an argument, but we don’t do anything if that’s the one that has been selected. It is a nice trick to avoid having this message trigger anything and even to not have it from the right output of [select].

Here, we send all the messages received from Arduino, except 10 or 13, to the [zl group 78] object. The latter is a powerful list for processing many features. The group argument makes it easy to group the messages received in a list. The last argument is to make sure we don’t have too many elements in the list. As soon as [zl group] is triggered by a bang or the list length reaches the length argument value, it pops out the whole list from its left outlet.

Here, we “accumulate” all the messages received from Arduino, and as soon as a carriage return is sent (remember we are doing that in the last rows of the loop() block in the firmware), a bang is sent and all the data is passed to the next object.

We currently have a big list with all the data inside it, with each value being separated from the other by a space character (the famous ASCII code 32 we added in the last function of the firmware).

This list is passed to the [itoa] object. itoa stands for integer to ASCII . This object converts integers to ASCII characters.

The [fromsymbol] object converts a symbol to a list of messages.

Finally, after this [fromsymbol] object we have our big list of values separated by spaces and totally readable.

We then have to unpack the list. [unpack] is a very useful object that provides a way to cut a list of messages into individual messages. We can notice here that we implemented exactly the opposite process in the Arduino firmware while we packed each value into a big message.

[unpack] takes as many arguments as we want. It requires knowing about the exact number of elements in the list sent to it. Here we send 12 values from Arduino, so we put 12 i arguments. i stands for integer . If we send a float, [unpack] would cast it as an integer. It is important to know this. Too many students are stuck with troubleshooting this in particular.

We are only playing with the integer here. Indeed, the ADC of Arduino provides data from 0 to 1023 and the digital input provides 0 or 1 only.

We attached a number box to each output of the [unpack] object in order to display each value.

Then we used a [change] object. This latter is a nice object. When it receives a value, it passes it to its output only if it is different from the previous value received. It provides an effective way to avoid sending the same value each time when it isn’t required.

Here, I chose the argument -1 because this is not a value sent by the Arduino firmware, and I’m sure that the first element sent will be parsed.

So we now have all our values available. We can use them for different jobs.

But I propose to use a smarter way, and this will also introduce a new concept.

Distributing received data and other tricks

Let’s introduce here some other tricks to improve our patching style.

Cordless trick

We often have to use some data in our patches. The same data has to feed more than one object.

A good way to avoid messy patches with a lot of cord and wires everywhere is to use the [send] and [receive] objects. These objects can be abbreviated with [s] and [r], and they generate communication buses and provide a wireless way to communicate inside our patches.

These three structures are equivalent.

The first one is a basic cord. As soon as we send data from the upper number box, it is transmitted to the one at the other side of the cord.

The second one generates a data bus named busA. As soon as you send data into [send busA], each [receive busA] object in your patch will pop out that data.

The third example is the same as the second one, but it generates another bus named busB.

This is a good way to distribute data.

I often use this for my master clock, for instance. I have one and only one master clock banging a clock to [send masterClock], and wherever I need to have that clock, I use [receive masterClock] and it provides me with the data I need.

If you check the global patch, you can see that we distribute data to the structures at the bottom of the patch. But these structures could also be located elsewhere. Indeed, one of the strengths of any visual programming framework such as Max 6 is the fact that you can visually organize every part of your code exactly as you want in your patcher. And please, do that as much as you can. This will help you to support and maintain your patch all through your long development months.

Check the previous screenshot. I could have linked the [r A1] object at the top left corner to the [p process03] object directly. But maybe this will be more readable if I keep the process chains separate. I often work this way with Max 6.

This is one of the multiple tricks I teach in my Max 6 course. And of course, I introduced the [p] object, that is the [patcher] abbreviation.

Let’s check a couple of tips before we continue with some good examples involving Max 6 and Arduino.

Encapsulation and subpatching

When you open Max 6 and go to File | New Patcher , it opens a blank patcher. The latter, if you recall, is the place where you put all the objects. There is another good feature named subpatching . With this feature, you can create new patchers inside patchers, and embed patchers inside patchers as well.

A patcher contained inside another one is also named a subpatcher.

Let’s see how it works with the patch named ReadAllCutest.maxpat.

There are four new objects replacing the whole structures we designed before.

These objects are subpatchers. If you double-click on them in patch lock mode or if you push the command key (or Ctrl for Windows), double-click on them in patch edit mode and you’ll open them. Let’s see what is there inside them.

The [requester] subpatcher contains the same architecture that we designed before, but you can see the brown 1 and 2 objects and another blue 1 object. These are inlets and outlets. Indeed, they are required if you want your subpatcher to be able to communicate with the patcher that contains it. Of course, we could use the [send] and [receive] objects for this purpose too.

The position of these inlets and outlets in your subpatcher matters. Indeed, if you move the 1 object to the right of the 2 object, the numbers get swapped! And the different inlets in the upper patch get swapped too. You have to be careful about that. But again, you can organize them exactly as you want and need.

Check the next screenshot:

And now, check the root patcher containing this subpatcher. It automatically inverts the inlets, keeping things relevant.

Let’s now have a look at the other subpatchers:

The [p portHandler] subpatcher

The [p dataHandler] subpatcher

The [p dataDispatcher] subpatcher

In the last figure, we can see only one inlet and no outlets. Indeed, we just encapsulated the global data dispatcher system inside the subpatcher. And this latter generates its data buses with [send] objects. This is an example where we don’t need and even don’t want to use outlets. Using outlets would be messy because we would have to link each element requesting this or that value from Arduino with a lot of cords.

In order to create a subpatcher, you only have to type n to create a new object, and type p, a space, and the name of your subpatcher.

While I designed these examples, I used something that works faster than creating a subpatcher, copying and pasting the structure on the inside, removing the structure from the outside, and adding inlets and outlets.

This feature is named encapsulate and is part of the Edit menu of Max 6.

You have to select the part of the patch you want to encapsulate inside a subpatcher, then click on Encapsulate , and voilà! You have just created a subpatcher including your structures that are connected to inlets and outlets in the correct order.

Encapsulate and de-encapsulate features

You can also de-encapsulate a subpatcher. It would follow the opposite process of removing the subpatcher and popping out the whole structure that was inside directly outside.

Subpatching helps to keep things well organized and readable.

We can imagine that we have to design a whole patch with a lot of wizardry and tricks inside it. This one is a processing unit, and as soon as we know what it does, after having finished it, we don’t want to know how it does it but only use it .

This provides a nice abstraction level by keeping some processing units closed inside boxes and not messing the main patch.

You can copy and paste the subpatchers. This is a powerful way to quickly duplicate process units if you need to. But each subpatcher is totally independent of the others. This means that if you need to modify one because you want to update it, you’d have to do that individually in each subpatcher of your patch.

This can be really hard.

Let me introduce you to the last pure Max 6 concept now named abstractions before I go further with Arduino.

Abstractions and reusability

Any patch created and saved can be used as a new object in another patch. We can do this by creating a new object by typing n in a patcher; then we just have to type the name of our previously created and saved patch.

A patch used in this way is called an abstraction .

In order to call a patch as an abstraction in a patcher, the patch has to be in the Max 6 path in order to be found by it. You can check the path known by Max 6 by going to Options | File Preferences . Usually, if you put the main patch in a folder and the other patches you want to use as abstractions in that same folder, Max 6 finds them.

The concept of abstraction in Max 6 itself is very powerful because it provides reusability .

Indeed, imagine you need and have a lot of small (or big) patch structures that you are using every day, every time, and in almost every project. You can put them into a specific folder on your disk included in your Max 6 path and then you can call (we say instantiate ) them in every patch you are designing.

Since each patch using it has only a reference to the one patch that was instantiated itself, you just need to improve your abstraction; each time you load a patch using it, the patch will have up-to-date abstractions loaded inside it.

It is really easy to maintain all through the development months or years.

Of course, if you totally change the abstraction to fit with a dedicated project/patch, you’ll have some problems using it with other patches. You have to be careful to maintain even short documentation of your abstractions.

Let’s now continue by describing some good examples with Arduino.

LEAVE A REPLY

Please enter your comment!
Please enter your name here