Project tutorial
Simple Command Line Interface

Simple Command Line Interface © GPL3+

You need to send a command to Arduino.

  • 8,210 views
  • 4 comments
  • 7 respects

Components and supplies

About this project

Command Line

There comes a time when you need to find the value of a sensor or you want to tell your robot to do something like "move left". Maybe you need to ask your program for the runtime value of a variable, or you need to set the value of a digital potentiometer.

What you need is a command line. Yes, it's easy to get your Arduino to respond to text commands.

Here is one you can build on. It's a simple, fast, and a very memory efficient Command Line Interface (CLI) that you can cut and paste in a few minutes into your own code and be up and running. Commands consist of a name followed by multiple arguments. It even supports backspace when you are typing your commands in.  

Quick Overview

Every sketch has a loop() function. Yours could be as as simple as the one below. It calls two routines in a separate tab named CommandLine.h. I'll walk you through these two files and you'll be up and running.

Loop

What it does:  Every time loop() runs it checks to see if we have a command from the serial port, calling getCommandLineFromSerialPort(). The variable CommandLine is declared in CommandLine.h but stylistically you might want to move it to the main Loop tab. When a full command has arrived to the Serial port and has been copied to the command buffer:

        char   commandLine[COMMAND_BUFFER_LENGTH + 1];

Then loop() calls DoMyCommand() to execute the proper command. 

Now let's take a look at what's in CommandLine.h. I should point out that I put all the code into CommandLine.h, because then all you have to do is cut and paste this code into a new tab in your Arduino IDE (be sure to give the tab a name that ends with ".h"). Then include this file in your main file, i.e.

 #include "CommandLine.h"

What this does is allow you to put all the command line code in one tab and yet refer to its routines elsewhere in your program without any extra code.

Looking in CommandLine.h

The file CommandLine.h is included at the end of this post. Within CommandLine.h, each line that you need to modify is marked with the comment, //Modify here. The file includes two sample commands add and sub and shows how they are called from within DoMyCommand.

For many of you, that's all you'll need. Just go through CommandLine.h. For those who would like a more detailed look, keep reading.

Looking Deeper

Within CommandLine.h we first we include <string.h>.  String.h is a C standard library. If you haven't run into C libraries before do a quick Internet Search for "The C Programming Language". The C bible was written years ago by Brian Kernigan and Dennis Ritchie and has been kept up to date. Most people own a copy but you can find it online for free.  

We'll only use the strtok() routine (string to token) from <string.h>. This routine reads a token, that is, a word delimited by certain characters (the second parameter of strtok). It works like this.

  • When you first call it, you pass it a string ptr and it will return the first token
  • On subsequent calls, (here comes the glorious hack part) pass it NULL instead of a string ptr and it will continue where it left off with the initial string, thus getting one token (roughly a word) at a time.

We also include <stdlib.h> from which we only use atoi() for ascii-to-integer conversion. Don't worry, the compiler will only include this one routine not the whole library but you might want to check out the other routines in these libraries as they are useful.

Next is an optional little macro I wrote called print2:

#define print2(x,y) (Serial.print(x), Serial.println(y)

I'm always wanting to print out a label and a string. You use it like this:

print2("myVar = ", myVar);

If myVar is 25 then this will print to the serial window:

myVar = 25

CommandLine.h contains getCommandLineFromSerialPort() which assembles a command line from the serial port. Each time it's called it reads from the Serial port and stores the input into the global input buffer, CommandLine[]. When it reaches a return character signifying the end of the command it returns true.

The fully assembled buffer is now passed to DoMyCommand() which figures out which function the user is requesting and calls it.  With a large number of commands you can get a fairly unwieldy if-the-else statement. 

If you have a truly enormous number of commands there are a lot of ways to speed things up like adopting what is called a hashing function. Alternatively you can make the commands only one character and then use that one character as the label of a switch-case statement. I find neither of these is necessary very often. A word is much easier to remember than a single character and given that this is an Arduino, how many commands can you really have before you run out of room on the chip?

Add and Sub

