Introduction: Plotting Real-time Data From Arduino Using Python (matplotlib)

Arduino is fantastic as an intermediary between your computer and a raw electronic circuit. Using the serial interface, you can retrieve information from sensors attached to your Arduino. (You can also send information via the serial interface to actuate circuits and devices (LEDs, relays, servos, etc.) connected to your Arduino.) Once you have the data in your computer, you can do all sorts of things with it – analyze it, display it, or share it on the internet, for instance.

In this instructable, I will be reading and displaying analog data from a pair of LDRs connected to an Arduino. Attached is the schematic.

The Arduino sketch is very simple – it just reads the values from analog pins A0 and A1 (in the range [0, 1023]) and prints it to the serial port.

Here is the code:

The serial port sends values in the format:

512 300
513 280
400 200

On the computer side, I need to read these values, and plot them as a function of time. I am using Python and the Matplotlib library for this. I wanted to display this as a scrolling graph that moves to the right as data keeps coming in. For that, I am using the Python deque class to keep and update a fixed number of data points for each time frame.

You can see the full implementation here:


RitwikaM made it!(author)2016-05-26

Hello Elecronut,

Thank you for the code. I am very new to this, so I have been facing quite a few issues with running the code. I keep getting errors while running the argparse command. I replaced port with COM3 like below, but keep getting errors!

def main():

# create parser

parser = argparse.ArgumentParser(description="LDR serial")

# add expected arguments

parser.add_argument('--COM3', dest='COM3', required=True)

Error in python:

usage: [-h] --COM3 COM3 error: argument --COM3 is required

My problem is I am not sure where to replace strPort or port with COM3.

Please help!



electronut made it!(author)2016-05-26

Without changing the code, run it as:

>python --port COM3

What is the output?

RitwikaM made it!(author)2016-05-27

Thanks, it works!!! I am trying now to add the data to a csv file. Thank you for all the help again!

fmarengo made it!(author)2017-07-12

Hi RitwikaM

Did you happen to execute the python script from a python editor (e.g Spyder)? If your answer is yes, could you please send me your code?
I would like to modify the code so as to plot ONE data series from port COM10. Also, I would like to save the data series on the local HDD.

electronut made it!(author)2016-05-27


fmarengo made it!(author)2017-07-12

Dear electronut,

I tried to implement your code in my PC using Spyder editor, but unfortunately it gives me an error (see at the bottom). I also replaced

strPort = args.port


strPort = "COM10"

since COM10 is the port I am using in my PC (Windows7 64bits). Even in this case, the error is the same. How can I solve it from the editor, please? Thanks!

usage: [-h] --port PORT error: argument --port is required
An exception has occurred, use %tb to see the full traceback.
SystemExit: 2
Dylan+TaylorA made it!(author)2017-02-11

Works 'out of the box' which is seriously just fantastic in the world of analog and digital communication. Serial port can be found by looking in your arduino IDE under tools-> serial port. for me it was /dev/ttyACM0

Great work!

aghosca made it!(author)2017-01-24

Great! How to reverse the flow of the graph from right to left?

cristhianc12 made it!(author)2016-10-17

good day apologize for my English, I'm from Colombia I am currently developing my thesis project and have been almost impossible to create the code acquisition 4 sensors, I wonder if I can use some of their code in my project. and I wonder if there is any way to predefine the port COM6 not run the program from the console. thank you very much for contribution.

Guddi11 made it!(author)2016-09-06


I also tried your code and I also get a nice Tk Window, BUT no plot…
apparently analogPlot.update is not called or at least does not do
anything. I already tried some things to fix it, but without success.
Any hint?

Could it be the backend, that doesn’t want to show me anything?

Otherwise, a nice, easy and understandable code!!! Well done!

Thanks in advance.


Guddi11 made it!(author)2016-09-07

Woohooo! I made it - it was only some wrong or missing dependencies between matplotlib and Tkinter. Found some nice solution for that here:

Thanks again!


batmanDIY made it!(author)2016-06-16

Awesome....neat and quick way without using octave or matlab

Screenshot from 2016-06-17 01-02-59.png
russ_hensel made it!(author)2016-01-23

I love the combination of Python and the Arduino. So I have created a collection about it. I have added your instructable, you can see the collection at: >>

JordiG2 made it!(author)2015-06-16

Hello elecronut,

Thank you for sharing. I like your project and I will use with my robot.

I have a ultrasonic attached to a servo to mapping obstacles. I send to serial port to numbers: angle of the servo and detected distance. Now, I want to graphic the obstacles.

I try your code but I need a XY praph. I mean, use the first number (angle) for X and the second number (distance) as Y.

I tried to make some modification but without any success.

Could you give me any suggestions?

