Pyserial: la libreria che semplifica la seriale

Nell'articolo di oggi parleremo della libreria pyserial di Python. Questo modulo incapsula l'accesso alla porta seriale fornendone gli strumenti per la gestione su sistemi OSX, Windows, Linux, BSD, IronPython e Jython selezionandone in maniera automatica il backend appropriato, in completa trasparenza da parte dell'utente che ne utilizza le funzionalità. La libreria pyserial ci permette con poche righe di codice di poter accedere ed usare i vari servizi necessari alla gestione delle porte seriali avendo un software portabile sulle più importanti piattaforme esistenti. Oltre ad essere multipiattaforma e semplice da usare, pyserial supporta varie velocità di trasferimento dati, il bit di stop e la parità, può lavorare con o senza timeout di ricezione. Scritta totalmente in Python viene supportata sia dalla versione 2 che dalla versione 3 di esso. A seguire vedremo nel dettaglio le varie funzionalità che ci propone, daremo uno sguardo alla seriale in generale e vedremo alcuni esempi applicati su schede quali Arduino e Raspberry Pi.

In questo articolo analizzeremo i seguenti punti:

  • Introduzione alla libreria pyserial
  • Ripasso protocollo RS232
  • Descrizione dei metodi che compongono la libreria pyserial
  • Esempi di applicazioni reali sulla libreria pyserial

Introduzione a pyserial

La libreria pyserial ha la funzione di agire direttamente sulla porta seriale tramite il linguaggio di programmazione Python per dar modo d'interagire con eventuali periferiche collegate, quali possono essere una schedina Arduino o Raspberry Pi, un lettore di barcode o QR, una telecamera, dispositivi analogici per varie misurazioni, dispositivi industriali, altri terminali, e altri device. Scritta interamente in linguaggio Python, rilasciata open source, ci permette di sfruttare appieno le richieste del protocollo RS232 con il minimo sforzo (peculiarità del linguaggio Python, ovvero fare tutto in maniera semplice) e scrivendo solamente poche righe di codice il nostro software può comunicare tranquillamente con porte com reali, porte com virtuali, porte USB-seriale, da TCP a seriale, e porte COM bluetooth. Tra le proprie caratteristiche evidenziamo che è crossplatform, ovvero gira su tutti i sistemi operativi desktop o laptop più importanti quali OSX, Linux, Windows, BSD (variante originaria del sistema operativo UNIX), IronPython (implementazione del linguaggio Python funzionante sulle implementazioni del .NET framework) e Jython (implementazione del linguaggio Python scritto in Java), adattando in maniera automatica il backend da usare per il sistema in uso in maniera totalmente trasparente al programmatore o chi ne fa uso. La libreria pyserial dispone di un supporto per le diverse dimensioni di byte, per il bit di stop, la parità e il controllo di flusso dando la possibilità di poter usare o no un timeout di ricezione. Supportata sia dalla versione di Python 2 che dalla versione 3, sempre in continuo aggiornamento, la libreria pyserial risulta essere uno strumento valido e completo.

Una volta installata ci vengono forniti due interessanti strumenti da usare tramite l'interprete Python, uno serve per elencare tutte le porte seriali disponibili digitando il seguente comando:

python -m serial.tools.list_ports

Verrà stampato il nome di tutti i dispositivi seriali collegati al computer. Questo strumento è molto utile per determinare il nome della porta seriale per una qualsiasi periferica collegata.

L'altro strumento è miniterm, un semplice terminale seriale compreso nel pacchetto pyserial, che una volta individuata la porta a cui è collegata la periferica interessata (vedi comando precedente), e lanciando il comando:

python -m serial.tools.miniterm Nome-Porta 115200

sarà possibile interagirvi inviando e ricevendo dati con il dispositivo collegato digitando i comandi nel terminale.
Come detto in precedenza, la semplicità è l'eccellenza di Python e il modulo pyserial non fa di certo eccezione, dall'installazione tramite l'installatore di Python pip, all'apertura della comunicazione che richiede l'utilizzo di soli due parametri, fino ad arrivare alla lettura e alla scrittura dei dati e alla gestione degli errori, il tutto viene gestito da pochi e semplici comandi.

Ripasso protocollo RS232

