├── README.md ├── doc ├── plot_gif.gif └── single_joystick.png ├── joystick_readings └── joystick_readings.ino └── real_time_plot.py /README.md: -------------------------------------------------------------------------------- 1 | # Real-time plot of analog joystick readings using pyqtgraph 2 | 3 | In this project, the analog signals of a joystick module are plotted in real-time using the Python's pyqtgraph library. The joystick signals are processed using an Arduino Microcontroller. 4 | 5 | ## Table of contents 6 | 7 | * [Arduino set-up](#arduino-set-up) 8 | * [Real-time plotting using pyqtgraph](#real-time-plotting-using-pyqtgraph) 9 | 10 | 11 | ## Arduino set-up 12 | 13 | The following image shows the Arduino schmetics to work with a commonly available [joystick](https://www.amazon.de/AZDelivery-Joystick-KY-023-Arduino-gratis/dp/B07CKCBHF4/ref=sr_1_1_sspa?ie=UTF8&qid=1538845699&sr=8-1-spons&keywords=joystick+arduino&psc=1) module. These joysticks basically consist of two [potentiometers](https://en.wikipedia.org/wiki/Potentiometer) (for x- and y-axis) as well as a single button (z-direction). 14 | 15 | 16 | The five pins of the joysticks are connected as follows: 17 | 18 | |Arduino schematics|Pin connections| 19 | |--|--| 20 | |![awd](doc/single_joystick.png)|
Pins JoystickPins Arduino
GNDGND
+5V5V
VRxA1
VRyA0
SW11
| 21 | 22 | The corresponding Arduino [code](joystick_readings.ino) simply reads the analog values of the joystick and prints them on the serial port to be accessable by Python. In this example, the analog readings are published with 200 Hz. We can now use Python to access the serial port and plot the streaming data in real-time. 23 | 24 | ## Real-time plotting using pyqtgraph 25 | 26 | Although matplolib is the common plotting library for Python and allows creating beautiful graphics, pyqtgraph comes with the possibility to update plots much faster, which allows real-time data plots with high frequencies.
The script [real_time_plot.py](real_time_plot.py) creates a real-time plot of the analog output (0-1023) for y over x. Note that we are not plotting single signal pairs but arrays of the last 20 points. The gif below shows how the real-time plot might look like. 27 |

28 | 29 | 30 | I also created an explaining youtube video on this project. Just click below. 31 | 32 | 33 |

34 | 35 | 36 |