Thank you in advance!

electronut made it!(author)2015-06-16


You need to specify the axes data differently. Take a look at the matplotlib tutorial below:


JordiG2 made it!(author)2015-06-17

Thank you!

Now I can see the problem. I have made some changes and... it works :)!

DanielleC4 made it!(author)2015-11-25

Hello, I am currently working with the same thing but in a different project. I am just a newbie in python programming would you mind if I ask a copy of the changes you made to make the x axis have a different value instead of time or could you tell me how? I am trying to plot x-axis as velocity and y-axis as position for a phase plot in my case study. Thank You in Advance.

By the way I've already tried this line: a1,=ax.plot([0], [1]), 0-velocity and 1-position, however i didn't get anything on the plotting window.

electronut made it!(author)2015-11-26


The axes can be anything. I've created an example for you - an animation that shows an oscillating circle:

For phase plots, also take a look at:

DanielleC4 made it!(author)2015-11-29

Thank you for the example you've made, i will try this in my vertical spring oscillation with arduino, hoping it would work :)

electronut made it!(author)2015-11-30

Great, good luck!

electronut made it!(author)2015-06-17


ice_lin made it!(author)2015-11-18

Hello, electronut,

Thanks for your tutorial. This code works.

However, when my arduino device writes the serial at 200Hz, I notice there is a significant delay (about 7 seconds) of drawing.

I have tried to reduce the interval to 1, but it does not solve this problem.

is there any way to reduce such delay?

electronut made it!(author)2015-11-18


I don't think the plot will be able to keep up at 200 Hz. Have you tried blit=True?

ice_lin made it!(author)2015-11-19

yes, I have tried blit=true, it helps, but does not solve it completely.

I was wondering:

1)what is the reason for such degraded performance? what is the bottleneck?

2) is there any way to draw real data sampled at high frequency, i.e., 300 Hz

electronut made it!(author)2015-11-19

Matplotlib I don't think was designed to update animations at the rate you mentioned. If you need to draw things that update very fast, you'll have to look a low level API - something which uses OpenGL for example. Here's one:

agu3rra made it!(author)2015-09-19

Made it work. I find it odd, that the performance degrades when removing the delay between reads. I'll try to change the code for a single Ain read in order to see if I can increase the baud rate on the serial comms to get higher sampling rates. Thanks for your post! :)

electronut made it!(author)2015-09-19


putz182 made it!(author)2015-08-26

The code work, but when I put blit=True for better performance, I get overlapped plots. Probably missing init_func?

electronut made it!(author)2015-08-26

Probably the older frame is not getting cleared in the animation. I did not use this flag because it wasn't working well across different OS.

putz182 made it!(author)2015-08-26

I've made a similar program but with 6 subplots (12 arduino variáveis). It is almost impossible to run without blit. I think init function can fix it since it works for a single plot, but I have no idea how to perform it since init should stay inside the class and must receive some parameters. The documentation doesn't cover how to use init_func with parameters. Can you point some direction or how to perform serial communication outside a class.

electronut made it!(author)2015-08-26

I am not sure what you mean by init_func. As for serial comms, you can just take the code out of the class:

ser = serial.Serial(strPort, 9600)

line = ser.readline()

# close when done



mba7 made it!(author)2014-09-21

I made an equivalent tool in python that print real time data from ADXL345 accelerometer.

may be it will be helpful for someone

MauiJerry made it!(author)2015-08-12

sweet!!! was just setting up to do just such an adxl345 project (actually 2 of them but thats advanced version). Will be back with more comments as I progress

An%C4%B1l%C3%961 made it!(author)2015-06-23

Hello electronut,

I am trying to plot x and y axis of my joystick sensor.But I didn't understand this line;

parser.add_argument('--port', dest='port', required=True)

What should I write to '--port' section?

Thank you

electronut made it!(author)2015-06-23


The --port argument is for your serial port name. (For eg. something like COM3 under windows or /dev/tty.* under OS X) One easy way to find this information is look under Tools->Port in the Arduino software.


rohankotwani made it!(author)2015-04-11

I really have finally finished making this work. I would like to thank @bartons2 for asking those noob questions. They really helped me.. Everything to make this work is in this comments.

I used -h

(1)I used this to list the ports and find the one I needed. (this is a given from documentation). (2) I used this to figure out how to output the port. I also have to make sure the output data on serial screen was in correct format.

My ploy data is analog, and I was originally receiving input every 33 mili-seconds. The plot could not keep up. I lowered it to 150.

I will now try to figure out how to make it discrete. The plot is of a position sensor, and I just put my hand in front of it.

Wrrr+10-G made it!(author)2014-11-30

Hello Electronut,

