Vai al contenuto

Articolo: Arduino Tutorial: Python - Leggere ed elaborare con il PC i valori sulle porte di Arduino


RyujiAndy

Messaggi raccomandati

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:

http://www.nonsologaming.com/vd/arduino_led_potv1.png

...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
Link al commento
Condividi su altri siti

Se hai a disposizione un collegamento Internet puoi anche valutare di usare il servizio online Cosm (prima noto come Pachube) dove tu invii i dati e lui costruisce automaticamente il grafico visualizzabile su browser con parecchie personalizzazioni. Non ricordo se va anche su smartphone ma credo di si.
Link al commento
Condividi su altri siti

Crea un account o accedi per lasciare un commento

Devi essere un membro per lasciare un commento

Crea un account

Iscriviti per un nuovo account nella nostra community. È facile!

Registra un nuovo account

Accedi

Sei già registrato? Accedi qui.

Accedi Ora

Giochi in Uscita



  • Community Hive Community Hive

    Community Hive allows you to follow your favorite communities all in one place.

    Follow on Community Hive
  • Utenti

    Non ci sono membri da mostrare

×
×
  • Crea Nuovo...