Go Down

Topic: Sending multiple arrays from VB.NET to Arduino (Read 374 times) previous topic - next topic

dtbingle

How do you send multiple arrays from VB.NET to Arduino and have the Arduino update it's array values?

I can read Serial.println() code from Arduino in VB.NET and can send data from VB.NET to arduino, but only in very basic situations.  For example:

Send SerialPort1.WriteLine("R") in VB.NET
Then on the Arduino side.....if (Serial.Read() == 'R') //do something

This works, but what I'm after is sending 3 arrays of integers from VB.NET and have the Arduino update its 3 arrays to mirror them.  To give some background, the Arduino program has 3 integer arrays - Engine RPM, Throttle Position, and Engine Torque.  The Arduino reads some data from the vehicle, does some calculations based on the arrays, and then changes the exhaust valve position to that calculated value.  I want to be able to update these 3 arrays from a VB.NET program.

Example flow
In VB.NET,
click "Download" to send the RPM, throttle, and torque array data.  The code on the VB.NET side might look like:

// R = RPM data start
// P = Pedal or throttle data start
// T = Torque data start
// < and > show beginning and end of data, data delimited with ,
SerialPort1.WriteLine("R<600,800,1000,1200,1400,1600,1800,2000,2200>P<0,20,40,60,80,100>T<-100,0,200,400,600>")

On the Arduino side:

if (Serial.available() > 0)
{
     char command=Serial.read();
     if (command == 'R')
     {
          //Need help here.  How would you read serial data considering that it begins with '<' and runs until '>' before breaking out of the 'if' statement to search for the 'P' data?
     }

     if (command == 'P')
     {
     }
}

jaholmes

Here's a good tutorial on how to read strings:
http://www.gammon.com.au/serial

Once you've gotten the whole string, there are a gazillion ways to chop it up.  If the number of array elements transmitted will always be the same, then the laziest thing would just be to let sscanf() do the heavy lifting of matching the string to a template and extracting the numbers, for e.g.:

Code: [Select]

  int R[9], P[6], T[5];
  int fieldsReceived;
 
  fieldsReceived = sscanf(
    inputString,
    "R<%d,%d,%d,%d,%d,%d,%d,%d,%d>P<%d,%d,%d,%d,%d,%d>T<%d,%d,%d,%d,%d>",
    &R[0], &R[1], &R[2], &R[3], &R[4], &R[5], &R[6], &R[7], &R[8],
    &P[0], &P[1], &P[2], &P[3], &P[4], &P[5],
    &T[0], &T[1], &T[2], &T[3], &T[4]
    );

  if (fieldsReceived == 20)
  {
    // Hooray!  Got good input.  Now do something with it.
  }


sscanf() is a fairly beefy piece of code, and will eat up a few kb of your sketch space straight away.

Another approach, especially relevant if your data arrays are variable-length, would be to do a state machine.  With that approach, you'd process each received character according to the "state", or context in which you received it.  For example, if you've received nothing yet, you're in the "expecting array name" state.  When an "R" comes along, you advance to the "expecting array start" state."  When a "<" comes along, you advance to the "expecting sign or digit" state.  Etc. Etc.

PaulS

Three arrays of three values is only 9 values. Sending something like "<0, 2, 47>", where 0 means the first array, 2 means the last position in that array, and 47 is the value to put in that array, seems like the simplest protocol to implement on each end.
The art of getting good answers lies in asking good questions.

dtbingle

#3
Feb 28, 2017, 03:17 pm Last Edit: Feb 28, 2017, 03:26 pm by dtbingle
Here's a good tutorial on how to read strings:
http://www.gammon.com.au/serial

Once you've gotten the whole string, there are a gazillion ways to chop it up.  If the number of array elements transmitted will always be the same, then the laziest thing would just be to let sscanf() do the heavy lifting of matching the string to a template and extracting the numbers, for e.g.:

Code: [Select]

  int R[9], P[6], T[5];
  int fieldsReceived;
 
  fieldsReceived = sscanf(
    inputString,
    "R<%d,%d,%d,%d,%d,%d,%d,%d,%d>P<%d,%d,%d,%d,%d,%d>T<%d,%d,%d,%d,%d>",
    &R[0], &R[1], &R[2], &R[3], &R[4], &R[5], &R[6], &R[7], &R[8],
    &P[0], &P[1], &P[2], &P[3], &P[4], &P[5],
    &T[0], &T[1], &T[2], &T[3], &T[4]
    );

  if (fieldsReceived == 20)
  {
    // Hooray!  Got good input.  Now do something with it.
  }


sscanf() is a fairly beefy piece of code, and will eat up a few kb of your sketch space straight away.

Another approach, especially relevant if your data arrays are variable-length, would be to do a state machine.  With that approach, you'd process each received character according to the "state", or context in which you received it.  For example, if you've received nothing yet, you're in the "expecting array name" state.  When an "R" comes along, you advance to the "expecting array start" state."  When a "<" comes along, you advance to the "expecting sign or digit" state.  Etc. Etc.
Ahhhh this is perfect!  Using sscanf worked beautifully and reading the entire string was as simple as copy/pasting the processing code in the gammon link.  Yes, all of my arrays will be of static length and on top of that it's okay to wait until the full string comes in, rather than read byte-by-byte under the state machine method.  I will actually use the state machine method on the VB.NET side to get current positions back though.

Code: [Select]
void loop()
{
  while (Serial.available () > 0) { processIncomingByte(Serial.read()); }
}

void process_data (const char * data)
  {

  fieldsReceived = sscanf(data, "R<%d,%d,%d,%d,%d>P<%d,%d,%d>T<%d,%d,%d,%d>E",&R[0], &R[1], &R[2], &R[3], &R[4], &P[0], &P[1], &P[2], &T[0], &T[1], &T[2], &T[3]);

  if (fieldsReceived == 12)
  {
    // For testing to make sure it's read in right
    Serial.print(R[0]);
    Serial.print(R[1]);
    Serial.print(R[2]);
    Serial.print(R[3]);
    Serial.print(R[4]);
    Serial.print(P[0]);
    Serial.print(P[1]);
    Serial.print(P[2]);
    Serial.print(T[0]);
    Serial.print(T[1]);
    Serial.print(T[2]);
    Serial.println(T[3]);
  }
 }

void processIncomingByte(const byte inByte)
  {
  static char input_line [MAX_INPUT];
  static unsigned int input_pos = 0;

  switch (inByte)
    {

    case 'E':   // end of text
      input_line [input_pos] = 0;  // terminating null byte
     
      // terminator reached! process input_line here ...
      process_data(input_line);
     
      // reset buffer for next time
      input_pos = 0; 
      break;

    case '\r':   // discard carriage return
      break;

    default:
      // keep adding if not full ... allow for terminating null byte
      if (input_pos < (MAX_INPUT - 1))
        input_line [input_pos++] = inByte;
      break;

    }
   
  }

From the VB.NET side, you would have your data string generated in the format:
SerialPort1.WriteLine("R<1,2,3,4,5>P<6,7,8>T<9,10,11,12>E")

R, P, and T denote the three different tables, and E dictates end of text.


One question though about the processIncomingByte function.  It seems you specify MAX_INPUT as a global variable.  In my particular set of arrays, the character length can vary quite a bit.  Across the 3 arrays, there are 20-25 entries in each (60-75 total).  Now consider that each entry can be 0 to 100 - so either 1-3 chars.  So you can see there is quite a bit of variability of (60-75) x (1-3) + delimiters.

Is there any downside to just making the array something huge like MAX_INPUT=600 if there is space available?

Go Up