Thanks for this excellent foolproof ;) howto, I had my 5 MQ135 gas sensors up and running in no time.
you point me in the right direction if I wanted to have your program
additionally store / log the values it receives from the serial device,
in a separate log file?

Thanks for your time, \W.

electronut made it!(author)2014-11-30

I am glad my code was useful to you. For logging data, you can do something like this:

>>> f = open('log.txt', 'w')
>>> str = "10 100\n"
>>> f.write(str)
>>> f.close()
>>> exit()

Do the open() right after serial.Serial() and do the file write in update() after retrieving line from serial port.

Hope that gets you started. Good luck!

Wrrr+10-G made it!(author)2015-03-01

Dear electronut,

It's bin a while, I ordered some new MQ135's and started my project again last week. The plotting still works fine with your original program. However, I have entered the code lines to additionally log to a file as you suggested but "NameError: global name 'f' is not defined" keeps bugging me. I am doing some very basic thing wrong, but my mileage is way too low to figure it out myself. Could you help me out?
Not sure I'm allowed to paste the code in this box, but here goes nothing.. My alterations are commented with double hashes ##.


Display analog data from Arduino using Python (matplotlib)
... and log the serial output to a file as well
5 MQ135 airquality sensors simultaneous measuring
Author: Mahesh Venkitachalam


import sys, serial, argparse, threading
import numpy as np
import time
from time import sleep
from datetime import datetime
from collections import deque

import matplotlib.pyplot as plt
import matplotlib.animation as animation

# plot class
class AnalogPlot:
  # constr
  def __init__(self, strPort, maxLen):
      # open serial port
      self.ser = serial.Serial(strPort, 9600)
      f = open('probe.log', 'w')  ## open log file, write "w" or append "a"
      str="a0+' '+a1+' '+a2+' '+a3+' '+a4+'\n" ## parse serial string to be logged
      self.av = deque([0.0]*maxLen) = deque([0.0]*maxLen) = deque([0.0]*maxLen)
      self.ay = deque([0.0]*maxLen) = deque([0.0]*maxLen)
      self.maxLen = maxLen

  # add to buffer
  def addToBuf(self, buf, val):
      if len(buf) < self.maxLen:

  # add data
  def add(self, data):
      assert(len(data) == 5)
      self.addToBuf(self.av, data[0])
      self.addToBuf(, data[1])
      self.addToBuf(, data[2])
      self.addToBuf(self.ay, data[3])
      self.addToBuf(, data[4])

  # update plot
  def update(self, frameNum, a0, a1, a2, a3, a4):
          f.write(str)  # write to log
          f.close()     # close log
          exit()        # exit logwrite (?)
          line = self.ser.readline()
          data = [float(val) for val in line.split()]
          # print data
          if(len(data) == 5):
              a0.set_data(range(self.maxLen), self.av)
              a3.set_data(range(self.maxLen), self.ay)
      except KeyboardInterrupt:

      return a0,

  # clean up
  def close(self):
      # close serial

# main() function
def main():
  # create parser
  parser = argparse.ArgumentParser(description="LDR serial")
  # add expected arguments
  parser.add_argument('--port', dest='port', required=True)

  # parse args
  args = parser.parse_args()

  #strPort = '/dev/ttyACM0'
  strPort = args.port

  print('reading from serial port %s...' % strPort)

  # plot parameters
  analogPlot = AnalogPlot(strPort, 5000)

  print('plotting data...')

  # set up animation
  fig = plt.figure()
  ax = plt.axes(xlim=(0, 5000), ylim=(0, 1023))
  a0, = ax.plot([], [])
  a1, = ax.plot([], [])
  a2, = ax.plot([], [])
  a3, = ax.plot([], [])
  a4, = ax.plot([], [])
  anim = animation.FuncAnimation(fig, analogPlot.update,
                                 fargs=(a0, a1, a2, a3, a4),
## Taken from:
# the following does not work:
#  row = (a0, a1, a2, a3, a4)   #combines lists together
#  row_arr = np.array(row)      #creates array from list
#  np.savetxt("probe.log", row_arr)  #save data in file

  # show plot

  # clean up


# call main
if __name__ == '__main__':

electronut made it!(author)2015-03-01

Make `f` a member of the AnalogPlot class. So use `self.f` everywhere and it should be fine.

CopernicusM made it!(author)2014-12-07

Hi Electronut,

I appreciate your spirit to share your code with us :-) However, you can make your code more simple, faster, no error outputs when you hit keyboard interrupt, and naturally save the output file as a text file :-)

I would probably not use class objects since our task is so simple, but you can keep the parsers (user inputs from terminal). Here is my version with only two methods (your Arduino is required to have 2 outputs, for instance V0 and V1, but you can simply change the number of expected columns from Arduino):


