Go Down

Topic: Serial Input Basics (Read 358041 times) previous topic - next topic

halfdome

#30
Feb 15, 2015, 02:19 pm Last Edit: Feb 15, 2015, 02:59 pm by halfdome
Unfortunately, my observation of saving memory was based on a typo - using "SerialEvent() " instead of "serialEvent()". Since this new function SerialEvent was never called, the compiler optimized it away...

Robin2

Unfortunately, my observation of saving memory was based on a typo - using "SerialEvent() " instead of "serialEvent()". Since this new function SerialEvent was never called, the compiler optimized it away...
Thank you very much for your honesty.

It could happen to anyone.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Dylan144GT

Hello Robin,

I am struggling with a wireless serial transmission issue and I was wondering if you could please assist me with it? I have started a thread here.

Thanks very much for your time, it is much appreciated!

Dylan

ElCaron


I don't quite get recvWithStartEndMarkers(). What if a single  "while Serial.available()" loop iteration finishes before the counterside has send a new character? as far as I can see, the index for the buffer is reset to 0 in each call of the function, so the buffer doesn't seem to be preservered over multiple loop() iterations?

ShapeShifter

as far as I can see, the index for the buffer is reset to 0 in each call of the function, so the buffer doesn't seem to be preservered over multiple loop() iterations?
ndx, as well as recvInProgress, are both marked static. This means that they are allocated once (static, as in fixed) and are not dynamically allocated on the function's stack frame. This means that they do not get created and destroyed on each call, and their values are preserved from call to call. An assignment made in a static variable's declaration only actually happens once, at system startup time, in order to give it an initial value.

Static variables are allocated at fixed addresses and behave just as if they were globals at the top of the file. But by putting them in the function, their visibility is limited to just that function. So it's a way to have the best of both worlds: the lifespan and persistence of global variables so that values are preserved from call to call, and the limited visibility of local variables so other functions can't mess with the values.

ElCaron

Ooooooooh, I overlooked that. Thanks!

denvlad

Nice code! Though one problem with using
Code: [Select]

while (Serial.available()) {
  Serial.read();
}

