Hello All,
I've been looking around for some advice on an efficient way of comparing character strings using boolean operators, and was hoping that someone could give me some words of advice...
The jist:
I have a serial interface set up where the arduino logs 5 character inputs from the serial monitor, using the following code (where 'letters' is an int initialized as 'int letters=0' and serial_command is a string 'char serial_command[6]':
if(Serial.available() == 1) //if there is a byte to read
{*
serial_command[(letters)]=Serial.read(); //write that character to the appropriate address of the string*
letters++;*
}*
if (letters == 5) //if we have collected a 5 letter word*
{*
letters=0; //reset letters to grab the next word*
Serial.println("serial_command is :");*
Serial.println(serial_command); //this is the word we received from the serial port*
In short, I want to add a series of "if" statements executing different commands if the word received matches the criteria. But boolean operators won't compare every element of a character string to every element of a criteria string. If there an effective way of doing this?
i.e. the following code won't work, but you get the idea of what i'm trying to do:
char hello_call[6]="hello"; if (serial_command == hello_call) {
//function to execute when 'hello' is written in the serial port* }
It would be possible to do something like:
boolean criteria == TRUE; for(i=0;i<=5;i++) { criteria=(serial_command && hello_call*) && criteria* } if (criteria) { * //function to execute* } This could compare all the elements of the arrays, and if they all match then the boolean variable 'criteria' afterwards would be true. This is really tedious and I hope there is a way to do this more simple and elegantly using functions and code that I do not know (I am not very familiar with the intricacies of serial communication...). Thanks! -NICK
There are C++ library functions for comparing null-terminated character strings. You may have to put in #include <strcmp.h>
strcmp(a, b) will return a 0 if the two strings are equal.
stricmp(a, b) will do the same but will also ignore case so "STRING" will be equal to "string".
You could also switch to String objects instead of character arrays. The String object (see the Arduino reference) has comparison methods and will even work with the == operator.
nicholas.masson:
...way of comparing character strings using boolean operators
Use the standard library function strcmp(). This function returns a value of zero if the two C-style "strings" are equal.
...In short, I want to add a series of "if" statements executing different commands if the word received matches the criteria. ...
I might do the following:
Set up an array of pointers to char. The array would be initialized with string literals that give the command name.
Then might use strcmp() in a loop to see whether the user input matches one of the strings in the table.
Then I might use a switch statement to call the appropriate function.
//
// All commnds consist of exactly five characters. It is assumed
// that there are no superfluous input characters like '\r' or
// '\n' or any other extraneous whitespace.
//
// davekw7x
//
void setup()
{
Serial.begin(9600);
}
char serial_command[6];
int letters;
//
// Table of valid command strings. Obviously, you would use
// names that make some kind of sense to the user. You might
// want to give a prompt that gives the user the names of
// acceptable commands.
//
const int numCommands = 3;
char *commands[3] = {
"cmnd1",
"cmnd2",
"cmnd3"
};
void loop()
{
if(Serial.available() > 0) //if there is a byte to read
{
serial_command[(letters)]=Serial.read(); //write that character to the appropriate address of the string
letters++;
}
if (letters == 5) //if we have collected a 5 letter word
{
letters=0; //reset letters to grab the next word
int i;
for (i = 0; i < numCommands; i++) {
if (strcmp(serial_command, commands[i]) == 0) {
Serial.print(serial_command);
Serial.print(" matches commands[");
Serial.print(i);
Serial.println("]");
break;
}
}
switch(i)
{
case 0:
f1();
break;
case 1:
f2();
break;
case 2:
f3();
break;
default:
// Invalid input. Give the user a clue.
Serial.print("No command matches ");
Serial.println(serial_command);
Serial.println("The valid commands are");
for (int i = 0; i < numCommands; i++) {
Serial.print(" ");
Serial.println(commands[i]);
}
Serial.println();
break;
}
Serial.println();
}
}
//
// Since there is a separate case label for each of the commands, the functions can
// be anything you want. For purposes of illustration, I'll just make them all the
// same footprint: Return type is void and there are no parameters.
//
void f1()
{
Serial.println(" This is f1");
}
void f2()
{
Serial.println(" This is f2");
}
void f3()
{
Serial.println(" This is f3");
}
Here's a run. First, I entered cmnd3, then I entered qwert:
[color=blue]cmnd3 matches commands[2]
This is f3
No command matches qwert
The valid commands are
cmnd1
cmnd2
cmnd3
[/color]
Wonderful. Thank you both. And Dave for the effort in writing a comprehensive sample code. It is gratifying to always learn new tools in programming rather than reverting to algorithms based on the fundamental operations I know.
Cheers,
-Nick
if(Serial.available() == 1) //if there is a byte to read
What happens if two characters arrive at the serial port before your code gets back to checking? You will never read another byte from the serial port, because the number of bytes to be read can never get back down to 1.
So, == in a Serial.available() is not a good idea. You should use >=, instead.
Also, when I originally wrote the loop to grab the values from the serial buffer, I was wondering if there is a way to retrieve/read ALL the current values in the buffer. I speculate that since Serial.read() deletes the current byte once it is read, then the next iteration of Serial.read() will read the next byte. So if you execute Serial.read() for however Serial.available() bytes there are, then you will eventually get all the bytes in the buffer and effectively flush it. But is there a way to read subsequent bytes WITOUT deleting them?
In other words, is there a more effective and elegant way of retrieving all the data in the buffer (so if the command is "hello" and those characters are sitting in the buffer, then I can retrieve all those characters at once?) Then when the buffer gets to, say 6 characters, then it can be flushed once it has been read...
In any case, just a thought. The code is functional and speed/memory isn't an issue in the program I am working with, so it's not a big deal... But I would love to learn a better way to deal with serial data than what I have figured out on my own.
Thanks!
I speculate that since Serial.read() deletes the current byte once it is read, then the next iteration of Serial.read() will read the next byte. So if you execute Serial.read() for however Serial.available() bytes there are, then you will eventually get all the bytes in the buffer and effectively flush it.
No speculation required. This is what happens.
But is there a way to read subsequent bytes WITOUT deleting them?
There is a peek() function that will allow you to look at the next byte, without removing it from the buffer, but only that one byte.
When you read the data from the buffer, store it in a character array, as dave's code is doing.
Then when the buffer gets to, say 6 characters, then it can be flushed once it has been read...
You can't tell, when the buffer contains 6 characters what those 6 characters are, without reading them. Once you've read them, there is no reason to flush the buffer.
At this point in your understanding, forget that the Serial class provides a flush() method. The flush() method throws away random amounts of data. There is almost never a need to use that function.
Here's a code snippet that will read chars from Serial, handle things like backspace, then use a jump table when a CR or LF is received to run a function.
// General ASCII defines
#define CR 0x0A
#define LF 0x0D
#define BACKSPACE 0x08
#define NULLCHAR '\0'
#define SPACE ' '
#define TAB '\t'
#define ESC 0x1b
typedef struct command {
char cmd[10];
void (*func) (void);
};
static int cmd_buffer_index = 0;
static char cmd_buffer[50];
static char last_cmd[50];
void Cmd_ONE () {
Serial.println ("\nCommand one");
}
void Cmd_TWO () {
Serial.println ("\nCommand two");
}
void Cmd_THREE () {
Serial.println ("\nCommand three");
}
command commands[] = {
{"one", Cmd_ONE},
{"two", Cmd_TWO},
{"three", Cmd_THREE}
};
const int N_COMMANDS = sizeof (commands)/ sizeof (command);
void ProcessCommand () {
int cmd;
boolean cmd_found = false;
Serial.println("");
/////////////////////////////////////////////////////
// trap just a CRLF
if (cmd_buffer[0] == NULLCHAR) {
Serial.print("> ");
return;
}
/////////////////////////////////////////////////////
// save this command for later use with TAB or UP arrow
memcpy(last_cmd, cmd_buffer, sizeof(last_cmd));
/////////////////////////////////////////////////////
// Scan the command table looking for a match
for (cmd = 0; cmd < N_COMMANDS; cmd++) {
if (strcmp (commands[cmd].cmd, (char *)cmd_buffer) == 0) {
commands[cmd].func();
cmd_found = true;
}
}
if (!cmd_found) Serial.println ("wtf?");
cmd_buffer_index = 0;
cmd_buffer[0] = NULLCHAR;
Serial.print ("> ");
}
void setup () {
Serial.begin (115200);
Serial.print ("Command entry code snippet\n> ");
}
void loop() {
char c;
if (Serial.peek() > 0) {
c = Serial.read();
if (ESC == c) {
while (Serial.available() < 2) {};
c = Serial.read();
c = Serial.read();
switch (c) {
case 'A': // up arrow
// copy the last command into the command buffer
// then echo it to the terminal and set the
// the buffer's index pointer to the end
memcpy(cmd_buffer, last_cmd, sizeof(last_cmd));
cmd_buffer_index = strlen (cmd_buffer);
Serial.print (cmd_buffer);
break;
}
} else {
c = tolower(c);
switch (c) {
case TAB:
memcpy(cmd_buffer, last_cmd, sizeof(cmd_buffer));
ProcessCommand ();
break;
case BACKSPACE:
if (cmd_buffer_index > 0) {
cmd_buffer[--cmd_buffer_index] = NULLCHAR;
Serial.print (BACKSPACE, BYTE);
Serial.print (SPACE);
Serial.print (BACKSPACE, BYTE);
}
break;
case LF:
case CR:
ProcessCommand ();
Serial.flush(); // remove any following CR or LF
break;
default:
cmd_buffer[cmd_buffer_index++] = c;
cmd_buffer[cmd_buffer_index] = NULLCHAR;
Serial.print (c);
}
}
}
}
It will also re-execute the last command when a TAB is received and recall the last command to the screen when it sees an UP arrow.
Too much work for 1-2 commmands but of course the code remains the same for 10s or 100s of commands, just add functions and entries in the command array.
I've been using a variation on this for quite a while as a UI for a project, it works well.
The code above is a stand-alone snippet, it should paste into the IDE and run.
PaulS:
So, == in a Serial.available() is not a good idea. You should use >=, instead.
Might I respectfuly suggest that your reply might encompass best practise C programming style. In other words >= is not necessary. All you need to do is if(Serial.available()) . Serial.available() is a function that returns a zero value if there's nothing to read. Hence, the test: if(0) will fail.
Might I respectfully suggest that that is NOT a best practice. For one thing, it makes Serial.available() look like it returns true/false when that is NOT the case. For another, there are times when it is important to do something only when a specific number of characters, or more, is available. If all the examples show Serial.available() used like this, the fact that Serial.available() actually returns the number of characters available gets lost.
I think that EXPLICITLY checking that Serial.available() > 0 or >= 1 is a much better way to show intent.
In any case, I wanted the original poster to understand that an explicit equality check was not a good idea.
Is only true if you want to read one char; besides it works only because 0 == false. For other functions such shorthand may fail , e.g. if(strcmp(a,b)) sounds like the strings are equal 'if they compare' but the opposite is true.
I think that EXPLICITLY checking that Serial.available() > 0 or >= 1 is a much better way to show intent.
Agree, its the only way to write maintainable code. And from the two variations above the >= 1 is the better one (imho) as it mentions the exact number to be processed in the if clause.