Tramite la porta USB arduino può inviare una serie di dati che il computer può leggere e visualizzare. Questo è particolarmente utile perchè un programma sviluppato ad hoc sarebbe in grado di elaborare i dati forniti da Arduino.
Una delle possibili applicazioni di questa funzione è quella di automatizzare la creazione di grafici sulla base dei dati raccolti: immaginatevi ad esempio un sensore di temperatura (come un resistore che varia il suo valore al variare della temperatura) collegato ad Arduino, e il PC che continua a leggere il valore del resistore trasformando il dato in °C e mettendo il tutto su di un grafico. Dopo aver letto questo tutorial sarete in grado di fare anche una cosa del genere.
Ora dobbiamo dire quale valore Arduino deve inviare sulla porta seriale: io ho preso in considerazione il valore del potenziometro quindi la funzione loop diventa:
Sull'IDE di Arduino c'è un pulsante denominato "serial monitor": se noi carichiamo l'attuale programma e poi clicchiamo su "serial monitor" ci verrà mostrata una serie di numeri che variano se spostiamo il potenziometro... ecco, questi sono i valori della variabile valpot, che come tutti gli ingressi analogici di Arduino può assumere tutti gli stati che vanno da 0 a 1023 (come abbiamo già visto).
Per vedere il flusso di dati non è necessario avere necessariamente sempre aperto l'IDE di arduino, infatti grazie al linguaggio Python è possibile leggere questo flusso, e sviluppare di conseguenza dei programmi che possono elaborare i dati inviati al PC dalla board.
C'è da ricordare comunque che per fare questo è necessario prima installare python-serial, atrimenti python non è in grado di aprire la porta seriale.
Creiamo il file "Arduino_Monitor.py" e all'interno andiamo a scrivere:
from threading import Thread
import time
import serial
last_received = ''defreceiving(ser):
global last_received
buffer = ''whileTrue:
buffer = buffer + ser.read(ser.inWaiting())
if'\n'in buffer:
lines = buffer.split('\n')
last_received = lines[-2]
buffer = lines[-1]
classSerialData(object):
def__init__(self, init=50):
try:
self.ser = ser = serial.Serial(
port='/dev/ttyACM0',
baudrate=9600,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0.1,
xonxoff=0,
rtscts=0,
interCharTimeout=None
)
except serial.serialutil.SerialException:
self.ser = Noneelse:
Thread(target=receiving, args=(self.ser,)).start()
defnext(self):
ifnot self.ser:
return100for i inrange(40):
raw_line = last_received
try:
returnfloat(raw_line.strip())
except ValueError:
print'bogus data',raw_line
time.sleep(.005)
return0.def__del__(self):
if self.ser:
self.ser.close()
if __name__=='__main__':
s = SerialData()
for i inrange(500):
time.sleep(.015)
print s.next()
Andando ad eseguire questo nostro programma, vedremo sul terminale che non fa altro che scrivere a schermo i valori del potenziometro, esattamente come succede con l'IDE di arduino.
Avendo a disposizione anche il modulo wx e matplotlib di python possiamo creare un vero e proprio grafico, basta creare un altro file python, ad esempio "wx_arduino_dynamic_graph.py" e all'interno aggiungiamo:
Python ed Arduino
Tramite la porta USB arduino può inviare una serie di dati che il computer può leggere e visualizzare. Questo è particolarmente utile perchè un programma sviluppato ad hoc sarebbe in grado di elaborare i dati forniti da Arduino.
Una delle possibili applicazioni di questa funzione è quella di automatizzare la creazione di grafici sulla base dei dati raccolti: immaginatevi ad esempio un sensore di temperatura (come un resistore che varia il suo valore al variare della temperatura) collegato ad Arduino, e il PC che continua a leggere il valore del resistore trasformando il dato in °C e mettendo il tutto su di un grafico. Dopo aver letto questo tutorial sarete in grado di fare anche una cosa del genere.
Innanzitutto riprendiamo lo schema dell'ultima volta:
...e il programma scritto per Arduino:
int pinpot = 0; int pinled = 9; int valpot = 0; int valled = 0; void setup() { pinMode(pinled, OUTPUT); } void loop() { valpot = analogRead(pinpot); valled = map(valpot, 0, 1023, 0, 255); analogWrite(pinled, valled); delay(10); }
Per aprire un flusso di dati dobbiamo inizializzare la periferica seriale su Arduino aggiungendola alla funzione setup:
void setup() { pinMode(pinled, OUTPUT); Serial.begin(9600); }
Ora dobbiamo dire quale valore Arduino deve inviare sulla porta seriale: io ho preso in considerazione il valore del potenziometro quindi la funzione loop diventa:
void loop() { valpot = analogRead(pinpot); valled = map(valpot, 0, 1023, 0, 255); analogWrite(pinled, valled); Serial.println(valpot); delay(10); }
Sull'IDE di Arduino c'è un pulsante denominato "serial monitor": se noi carichiamo l'attuale programma e poi clicchiamo su "serial monitor" ci verrà mostrata una serie di numeri che variano se spostiamo il potenziometro... ecco, questi sono i valori della variabile valpot, che come tutti gli ingressi analogici di Arduino può assumere tutti gli stati che vanno da 0 a 1023 (come abbiamo già visto).
Per vedere il flusso di dati non è necessario avere necessariamente sempre aperto l'IDE di arduino, infatti grazie al linguaggio Python è possibile leggere questo flusso, e sviluppare di conseguenza dei programmi che possono elaborare i dati inviati al PC dalla board.
C'è da ricordare comunque che per fare questo è necessario prima installare python-serial, atrimenti python non è in grado di aprire la porta seriale.
Creiamo il file "Arduino_Monitor.py" e all'interno andiamo a scrivere:
from threading import Thread import time import serial last_received = '' def receiving(ser): global last_received buffer = '' while True: buffer = buffer + ser.read(ser.inWaiting()) if '\n' in buffer: lines = buffer.split('\n') last_received = lines[-2] buffer = lines[-1] class SerialData(object): def __init__(self, init=50): try: self.ser = ser = serial.Serial( port='/dev/ttyACM0', baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=0.1, xonxoff=0, rtscts=0, interCharTimeout=None ) except serial.serialutil.SerialException: self.ser = None else: Thread(target=receiving, args=(self.ser,)).start() def next(self): if not self.ser: return 100 for i in range(40): raw_line = last_received try: return float(raw_line.strip()) except ValueError: print 'bogus data',raw_line time.sleep(.005) return 0. def __del__(self): if self.ser: self.ser.close() if __name__=='__main__': s = SerialData() for i in range(500): time.sleep(.015) print s.next()
Andando ad eseguire questo nostro programma, vedremo sul terminale che non fa altro che scrivere a schermo i valori del potenziometro, esattamente come succede con l'IDE di arduino.
Avendo a disposizione anche il modulo wx e matplotlib di python possiamo creare un vero e proprio grafico, basta creare un altro file python, ad esempio "wx_arduino_dynamic_graph.py" e all'interno aggiungiamo:
import os import pprint import random import sys import wx REFRESH_INTERVAL_MS = 90 import matplotlib matplotlib.use('WXAgg') from matplotlib.figure import Figure from matplotlib.backends.backend_wxagg import \ FigureCanvasWxAgg as FigCanvas, \ NavigationToolbar2WxAgg as NavigationToolbar import numpy as np import pylab from Arduino_Monitor import SerialData as DataGen class BoundControlBox(wx.Panel): def __init__(self, parent, ID, label, initval): wx.Panel.__init__(self, parent, ID) self.value = initval box = wx.StaticBox(self, -1, label) sizer = wx.StaticBoxSizer(box, wx.VERTICAL) self.radio_auto = wx.RadioButton(self, -1, label="Auto", style=wx.RB_GROUP) self.radio_manual = wx.RadioButton(self, -1, label="Manual") self.manual_text = wx.TextCtrl(self, -1, size=(35,-1), value=str(initval), style=wx.TE_PROCESS_ENTER) self.Bind(wx.EVT_UPDATE_UI, self.on_update_manual_text, self.manual_text) self.Bind(wx.EVT_TEXT_ENTER, self.on_text_enter, self.manual_text) manual_box = wx.BoxSizer(wx.HORIZONTAL) manual_box.Add(self.radio_manual, flag=wx.ALIGN_CENTER_VERTICAL) manual_box.Add(self.manual_text, flag=wx.ALIGN_CENTER_VERTICAL) sizer.Add(self.radio_auto, 0, wx.ALL, 10) sizer.Add(manual_box, 0, wx.ALL, 10) self.SetSizer(sizer) sizer.Fit(self) def on_update_manual_text(self, event): self.manual_text.Enable(self.radio_manual.GetValue()) def on_text_enter(self, event): self.value = self.manual_text.GetValue() def is_auto(self): return self.radio_auto.GetValue() def manual_value(self): return self.value class GraphFrame(wx.Frame): title = 'Demo: dynamic matplotlib graph' def __init__(self): wx.Frame.__init__(self, None, -1, self.title) self.datagen = DataGen() self.data = [self.datagen.next()] self.paused = False self.create_menu() self.create_status_bar() self.create_main_panel() self.redraw_timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.on_redraw_timer, self.redraw_timer) self.redraw_timer.Start(REFRESH_INTERVAL_MS) def create_menu(self): self.menubar = wx.MenuBar() menu_file = wx.Menu() m_expt = menu_file.Append(-1, "&Save plot\tCtrl-S", "Save plot to file") self.Bind(wx.EVT_MENU, self.on_save_plot, m_expt) menu_file.AppendSeparator() m_exit = menu_file.Append(-1, "E&xit\tCtrl-X", "Exit") self.Bind(wx.EVT_MENU, self.on_exit, m_exit) self.menubar.Append(menu_file, "&File") self.SetMenuBar(self.menubar) def create_main_panel(self): self.panel = wx.Panel(self) self.init_plot() self.canvas = FigCanvas(self.panel, -1, self.fig) self.xmin_control = BoundControlBox(self.panel, -1, "X min", 0) self.xmax_control = BoundControlBox(self.panel, -1, "X max", 50) self.ymin_control = BoundControlBox(self.panel, -1, "Y min", 0) self.ymax_control = BoundControlBox(self.panel, -1, "Y max", 100) self.pause_button = wx.Button(self.panel, -1, "Pause") self.Bind(wx.EVT_BUTTON, self.on_pause_button, self.pause_button) self.Bind(wx.EVT_UPDATE_UI, self.on_update_pause_button, self.pause_button) self.cb_grid = wx.CheckBox(self.panel, -1, "Show Grid", style=wx.ALIGN_RIGHT) self.Bind(wx.EVT_CHECKBOX, self.on_cb_grid, self.cb_grid) self.cb_grid.SetValue(True) self.cb_xlab = wx.CheckBox(self.panel, -1, "Show X labels", style=wx.ALIGN_RIGHT) self.Bind(wx.EVT_CHECKBOX, self.on_cb_xlab, self.cb_xlab) self.cb_xlab.SetValue(True) self.hbox1 = wx.BoxSizer(wx.HORIZONTAL) self.hbox1.Add(self.pause_button, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) self.hbox1.AddSpacer(20) self.hbox1.Add(self.cb_grid, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) self.hbox1.AddSpacer(10) self.hbox1.Add(self.cb_xlab, border=5, flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL) self.hbox2 = wx.BoxSizer(wx.HORIZONTAL) self.hbox2.Add(self.xmin_control, border=5, flag=wx.ALL) self.hbox2.Add(self.xmax_control, border=5, flag=wx.ALL) self.hbox2.AddSpacer(24) self.hbox2.Add(self.ymin_control, border=5, flag=wx.ALL) self.hbox2.Add(self.ymax_control, border=5, flag=wx.ALL) self.vbox = wx.BoxSizer(wx.VERTICAL) self.vbox.Add(self.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW) self.vbox.Add(self.hbox1, 0, flag=wx.ALIGN_LEFT | wx.TOP) self.vbox.Add(self.hbox2, 0, flag=wx.ALIGN_LEFT | wx.TOP) self.panel.SetSizer(self.vbox) self.vbox.Fit(self) def create_status_bar(self): self.statusbar = self.CreateStatusBar() def init_plot(self): self.dpi = 100 self.fig = Figure((3.0, 3.0), dpi=self.dpi) self.axes = self.fig.add_subplot(111) self.axes.set_axis_bgcolor('black') self.axes.set_title('Arduino Serial Data', size=12) pylab.setp(self.axes.get_xticklabels(), fontsize=8) pylab.setp(self.axes.get_yticklabels(), fontsize=8) self.plot_data = self.axes.plot( self.data, linewidth=1, color=(1, 1, 0), )[0] def draw_plot(self): if self.xmax_control.is_auto(): xmax = len(self.data) if len(self.data) > 50 else 50 else: xmax = int(self.xmax_control.manual_value()) if self.xmin_control.is_auto(): xmin = xmax - 50 else: xmin = int(self.xmin_control.manual_value()) if self.ymin_control.is_auto(): ymin = round(min(self.data), 0) - 1 else: ymin = int(self.ymin_control.manual_value()) if self.ymax_control.is_auto(): ymax = round(max(self.data), 0) + 1 else: ymax = int(self.ymax_control.manual_value()) self.axes.set_xbound(lower=xmin, upper=xmax) self.axes.set_ybound(lower=ymin, upper=ymax) if self.cb_grid.IsChecked(): self.axes.grid(True, color='gray') else: self.axes.grid(False) pylab.setp(self.axes.get_xticklabels(), visible=self.cb_xlab.IsChecked()) self.plot_data.set_xdata(np.arange(len(self.data))) self.plot_data.set_ydata(np.array(self.data)) self.canvas.draw() def on_pause_button(self, event): self.paused = not self.paused def on_update_pause_button(self, event): label = "Resume" if self.paused else "Pause" self.pause_button.SetLabel(label) def on_cb_grid(self, event): self.draw_plot() def on_cb_xlab(self, event): self.draw_plot() def on_save_plot(self, event): file_choices = "PNG (*.png)|*.png" dlg = wx.FileDialog( self, message="Save plot as...", defaultDir=os.getcwd(), defaultFile="plot.png", wildcard=file_choices, style=wx.SAVE) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self.canvas.print_figure(path, dpi=self.dpi) self.flash_status_message("Saved to %s" % path) def on_redraw_timer(self, event): if not self.paused: self.data.append(self.datagen.next()) self.draw_plot() def on_exit(self, event): self.Destroy() def flash_status_message(self, msg, flash_len_ms=1500): self.statusbar.SetStatusText(msg) self.timeroff = wx.Timer(self) self.Bind( wx.EVT_TIMER, self.on_flash_status_off, self.timeroff) self.timeroff.Start(flash_len_ms, oneShot=True) def on_flash_status_off(self, event): self.statusbar.SetStatusText('') if __name__ == '__main__': app = wx.PySimpleApp() app.frame = GraphFrame() app.frame.Show() app.MainLoop()
Vi ricordo che questo programma è dipendente da quello precedente infatti il primo legge il flusso di dati ed il secondo li visualizza graficamente.
Il tutto è stato programmato su Ubuntu 12.04 con python3.
Modificato da RyujiAndy