import sys, serial, argparse
import numpy as np
from time import sleep
from collections import deque

import matplotlib.pyplot as plt
import matplotlib.animation as animation

# constants
maxLen = 200
v0 = deque([0.0]*maxLen)
v1 = deque([0.0]*maxLen)
#ardtime = deque([0.0]*maxLen)

strPort = '/dev/ttyACM0'
ser = serial.Serial(strPort, 9600)

# set up animation
fig = plt.figure(1, figsize=(15,10))
ax = plt.axes(xlim=(0, 200), ylim=(0, 1023))
line_1, = ax.plot([], [], 'ko-')
line_2, = ax.plot([], [], 'ro-')

# initialization function: plot the background of each frame
def init():
line_1.set_data([], [])
line_2.set_data([], [])
return line_1, line_2,

# animation function. This is called sequentially
def animate(i):
line = ser.readline()
data = [float(val) for val in line.split()]
if(len(data) == 2): # expected no. of columns from Arduino

# save data locally and put header
if i==0:
thefile = open(my_file, 'w')
thefile.write('Microcontroller time [ms], Arb. unit [bits]\n')
with open(my_file, 'a') as thefile:
thefile.write("%s\n" % line.strip())

except KeyboardInterrupt:

return line_1, line_2

# call the animator. blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init, interval=50, blit=True)

string_1 = ''.join([r'$\Delta$time $\approx$ Arduino_delay'])
plt.xlabel(string_1, fontsize=20)
plt.ylabel("Bits, arb. units", fontsize=20)
plt.tick_params(axis="both", labelsize=20)

# show plot

# flush and close the serial port

electronut made it!(author)2014-12-07

Thanks, but your code indentation is all mangled, making it hard to understand. (I recommend creating a github gist.)

Also, a few points:

1. blit=True does not work across all platforms. On OS X it crashes for me, which is why I did not use the flag.
2. Using classes makes your code modular and much easier to comprehend.
3. Your code doesn't support command line args.

CopernicusM made it!(author)2014-12-08

Hi electronut,

My code has no parsers, that's correct. It is just because I prefer to make changes and typing in the editor :-)

Your functions "addToBuf" and "add" can be included in the "update" function by adding only 3 lines. The most importing thing to remember is that matplotlib method FuncAnimation expects array returns from method "set_data" :-) The rest of the coding is just matter of taste.

All the best,


bartons2 made it!(author)2014-11-11

Hi, total newb here. When I run your scripts, I get:

Traceback (most recent call last):

File "/Users/barton/Documents/Arduino/", line 10, in <module>

import sys, serial, argparse

ImportError: No module named serial

Using a Mega with macbook and OSX10. I can see from discussion below that Python doesn't know which serial port I am using. Where do I put that in?

electronut made it!(author)2014-11-11


I think you need to install pyserial:

Also, your Arduino IDE Tools->Serial Port will tell you the name of the serial port used on your Mac. My code takes this as the --port command line argument.

Good luck!

bartons2 made it!(author)2014-11-13

Thanks, you were right. I was missing many elements of Python (which I've never used before). The code runs and makes a plot with no data. The plot doesn't look like yours (see pic). I've checked the serial window in Arduino and the data is flowing. I changed the line about the serial port to strPort = '/dev/tty.usbmodem1411' which is what my port is called in the Arduino IDE. Any more advice? I appreciate the help. If it matters, I am building a logger for my fermenter.

electronut made it!(author)2014-11-13

When you deal with serial port (or other real-time comms) it won't work from inside the Python interpreter. Try running it from the shell as in:


Also, print your data out (instead of plotting) to ensure that the values make sense.

I'd love to see pics of your project. By the way, my Python book is going to be out soon:

bartons2 made it!(author)2014-11-13

Right again. Next problem seems to be that the data from the arduino is not formatted the way your python code wants or like you specified above. Instead of

512 300
513 280

I am getting




My sketch is identical to yours.

I will send pics once it's pretty!

electronut made it!(author)2014-11-13

Are you using the updated Python code below?

Also, please post your code that's printing the values. Instead of cut & paste, please upload it as a github gist and post the link here (useful to have a gthub account anyway) so we can see it properly. Or you can just use pastebin - no setup required:

bartons2 made it!(author)2014-11-14

I am using your new Python code. Here is my sketch:

About This Instructable




Bio: Open Source Hardware Projects - Microcontrollers, Programming, and new prototyping technologies.
More by electronut:Touch Activated Blinky BadgeTemperature/Altitude/Pressure Display using Arduino & BMP180A simple hack to adapt an 8 pin male dual row header to a breadboard.
Add instructable to: