La Comunicazione Seriale

Ci sto lavorando

 

Documentazione:

UNI roma 1 - Trasmissione seriale
wikipedia - USB
Java-simple-serial-connector - Sito dello sviluppatore
Download jssc2.8.0 ultima versione
JSSC Reference

 

Questo è un argomento molto importante in quanto permette la comunicazione del PC con il mondo esterno ed in particolare con Arduino un microcontrollore che fa miracoli. Esso è in grado di interfacciasi con un'infinità di sensori consentendo l'accesso ad un'infinità di applicazioni. La comunicazione seriale avviene attraverso la vecchia porta seriale RS- 232 ormai sostituita dalla più performante USB. Comunicare con Arduino è l'obiettivo principale di queste pagine. Vogliamo farlo mediante l'uso del linguaggio java, per questo bisogna scaricare delle apposite librerie. Se ne trovano alcune in rete una è la RxTx abbasta complessa da gestire e, sembra non più supportata, quindi dopo alcuni tentativi ho preferito cercare qualcosa di più accessibile e credo di averlo trovato in una libreria il cui nome è esplicativo JSSC acronimo di "Java Simple Serial Connector" scritta dal russo Alexey Sokolov alias scream3r.

 

 

Istallazione:

Il materiale scaricato consta di:

JSSC-2.8.0.jar - è la vera e propria libreria, ossia la collezione di classi compilate (bycode) tipo file.class.

JSSC-2.80-javadoc.jar - è il reference. Qui il jar ha funzione di file zip. Se vogliamo averlo a disposizione in locale possiamo scompattare il file jar con winzip ed inserire il tutto in una direttory sul pc bastera eseguire l'index.html. Se si vuole risparmiare spazio c'è chi lo ha messo online per noi lo troviamo nei link della documentazione (qui sopra) alla voce JSSC Reference.

JSSC-2.8.0-sources.jar - ci sono i sorgenti i file.java delle 7 classi che compongono la libreria.

 

A questo punto dobbiamo aggiungere la libreria (JSSC-2.8.0.jar) nel posto giusto e questo è:

C:\Program Files\Java\jre1.8.0_151\lib\ext.

Copiamola in questa posizione.

Per testarla ho pensato di creare un progetto di nome "ArduinoRGB" con il quale andremo a colloquiare via porta seriale con Arduino sul quale avremo allestito un semplice circuito costituito da un led ed un potenziometro lineare. La comunicazione sarà bidirezionale ossia dal PC potremo inviare comandi per accendere o spengere il led o farlo lampeggiare con una frequenza che potremo impostare. Dal lato Arduino potremo agendo sul potenziometro far variare in modo continuo la luminosità del led da zero alla massima luminosità inviando le relative informazioni al PC che le userà per visualizzare i valori da o a 5 Volt della tensione ai capi del potensiometro mediante una sorta di voltmetro digitale. Si potrà viceversa pilotare il led simulando l'azione del potenziometro tramite uno slider che spostato con il mouse farà variare le luminosità del led.

Questo è il pannello di controllo lato PC:

 

 

 

Creiamo il progetto ed importiamo la libreria JSSC-2.8.0.jar così:

 

Clicchiamo su Add JAR/Folder... e andiamo a pescarla dove l'abbiamo messa ossia in:

C:\Program Files\Java\jre1.8.0_151\lib\ext.

 

 

Ora possiamo passare al codice:

  1 package arduinorgb;
  2 
  3 import java.awt.*;
  4 import java.awt.Color;
  5 import java.awt.event.*;
  6 import java.util.Locale;
  7 import javax.swing.*;
  8 import jssc.SerialPort;
  9 import jssc.SerialPortEvent;
 10 import jssc.SerialPortEventListener;
 11 import jssc.SerialPortException;
 12 
 13 public class cFrame extends JFrame {
 14     JDialog dialog;
 15     JLabel labelDialog1;
 16     SerialPort serialPort;         
 17     public cFrame() {
 18         serialInitialize();
 19         initComponents();
 20         myInitialize();
 21     }
 22     @SuppressWarnings("unchecked")
 23     // <editor-fold defaultstate="collapsed" desc="Generated Code">
 24     private void initComponents() {
...
127 
128     private void jToggleButton1ItemStateChanged(java.awt.event.ItemEvent evt) { 
129         //Accendi o spegni il led
130         byte[] bb = new byte[1];               
131         int status = evt.getStateChange();
132             if(status == ItemEvent.SELECTED){               
133                 jToggleButton1.setText("Spegni");
134                 jToggleButton3.setSelected(false);
135                 jToggleButton3.setText("Attiva il Potenziometro");
136                 bb[0]=(byte)'1';//Accendi led               
137             }else{
138                 bb[0]=(byte)'0';//Spegni led
139                 jToggleButton1.setText("Accendi");
140             }
141             try{
142                 serialPort.writeBytes(bb);//invia il comando (0/1)
143             }catch (SerialPortException ex) {
144                 System.out.println(ex);
145             }       
146     }                                               
147 
148     private void jToggleButton2ItemStateChanged(java.awt.event.ItemEvent evt) {
149         //Attiva o disattiva il blinking      
150         byte[] bb = new byte[1];        
151         int status = evt.getStateChange();
152             if(status == ItemEvent.SELECTED){               
153                 jToggleButton2.setText("No Blink");
154                 jToggleButton3.setSelected(false);
155                 jToggleButton3.setText("Attiva il Potenziometro");
156                 jToggleButton1.setSelected(false);
157                 jToggleButton1.setText("Accendi");
158                 bb[0]=(byte)'2';//attiva il blinking              
159             }else{              
160                 bb[0]=(byte)'0';//spegni il led
161                 jToggleButton2.setText("Blink");
162             }           
163             try{
164                 serialPort.writeBytes(bb);//invia comando (blink/spegni) 
165             }catch (SerialPortException ex) {
166                 System.out.println(ex);
167             }       
168     }                                               
169 
170     private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { 
171         //Imposta il Delay del blink
172         String txt=jTextField1.getText();//leggi il valore in millisecondi dal texbox 
173         if(isNumeric(txt)){//solo se è un numero
174             txt = "w"+txt;//comando w seguito dal numero es. w255
175             try{
176                 serialPort.writeString(txt);//invia la stringa  
177             }catch (SerialPortException ex) {
178                 System.out.println(ex);
179             }  
180         }else{
181             dialog.setVisible(true);//se non è un numero compare messaggio di errore
182         }
183         
184     }                                        
185 
186     private void jToggleButton3ItemStateChanged(java.awt.event.ItemEvent evt) {     
187         //Attiva o disattiva il Potenziometro      
188         byte[] bb = new byte[1];        
189         int status = evt.getStateChange();
190             if(status == ItemEvent.SELECTED){               
191                 jToggleButton3.setText("Disattiva il Potenziometro");
192                 jToggleButton2.setSelected(false);
193                 jLabel3.setVisible(true);
194                 jToggleButton2.setText("Blink");
195                 bb[0]=(byte)'3';//potenziometro attivo              
196             }else{              
197                 jToggleButton3.setText("Attiva il Potenziometro");  
198                 bb[0]=(byte)'4';//potenziometro disattivo
199                 jLabel3.setVisible(false);
200             }  
201             try{
202                 serialPort.writeBytes(bb);//invia comando
203             }catch (SerialPortException ex) {
204                 System.out.println(ex);
205             }       
206     } 
207 
208     private void jSlider1StateChanged(javax.swing.event.ChangeEvent evt) { 
209         //come sposto lo slider scateno un evento
210         jToggleButton3.setText("Attiva il potenziometro");
211         jToggleButton3.setSelected(false);
212         int gv=jSlider1.getValue();//preleva il valore
213         String st=""+gv;//trasforma l'int in string       
214         jLabel4.setText(st);       
215         st = "s"+st+"\r\n";//aggiungi s davanti ed un LF-CR in coda           
216         try{
217             serialPort.writeString(st);//invia la stringa
218         }catch (SerialPortException ex) {
219             System.out.println(ex);
220         }
221     }                                     
222     //verifica se è un numero con un '-' opzionale e dei decimali
223     public boolean isNumeric(String str){
224         return str.matches("-?\\d+(\\.\\d+)?");  
225     }
226     public void myInitialize(){
227         String desc = "<html><p align=\"center\">Attenzione!<br/>"
228                 +"Non è un numero</p></html>";        
229         labelDialog1 = new JLabel(desc,JLabel.CENTER);
230         labelDialog1.setFont(new java.awt.Font("Tahoma", 1, 24));
231         jLabel3.setVisible(false);
232         
233         dialog = new JDialog(this, "JDialog", true);
234         dialog.setBounds(850, 575, 400, 150);
235         dialog.getContentPane().setLayout(new BorderLayout());
236         dialog.getContentPane().add(BorderLayout.CENTER,labelDialog1);
237         dialog.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);          
238     }
239     public void serialInitialize() { 
240         try {
241             serialPort = new SerialPort("COM3");
242             // open serial port 
243             System.out.println("Port opened: " + serialPort.openPort()); 
244             // set the serial port parameters 
245             System.out.println("Params setted: " + serialPort.setParams(115200, 8, 1, 0)); 
246             Thread.sleep(2000);//Dai tempo ad arduino di resettarsi 
247             //init = true; 
248             int mask = SerialPort.MASK_RXCHAR;
249             //Imposta la maschera
250             serialPort.setEventsMask(mask);            
251             //Aggiungi un'interfaccia attraverso la quale riceveremo informazioni sugli eventi
252             serialPort.addEventListener(new SerialPortReader());
253              System.out.println("La porta è: " + serialPort.getPortName());
254              System.out.println("PRONTI******"); 
255         } catch (SerialPortException | InterruptedException ex){ 
256             System.out.println(ex); 
257           } 
258     }
259     public class SerialPortReader implements SerialPortEventListener {
260         @Override
261         public void serialEvent(SerialPortEvent event) { 
262             String str="",stv;
263             byte[] by= new byte[1];                       
264             while(true){
265                 try {             
266                     by = serialPort.readBytes(1);
267                     if(by[0]!=10 && by[0]!=13){                         
268                          if(by[0]>47 && by[0]<58){//se è un numero
269                             str +=(char)by[0];//accumola nella stringa 
270                          }                        
271                     }else{                       
272                         if(by[0]==13) {                                  
273                             try{
274                                 stv=toVolt(str);
275                                 try{
276                                     jLabel3.setText(stv);   
277                                 }catch(NullPointerException ex){
278                                     //System.out.println(ex);
279                                 }                               
280                                 break;
281                             }catch(NumberFormatException ex){
282                                 //System.out.println(ex);
283                             }                                            
284                         }
285                      }               
286                 }catch (SerialPortException ex) {
287                    System.out.println(ex);
288                 }                    
289             }                           
290         }
291     }
292     private String toVolt(String str){        
293         double dbt=0.0;
294         double dbr;
295         String ris;        
296         try{
297             dbt = Double.parseDouble(str);//da stringa a double     
298         }catch(NullPointerException np){
299             System.out.println("toVolt(): "+np);      
300         }            
301         dbr=(dbt*5)/1023;                    
302         ris=String.format(new Locale("en"),"%1$.2f",dbr);
303         return(ris);       
304     }
305     public static void main(String args[]) {
306         /* Set the Nimbus look and feel */
...
328 
329         /* Create and display the form */
330         java.awt.EventQueue.invokeLater(new Runnable() {
331             public void run() {
332                 cFrame cf = new cFrame();
333                 cf.getContentPane().setBackground(new Color(240,240,240));
334                 cf.setVisible(true);               
335             }
336         });
337     }    
338     // Variables declaration - do not modify
339     private javax.swing.JButton jButton1;
340     private javax.swing.JLabel jLabel1;
341     private javax.swing.JLabel jLabel2;
342     private javax.swing.JLabel jLabel3;
343     private javax.swing.JLabel jLabel4;
344     private javax.swing.JSlider jSlider1;
345     private javax.swing.JTextField jTextField1;
346     private javax.swing.JToggleButton jToggleButton1;
347     private javax.swing.JToggleButton jToggleButton2;
348     private javax.swing.JToggleButton jToggleButton3;
349     // End of variables declaration                   
350 }

 

Discussione:

L'unica classe realizzata estende un JFrame ed è anche eseguibile (righe da 340 a 348) con l'uso della classe anonima che implementa l'interfaccia Runnable con l'override del suo unico metodo run e questo, ricordo, per far si che i componenti swing lavorino nel thread EDT.

Il frame di base è stato creato in modalità visual, le aree a sfonfo grigio sono autogenerate da Netbeans e non sono editabili e sono contenute nel metoto initComponents() (righe 31-135). Per completare il form ho creato il metodo myInizialize() (righe 236-248). Le parti del codice che interessano la comunicazione seriale sono nel metodo serialInitialize() (righe 249-269) e serialPortReader() (righe 270-302).

Iniziamo con l'analizzare serialInitialize()

.
239     public void serialInitialize() { 
240         try {
241             serialPort = new SerialPort("COM3");
242             // open serial port 
243             System.out.println("Port opened: " + serialPort.openPort()); 
244             // set the serial port parameters 
245             System.out.println("Params setted: " + serialPort.setParams(115200, 8, 1, 0)); 
246             Thread.sleep(2000);//Dai tempo ad arduino di resettarsi 
247             //init = true; 
248             int mask = SerialPort.MASK_RXCHAR;
249             //Imposta la maschera
250             serialPort.setEventsMask(mask);            
251             //Aggiungi un'interfaccia attraverso la quale riceveremo informazioni sugli eventi
252             serialPort.addEventListener(new SerialPortReader());
253              System.out.println("La porta è: " + serialPort.getPortName());
254              System.out.println("PRONTI******"); 
255         } catch (SerialPortException | InterruptedException ex){ 
256             System.out.println(ex); 
257           } 
258     }

 

