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