Partiamo dando la definizione di protocollo: "Le regole e le convenzioni utilizzate per la comunicazione con lo stesso livello di un'altra entità vengono dette Protocollo". Detto ciò, diciamo che RS232 è un protocollo seriale capace di collegare due entità distanti anche più di 15m a una velocità massima di 20Kbit/s. Fatta per connessioni a bassa velocità per lo scambio dati tra dispositivi digitali, invia e riceve dati in sequenza di un bit alla volta a gruppi di byte su di un solo filo da cui prende il nome di seriale. Nata per interconnettere un terminale ad una periferica ed in tal senso sono stati coniati gli acronimi DTE (Data Terminal Equipment) per il terminale e DCE (Data Communication Equipment) per la periferica.

Lo scambio di informazioni avviene tra una linea di invio dati detta TD e una linea di ricezione dati detta RD, oltre ad esse esistono una serie di circuiti di controllo usati per gestire la comunicazione tra DTE e DCE. Le linee di dati con i propri circuiti di controllo operano solamente in una direzione, possono essere inviati dal DTE e ricevuti dal DCE o viceversa, oppure la trasmissione può anche avvenire in maniera contemporanea, questo sistema è detto full duplex (in assenza di contemporaneità il sistema si chiama half duplex). Il connettore standard è un connettore DB-25 a 25 pin che può sostenere sia la modalità sincrona che asincrona, ormai però questo tipo di supporto è stato completamente abbandonato lasciando il posto a una versione a 9 pin detta ridotta che supporta solamente la modalità asincrona. I connettori anche se ormai sono stati sostituiti dalle porte USB si possono trovare su PC datati o PC per uso industriale dove le periferiche seriali sono ancora molto usate e vengono (o venivano) installati direttamente nelle schede madri, su computer più vecchi potevano risiedere su speciali schede multiple inserite nell'alloggio d'espansione. Per poter usare una periferica seriale su un computer non dotato di porte seriali si possono usare dei convertitori seriale-USB acquistabili a pochi euro (consiglio personale: acquistarli buoni perché non sempre funzionano bene), oppure usando delle porte seriali virtuali che simulano in tutto e per tutto il comportamento di una porta reale. Per testare il funzionamento delle porte seriali  o interagire con esse possiamo utilizzare speciali programmi detti monitor seriali che hanno la funzione d'iterazione tra noi e la porta. In Figura 1 si può vedere lo schema della piedinatura di un connettore DB-9.

Figura 1: Schema piedinatura connettore DB9

Le porte seriali rispetto alle porte USB hanno il vantaggio di poter coprire distanze maggiori arrivando a superare i 15m contro i 5m delle USB, minor numero di fili conduttori, costi minori per le interfacce, maggior facilità di implementazione dei protocolli, dall'altro canto l'USB è polivalente ovvero supporta più funzioni; al contrario della seriale che ha un'unica funzione di trasmettere e ricevere, la USB può fornire elettricità ad un dispositivo invece la seriale no, la USB è più veloce della porta seriale, la porta USB supporta il collegamento di molte periferiche in cascata usando degli HUB, mentre la seriale permette un collegamento solo alla volta.

Figura 2: Convertitori seriali-USB

Tramite il protocollo RS232 si possono collegare due DTE (PC), utilizzandone uno come se fosse un DCE, ciò è possibile scambiando alcuni segnali, questo tipo di collegamento è detto NULL MODEM.

Figura 3: Schema collegamento tipo NULL MODEM

In Figura 3 si può vedere lo schema di collegamento di tipo NULL MODEM, da notare che per poter far funzionare una connessione seriale potrebbero bastare anche solo 3 fili quali il TxD, l'RxD e il GND. Il principio di funzionamento delle porte seriali è molto semplice, il dato inviato in uscita viene scomposto in bit che vengono trasmessi in sequenza uno per volta in gruppi di byte (8 bit, ma possono essere anche 5, 6, 7, 9), quando arriva in uscita il dato viene ricomposto e reso disponibile per l'uso. La tensione della linea ha un valore alto compreso tra i +5V e +12V che corrisponde allo 0 logico (detto anche space). In assenza di trasmissione la tensione sulla linea ha un valore basso compreso tra i -5V e -12V che corrisponde ad un livello logico 1 (detto anche mark), le tensioni in uscita variano da +3V a -3V. All'inizio della transizione la linea si trova ad uno stato basso fino ad incontrare un bit detto bit di start che porta la tensione ad uno stato alto che viene letto per un tempo di 1/baud, da quel momento inizia la trasmissione, a seguire arriva il dato meno significativo (detto LSB) fino ad arrivare a quello più significativo (MSB), il byte viene chiuso da un bit detto di stop che riporta la linea ad uno stato di riposo, quindi eventualmente inizia la lettura di un nuovo pacchetto partendo dalla lettura di un nuovo bit di start. Da notare che in un pacchetto il bit di start è sempre pari ad uno, invece il numero di bit di stop può variare.

