A better strategy to separate incoming serial messages?

Markus Baumknecht's icon

Hi everyone!

I'm trying to read sensor input from Arduino in Max and I'm thinking there must be a both visually as well as computationally more elegant way to read out the sensor input / separate the incoming messages than my solution (attached) ... perhaps even starting on the Arduino side?

My 24 sensors output a stream of messages like:

"Red_0: 123 Green_0: 345 Blue_0: 567 Clear_0: 789
Red_1: ..."

Any suggestions are much appreciated!

message separator.maxpat
text/plain 177.41 KB

Source Audio's icon

Depends on what arduino is sending and when it is sending.
Usual way is to try to send a value only if it changed, to avoid unneeded strain on serial port.
that way one can send fast values if they change fast.
then instead of using that many letters, you send a list with 4 values and kind of ID prepended so that you know which LED number is sending the values.
like 1 for Led 1 followed by 4 values.
In max just use route 1 2 3 4 ... 24 to separate messages.
-----
if you provide few more infos about sensors used or even arduino sketch
it would be easier to help

Source Audio's icon

P.S. I see you use only last of 4 values
Clear_0: nn
If you don't need other color values, that could
speed up the whole process of reading sensors and sending values
which could at the end be sent as list of 24 values.
One could also downscale 12bit 0-4095 to 8bit 0-255 in Arduino,
before sending the data. Another reduction...
But if you prefer to keep sending that long string as is
one could simplify the split and cell messages down to few objects.
All I need to know is if all 24 sensors send 1 long list,
or if each sensor sends separately

Markus Baumknecht's icon

heya, thanks for getting back so quickly - I found your reply very useful!

I'm attaching the Arduino code at the end; the sensors are apds9960 colour & gesture sensors (atm I'm only using the colour).

If you don't need other color values, that could speed up the whole process of reading sensors and sending values which could at the end be sent as list of 24 values.

uff yeah, so obvious, but really didn't think of that.

Usual way is to try to send a value only if it changed, to avoid unneeded strain on serial port.

One could also downscale 12bit 0-4095 to 8bit 0-255 in Arduino,
before sending the data.

how would I do that?

All I need to know is if all 24 sensors send 1 long list, or if each sensor sends separately

the sensors send the values separately, not as a list.

here's the Arduino code:

/**/

#include<Wire.h>
#include "Adafruit_APDS9960.h" /*include libraries*/

/***********************************************************************************************************************************************************************************************************************************************************/

// declarations

Adafruit_APDS9960 apds[24]; /*initialize sensor*/

byte tcaI2CAddress[] = {0x70, 0x71, 0x72, 0x73}; /*define array containing all the addresses*/
byte numberOfTCAs = 4; /* define number of TCAs used */
byte numberOfDevicesPerTCA = 6; /* define number of Sensors per TCA */
const int numberOfDevices = 24; /* define number of total devices */

/***********************************************************************************************************************************************************************************************************************************************************/

void setup() {
  Wire.begin();
  Serial.begin(115200); /*begin serial communication (Arduino -> PC) at 115200 Baud rate*/

  // 0. run i2c scanner
  Serial.println("I2C Scanner ready!");
  Serial.println();
  scanI2C(400000); /*run i2c scanner function*/
  Serial.println("-------------------------------");
  Serial.println();

  // 1. switch off all TCAs
  for (int i = 0; i < numberOfTCAs; i++){
  Wire.beginTransmission(tcaI2CAddress[i]);
  Wire.write(0);
  Wire.endTransmission(); 
  }

  // 2. initialize sensors
  for (int i = 0; i < numberOfDevices; i++){

    byte tca = setTCAAndChannel(i);

    Serial.print("Initializing APDS_");
    Serial.println(i);
    Serial.println();
    
  if(!apds[i].begin()){ /*if function returns 0, print "failed to initialize...", else print "initialized!"*/
    Serial.println("failed to initialize device! Please check your wiring.");
    Serial.println();
  }
  else {Serial.println("Device initialized!");
  Serial.println();
  }
  
  apds[i].enableColor(true); /*enable color sensing mode*/

  // 3. close TCA
  closeTCA(tca);
  }
  
}

void loop() { 

  // 1. switch off all TCAs
  for (int i = 0; i < numberOfTCAs; i++){
  Wire.beginTransmission(tcaI2CAddress[i]);
  Wire.write(0);
  Wire.endTransmission(); 
  }

  // 2. run color readings

  for (int i = 0; i < numberOfDevices; i++){

  byte tca = setTCAAndChannel(i);
    
  uint16_t r, g, b, c; /*create some variables to store the color data in*/

  while(!apds[i].colorDataReady()){ /*wait for color data to be ready*/
    delay(1); /*Adafruit: 5; THIS DELAY ALONE AMOUNTS TO 125 ms !!!!!*/
  }

  apds[i].getColorData(&r, &g, &b, &c); /*get the data and print the different channels*/

  Serial.print("Red_");
  Serial.print(i);
  Serial.print(": ");
  Serial.print(r);
  Serial.print(" Green_");
  Serial.print(i);
  Serial.print(": ");
  Serial.print(g);
  Serial.print(" Blue_");
  Serial.print(i);
  Serial.print(": ");
  Serial.print(b);
  Serial.print(" Clear_");
  Serial.print(i);
  Serial.print(": ");
  Serial.println(c);
  Serial.println();

  // 3. close TCA
  closeTCA(tca);
  }
  Serial.println("****************************");  
  delay(1);
}

