Tutorial: passare valori dal PC ad Arduino per via seriale

Arduino-tutorial-serial-2

Premessa

Salve a tutti, oggi voglio scrivere un breve tutorial in risposta ad una richesta che abbiamo ricevuto ieri sul sito da parte di pensodisi. 

Spero che potrà essere utile sia a lui sia a tutti gli altri che hanno esigenze simili.

Ogni vostra richiesta o suggerimento sarà sempre per noi uno stimolo a migliorare.

Introduzione

Per chi si diletta a realizzare progetti con Arduino, prima o poi dovrà avere a che fare con lo scambio di valori tra Arduino e il PC a cui è collegato.

Se useremo Arduino collegato ad un sensore (vedi Fig.1), la scheda Arduino produrrà una serie di dati che potranno essere inviati ad un computer per poter essere immagazzinati in un file, visualizzati o elaborati in qualche modo. Se invece useremo Arduino collegato ad un attuatore (vedi Fig.2), come per esempio un motore passo passo, molto probabilmente sarà il computer a cui Arduino è collegato, che dovrà inviare una serie di dati alla scheda Arduino. Quest’ultima processerà i dati ricevuti convertendoli opportunamente in comandi da inviare al motore per farlo muovere della quantità di step necessaria.

tutorial-serial-data-sensor
Fig.1: Arduino invia i dati del sensore al PC
tutorial-serial-data-actuator
Fig.2: Arduino riceve i dati dal PC per pilotare l’attuatore

Tratterò questo argomento in due diversi articoli.

In questo tutorial vedremo l’esempio dell’uso di Arduino come controllo per un attuatore. Per rendere il tutorial il più semplice possibile, utilizzeremo come attuatore un servo motore.

servo motore
Fig.3: Un servo motore per modellismo

Invece in un prossimo articolo, tratterò la raccolta dei dati da un sensore collegato ad Arduino e l’invio di questi valori ad un PC.

Per quanto riguarda i vari comandi per pilotare il servo motore, o altri tipi di motore (continuo o passo-passo) vi consiglio di fare riferimento all’articolo Controllo Motori con Arduino e la scheda Adafruit Motorshield v2.

Pilotare un servo motore con una sequenza di angoli su un file

Ho scelto un servo motore proprio per la sua semplicità, soprattutto per quanto riguarda i comandi. L’esempio di questo tutorial sarà quello di leggere un file TXT, o CSV con all’interno un elenco di angoli che il servo motore dovrà assumere nel tempo leggendo riga per riga.

arduino-servo-csv-data
Fig.4: Il file CSV contiene la sequenza degli angoli di rotazione

Ho scelto questo esempio, poichè nonostante la sua semplicità, contiene tutti gli elementi fondamentali rimanendo semplice e facilmente intuitibile. Sarà poi semplicissimo estendere questo esempio a progetti più complessi.

Diamo un occhiata allo schema generale di ciò che realizzeremo in questo tutorial:

sketch-python-arduino-serial2
Fig.5: questo è lo scenario del nostro tutorial

Come possiamo vedere stabiliremo una connessione seriale tra la scheda Arduino ed il PC. Sulla scheda Arduino programmeremo uno sketch che si occuperà di “mettersi in ascolto” per eventuali invii di valori (angoli) da parte del PC. Mentre sul PC attiveremo una semplice sessione in Python (ma può essere incorporato in qualsiasi programma in Python) che si occuperà di leggere il contenuto del file CSV (o TXT) inviando gli opportuni segnali via seriale alla scheda Arduino.

Ho scelto il servo motore come attuatore anche perchè è possibile collegarlo direttamente alla scheda Arduino senza l’uso di opportune schede di controllo. Fate riferimento alla figura 6 per i collegamenti.

arduino_servo_sketch
Fig.6: Il cavetto giallo deve essere collegato al PIN 10

Per chi avesse la Motorshield v2 di Arduino può fare riferimento alla figura seguente (Fig.7).