Oltre ai bit del dato inviato, il bit di start, il bit di stop possiamo avere anche un ulteriore bit facoltativo, utile per verificare la correttezza del dato ricevuto, questo bit è detto bit di parità e può essere impostato a None, pari (even), dispari (odd). Ciò significa che se il valore sarà None non ci sarà nessun tipo di controllo, se impostato ad even il bit di parità dovrà fare in modo che il numero di bit alti sia un numero pari (compreso quello di parità), se impostato ad odd il bit di parità dovrà fare in modo che il numero di bit alti sia un numero dispari (compreso quello di parità). Inoltre, esistono altri due tipi di parità che sono di tipo Mark e di tipo Space.
Detto tutto ciò, passiamo a spiegare il significato della sigla d'esempio 9600 8n1, dove 9600 indica la velocità di trasmissione dati espressa in baud (alcuni valori di velocità supportati possono essere 9600, 14400, 19200, 28800, 38400, ecc.) indicando il numero di transizioni al secondo che avvengono sulla linea, un altro metodo per valutare la velocità è il bps che indica quanti bit sono scambiati al secondo. Continuando l'esposizione della sigla troviamo il valore 8 che indica il numero di bit trasmessi in un pacchetto di cui 8 risulta essere uguale ad un byte, possiamo avere anche come numero di bit 5, 6, 7, 9, a seguire troviamo valore none che indica la parità che in questo caso è none ma può essere anche even o odd, infine troviamo quanti bit di stop possiamo usare che può essere uno o più.

Installazione libreria pyserial

L'installazione della libreria pyserial nella maggior parte dei sistemi operativi che la supportano è molto semplice, basta solamente usare l'installatore dei pacchetti di Python Pip digitando il seguente codice:

pip install pyserial

per verificare se il pacchetto è stato installato correttamente digitare il comando:

pip list

Se la libreria apparirà nella lista vorrà dire che è stata installata correttamente.

Metodi e proprietà

Quando si utilizza pyserial, è necessario configurare alcuni parametri quali:

  • Baud rate
  • Il nome della porta
  • Il bit di parità
  • Il bit di stop
  • Il timeout

Il baud rate indica quanto velocemente opera la porta a trasmettere dati, rappresentando il numero di transizioni al secondo che avvengono sulla linea. Come appena detto, la velocità di trasmissione si misura in baud o bps (bit per secondo).
Il nome della porta è l'identificativo nominale della porta usata, per i sistemi Windows sarà indicata con COM1, COM2, ecc. Per i sistemi GNU/Linux sarà indicata con /dev/ttyUSB0.
Il bit di parità usato per il controllo di eventuali errori.
Il bit di stop indica la fine della trasmissione di un dato, può essere uno o più di uno.
Il timeout imposta un timeout in lettura e serve per evitare che la porta si blocchi ad esempio quando si usa il metodo readline().

Vediamo ora un semplice esempio di come definire i parametri sopra indicati:

import serial

seriale = serial.Serial(port = "COM1",
                        baudrate = 115200,
                        bytesize = 8,
                        timeout = 2,
                        stopbits = serial.STOPBITS_ONE
                        )

Il costrutto appena definito indica che saremo collegati alla porta COM1 ad una velocità di 115200 baud senza parità, la trasmissione avverrà a gruppi di 8 bit alla volta, ci sarà un solo bit di stop e 2 secondi di timeout. Generalmente, si tende a definire solamente due dei parametri appena indicati, che sono il nome della porta e la velocità di trasmissione, in realtà l'unico parametro obbligatorio è il nome della porta, tutti gli altri hanno un valore predefinito e possono essere omessi dall'inizializzazione del costruttore. I valori di default sono 9600 baud per la velocità, la parità è None, il timeout è pari a 0, e abbiamo un solo bit di stop. Usando una forma un pò più contratta possiamo riscrivere il costrutto di prima nel seguente modo.

import serial
seriale = serial.Serial("COM1",115200)

Possiamo definire le proprietà anche in secondo momento dall'inizializzazione del collegamento, ciò è fatto come segue:

import serial
seriale = serial.Serial()
seriale.port = "COM1"
seriale.baudrate = 115200
seriale.parity = "N"
seriale.bytesize = 8
seriale.timeout = 0