Each command function is responsible for parsing out it's own arguments. This is a simple way to do it and is easy enough to modify. An alternative to this is to read all the arguments immediately in DoMyCommand. You could put individual arguments into a global array of strings argv[].

For the purposes of this example I have defined two commands: add and sub.  Both of these use numeric arguments but I've included readWord (which could be called readStringToken) to return a word. You could also modify this readStringToken to allow strings such as "this is a string". Consider it an exercise left to the reader.

The fastest way to be running

If you have a main loop file as shown above, then create a new tab using the downward pointing triangle way over on the right of your Arduino IDE window, and copy CommandLine.h (below) file into it, you should be able to type add and sub commands.  

Now it's up to you!

Code

Simple Command Line Interpreter for ArduinoC/C++
See the comments in this article or in the code to add a simple command line to your Arduino sketch.

You can add commands like this:
add 5, 10
subtract 10, 5

Or anything else you need
/*****************************************************************************

  How to Use CommandLine:
    Create a sketch.  Look below for a sample setup and main loop code and copy and paste it in into the new sketch.

   Create a new tab.  (Use the drop down menu (little triangle) on the far right of the Arduino Editor.
   Name the tab CommandLine.h
   Paste this file into it.

  Test:
     Download the sketch you just created to your Arduino as usual and open the Serial Window.  Typey these commands followed by return:
      add 5, 10
      subtract 10, 5

    Look at the add and subtract commands included and then write your own!


*****************************************************************************
  Here's what's going on under the covers
*****************************************************************************
  Simple and Clear Command Line Interpreter

     This file will allow you to type commands into the Serial Window like,
        add 23,599
        blink 5
        playSong Yesterday

     to your sketch running on the Arduino and execute them.

     Implementation note:  This will use C strings as opposed to String Objects based on the assumption that if you need a commandLine interpreter,
     you are probably short on space too and the String object tends to be space inefficient.

   1)  Simple Protocol
         Commands are words and numbers either space or comma spearated
         The first word is the command, each additional word is an argument
         "\n" terminates each command

   2)  Using the C library routine strtok:
       A command is a word separated by spaces or commas.  A word separated by certain characters (like space or comma) is called a token.
       To get tokens one by one, I use the C lib routing strtok (part of C stdlib.h see below how to include it).
           It's part of C language library <string.h> which you can look up online.  Basically you:
              1) pass it a string (and the delimeters you use, i.e. space and comman) and it will return the first token from the string
              2) on subsequent calls, pass it NULL (instead of the string ptr) and it will continue where it left off with the initial string.
        I've written a couple of basic helper routines:
            readNumber: uses strtok and atoi (atoi: ascii to int, again part of C stdlib.h) to return an integer.
              Note that atoi returns an int and if you are using 1 byte ints like uint8_t you'll have to get the lowByte().
            readWord: returns a ptr to a text word

   4)  DoMyCommand: A list of if-then-elses for each command.  You could make this a case statement if all commands were a single char.
      Using a word is more readable.
          For the purposes of this example we have:
              Add
              Subtract
              nullCommand
*/
/******************sample main loop code ************************************

  #include "CommandLine.h"

  void
  setup() {
  Serial.begin(115200);
  }

  void
  loop() {
  bool received = getCommandLineFromSerialPort(CommandLine);      //global CommandLine is defined in CommandLine.h
  if (received) DoMyCommand(CommandLine);
  }

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

//Name this tab: CommandLine.h

#include <string.h>
#include <stdlib.h>

//this following macro is good for debugging, e.g.  print2("myVar= ", myVar);
#define print2(x,y) (Serial.print(x), Serial.println(y))


#define CR '\r'
#define LF '\n'
#define BS '\b'
#define NULLCHAR '\0'
#define SPACE ' '

#define COMMAND_BUFFER_LENGTH        25                        //length of serial buffer for incoming commands
char   CommandLine[COMMAND_BUFFER_LENGTH + 1];                 //Read commands into this buffer from Serial.  +1 in length for a termination char

const char *delimiters            = ", \n";                    //commands can be separated by return, space or comma

/*************************************************************************************************************
     your Command Names Here
*/
const char *addCommandToken       = "add";                     //Modify here
const char *subtractCommandToken  = "sub";                     //Modify here