adafruit_motorshield_servo
Fig.7: Collegamento tra il servo motore e la Motorshield

Lo sketch

Per prima cosa occupiamoci dello sketch da sviluppare sull’Arduino IDE. (Clicca qui per collegari alla pagina ufficiale di Arduino scaricare l’ultima release di Arduino IDE).

Per prima cosa importiamo la libreria Servo già inclusa all’interno dell’Arduino IDE

#include <Servo.h>

Questa libreria ci fornisce tutta una serie di comandi per gestire in maniera facile ed intuitiva i Servo Motori direttamente collegati ad Arduino. (Per chi è interessato può consultare qui la pagina ufficiale).

Servo myservo;
int angle = 0;
int newAngle = 0;
const int MaxChars = 4;
char strValue[MaxChars+1];
int index = 0;

Per prima cosa definiamo un oggetto myservo, che corrisponderà al nostro servo motore. Definiamo inoltre le variabili angle e newAngle che conterranno i valori degli angoli. Poi definiremo un array strValue di caratteri che conterrà al massimo 4 caratteri (per esempio ‘123,’ ). Questo array serve a immagazzinare i valori degli angoli inviati per via seriale dal PC.  Infine un indice index che ci servirà successivamente per la scansione dell’array di caratteri.

void setup()
{
 Serial.begin(9600);
 myservo.attach(10);
 angle = 90;
}
void loop()
{}

Definiamo adesso le due funzioni standard setup() e loop().

La funzione setup praticamente si occupa della fase di inizializzazione della scheda, e quindi, prima di tutto attiveremo una comunicazione seriale a 9600 baud. Poi definiremo che il nostro servo motore è collegato al PIN 10 attraverso la funzione attach() della libreria Servo. Infine definiamo un angolo di partenza del motore (HOME position) che nel mio caso ho definito come 90°.

La funzione loop invece dovrà contenere tutti i comandi che verranno eseguiti dalla scheda a ciclo continuo. Io ho lasciato vuota questa funzione, ma qui voi potete implementare tutto ciò di cui avrete bisogno per adempiere alle vostre specifiche esigenze.

Infine definiamo una nuova funzione chiamata serialEvent(). Questa funzione, anche se non è inclusa all’interno di loop(), è sempre in ascolto, e quando un evento seriale viene catturato dalla scheda Arduino, come nel nostro caso l’invio dal PC di un valore numerico, viene attivata, ed il codice contenuto all’interno viene eseguito.

void serialEvent()
{
   while(Serial.available()) 
   {
      char ch = Serial.read();
      Serial.write(ch);
      if(index < MaxChars && isDigit(ch))              {                               
            strValue[index++] = ch;                     
      }else{                               
            strValue[index] = 0;                               
            newAngle = atoi(strValue);                               
            if(newAngle > 0 && newAngle < 180){
               if(newAngle < angle)                                                
                   for(; angle > newAngle; angle -= 1) {
                   myservo.write(angle);
               }  
            else 
               for(; angle < newAngle; angle += 1){
                   myservo.write(angle);
                } 
         }
         index = 0;
         angle = newAngle;
      }  
   }
}

Ciascun valore inviato dal PC viene letto carattere per carattere ed inserito all’interno dell’array strValue. Se il carattere letto sarà un numero (da 0 a 9) verrà immagazzinato all’interno dell’array, se invece sarà un carattere non numerico (nel nostro caso la virgola ‘,’) la lettura verrà interrotta e il valore all’interno dell’array verrà convertito in un valore numerico vero e proprio attraverso la funzione atoi().

Il valore numerico così ottenuto viene immagazzinato all’interno della variabile newAngle, e rappresenta il nuovo angolo su cui dovrà impostarsi il servo motore. Questo valore per essere accettabile dovrà essere compreso tra 0 e 180 gradi. Successivamente verrà confrontato con l’angolo corrente angle, e a seconda se maggiore o minore, incrementeremo o diminuiremo gradualmente (grado per grado) l’angolo del servo motore. Questo per evitare bruschi salti da parte del servo motore. L’angolo su cui deve essere impostato il motore viene definito dalla funzione write() della libreria Servo.