Vediamo cosa accade. Si crea un'istanza della classe serialPort fornendo al costruttore la porta, la COM3 nel nostro caso. C'e anche il modo di fargli rilevare la lista delle com disponibili ma puichè nel mio PC Arduino si collega mediante la COM3 sono andato diretto. Una volta creato l'oggetto porta seriale bisogna aprirla. Questo provoca una singolare reazione da parte di Arduino il quale in seguito alla apertura della porta si resetta. Una volta aperta bisogna impostarla il metodo setParams riceve 4 argomenti che sono

  1. baudRate - data transfer rate (115200)
  2. dataBits - number of data bits (8)
  3. stopBits - number of stop bits (1)
  4. parity - parity (0)

La pausa di 2 secondi Thread.sleep(2000); servirà a dare il tempo ad Arduino di resettarsi.

Ora parliamo un attimo di mask. Faccio una premessa molto sintetica (per un approfondimento si veda la documentazione 1° link) nella trasmissione seriale RS 232 ci sono 9 pin (9 cavi) due servono per la trasmissione dei dat uno TX da PC a Arduini e RX da arduino a PC poi ce ne sono altri che servono ai dispositivi per passarsi informazioni di altro tipo per esempio sul RTS il PC dice ad Ar. sei pronto che ti devo inviare dei dati e Ar. se prondo gli risponde sul CTS si ok sono pronto invia pure. Invece il PC avverte Ar di essere pronto a ricevere dati sul DTR ed Ar gli risponde che è pronto adf inviargleli sul DSR. Quindi se parliamo di ricezione avremo i dati su RX ed i messaggi su CTS e DSR. Ora sinceramente di quello che si dicono fra loro il PC con Ar a noi non ci interessa la cosa che invece ci interessa sono i dati in arrivo parliamo di serialPortReader() lettura. La maschera serve a filtrare fa passare solo i dati che ci interessano.