/*************************************************************************************************************
    getCommandLineFromSerialPort()
      Return the string of the next command. Commands are delimited by return"
      Handle BackSpace character
      Make all chars lowercase
*************************************************************************************************************/

bool
getCommandLineFromSerialPort(char * commandLine)
{
  static uint8_t charsRead = 0;                      //note: COMAND_BUFFER_LENGTH must be less than 255 chars long
  //read asynchronously until full command input
  while (Serial.available()) {
    char c = Serial.read();
    switch (c) {
      case CR:      //likely have full command in buffer now, commands are terminated by CR and/or LS
      case LF:
        commandLine[charsRead] = NULLCHAR;       //null terminate our command char array
        if (charsRead > 0)  {
          charsRead = 0;                           //charsRead is static, so have to reset
          Serial.println(commandLine);
          return true;
        }
        break;
      case BS:                                    // handle backspace in input: put a space in last char
        if (charsRead > 0) {                        //and adjust commandLine and charsRead
          commandLine[--charsRead] = NULLCHAR;
          Serial << byte(BS) << byte(SPACE) << byte(BS);  //no idea how this works, found it on the Internet
        }
        break;
      default:
        // c = tolower(c);
        if (charsRead < COMMAND_BUFFER_LENGTH) {
          commandLine[charsRead++] = c;
        }
        commandLine[charsRead] = NULLCHAR;     //just in case
        break;
    }
  }
  return false;
}


/* ****************************
   readNumber: return a 16bit (for Arduino Uno) signed integer from the command line
   readWord: get a text word from the command line

*/
int
readNumber () {
  char * numTextPtr = strtok(NULL, delimiters);         //K&R string.h  pg. 250
  return atoi(numTextPtr);                              //K&R string.h  pg. 251
}

char * readWord() {
  char * word = strtok(NULL, delimiters);               //K&R string.h  pg. 250
  return word;
}

void
nullCommand(char * ptrToCommandName) {
  print2("Command not found: ", ptrToCommandName);      //see above for macro print2
}


/****************************************************
   Add your commands here
*/

int addCommand() {                                      //Modify here
  int firstOperand = readNumber();
  int secondOperand = readNumber();
  return firstOperand + secondOperand;
}

int subtractCommand() {                                //Modify here
  int firstOperand = readNumber();
  int secondOperand = readNumber();
  return firstOperand - secondOperand;
}

/****************************************************
   DoMyCommand
*/
bool
DoMyCommand(char * commandLine) {
  //  print2("\nCommand: ", commandLine);
  int result;

  char * ptrToCommandName = strtok(commandLine, delimiters);
  //  print2("commandName= ", ptrToCommandName);

  if (strcmp(ptrToCommandName, addCommandToken) == 0) {                   //Modify here
    result = addCommand();
    print2(">    The sum is = ", result);

  } else {
    if (strcmp(ptrToCommandName, subtractCommandToken) == 0) {           //Modify here
      result = subtractCommand();                                       //K&R string.h  pg. 251
      print2(">    The difference is = ", result);

    } else {
      nullCommand(ptrToCommandName);
    }
  }
}

Comments

Similar projects you might like

Sienci Mill One - Simple and Affordable Desktop CNC

Project tutorial by Sienci Labs

  • 17,983 views
  • 2 comments
  • 72 respects

Simple UNO calculator

Project tutorial by Joprp05

  • 6,351 views
  • 0 comments
  • 6 respects

Simple Object avoider robot the using Actobotics Runt

Project tutorial by Scott Beasley

  • 7,211 views
  • 4 comments
  • 17 respects

A Simple Light Follower

Project tutorial by a_guadalupi

  • 5,519 views
  • 1 comment
  • 9 respects

GLCD simple house

Project showcase by soso artist

  • 656 views
  • 0 comments
  • 1 respect
Add projectSign up / Login