Arduino-sketch-code-01
Fig.8: Il codice (prima parte)
Arduino-sketch-code-02
Fig.9: Il codice (seconda parte)

Se adesso eseguiamo il codice, vedremo il servo motore ruotare per portarsi alla posizione corrispettiva all’angolo di 90°. Poi attenderà immobile in ascolto dei valori inviati dal PC.

Prima di passare a Python, facciamo un po’ di prove. Attiviamo il Monitor Seriale all’interno dell’Arduino IDE, cliccando sulla voce Menu Strumenti > Monitor Seriale.

Arduino-serial-monitor
Fig.10: apri il Monitor Seriale dal menu

Una nuova finestra ci apparirà, il Monitor Serial appunto, in cui potremo fare delle prove scrivendo direttamente gli angoli a cui vogliamo impostare il servo Motore. Scriviamo per esempio ‘123;’ e premiamo INVIO.

Arduino-serial-monitor-2
Fig.11: inserisci direttamente i valori degli angoli

Se abbiamo scritto correttamente lo sketch ed effettuato tutti i collegamenti, vedremo ruotare il servo motore dalla posizione HOME (90°) a quella di 123° appena inviata.

Python_shell

I Comandi da Python

Adesso occupiamoci della parte Python per quanto riguarda il lato PC.

Per prima cosa avviamo una shell di Python. Se non l’avete ancora installato sul vostro PC andate qui.

Python_shell
Fig.12: La shell di Python

Una volta aperta la shell importiamo la libreria serial, e creiamo una connessione seriale ser sulla porta in ascolto della scheda Arduino anche questa a 9600 baud.

import serial
ser = serial.Serial('/dev/ttyACM1',9600)

Attenzione la porta USB in ascolto può variare, quindi controllate dall’Arduino IDE la porta (Per es. su Windows può essere una COM 1,2,3 o 4). Quindi sostituite la porta all’interno della funzione Serial().

Adesso se scrivete

ser.write('110,')

otterrete un risultato identico a quello ottenuto prima con il Monitor Serial. Il servo motore ruoterà fino ad assumere la posizione corrispettiva all’angolo 110°.

Ma il nostro scopo è quello di leggere una sequenza di valori elencati all’interno di un file CSV. Quindi creiamo un file CSV con una sequenza di angoli e salviamolo come samples.csv.

Arduino-angoli-csv

Copiamo il file appena creato nella working directory della shell di Python. Se non vi ricordate o non sapete quale sia, inserite i seguenti comandi:

import os
os.getcwd()

Vi apparirà la directory dove la shell di Python va a leggere direttamente. Copiato il file, scriviamo

import time
file = open('samples.csv')
while 1:
   line = file.readline()
   if not line:
         break
   ser.write(line)
   time.sleep(3)

Ogni 3 secondi (definito da time.sleep()) la shell di Python leggerà un riga dal file CSV inviando il valore contenuto, cioè l’angolo, alla scheda Arduino in ascolto, che a sua volta azionerà il servo motore.

Una volta concluso, chiudete la lettura del file con il comando.

file.close

Conclusioni

Come potete vedere il tutorial è essenziale, semplice e facilmente producibile. E’ un buon punto di partenza per qualsiasi progetto che ha tra le sue funzioni lo scambio di dati.

Le righe di comando di Python possono essere implementate in un programma, e per la comunicazione seriale potremmo utilizzare una libreria specifica, come per esempio la llibreria pySerial.

Per quanto riguarda i valori io ho usato il valore degli angoli, ma potrebbe essere usato qualsiasi tipo di valore, per esempio gli step, una distanza, ecc.

Inoltre, nel caso di più motori, per esempio in un’automazione CNC a 2 motori (XY) o a 3 motori (XYZ) è possibile passare una riga con più valori, per esempio il primo valore il numero degli step e come secondo valore il motore da muovere (X o Y o Z).

