Progetto per comunicazione seriale

con protocollo Modbus RTU

in Visual Basic

V.1.00

 

 

Il progetto allegato a questo documento e’ essenzialmente un tentativo di realizzare un Master Modbus RTU che possa comunicare con degli slave attraverso la porta seriale di un PC.

 

Questo protocollo e’ molto usato specialmente nella comunicazione con PLC o strumentazione varia.

 

Girando in Internet ho potuto verificare che non esiste un progetto completamente free in Visual Basic che dia la possibilità di effettuare comunicazioni seriali con protocollo Modbus RTU.

Ho trovato solo delle ActiveX a pagamento (e che a che prezzi !!!).

 

Ho tentato quindi di realizzare un mio programma che sfrutti solo ed esclusivamente componenti generici di Visual Basic e che possa essere modificato a piacimento nonostante tutte le limitazioni che questa scelta comporta.

 

Le specifiche Modbus a cui ho fatto riferimento sono contenute nel documento PI-MBUS-300rev.J.PDF reperibile in rete o presso il sito www.modicom.com.

 

Esiste inoltre un altro sito www.modbus.org che tratta esclusivamente problematiche relative al protocollo in oggetto. Da questo sito e’ possibile scaricare altra documentazione che va ad integrare il documento sopraccitato che resta comunque il “vangelo” per tutti gli integratori di tale protocollo.

 

Ho integrato i comandi principali previsti dal protocollo, e più specificatamente la lettura e scrittura degli Holding Register (03h Read Holding Registers, 06h Write single register, 10h Write multiple registers), il comando Diagnostics (08h Diagnostics, solo subfunction 0), la lettura degli Ingressi Digitali (02h Read Discrete Inputs), la lettura e scrittura delle Uscite Digitali (01h Read coils, 05h Write single coil, 0Fh Write multiple coils) e la lettura degli Input Registers (04h Read Input Registers).

 

Per testare il mio programma, ho connesso 2 PC attraverso un cavo seriale RS232 incrociato ed ho utilizzato come slave un utile programmino che si chiama ModRSsim.exe reperibile in rete.

Questo programma simula la presenza contemporanea di 255 slave e oltre a visualizzare le varie aree di memoria, può visualizzare il traffico sulla linea seriale permettendo così un debug del protocollo molto comoda.

Ho scoperto però che ha qualche bacarozzo, spero che cio’ non vada a compromettere il mio programma J.

Uno di questi bachi e’ che la risposta al comando 06h non e’ conforme alle specifiche. Le specifiche infatti indicano la risposta come un puro echo del messaggio ricevuto dallo slave. Il programma ModRSim, invece, risponde con l’indirizzo diviso per 2 e il valore precedente del registro.

Siccome sono disponibili anche i sorgenti di questo programma (in C++) ho provato ad apportare le correzioni necessarie per scoprire poi (con profondo rammarico) che detti sorgenti sono della versione 3.6 quando il programma eseguibile e’ alla versione 4.1. Ovviamente mancano all’appello un sacco di funzioni presenti nell’ultima versione per cui ho lasciato perdere.

 

A causa dei sistemi Windows e di Visual Basic il programma ha le seguenti limitazioni:

 

1)       Non gestisce il segnale RTS della seriale, questo perché non e’ possibile assicurare un tempo certo tra l’uscita dell’ultimo carattere dallo shift register della seriale, e il momento in cui si va a girare il segnale, per cui eventuali interfacce RS232-RS485 che venissero utilizzate in collegamenti multi punto devono essere di tipo Auto-Switch. In altre parole devono arrangiarsi a capire quando devono pilotare la linea RS485 e quando devono restare in ascolto.

 

2)       Le tempistiche di Inter-messaggio (t35 delle specifiche) e di Inter-carattere (t15 delle specifiche) sono abbondantemente sovradimensionati in quanto Visual Basic non gestisce (che io sappia) tempi inferiori al millisecondo. Quindi e’ impossibile gestire tempi tra un carattere e l’altro dell’ordine di 750uS e tempi tra un messaggio e l’altro di 1.750uS. Tutto cio’ si traduce esclusivamente in un tempo di comunicazione più lungo del necessario, ma essendo il programma un Master non dovrebbero esserci problemi di comunicazione.

 

