int main() {‎ > ‎

char *arduino_communication;

Handling two-way communication with an Arduino is something I have had to do with several projects. In this short article, I discuss and demonstrate the method I typically use to communicate from a Python Tkinter graphical user interface to an Arduino which is actively also performing another task besides waiting and responding to serial communication. 

Please download the example source code through by bitbucket repository. The source code files are available as a gzipped tar file: arduino-comm-example-1.1.tar.gz or as a zip file: arduino-comm-example-1.1.zip.

The Arduino Sketch

Communication is all about organization. I am assuming that the Arduino has other things to do besides monitor and respond to communication. If that is all the Arduino is doing, then the problem becomes much easier. Generally this is not the case. For the example online, I chose to have the Arduino blink the LED on pin 13 off and on in 1 second intervals. For you application, you may be controlling a motor, reading in sensor information, or even something more complex. In order to handle that task and communication, the Arduino must run through all the tasks it has to perform in every loop. The basic architecture for communication is, for each loop:
  1. Perform any duties the Arduino must perform each loop.
  2. Check for input from the serial line. If a full command is received then execute that command.
Since I tend to do two way communication with the Arduino, I tend to build up a short list of commands and responses. I use ASCII and tend to transmit back and forth at 9600 baud because usually I am not that concerned with speed, however I have used 115,200 baud and binary communication without any problems. Let's start with ASCII at 9600 baud.

In the setup() portion of the Arduino sketch, be sure to turn on serial communication with the command:

Serial.begin(9600);

I tend to define two functions for dealing with input. The first, void checkInput() is generic to all my sketches which do simple two-way ASCII only I/O over the serial line. This function checks to see if there is available input from the serial line. If there is, then it gets 1 character and places it on the buffer. The buffer is set to be 128 characters long. If the buffer is going to overflow, then it clears the buffer and issues an ERROR response which it sends down the serial line. If the character it receives is a newline character (10 in decimal) then the program has received a complete command. It creates a String holding the command and passes it to the second function, void parseAndExecuteCommand(String command). The second function needs to be rewritten for the commands of the particular Arduino project. Splitting up the input handling this way allows me to split the work into the generic input handling routines and the specific lexical parsing for that project. 

The void checkInput() function is shown below. 

/* This routine checks for any input waiting on the serial line. If any is
 * available it is read in and added to a 128 character buffer. It sends back
 * an error should the buffer overflow, and starts overwriting the buffer
 * at that point. It only reads one character per call. If it receives a
 * newline character is then runs the parseAndExecuteCommand() routine.
 */
void checkInput() {
  int inbyte;
  static char incomingBuffer[128];
  static char bufPosition=0;
  
  if(Serial.available()>0) {
    // Read only one character per call
    inbyte = Serial.read();
    if(inbyte==10) {
      // Newline detected
      incomingBuffer[bufPosition]='\0'; // NULL terminate the string
      bufPosition=0; // Prepare for next command
      
      // Supply a separate routine for parsing the command. This will
      // vary depending on the task.
      parseAndExecuteCommand(String(incomingBuffer));
    }
    else {
      incomingBuffer[bufPosition]=(char)inbyte;
      bufPosition++;
      if(bufPosition==128) {
        Serial.println("ERROR Command Overflow");
        bufPosition=0;
      }
    }
  }
}

An example of a simple void parseAndExecuteCommand(String command) function is shown below. This particular function only recognizes two commands:
  • CHECK : This command is issued in order to check the status of the connection. If the Arduino receives this command it should respond with "CONNECT".
  • QUERY : This command is issues in order to check the state of the LED which is blinking in the example. I store the current state of the LED in the global variable ledState. If the LED is on, then the program will return the word "HIGH" along the serial line. If the LED is off, then the program will return the word "LOW" along the serial line.
/* This routine parses and executes any command received. It will have to be
 * rewritten for any sketch to use the appropriate commands and arguments for
 * the program you design. I find it easier to separate the input assembly
 * from parsing so that I only have to modify this function and can keep the
 * checkInput() function the same in each sketch.
 */
void parseAndExecuteCommand(String command) {
  if(command.equals(String("CHECK"))) {
    // Check connection, respond with CONNECT
    Serial.println("CONNECT");
  }
  else if(command.equals(String("QUERY"))) {
    // Query state of the LED
    if(ledState==LOW) {
      Serial.println("LOW");
    }
    else {
      Serial.println("HIGH");
    }
  }
  else {
    // Unrecognized command
    Serial.println("ERROR Unrecognized Command");
  }
}

Given these two routines, we can now write the main loop of the Arduino sketch. It will follow the following pattern:

void loop() {
    /*
     * Insert non-communication code here.
     */

    checkInput(); // Now handle Serial I/O
}

For the example sketch, the main loop just checks to see if it is time to change the LED state, and does so if a second has passes since the last state change. This is shown below.

/* The loop is set up in two parts. Firs the Arduino does the work it needs to
 * do for every loop, next is runs the checkInput() routine to check and act on
 * any input from the serial connection.
 */
void loop() {
  long int currentTime;
  int inbyte;
  
  // Perform work to be done
  currentTime = millis();
  if(currentTime>=nextStateChange) {
    changeLEDState();
    nextStateChange = currentTime+1000l; // Note that is a lower case L at the end of 1000.
  }
  
  // Accept and parse input
  checkInput();
}

Information can also be sent from the Arduino back over the serial line. In order to demonstrate this, the void changeLEDState() function reports back over the serial line how many times its flashed ever 10 flashes using the CYCLE command.

void changeLEDState() {
  if(ledState==LOW) {
    ledState = HIGH;
    digitalWrite(13,HIGH);
    cycle_number++;
    if(cycle_number%10==0) {
      // Report every 10th cycle
      Serial.print("CYCLE ");
      Serial.println(cycle_number);
    }
  }
  else {
    ledState = LOW;
    digitalWrite(13,LOW);
  }
}
 

The Python Graphical User Interface



The user interface is shown above. At the top is a blank to specify the serial interface. On my mac the interface is /dev/tty.usbmodemfd321. This is just a file in the filesystem, so I was going to use an open file dialog to select this file, but you cannot open a file in the /dev directory from a standard open file dialog, so I had to leave a text entry blank. I found this annoying. 

Below that first row is a text area where information is printed. After clicking the connect button, you will receive the message "Arduino Connected" if the CHECK command was sent and a CONNECT response was received from the Arduino. I typically include a short waiting period (2 seconds) because I have noticed that after establishing the connection an immediate query leads to some unpredictable responses. The "Query" button sends the QUERY command to the Arduino. When a response is received it is interpreted and the message "LED is Off" or "LED is On" is printed to the text area. Finally a "Quit" button exits the program.

Note: This code requires the PySerial Python Serial Port Extension.

Starting Up

I divided the code up into two main classes. The CommunicationManager which handles communication back and forth to the Arduino, and the GuiClass which handles the user interface. I also initialize the queues they will use to communicate back and forth. Once I have instantiated and initialized these classes, I enter the Tkinter event loop. The CommunicationManager will take care of establishing communication once the serial port name is given, and will create the threads that handle input and output. The code to do this is below.

import sys
import Tkinter
import tkMessageBox
import threading
import Queue
import serial

def main(argv=None):
    if argv is None:
        argv = sys.argv
    
    inputQ = Queue.Queue()
    outputQ = Queue.Queue()
    manager = CommunicationManager(inputQ,outputQ)
    gui = GuiClass(inputQ,outputQ,manager)

    gui.go()

    return 0

The Tkinter Graphical User Interface

The GuiClass needs to remember to poll the input queue periodically, I have therefore made a method within the GuiClass which handles periodic polling of the input queue. It splits the input into tokens and then, based on the response, decides how to proceed. Note that after running, it schedules itself for another run in 100 milliseconds.

class GuiClass(object):
    # Download the example to see code I am not showing here
    def periodic_check(self):
        try:
            inp = self.inputQ.get_nowait()
            tokens = inp.split()
            if len(tokens)>=1:
                if tokens[0]=="CONNECT":
                    self.writeline("Arduino Connected")
                elif tokens[0]=="HIGH":
                    self.writeline("LED is On")
                elif tokens[0]=="LOW":
                    self.writeline("LED is Off")
                elif tokens[0]=="CYCLE":
                    self.writeline("tokens[1]+" LED Flashes Completed.")
                elif tokens[0]=="IOHALT":
                    self.writeline("IO threads halted")
                elif tokens[0]=="ERROR":
                    self.writeline("Error: "+' '.join(tokens[1:]))
                else:
                    self.writeline("Unrecognized response:")
                    self.writeline(inp)
        except Queue.Empty:
            pass
            # self.writeline("No Data")
        self.root.after(100, self.periodic_check)

Note that the GUI has to schedule the first periodic check as it enters the main loop. I put that in the GuiClass.go method.

    def go(self):
        self.root.after(100, self.periodic_check)
        self.root.mainloop()

For output the GuiClass just places complete commands on the output queue. These will be popped off the queue by the CommunicationManager and sent to the Arduino. For example, the query button executes the following method of GuiClass:

    def query(self):
        self.outputQ.put("QUERY")

The Communication Manager

The event driven GUI generates instructions for the Arduino which it places on the output queue, and receives information from the Arduino via the input queue, but it's the communication manager that handles the serial communication. We initialize the CommunicationManager class with only the input and output queues. Initially it is not even connected to the Arduino as it does not know the name of the serial port. I do not create worker threads to send and receive information to and from the Arduino until after a connection. The initialization method of the CommunicationManager class is shown below.

class CommunicationManager(object):
    def __init__(self,inputQ,outputQ):
        self.inputQ=inputQ
        self.outputQ=outputQ
        self.serialPort=None
        self.inputThread = None
        self.outputThread = None
        self.keepRunning = True
        self.activeConnection = False

Once a serial file name is entered and the connect key is pressed, the communication manager establishes a connection and starts the worker threads. Note that if the user decides to connect to a different arduino while the program is still running, the communication manager will have to stop the threads and close the connection. This is possible, and you will note that the connect method checks to see if there is already an active connection and closes it before opening a new one.

  def connect(self,filename):
        if self.activeConnection:
            self.close()
            self.activeConnection = False
        try:
            self.serialPort = serial.Serial(filename,9600)
        except serial.SerialException:
            self.inputQ.put("ERROR Unable to Connect to Serial Port")
            return
        self.keepRunning=True
        self.inputThread = threading.Thread(target=self.runInput)
        self.inputThread.daemon=True
        self.inputThread.start()
        self.outputThread = threading.Thread(target=self.runOutput)
        self.outputThread.daemon=True
        self.outputThread.start()
        self.activeConnection = True

The worker threads have very simple jobs. The input thread blocks on reading a serial input line until the entire line is received, it then strips the newline (and any other whitespace) off the end of the string and places the information on the input queue.

  def runInput(self):
        while self.keepRunning:
            try:
                inputline = self.serialPort.readline()
                self.inputQ.put(inputline.rstrip())
            except:
                # This area is reached on connection closing
                pass
        return

The output thread waits for information to be added to the output queue and then sends that to the Arduino, appending a newline.

    def runOutput(self):
        while self.keepRunning:
            try:
                outputline = self.outputQ.get()
                self.serialPort.write("%s\n"%outputline)
            except:
                # This area is reached on connection closing
                pass

        return

Note that both threads will continue to loop until self.keepRunning is False. When the close method is run, self.keepRunning is set to false, however that is not enough to stop the threads, which are most likely blocking for either input from the serial port or waiting for something to be placed on the output queue. In order to unblock the threads, we first close the serial port, which will cause the input thread to throw an exception. This exception is ignored and the input thread ends. For the output thread we need to place some junk on the queue. This will cause the output thread to try to write to a now closed serial port which will cause an exception which is ignored and then the thread returns. Two join commands wait for those processes to occur, before a signal is placed on the input queue indicating that the connection is closed. The close method is shown below:
 
  def close(self):
        self.keepRunning=False;
        self.serialPort.close()
        self.outputQ.put("TERMINATE") # This does not get sent, but stops the outputQ from blocking.
        self.inputThread.join()
        self.outputThread.join()
        self.inputQ.put("IOHALT")

Summary

Handling serial input and output from an Arduino is very straightforward. Using the techniques above it is possible for the Arduino to become a great intermediary between your computer and a host of sensors, electric, and mechanical devices outside your computer. Please download the example code by clicking on the appropriate link below.

 Gzipped tar source file: arduino-comm-example-1.1.tar.gz
 Zip file: arduino-comm-example-1.1.zip

Comments