Insomma questo tutorial vi dovrebbe aprire la strada a molti progetti. E se vi sarà di ispirazione, pubblicate i vostri progetti sul sito Meccanismo Complesso, ne saremmo fieri.[:en]

9 commenti su “Tutorial: passare valori dal PC ad Arduino per via seriale

  1. juycce

    I just love the way you poste it guys, realy complete tutorial, very well explaind and easy to understant. Thank you for learning us. I hope you post more Arduino tutorials.

  2. francis_bell

    I have been trying to follow this tutorial, but have run across a problem, some help would be greatly appreciated.

    All fine up until I try to
    use the code line in python:
    ser = serial.Serial(‘/dev/ttyACM1’,9600)

    It comes up with an error:

    Traceback (most recent call last):
    File “”, line 1, in
    ser = serial.Serial(‘/dev/ttyACM2’,9600)
    File “C:\Users\xxx\Python35-32\lib\site-packages\serial\serialwin32.py”, line 31, in __init__
    super(Serial, self).__init__(*args, **kwargs)
    File “C:\Users\xxx\Python35-32\lib\site-packages\serial\serialutil.py”, line 236, in __init__
    self.open()
    File “C:\Users\Python35-32\lib\site-packages\serial\serialwin32.py”, line 62, in open
    raise SerialException(“could not open port {!r}: {!r}”.format(self.portstr, ctypes.WinError()))
    serial.serialutil.SerialException: could not open port ‘/dev/ttyACM2’: FileNotFoundError(2, ‘The system cannot find the path specified.’, None, 3)

    I am pretty stumped on what this means, I have tried with ACM1, ACM2, ACM3, ACM 4 but will still not work,

    Any ideas?

    • admin

      Hi, from the error message I understand that you are using Windows. Thus, the USB port are not named ttyXXXX but COM1, COM2, … and so on. Replace the ttyACM1 in your code with the current COM used from you Arduino.
      You can check this directly from the ArduinoIDE.

  3. simone.bergamo

    Ciao Fabio,
    con un approccio simile, al posto del pc potrei collegare anche un dispositivo (es. una bilancia elettronica) che spara stringhe sempre tramite seriale? Ovviamente non con la logica di comandare un servo motore…

    • admin

      Si, se gestisci le stringhe ricevute da Arduino, comprendendo la loro codifica in altrettanti comandi od operazioni che vuoi eseguire.

  4. Jim

    Thank you for this great tutorial , I am willing to send from python to arduino “only” one no. for now representing no. to turn the dc motor on and count the rotation encoder steps ,the arduino code is working fine when I write in serial window also form python IDE but when reading from csv file not ! my code is from your suggestion with no error and printing on python shell the no. I want

    import serial
    import time
    import os
    os.getcwd()
    ser = serial.Serial(‘com3’,9600)
    file = open(‘C:\\Python27\\robosampl.csv’,’rb’)
    while 1:
    line = file.readline()
    if not line:
    break
    print(line)
    ser.write(line)
    time.sleep(3)

    knowing that the csv file was text file and I changed its extension to csv

  5. Jim

    Hi : sorry for th eprevious “messy” code but I solved the issue of sending one no. from csv to arduino LED and Servo but as I mentioned sending one no. (representing no. of encoder steps for dc motor) is still not solved,the code for python csv to LED/servo I used

    import serial
    import time
    import csv
    i=0
    ser=serial.Serial(‘com3’,9600)
    f = open (‘C:\\Python27\\robosampl.csv’,’rb’)
    readFile=csv.reader(f)

    for row in readFile:

    ser.write(row[i])
    print(row[i])

  6. Yaron

    Hi
    Thanks for the great tutorial!
    I came in this page looking for help regarding reading the PC language status (for multi language users) and sending it to the arduino

    Can I do it with python and make a windows app using it? I would love for some help in writing such a code

    thanks!

Lascia un commento