CHAPTER 8 - RESPONSE FROM USER
This chapter deals with getting the response from the user. In the previous chapter we created the variable where we can save the user's response.
It's worth taking a little time to think about the process of receiving Serial data.
Even at 115,200 baud (about 11,000 characters per second) serial data is sent very slowly by Arduino standards so it is not a good idea to have your program hanging around waiting for data if there are other things to do.
Incoming data is stored in the Serial input buffer in the background so we don't need to react as every character arrives. The standard buffer can hold 64 characters. When your code uses Serial.read() it takes a character from the input buffer and makes room for another incoming character. If you don't take data out of the buffer before it fills up all subsequent incoming characters will be lost. This is not likely to be a problem for this project.
For this project we have no control over how many characters the user will send. S/he might send 1 or 20 (or more). How will we know that we have everything?
We could keep checking if there is at least one character in the buffer with
if(Serial.available() > 0)
but we might accidentally check in the interval between two incoming bytes at an instant when the buffer was empty so we would incorrectly think we had everything.
The simplest reliable way to check that we have all the data is to ensure there is a special character - an end-marker - as the last thing that is sent. Obviously the end-marker must be something that would never occur in the body of the response. The code will keep checking for characters until it detects the end marker.
At the bottom of the Arduino Serial Monitor window there is a box which probably says "No line ending". If you change it to "Carriage return" the Serial Monitor with add a carriage-return character ('\r' or 13) after the text the user enters and we can use this as our end-marker.
This all means we need variables to record what we are using as the end-marker and to keep track of how many bytes have been received so far.
const char endMarker = '\r';
byte bytesRecvd = 0;
I mentioned earlier that we don't want our getUserResponse() function to hang around waiting for the user to send a response. Instead we want it to make a quick check to see if there is a character in the buffer. If there is it should copy that character to our userResponse[] array. And if the character is a carriage-return it should do whatever is necessary when the full response has arrived.
It is probably easier to explain this process if I show the code for getUserResponse() first. It's a bit longer than the earlier pieces
if (waitingForResponse == false) {
return;
}
if(Serial.available() == 0) {
return;
}
char inChar = Serial.read();
if (inChar != endMarker) {
userResponse[bytesRecvd] = inChar;
bytesRecvd ++;
if (bytesRecvd == buffSize) {
bytesRecvd = buffSize - 1;
}
}
else { // inChar is the endMarker
waitingForResponse = false;
userResponse[bytesRecvd] = 0;
prevResponseMillis = currentMillis;
// do something now that response is received
}
The first part should be familiar - it just terminates the function if the code is not waiting for a response.
The second part terminates the function if there is no data in the Serial buffer - so the program is not hanging around wasting time.
The rest of the code only applies if there is at least one character in the Serial buffer.
First it reads one character into the temporary variable inChar.
Then it tests to see if this is the end-marker.
If it is NOT the end-marker the character is added to the userResponse array and the number of bytesRecvd is incremented.
Note the little bit of code to make sure bytesRecvd does not exceed buffSize. If that did happen data would be written all over parts of memory where it has no business being. This is a nice example of something that is unlikely to happen but would make a dreadful mess if it did.
If the received character IS the end-marker, the code changes waitingForResponse to false, adds a 0 in the next character position as the string terminator and updates prevResponseMillis to cause the code to wait before asking the question again.
Note that there is no code here to set the value of bytesRecvd back to 0 ready for the next response from the user. That is just in case other parts of the code might be interested to know how many bytes were actually received.
The best place for the code
bytesRecvd = 0;
is at the end of the function askForUserInput() - in other words immediately before the user responds.
Now we need a short digression to deal with something I forgot when setting out the original actions in Chapter 2. I have marked the place in the code with a comment. We need to do something when the response from the user has been received. So let's create a new function for that - and let's call it acknowledgeResponse().
But from where are we going to call that function?
One solution is to replace the comment with a call to the function. However that is not consistent with the general approach of calling all our functions from loop(). Putting the function in loop() ensures that we know it exists just by reading through loop(). So let's assume the function is called from loop(). This is the code for it - all very basic.
if (ackResponse == false) {
return;
}
Serial.println();
Serial.println("Thank you");
Serial.print("You entered the following ");
Serial.print(bytesRecvd);
Serial.println(" bytes ");
Serial.print(" --- ");
Serial.println(userResponse);
Serial.println();
ackResponse = true;
There is a new variable here - ackResponse - which is used to ensure that acknowledgeResponse() only gets run once for every completed response. Although we already have the variable waitingForResponse, it is not suitable because it will continue to be false through several iterations of loop() and we only want this new function to be called once. This variable is set to true when the complete response is received by replacing the comment with
ackResponse = true;
and it is immediately set back to false when acknowledgeResponse() has printed its message.
This new acknowledgeResponse() function was not part of the original plan and it nicely illustrates how easy it is to add additional actions with little or no impact on the rest of the code.
The full code is in LessonG.ino - it is now too long to be included directly in this post
...R
LessonG.ino (4.36 KB)