Tutto il protocollo e’ contenuto nel modulo Modbus.bas.

Per utilizzarlo e’ necessario impostare le seguenti variabili prima di inizializzare il modulo:

 

Address: Integer che contiene l’indirizzo dello slave con il quale si desidera comunicare. Secondo le specifiche questo indirizzo può andare da 1 a 247.

L’indirizzo 0 indica un messaggio Broadcast. Da 247 a 255 sono indirizzi riservati.

 

Num_serial_port: Integer che contiene il numero della porta che si intende utilizzare per la comunicazione.

Baud_rate: String contenente il baud rate che si intende utilizzare (ad esempio 9600)

Parity: String contenente il tipo di parità desiderato. Può essere N (none), E (even) o O (odd)

Stop_bit: String contenente il numero di bit di stop. Può essere 1 o 2. Secondo le specifiche i bit di stop devono sempre rendere il carattere trasmesso si 11 bits, per cui se si utilizzano parità E o O bisogna impostare 1, con la parità N bisogna impostare 2.

 

Nel modulo Modbus.bas ho poi definito delle costanti che devono essere adattate alle proprie esigenze e che riguardano le tempistiche del protocollo:

 

DELAY35: Tempo di 3.5 caratteri tra un frame e l'altro (mS). Secondo le specifiche dovrebbe essere il tempo di trasmissione di 3.5 caratteri da 11bit dipendente dal baud rate. Al minimo deve essere 1.175mS.

 

DELAY15: Tempo di 1.5 caratteri tra un carattere e l'altro (mS). Secondo le specifiche dovrebbe essere il tempo di trasmissione di 1.5 caratteri da 11bit dipendente dal baud rate. Al minimo deve essere 0.175mS.

 

Timeout: Tempo di timeout ricezione da slave (mS).

 

Le funzioni da richiamare sono:

 

Modbus_Init_Ser() : Deve essere chiamata prima di utilizzare le funzioni di trasmissione/ricezione per inizializzare i parametri del protocollo e aprire la porta seriale. Se il valore di ritorno e’ 0 tutto e’ andato bene, altrimenti la porta seriale non e’ stata aperta.

 

Modbus_Close_Ser(): Deve essere chiamata prima di uscire dal programma per chiudere la porta seriale.

 

Modbus_Rd_Holding_Register(messaggio() As Byte, Slave_address As Byte, Start_address As Long, Num_of_registers As Integer): Questa funzione legge il numero di registri holding (area di memoria 4) indicato da Num_of_registers, a partire dal registro Start_address. I dati letti vengono impaccati nell’array messaggio come byte per cui poi bisogna andare a ricostruire il dato a 16 bit. Possono essere letti in una sola comunicazione da 1 a 125 registri. L’indirizzo di start può andare da 1 a 65536.

 

Modbus_Preset_Single_Register(messaggio() As Byte, Slave_address As Byte, Register_address As Long, Value_of_register As Long) : Questa funzione permette di scrivere un registro holding (area di memoria 4) alla volta. Il dato da trasmettere deve essere impostato nella variabile Value_of_register. L’array messaggio viene utilizzato per la risposta dallo slave.

 

Modbus_Diagnostics(messaggio() As Byte, Slave_address As Byte, Sub_Function As Long, Data() As Long, Num_Data_Int As Integer) : Questa funzione permette di eseguire varie funzioni di test sullo slave. La sub funzione 0, ad esempio, fa eseguire allo slave un puro echo del messaggio ricevuto. Questo e’ utile al master per testare se lo slave e’ presente e la comunicazione seriale e’ corretta.

 

Modbus_Write_Multiple_Registers(messaggio() As Byte, Slave_address As Byte, Start_address As Long, Num_of_registers As Integer, Value_of_registers() As Long) : Questa funzione permette di scrivere fino a 125 registri Holding (area di memoria 4) in una sola trasmissione. I dati da trasmettere devono essere impostati nell’array Value_of_registers. L’array messaggio viene utilizzato per la risposta dallo slave.

 