Adesso diamo un'occhiata ad alcuni dei metodi più importanti che compongono la classe.

  • open()
  • close()
  • read(size)
  • readline()
  • write(data)
  • is_waiting()

Il metodo open() serve per aprire la porta, close() la chiude, il metodo read(size) leggerà un determinato numero di bytes basato sul valore del parametro impostato, se non viene impostato nessun parametro leggerà un byte solo, il metodo readline() legge una riga intera fino al "\n" compreso, in questo caso è buona norma impostare un timeout perché se durante la lettura non viene trovato nessun carattere di fine linea il programma potrebbe bloccarsi restando in attesa all'infinito; il metodo write(data) serve per scrivere sulla porta, il metodo is_waiting() contiene il numero di byte nel buffer. Detto tutto ciò, siamo finalmente pronti a sporcarci le mani attraverso alcuni esempi pratici per capirne meglio il funzionamento.

Collegamento della porta

Per iniziare prendiamo una schedina Arduino UNO che collegheremo al PC (per questo esempio sto usando un sistema OSX), fatto ciò il problema più grosso è quello di individuare il nome della porta del computer a cui è stato assegnato Arduino. Possiamo fare questo semplicemente aprendo il terminale e digitando il comando:

ls /dev/tty.*

Ci verrà restituita la lista dei dispositivi connessi in quel momento, come mostrato in Figura 4.

Figura 4

Una volta individuato il nome della porta possiamo aprire un semplice editor e digitare il seguente codice:

import serial

try:
    seriale = serial.Serial("/dev/cu.usbserial-14540", 115200)

    if seriale.is_open:
       print ("\n Il collegamento è stato effettuato con successo  \n")

    seriale.close()

except:
    print("""
    Collegamento fallito,
    controllare se il dispositivo è stato connesso correttamente,
    o di avere inserito il nome della porta corretto \n""")

Da notare che è stato letto il valore dell'attributo is_open per verificare l'avvenuta connessione alla scheda.

Lettura dei dati

In questo esempio vedremo come leggere i dati ricevuti dalla seriale. Procediamo con l'esempio aprendo l'editor di Arduino, creiamo un nuovo sketch inserendo il seguente codice che poi provvederemo a caricare sulla scheda.

int contatore = 0;
void setup() {
  Serial.begin(9600);

}

void loop() {
  Serial.println(contatore);
  contatore ++;
  delay(500);
}

Partendo da una variabile di tipo intero pari a 0 essa verrà inviata per mezzo della seriale ogni mezzo secondo ed incrementata di 1.
Adesso apriamo un editor e scriviamo il codice Python per leggere i dati inviati da Arduino.

import serial

seriale = serial.Serial("/dev/cu.usbserial-14540", 9600, timeout=1)

while True:
    x = seriale.readline()
    print (x.decode())

seriale.close()

In Figura 5 viene esposto l'output.

Figura 5

Per questo esempio è stata usata la funzione readline(), che legge l'intera riga fino al \n (carattere di a capo) compreso. Nel listato precedente è stata usata la funzione decode() per decodificare il formato della stringa in lettura, infatti se non avessimo usato tale funzione le stringhe lette sarebbero state stampate come segue: b'0\r\n', b'1\r\n',b'2\r\n', ecc.
La notazione "b" viene utilizzata per specificare una stringa di byte in Python. Rispetto alle stringhe regolari, che hanno caratteri ASCII, la stringa di byte è un array di variabili di byte in cui ogni elemento esadecimale ha un valore compreso tra 0 e 255.

Vediamo ora la funzione read(size), che senza nessun parametro legge un solo carattere, altrimenti legge un numero di caratteri corrispondente al paramento indicato. Adesso verrà collegato un bottone ad Arduino e ad ogni pressione di esso verrà inviato un carattere. A seguire il codice Arduino:

[...]

ATTENZIONE: quello che hai appena letto è solo un estratto, l'Articolo Tecnico completo è composto da ben 3824 parole ed è riservato agli ABBONATI. Con l'Abbonamento avrai anche accesso a tutti gli altri Articoli Tecnici che potrai leggere in formato PDF per un anno. ABBONATI ORA, è semplice e sicuro.

Scarica subito una copia gratis

Una risposta

  1. Avatar photo ARTURAS TUSKANI 2 Ottobre 2023

Scrivi un commento

Seguici anche sul tuo Social Network preferito!

Send this to a friend