37 | -------------------------------------------------------------------------------- /doc/plot_gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaFeKl/joystick_real_time_plot_with_pyqtgraph/c8e975ad42f444cbc838b41148d9c52942bbfbe7/doc/plot_gif.gif -------------------------------------------------------------------------------- /doc/single_joystick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JaFeKl/joystick_real_time_plot_with_pyqtgraph/c8e975ad42f444cbc838b41148d9c52942bbfbe7/doc/single_joystick.png -------------------------------------------------------------------------------- /joystick_readings/joystick_readings.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Simple Joystick analog reading 3 | 4 | This simple arduino code reads the analog x and y values of a Joystick modules 5 | and print them using the serial port 6 | 7 | List of components: 8 | * Arduino board + serial cabel 9 | * Joystick module (e.g. KY-023) 10 | * 5 x Jumper Wires M/F 11 | 12 | Last modified 06/10/18 13 | By Jan-Felix Klein 14 | 15 | For the full project description, please refer to: 16 | https://github.com/JaFeKl/Joystick-analog-readings-real-time-plot-with-pyqtgraph/blob/master/README.md 17 | 18 | */ 19 | 20 | const int switchPin = 11; //SW Pin is connected to pin 11 21 | const int yAxisPin = A1; //VRy pin is connected to analog pin A1 22 | const int xAxisPin = A0; //VRx pin is connected to analog pin A0 23 | 24 | 25 | void setup() { 26 | Serial.begin(115200); //Turn on the serial port with desired baud rate 27 | pinMode(switchPin, INPUT); //Set the switchPin to be an input 28 | pinMode(xAxisPin, INPUT); //Set the xAxisPin to be an input 29 | pinMode(yAxisPin, INPUT); //Set the yAxisPin to be an input 30 | digitalWrite(switchPin, HIGH); //write HIGH to switchPin 31 | } 32 | 33 | 34 | void loop() { 35 | // For the purpose of this project, we only need to read the x and y value from the joystick 36 | Serial.print(analogRead(xAxisPin)); 37 | Serial.print(","); 38 | Serial.println(analogRead(yAxisPin)); 39 | 40 | delay(5); //5 ms delay --> reading with 200Hz 41 | } 42 | -------------------------------------------------------------------------------- /real_time_plot.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | from pyqtgraph.Qt import QtCore, QtGui 4 | import numpy as np 5 | import pyqtgraph as pg 6 | import pyqtgraph.exporters 7 | import serial 8 | 9 | 10 | class joystickPlot(QtGui.QMainWindow): 11 | def __init__(self, parent=None): 12 | super(joystickPlot, self).__init__(parent) 13 | 14 | # Set up GUI configuration 15 | self.mainbox = QtGui.QWidget() 16 | self.setCentralWidget(self.mainbox) 17 | self.mainbox.setLayout(QtGui.QVBoxLayout()) 18 | 19 | self.canvas = pg.GraphicsLayoutWidget() # create GrpahicsLayoutWidget obejct 20 | self.mainbox.layout().addWidget(self.canvas) 21 | 22 | self.label = QtGui.QLabel() # placeholder Qlabel object to display framerate 23 | self.mainbox.layout().addWidget(self.label) 24 | 25 | 26 | # Set up plot 27 | self.analogPlot = self.canvas.addPlot(title='Real-time joystick analog signal plot') 28 | self.analogPlot.setYRange(-100,1123) # set axis range 29 | self.analogPlot.setXRange(-100,1123) 30 | self.analogPlot.showGrid(x=True, y=True, alpha=0.5) 31 | x_axis = self.analogPlot.getAxis('bottom') 32 | y_axis = self.analogPlot.getAxis('left') 33 | 34 | x_axis.setLabel(text='x-axis reading') # set axis labels 35 | y_axis.setLabel(text='y-axis reading') 36 | 37 | ticks = [[(0,'0'),(1023,'1023')],[(200,'200'),(400,'400'),(600,'600'),(800,'800')]] 38 | x_axis.setTicks(ticks) # set ticks manually (first major-ticks than minor-ticks) 39 | y_axis.setTicks(ticks) 40 | 41 | self.drawplot = self.analogPlot.plot(pen='y') # yellow line plot 42 | 43 | 44 | # initialize sensor data variables 45 | self.numPoints = 20 # number of points that should be plottet at the same time stamp 46 | self.x = np.array([], dtype=int) # create empty numpy array to store xAxis data 47 | self.y = np.array([], dtype=int) # create empty numpy array to store yAxis data 48 | 49 | 50 | # initialize frame counter variables 51 | self.counter = 0 52 | self.fps = 0. 53 | self.lastupdate = time.time() 54 | 55 | 56 | # set up image exporter (necessary to be able to export images) 57 | QtGui.QApplication.processEvents() 58 | self.exporter=pg.exporters.ImageExporter(self.canvas.scene()) 59 | self.image_counter = 1 60 | 61 | 62 | # start updating 63 | self._update() 64 | 65 | 66 | 67 | def _framerate(self): 68 | now = time.time() # current time since the epoch in seconds 69 | dt = (now-self.lastupdate) 70 | if dt <= 0: 71 | dt = 0.000000000001 72 | fps2 = 1.0 / dt 73 | self.lastupdate = now 74 | self.fps = self.fps * 0.9 + fps2 * 0.1 75 | tx = 'Mean Frame Rate: {fps:.3f} FPS'.format(fps=self.fps ) 76 | self.label.setText(tx) 77 | QtCore.QTimer.singleShot(1, self._update) 78 | self.counter += 1 79 | 80 | 81 | 82 | def _save_image(self): 83 | filename = 'img'+("%04d" % self.image_counter)+'.png' 84 | self.exporter.export(filename) 85 | self.image_counter += 1 86 | 87 | 88 | def _update(self): 89 | while (arduinoData.inWaiting()==0): #wait until there is data available 90 | pass # do nothing 91 | arduinoString = arduinoData.readline() #read the text line from serial port 92 | if sys.version_info >= (3, 0): 93 | arduinoString = arduinoString.decode('utf-8', 'backslashreplace') 94 | dataArray = arduinoString.split(',') #split it into an array 95 | 96 | xAxis = int(dataArray[1]) #convert first element to an integer 97 | yAxis = 1023 - int(dataArray[0]) #convert and flip yAxis signal 98 | 99 | self.x = np.append(self.x, xAxis) # append new data point to the array 100 | if self.x.size >= self.numPoints: # make sure that the size of the array includes the most recent data points 101 | self.x = np.append(self.x[1:self.numPoints],xAxis) 102 | else: 103 | self.x = np.append(self.x,xAxis) 104 | 105 | self.y = np.append(self.y, yAxis) # append new data point to the array 106 | if self.y.size >= self.numPoints: # make sure that the size of the array includes the most recent data points 107 | self.y = np.append(self.y[1:self.numPoints],yAxis) 108 | else: 109 | self.y = np.append(self.y,yAxis) 110 | 111 | self.drawplot.setData(self.x, self.y) # draw current data set 112 | 113 | self._framerate() # update framerate, see corresponding function 114 | 115 | # self._save_image() # uncomment this to save each frame as an .png image in your current directory. Note that the framerate drops significantly by doing so 116 | 117 | 118 | 119 | if __name__ == '__main__': 120 | 121 | arduinoData = serial.Serial("com3", 115200); # Creating our serial object named arduinoData, make sure that port and baud rate is set up according to your arduino data stream 122 | app = QtGui.QApplication(sys.argv) 123 | plot = joystickPlot() 124 | plot.show() 125 | sys.exit(app.exec_()) 126 | --------------------------------------------------------------------------------