Sub Modbus_Read_Coils(messaggio() As Byte, Slave_address As Byte, Start_address As Long, Num_of_coils As Integer) : Questa funzione legge il numero di coils (bobine di uscita nei PLC)  (area di memoria 0) indicato da Num_of_coils, a partire dalla bobina Start_address. Le bobine lette vengono impaccate bit a bit nei byte dell’array  messaggio. La bobina di numero più basso e’ sul bit 0 (a destra) del byte. Possono essere lette in una sola comunicazione da 1 a 2000 bobine. L’indirizzo di start può andare da 1 a 65536.

 

Sub Modbus_Read_Discrete_Inputs(messaggio() As Byte, Slave_address As Byte, Start_address As Long, Num_of_inputs As Integer) : Questa funzione legge il numero di inputs (ingressi fisici nei PLC)  (area di memoria 1) indicato da Num_of_inputs, a partire dall’ ingresso Start_address. Gli inputs letti vengono impaccati bit a bit nei byte dell’array  messaggio. L’input di numero più basso e’ sul bit 0 (a destra) del byte. Possono essere letti in una sola comunicazione da 1 a 2000 input. L’indirizzo di start può andare da 1 a 65536.

 

Sub Modbus_Read_Input_Register(messaggio() As Byte, Slave_address As Byte, Start_address As Long, Num_of_registers As Integer) : Questa funzione legge il numero di registri di input (area di memoria 3) indicato da Num_of_registers, a partire dal registro Start_address. I dati letti vengono impaccati nell’array messaggio come byte per cui poi bisogna andare a ricostruire il dato a 16 bit. Possono essere letti in una sola comunicazione da 1 a 125 registri. L’indirizzo di start può andare da 1 a 65536.

 

Sub Modbus_Write_Single_Coil(messaggio() As Byte, Slave_address As Byte, Start_address As Long, status As Boolean) : Imposta lo stato di una coil singola (area di memoria 0). Lo stato puo’ essere On o OFF e viene impostato in status come true o false. L’indirizzo della bobina e’ indicato da Start_address. Start_address può andare da 1 a 65536.

 

Sub Modbus_Write_Multiple_Coils(messaggio() As Byte, Slave_address As Byte, Start_address As Long, Num_of_coils As Integer, Value_of_coils() As Byte) : Questa funzione può impostare con un’unica comunicazione fino a 1968 coils (area di memoria 0). Lo stato delle coils viene impaccato a byte nell’array Value_of_coils. La coil con indirizzo più basso viene posta sul bit 0 (a destra) del primo byte. Il primo indirizzo ad essere impostato e’ quello indicato da Start_address.

In Num_of_coils deve essere indicato il numero totale delle coils da impostare. Detto numero può essere anche non un multiplo di 8. L’ultimo byte, infatti, può essere anche incompleto. I bit eccedenti sono ignorati. 

 

 

Ho tentato di scrivere il codice e commentarlo meglio possibile (relativamente alle mie conoscenze di VB).

Sono comunque disponibile per suggerimenti e critiche L (spero poche)

 

Il codice può essere liberamente modificato e utilizzato, chiedo gentilmente solo 2 cose:

 

1)       Che sia sempre riportato il nome dell’autore (il mio J ) e il mio indirizzo E-mail.

2)       Se vengono corretti errori e/o effettuate migliorie e/o aggiunti comandi, che mi venga inviato il codice in modo che possa beneficiarne anch’io.

 

Attenzione:

 

Non mi assumo NESSUNA responsabilità per qualsiasi danno o altro causato dall’utilizzo di detto codice specialmente in applicazioni commerciali.

 

 

Giuseppe³

E-mail: gziggio@tin.it

 

Non assicuro risposte in quanto il tempo che ho a disposizione non e’ tantissimo, tenterò di fare il meglio che posso J.

 

 

Documento revisione 1.00 scritto in data 26 Ottobre 2003