to clear the input buffer (your last advice) is that it happens very fast, which means that we can easily be in a situation when the next character has not yet been received, so the loop will exit and fail to 'clear' the buffer (unless I'm doing something wrong, in which case please correct me :) ). One can easily test this by introducing
Code: [Select]

while (Serial.available()) {
  Serial.read();
  delayMicroseconds(10);
}

which even at 115200 baud rate will still be insufficient to keep up with the input; if you change 10 to 100, it starts to work.

So to alleviate that, we either have to introduce this (somewhat artificial) delay, or wait for the terminating character using something like
Code: [Select]

Serial.readStringUntil(endMarker);

which also has a nice feature of timing out after 1s (that can be changed with Serial.setTimeout())  if no ending marker is provided.

ShapeShifter

to clear the input buffer (your last advice) is that it happens very fast, which means that we can easily be in a situation when the next character has not yet been received, so the loop will exit and fail to 'clear' the buffer
I think you are over-analyzing it and making it harder than it needs to be. The goal is to clear the input buffer: if the next character has not been received, it's not in the input buffer, so how can it be cleared?

This is not a function to ignore characters until some arbitrary end of message marker, or until there is no more data to be received. It is simply to clear out the characters that have already been received. The code was proposed to counter the lack of a flush() function for the serial input. A flush() function typically has two purposes: for an output stream, it makes sure all output has actually been written; for an input stream it discards any input data already available (and not any future input data.) The Serial class implements a flush() function, but only on the output stream. The proposed code is to provide a similar functionality for the input stream.

The major point of this approach to serial processing is to not block processing under any situations. By arbitrarily redefining the semantics of flush() to discard data until some end of message indication subverts that goal by introducing a blocking scenario. If you do want to introduce a function to skip until a certain marker, it would take a little more effort to do it in a non-blocking way. But that was not the intent of the subject code.

Robin2

@ShapeShifter, thank you for your help here.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Robin2

#39
Apr 06, 2015, 10:51 am Last Edit: Apr 06, 2015, 10:52 am by Robin2
While trying to help someone else I discovered that the Parse example in Reply #1 will only work once on the same data because the strtok() function changes the array it is working on. In this case it replaces the commas with \0.

The following slightly modified version uses a temporary copy of the array for parsing.

Code: [Select]
// simple parse demo with repeating output
char receivedChars[] = "This is a test, 1234, 45.3" ;
char tempChars[32];        // temporary array for use by strtok() function

char messageFromPC[32] = {0};
int integerFromPC = 0;
float floatFromPC = 0.0;

char recvChar;
char endMarker = '>';
boolean newData = false;
byte dLen = 0;


void setup() {
    Serial.begin(9600);
    Serial.println("<Arduino is ready>");
}


void loop() {

        // this is necessary because strtok() alters the array
        //   in this case replacing commas with \0
    strcpy(tempChars, receivedChars);
    parseData();
    showParsedData();
    delay(2000);
}

    
void parseData() {

    // split the data into its parts
    
  char * strtokIndx; // this is used by strtok() as an index

  strtokIndx = strtok(tempChars,",");      // get the first part - the string
  strcpy(messageFromPC, strtokIndx); // copy it to messageFromPC
  
  strtokIndx = strtok(NULL, ","); // this continues where the previous call left off
  integerFromPC = atoi(strtokIndx);     // convert this part to an integer

  strtokIndx = strtok(NULL, ",");
  floatFromPC = atof(strtokIndx);     // convert this part to a float

}


void showParsedData() {
    Serial.print("Message ");
    Serial.println(messageFromPC);
    Serial.print("Integer ");
    Serial.println(integerFromPC);
    Serial.print("Float ");
    Serial.println(floatFromPC);
}


...R
Two or three hours spent thinking and reading documentation solves most programming problems.

ShapeShifter

The way I usually handle this is once a complete line is received, immediately use strtok() to step through the line finding each token. Each time strtok() returns a token pointer, stick it an an array of pointers, keeping track of the number of tokens. When done, you have an array of tokens (each token is NULL terminated) and a count, just like argc/argv[] passed to a C main function. It is this array that is repeatedly accessed while determining the current command and it's semantics, it is no longer necessary to parse the original string.

This has a few advantages, like knowing the number of tokens right from the start (makes it easier to validate a complete correct command sequence as soon as you decode the command ID in the first token), plus it eliminates the need to parse the string more than once, and does not require an extra copy of the string or special storage of each token.

Of course, the downside is that it changes the original string, and is a bit more complicated of a concept which starts to move it away from a "basics" discussion.

colin1

Many thanks,

I can see that I am seriously out of my depth with this and need to start from some other point! Basically, I don't have a picture of where the various bits of code relate to one another: takes information, gives information etc. But still, thanks for the various bits of code. Beginning to know what one does not know is a good place to start on a project!

Best wishes,

Colin.

Robin2

I can see that I am seriously out of my depth with this and need to start from some other point! Basically, I don't have a picture of where the various bits of code relate to one another: takes information, gives information etc. But still, thanks for the various bits of code. Beginning to know what one does not know is a good place to start on a project!
Is this your first post in this Thread?
I would be happy to help if you explain in more detail what you are having trouble with.
If your question is connected to another Thread about your project it may be better to deal with your question there - if you provide a link to it.

...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Robin2

#43
May 02, 2015, 12:58 pm Last Edit: May 02, 2015, 12:59 pm by Robin2
It has occurred to me that there are many cases where a user only wishes to input a single number and the Parse example is unnecessarily complex for that.

The following code is a small extension of the version that receives several characters with an end marker to include code to convert the receivedChars to an int.  I have marked all the new lines of code with // new for this version

Code: [Select]
const byte numChars = 32;
char receivedChars[numChars]; // an array to store the received data

boolean newData = false;

int dataNumber = 0; // new for this version

void setup() {
Serial.begin(9600);
Serial.println("<Arduino is ready>");
}

void loop() {
recvWithEndMarker();
showNewData();
}

void recvWithEndMarker() {
static byte ndx = 0;
char endMarker = '\n';
char rc;

if (Serial.available() > 0) {
rc = Serial.read();

if (rc != endMarker) {
receivedChars[ndx] = rc;
ndx++;
if (ndx >= numChars) {
ndx = numChars - 1;
}
}
else {
receivedChars[ndx] = '\0'; // terminate the string
ndx = 0;
newData = true;
}
}
}

void showNewData() {
if (newData == true) {
dataNumber = 0; // new for this version
dataNumber = atoi(receivedChars); // new for this version
Serial.print("This just in ... ");
Serial.println(receivedChars);
Serial.print("Data as Number ... "); // new for this version
Serial.println(dataNumber); // new for this version
newData = false;
}
}


...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Geneticus0

If I understand this thread completely, this method allows for a high utilization of the serial port for two way communication. Doesn't that come at a cost of processing ability on the Arduino? While not using blocking methods keeps the line "open", collecting everything in character arrays and casting them to other types seems more taxing on the Arduino, and could cause a bottleneck in processing with large data streams and potentially lead to data loss if the buffer backs up too much waiting for data to be processed. Is that a fair assessment?

Go Up