Plotting serial port data in real time using python and Matplotlib

It’s useful to be able to read and plot serial data in real time (for example, you might want to monitor the output of a laser scanner or IMU). While this is a trivial task in MATLAB or LabVIEW, I wondered if there was a low effort way to do it for free.

I’ve known for a while that Python has an easy-to-use serial library, but I wasn’t sure what kinds of plotting/graphing options might exist for Python. A quick search turned up Matplotlib – a MATLAB-like plotting API for Python. As it turns out, Matplotlib includes an animation API and a function called FuncAnimation, which can be used to animate data over time (or update a graph with some sensor data over time).

I’m using this page to document my attempt(s) to use Matplotlib to create a real time graph of data read from a serial port. For a proper introduction to Matplotlib, I’d recommend sentdex’s Matplotlib video series.

Items used

  • Laptop or PC running Ubuntu 18
  • Python 3.x
  • Matplotlib
  • pyserial
  • Arduino (or any programmable device with a serial port)

Installing matplotlib and pyserial on Ubuntu 18

sudo apt-get install python3-matplotlib
sudo apt-get install python3-serial

Generating some fake serial data with an Arduino

To test my code, I used an Arduino to put some data on the serial port. In the example code below, the arduino simulates a coin toss using the function random.

void setup() {
  Serial.begin(9600);

}

int i = 1;           //ith trial
int randNo = 1;      //random integer between 0 or 1 (say, 0 = heads, 1 = tails)
int sumTails = 0;   //add up all occurances of tails and store in sumTails
float relFreq = 0;   //compute and store relative frequency of tails here

void loop() {
  randNo=random(0,2);                    //generate random int between 0 and 1
  sumTails = sumTails + randNo;          //update tails tally
  relFreq = float(sumTails)/float(i);    //update relative frequency

  //report results: ,,
  Serial.print(i);
  Serial.print(',');
  //Serial.print(sumTails);
  //Serial.print(',');
  Serial.print(relFreq);
  Serial.print('\n');
  
  i=i+1;                                 //update ith trial
   
  delay(1000);                           //define update rate

}

On each iteration of the main loop, a random integer between 0 and 1 is generated, and the relative frequency of getting one side of the coin or the other is updated. This data is then sent to the serial port as comma delimitted line, where the termination character is ‘/n’. That is, the serial data looks like this:

The idea is that I’m putting some data on a serial port over time, and now I can write a python script to read and plot it.

Creating the real time plot

import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib import style
import numpy as np
import random
import serial

#initialize serial port
ser = serial.Serial()
ser.port = '/dev/ttyACM0' #Arduino serial port
ser.baudrate = 9600
ser.timeout = 10 #specify timeout when using readline()
ser.open()
if ser.is_open==True:
	print("\nAll right, serial port now open. Configuration:\n")
	print(ser, "\n") #print serial parameters

# Create figure for plotting
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
xs = [] #store trials here (n)
ys = [] #store relative frequency here
rs = [] #for theoretical probability

# This function is called periodically from FuncAnimation
def animate(i, xs, ys):

    #Aquire and parse data from serial port
    line=ser.readline()      #ascii
    line_as_list = line.split(b',')
    i = int(line_as_list[0])
    relProb = line_as_list[1]
    relProb_as_list = relProb.split(b'\n')
    relProb_float = float(relProb_as_list[0])
	
	# Add x and y to lists
    xs.append(i)
    ys.append(relProb_float)
    rs.append(0.5)

    # Limit x and y lists to 20 items
    #xs = xs[-20:]
    #ys = ys[-20:]

    # Draw x and y lists
    ax.clear()
    ax.plot(xs, ys, label="Experimental Probability")
    ax.plot(xs, rs, label="Theoretical Probability")

    # Format plot
    plt.xticks(rotation=45, ha='right')
    plt.subplots_adjust(bottom=0.30)
    plt.title('This is how I roll...')
    plt.ylabel('Relative frequency')
    plt.legend()
    plt.axis([1, None, 0, 1.1]) #Use for arbitrary number of trials
    #plt.axis([1, 100, 0, 1.1]) #Use for 100 trial demo

# Set up plot to call animate() function periodically
ani = animation.FuncAnimation(fig, animate, fargs=(xs, ys), interval=1000)
plt.show()

References

  • https://pythonhosted.org/pyserial/shortintro.html
  • https://www.youtube.com/watch?v=ZmYPzESC5YY
  • https://matplotlib.org/
  • https://matplotlib.org/api/_as_gen/matplotlib.animation.FuncAnimation.html#matplotlib.animation.FuncAnimation

About the author

Comments

  1. Hi Mike!

    I am working on very similar live-printing figures. I have composed very similar code; however, I noticed that over time (after about 3 hours), the figure starts to lag, and I start to get the “not responding” message at the top of the figure. Unfortunately, I would prefer this figure to run for about a week, so you can imagine my frustration when I saw it start to lag before the end of the first day. Is this type of animation optimized for speed and long term usage?

    Thank you so much for the example! It was great help!

    1. Hi Parker,

      Thank you for the comment! Good to know others have an interest in this.

      I wrote this mostly to test the idea of using python to create a real-time plot of serial data, similar to a LabVIEW Chart. I haven’t done any testing to look at performance. From your comment, I wonder if the lag issue you’re seeing is a buffer issue? One approach might be to plot only the last 100 points or so. If you need the rest of the data, you could write it to a log file.

  2. Hello,
    very nice Post 😀

    I have a question. I get two coordinates from a sensor (x and y). I want to plot the coordinates like a 2D-Map. But im able to plot just one coordinate (y-axis) over the trials i (x-axis). But i want to Plot the x-Values to the x-axis and the y-values to the y-axis. Is this possible via Serial Plot ?

  3. Hi Mike,

    I am working on very similar live-printing figures, in my case for acceleration sensor data plotting. In the code I developed i was constantly getting the same output, python stops to work. I saw your publication and aapted mine to your approch(I think is better solved) but i still get the same output the figure is a blank and python crashes. Any idea why this could be happening? may be because of a too high baud rate?

  4. Hi, I recently wrote a generic application for this called python-datamonitor
    (https://github.com/bablokb/py-datamon). The datamonitor can create multiple subplots at the same time, every subplot can have multiple data. It supports plotting csv-data or live-data piped into the application. Configuration is via a simple json-file, so no programming is necessary.

    BTW: the plotting-code uses under the hood the same techniques.

  5. Hi Mike,

    Thanks so much for your tutorial. It was very helpful for getting started with graphing serial data.
    I was tryng to plot the tilt angle of an Arduino, and found that after some time my graph was lagging from the real angle.

    I think the problem was with the animate function reading the serial data, which gets the buffer accumulated after some time.
    I was able to solve it by reading the data in an independent thread. Hopefully can also help other people.

    def animate(n,yangle,x):
    ax.clear()
    ax.plot(yangle,x,color=’purple’)

    def readingFunction():
    global serialPort,lineRead
    while True:
    try:
    global x,i,yangle
    line=serialPort.readline()
    lineSplit = line.split(b’\n’)
    angle = float(lineSplit[0])
    yangle.append(angle)
    x.append(i)
    i=i+1

    readingThread=threading.Thread(target=readingFunction)
    readingThread.start()
    ani = animation.FuncAnimation(fig, animate, fargs=(x, yangle), interval=10)
    plt.show()

Leave a Reply to Bernhard Cancel reply

Your email address will not be published. Required fields are marked *