Nella maschera, dobbiamo specificare i tipi di eventi che vogliamo monitorare. Se per esempio, ci interessa sapere solo se ci sono dati in arrivo la maschera deve avere il valore seguente: MASK_RXCHAR. Se, per esempio, abbiamo bisogno di sapere anche dei cambiamenti negli stati delle linee CTS e DSR, la maschera deve apparire così: SerialPort.MASK_RXCHAR + SerialPort.MASK_CTS + SerialPort.MASK_DSR.

Ma poiche a noi interessano solo i dati in arrivo useremo int mask = SerialPort.MASK_RXCHAR;

Nella righa 252 andiamo ad associare un ascoltatore (l'oggetto SerialPortReader) alla serialPort . L'ascoltatore è un oggetto che implementa un'interfaccia di tipo serialPoreEventListener e quindi sarà costretto a definire il metodo serialEvent all'interno del quale adremo a fargli fare quello che vogliamo quando si scatena l'evento, ossia quando arriva qualche dato. Ad esempio leggerlo.

Naturalmete il tuttio deve stare all'interno di un blocco try ... catch in quanto si potrebbe verificare una eccezione del tipo SerialPortException.

 

Ora passiamo a discutere la classe SerialPortReader

 

259     public class SerialPortReader implements SerialPortEventListener {
260         @Override
261         public void serialEvent(SerialPortEvent event) { 
262             String str="",stv;
263             byte[] by= new byte[1];                       
264             while(true){
265                 try {             
266                     by = serialPort.readBytes(1);
267                     if(by[0]!=10 && by[0]!=13){                         
268                          if(by[0]>47 && by[0]<58){//se è un numero
269                             str +=(char)by[0];//accumola nella stringa 
270                          }                        
271                     }else{                       
272                         if(by[0]==13) {                                  
273                             try{
274                                 stv=toVolt(str);
275                                 try{
276                                     jLabel3.setText(stv);   
277                                 }catch(NullPointerException ex){
278                                     //System.out.println(ex);
279                                 }                               
280                                 break;
281                             }catch(NumberFormatException ex){
282                                 //System.out.println(ex);
283                             }                                            
284                         }
285                      }               
286                 }catch (SerialPortException ex) {
287                    System.out.println(ex);
288                 }                    
289             }                           
290         }
291     }

 

Questa è la parte che si fa carico di rimanere in acolto da eventuale invio di dati da parte di Arduino.

SerialPortReader è l'ascoltatore ossia la classe che starà in ascolto e che verrà asociata al source cFrame (vedi linea 252 nel metodo serialInitialize()). Nel momento che verrà lanciato un evento, ogni volta che avremo dati in arrivo dalla seriale, verrà eseguito il metodo serialEvent(). Vediamo cosa fa questo metodo:

  • Crea un array di byte di un solo elemento
  • Entra in un ciclo while
  • Legge il primo byte a disposizione by = serialPort.readBytes(1);
  • righe 267-270 se questo carattere letto è diverso da un line feed 10 o carriage return 13 che di solito stanno ad indicare la fine della stringha inviata
  • verifico se è un numero. Per essere un numero deve avere un codice ASCII compreso tra 48 e 57 (0 e 9). Se lo è lo vado ad aggiungere ala stringa str
  • Se invece è un ritorno carrello (13) significa che la stringa inviata è completa e possiamo quindi utilizzarla. Il metodo toVolt trasforma il numero inviato da Arduino con valori da 0 a 1023 in valori espressi in volt da 0 a 5 ed in formato decimale con 2 decimali dopo il punto
  • Questo valore (in forma di stringa) viene visualizzato nella label

I vari cicli try catch sono necessari per intercettare le varie eccezioni che si possono verificare e che farebbero terminare il programma