Serial Commands

Evan Boldt's picture

Introduction

Serial communication through USB can be used to communicate between a host computer (like a laptop, or Raspberry Pi), and an Arduino. Doing so allows:

  • Use of the faster hardware of the host computer for calculations
  • Synchronization of multiple Arduinos
  • Use of other information provided by the host computer, like internet connections or larger data files
  • Creation of a graphical interface using the host's display or web server

Communication

Communication is easy. Arduino and Python have simple methods for establishing serial communication.

Arduino

void setup() {
    Serial.begin(9600); //Initialize serial communication at 9600 baud
}
void loop() {
    Serial.println('hello');
}

Python

from serial import *
ser = Serial( "/dev/ttyACM0" , 9600 )

while 1:
    print ser.readline()

Incomplete Messages

One of the most difficult issues with this sort of serial communication is ensuring that commands are received in entirety. If you send "1000" from Python, and want Arduino to read it as shown below, Arduino might read "10" and then in another loop() right afterward read "00".

char incoming[11] = "          ";
int i = 0;
while (Serial.available() > 0) {
    // read the incoming byte:
    char[i] = Serial.read();
    i++;
}
//Convert the incoming string into an integer!
int newNumber = atoi(incoming);

Fixed Length Packets

One way to solve this in an efficient manner is to use a fixed length for commands and wait until there are enough bytes waiting in the buffer. The downside is that no commands may be issued with a length longer than the fixed length, and requires remaining length be wasted.

Python

ser.write('%10d' % 1000 ) #pad the number so it is 10 characters long

Now just add a condition so that no reading is done unless a full command is waiting in the serial buffer.

Arduino

if ( Serial.available() >= 10 ) {
    char incoming[11] = "          ";
    int i = 0;
    while (Serial.available() > 0) {
        // read the incoming byte:
        char[i] = Serial.read();
        i++;
    }
    //Convert the incoming string into an integer!
    newNumber = atoi(incoming);
}

Delineation

Another way to solve this problem is to delineate commands by placing a special character at the end of each command to signify that the command is complete. The problem with this way is that the length of the incoming string is unknown, and a long or improperly formed command could overflow a fixed length array. To overcome this limitation, a dynamic class is usually used. The String object is a good solution as it can be easily converted back into a character array. Maintaining data types with C++ strings can quickly become confusing, and it is extremely important that the delineation character is never used in the command. There are some special command characters in ASCII that would be good choices.

Multiple Command Types

You can send different commands over the same communication line by attaching a prefix to the command. For example, speed and position could be sent as "p000001000" and "s000000200" to tell the Arduino to go to position 1000 at a speed of 200.

Python

ser.write('p%9d' % 1000 ) #send position, padded to 9 (plus 1 command char)
ser.write('s%9d' % 200 )  #send speed, padded to 9 (plus 1 command char)

Arduino

int position, speed;
if ( Serial.available() >= 10 ) {
    //read the first character as the command
    char command = Serial.read();
    
    char incoming[10] = "         ";
    int i = 0;
    // read the incoming byte:
        char[i] = Serial.read();
        i++;
    }
    //Convert the incoming string into an integer, based on command
    if ( command == 'p' ) {
        position = atoi(incoming);
    }
    else if ( command == 's' ) {
        speed = atoi(incoming);
    }
    else {
        //The command was not one of the recognized characters
        Serial.println("COMMAND ERROR - Not recognized");
    }
}

Now that your communication is established, it is up to you to figure out what to do with the data on either end.