/**************************************************************************************************************************************************************************************************************************************************************/

byte setTCAAndChannel(byte i){ /* define function for setting TCA and sensor */
  byte tca = i/numberOfDevicesPerTCA; /* divide i (total # of devices) by # of TCAs, that way because it's int, 1,5 will still be 1, thereby selecting the correct TCA */
  byte channel = i%numberOfDevicesPerTCA; /* modulo... divide i (the total number of devices) by the # of devices per TCA and give the rest, e.g. 2%4 = 0*/

  Wire.beginTransmission(tcaI2CAddress[tca]);
  Wire.write(1 << channel);
  Wire.endTransmission(); 

  // Serial.print("Calculation: ");
  // Serial.print(i);
  // Serial.print(" / ");
  // Serial.print(numberOfTCAs);
  // Serial.print(" = ");
  
  Serial.print("TCA: ");
  Serial.println(tca);
  
  Serial.print("Channel: ");
  Serial.println(channel);
   
  return tca; 
}

void closeTCA(byte tca){
  Wire.beginTransmission(tcaI2CAddress[tca]);
  Wire.write(0);
  Wire.endTransmission(); 
  }
  
void scanI2C(long frequency){
  
  String normal = "standard mode (100 kHz):";
  String fast = "fast mode (400 kHz):";
  String defaultStr = " !!!!! Unzulässige Frequenz !!!!!";
  
  bool error = true;
  bool addressFound = false;
  
  Serial.print("Scanne im ");
  switch(frequency){ /* switch...case is similar to an if...then statement */
    case 100000:
      Serial.println(normal); /*print string "standard mode (100kHz)", previously defined as 'normal'*/
      break;
    case 400000:
      Serial.println(fast); /*print string "fast mode (400kHz)", previously defined as 'fast'*/
      break; /*ends the switch...case statement*/
  }
  
  Wire.setClock(frequency);
  for(int i=1; i<128; i++){
    Wire.beginTransmission(i); /*begins transmission on the specified address i, e.g. 127*/
    error = Wire.endTransmission(); /*Wire.endTransmission() returns bytes for status of transmission: 0 = success, 1 = data too long, ... 4 = other ; here "error" is set to 0 or "false" if Wire.endTransmission() returns 0 (= transmssion successful) and "true" for everything else (1 - 4)*/
    if(error == 0){ /*if the "error" Boolean is set to "false" (meaning the transmission was successful), proceed by ... */
      addressFound = true; /*setting "addressFound" to true*/
      Serial.print("0x"); /*prints the address of the i2c device from two components, first "0x" ... */
      Serial.println(i,HEX); /* ... and second the number where a device was found as HEX, i.e. 127 = 7F, totalling 0x7F*/
    }
  }
  if(!addressFound){ /*if no address was found for all 128 addresses, print: */
    Serial.println("Keine Adresse erkannt");
  }
  Serial.println();
}
Source Audio's icon

As first I would remove all Serial.print() and println() that don't transmit
the values.
then remove sending of rgb and send only Clear value
and keep sensor number as id

Here with option to convert 0- 4095 to 0-255 by dividing it by 16


in max use route 0 1 2 3 4 5 6 ..23 to route individual sensors
------------
another option is to use the 2 values
sensor id and received value to directly set cell messages

If you divide /16 in arduino, then remove it in max.

Markus Baumknecht's icon

thanks for the in-depth answer, this is exactly what I needed!

one last question though: how do I avoid sending values if they haven't changed?

Source Audio's icon

you make something similar as change object in max.
example
int Sens1 = 0;
int exSens1 = 0:

void loop(){ Sens1 = analogRead(A0);
if (Sens1 != exSens1) { Serial.println(Sens1); exSens1 = Sens1;}
}
that would work on clean analog sensors which don't have any noise
or fluctuation is smaller than smallest unit used.
like if you read a pot and scale it to 0 - 127 for midi.
-------
if readings are fluctuating add a bit of change threshold to stabilise the readings

if (abs (Sens1- exSens1 >= 10)) { Serial.println(Sens1); exSens1 = Sens1;}
in this case only if compared readings changed by amount of 10
something would get sent.

there are also smoothing libraries for analog and digital inputs
like bounce or ResponsiveAnalogRead etc.

Markus Baumknecht's icon

this is great, thank you!

Source Audio's icon

in the meantime I looked at that adafruit sensor,
I did use older rgb sensors like tcs3200 etc, this one looks fast and easy to use, but it is not really clear to me what it sends.
Datasheet and python code suggest 16 bit output 0 - 65535
in your patch you had scale 0 4097 0 255, which I first thought
is a simple typing mistake, but infos on arduino forums also state
that strange range, probably a mistake in the source code.

to make it short, dividing 4097 by 16 would exceed 255 at the top
and if that is important for the cell messages, you would need to clip
the values to 0 255.
if you do that in arduino, you can use constrain or map
something like
apds[i].getColorData(&r, &g, &b, &c);
int cl = map(c, 0 , 4097, 0, 255);Serial.print(i);
Serial.print(" ");
Serial.println(cl);
---------------
or keep c/16 in Arduino and in Max insert clip 0 255, or even number limited to 0 255
in the received value path.

Markus Baumknecht's icon

ok, thanks for looking into it! I must have overlooked the strangeness of the value because it's so close to 4096 ...