Archivi categoria: Progetti Arduino

Progetti con Arduino

3. Modbus Mapping

Prima di implementare lo Slave su Arduino  è necessario decidere il mapping dei PIN e delle caratteristiche del dispositivo con l’indirizzamento ed il tipo di dato tipici del mondo Modbus. Lo strato software che si appoggia all’Arduino Helper si chiama ArduMap (MBArduMap.h e MBArduMap.cpp) e mappa Arduino con gli address modbus (e quindi le relative funzioni) secondo il seguente schema:

MBAddrMap

Come vediamo in tabella, i valori dei PIN analogici sono implementati come INPUT REGISTER (quindi a sola lettura) che hanno un valore a 16bit che rappresenta il valore di tensione (in mV) letta sul PIN dalla scheda Arduino. Questi registri hanno un address Modbus che va da 00H a 05H.

Gli I/O digitali sono implementati come COILS (registri da 1 bit in lettura e scrittura). Per gestire il tipo di COIL (se in lettura o in scrittura) c’è a disposizione un Holding Register che permette di settare il tipo di COIL, il registro si chiama COILS_SET00 ed ha indirizzo Modbus F0H. Nel registro ogni 2 bit rappresentano la configurazione di un PIN di I/O digitale. Il valore di questi 2 bit possono essere:

  • 00: il PIN è settato come INPUT
  • 01: il PIN è settato come OUTPUT
  • 10: il PIN è settato come INPUT con resistenza di PULLUP
  • 11: combinazione non ammessa.

Il mapping per il nostro caso specifico è il seguente:

Possible Value:
  - INPUT (default): Pins configured this way are said to be in a high-impedance state.
  - OUTPUT: Pins configured as OUTPUT are said to be in a low-impedance state.
  - INPUT_PULLUP: There are 20K pullup resistors built into the Atmega chip that can be accessed from software. The value of this pullup depends on the microcontroller used. On most AVR-based boards, the value is guaranteed to be between 20kO and 50k. On the Arduino Due, it is between 50k and 150k.
2 bit mapping:
  00: INPUT
  01: OUTPUT
  10: INPUT_PULLUP
*/
// bit 8, 9: DIGITAL4
// bit 6, 7: DIGITAL3
// bit 4, 5: DIGITAL2
// bit 2, 3: DIGITAL1
// bit 0, 1: DIGITAL0
#define MB_DIGITALS_SET00  0xF0

I COILS hanno indirizzamento Modbus da 20H a 24H.

I COUNTERS di Arduino hanno valori a 32bit. Abbiamo deciso di trattare questi registri in due modi distinti che possono essere utilizzati contemporaneamente:

  • Come Holding Register standard (indirizzi 10H-11H e 12H-13H): quindi ogni registro in realtà rappresenta o la word meno significativa o la word più significativa del dato a 32bit; questo tipo di gestione però ha il side effect che dalla lettura di una word alla lettura dell’altra word può passare un certo tempo ed il contatore potrebbe cambiare valore.
  • Come Holding Register Custom a 32bit (indirizzi 10H e 12H) con funzioni custom definite dall’utente.

Anche in questo caso abbiamo un Holding Register per il set dei contatori. Ogni 4 bit di questo registro serve a configurare un contatore con il seguente schema:

  • XY00: il contatore cambia quando il PIN relativo va a 0;
  • XY01: il contatore cambia quando il PIN cambia valore (da 0 a 1 o viceversa);
  • XY10: il contatore cambia quando il PIN relativo va ad 1;
  • XY11: il contatore cambia quando il PIN relativo va da 1 a 0.

I valori X ed Y hanno il seguente significato:

  • X=0: il contatore è disabilitato;
  • X=1: il contatore è abilitato;
  • Y=0: il contatore è incrementato;
  • Y=1: il contatore è decrementato.

Quindi nello specifico abbiamo:

Possible IRQ trigger:
  - LOW to trigger the interrupt whenever the pin is low,
  - CHANGE to trigger the interrupt whenever the pin changes value
  - RISING to trigger when the pin goes from low to high,
  - FALLING for when the pin goes from high to low. 
4 bit mapping:
  XY00: LOW
  XY01: CHANGE
  XY10: RISING
  XY11: FALLING
  X=0 counter is disabled, X=1 counter is enabled
  Y=0 increase counter, Y=1 decrease counter

In questi file (MBArduMap.h e MBArduMap.cpp) ci sono anche le funzioni per leggere i registri con gli indirizzi Modbus che verranno utilizzate nell’implementazione dello slave TCP e RTU. Il prototipo di queste funzioni è coerente per tutte e prevede come parametri di input il valore del registro Modbus, una variabile che contiene il dato da inserire nel registro oppure il valore letto dal registro. Le funzioni ritornano poi un eventuale errore tra quelli standard del protocollo:

// Exceptions code
enum MBException {
 // Success
 OK                     = 0,
 // FUNC non implemented or unknow
 EXC_ILLEGAL_FUNC_CODE  = 0x01,
 // Illegal address or address range
 EXC_ILLEGAL_DATA_ADDR  = 0x02,
 // Illegal data
 EXC_ILLEGAL_DATA_VALUE = 0x03,
 // Server internal error
 EXC_SERVER_FAILURE     = 0x04,
 // ACK, postpone the response
 EXC_ACKNOWLEDGE        = 0x05,
 // Server busy
 EXC_SERVER_BUSY        = 0x06,
 // Generate by gateway
 EXC_GW_PATH_NOT_AVAIL  = 0x0A,
 // Generate by gateway
 EXC_GW_DEVICE_FAIL     = 0x0B
};

Il prototipo delle fiìunzioni definite nei file è il seguente:

namespace MBArduMap {
	
 void ardumap_init();
    
 // Function return value are in MBException

 // Analogs
 int getInputReg(unsigned addr, unsigned* val);

 // Holding Register
 // No Atomic for Counters (so stop counter before get)
 int getHoldingReg(unsigned addr, unsigned* val);
 int setHoldingReg(unsigned addr, unsigned val);

 // 32bits Holding Register
 int getHoldingReg32(unsigned addr, unsigned long* val);
 int setHoldingReg32(unsigned addr, unsigned long val);

 // Digitals (only RO)
 int getDiscreteReg(unsigned addr, bool* val);
 // Coils
 int getCoilReg(unsigned addr, bool* val);
 int setCoilReg(unsigned addr, bool val);
};

La libreria fino a questo articolo si può scaricare al link qui riportato, i file vanno inseriti nella directory arduino/libraries lasciandoli nel loro folder MBArduino.
"Download (12,5 kB)"

Sketch per il test della libreria di mapping descritta in questo articolo.
"Download (1,6 kB)"

 

2. Modbus Arduino Helper

Il software che presentiamo in questa sezione è parte dello Slave Modbus Arduino che stiamo costruendo.

Una rappresentazione a strati (layer) è visibile nella seguente figura:SWLayer

Sull’hardware Arduino abbiamo costruito una libreria per la sua gestione che specializza ogni PIN della scheda secondo le esigenze del progetto, offrendo dei metodi per la loro gestione che astraggono dall’hardware sottostante. In questa libreria vengono definite anche alcune funzioni di utilità per la gestione dello storage persistente (EEPROM) e alcune funzioni per la gestione dell’interfaccia seriale da affiancare a quelle standard di Arduino.
Sopra questo strato implementeremo una libreria che effettua il MAPPING MODBUS, definendo funzioni Modbus e indirizzi Modbus, degli oggetti presentati dall’helper.
Ancora sopra c’è lo strato di logica e di gestione dello SLAVE Modbus. Lo SLAVE Modbus è una classe astratta in C++ che quindi dovrà essere sempre utilizzata come classe padre della classe concreta che la implementa. Per questo ci penserà l’ultimo strato presentato in figura, ove la classe padre viede derivata dalle classi concrete che implementano il protocollo Modbus con trasporto RS485 o TcpIP.

L’architettura software in termini di moduli, file e funzionalità è illustrata nella seguente figura:

SWArch

La parte che analizziamo in questo primo articolo riguarda il modulo ArduHelper ed i suoi diretti correlati (Timer2 e MBUtils) che preparano la scheda Arduino ad essere utilizzata per il modulo Slave Modbus e aggiungono delle funzionalità di aiuto/utilità (helper) per leggere e scrivere i dati provenienti o destinati verso l’hardware della scheda.
I moduli software che analizziamo hanno i seguenti scopi:

  • MsTimer2: è composto da una libreria di terze parti illustrata sul sito Arduino.cc chiamata MSTimer2 (http://playground.arduino.cc/Main/MsTimer2) (a cui si rimanda per approfondimenti) ed è una piccola libreria che rende facilmente utilizzabile il Timer2 del chip (ATmega328) utilizzato da Arduino Uno esponendo un’interfaccia molto semplice da utilizzare. La sua risoluzione massima è di 1ms.
  • Utils: definisce, attraverso il suo file MBUtils.h, una serie di costanti e macro per lavorare con BYTE, WORD (16bit), DOUBLE-WORD (32bit).
  • ArduHelper: composto dai file MBArduHelper.h e ArduHelper.cpp definisce l’utilizzo dei PIN della scheda Arduino fornendo alcune funzioni di utilità per la gestione della scheda stessa. Vedremo per bene come è fatto questo modulo nelle righe seguenti.

All’interno del file header (MBArduHelper.h) troviamo la definizione di costanti per l’utilizzo dei PIN della scheda secondo il seguente schema:

// Mapping with Arduino UNO PINs
/* 
Pin Usage          Name 
---------------------------------- 
A0  Analog input   ARDU_ANALOG00 
A1  Analog input   ARDU_ANALOG01 
A2  Analog input   ARDU_ANALOG02 
A3  Analog input   ARDU_ANALOG03 
A4  Analog input   ARDU_ANALOG04 
A5  Analog input   ARDU_ANALOG05 
0   Serial RX      ARDU_RX 
1   Serial TX      ARDU_TX 
2   External IRQ   ARDU_COUNTER00 
3   External IRQ   ARDU_COUNTER01 
4   Digital I/O    ARDU_DIGITAL00 
5   Digital I/O    ARDU_DIGITAL01 
6   Digital I/O    ARDU_DIGITAL02 
7   Digital I/O    ARDU_DIGITAL03 
8   Digital I/O    ARDU_DIGITAL04 
9   Serial CTRL    ARDU_CTRL 
10  SS             ARDU_SS 
11  MOSI           ARDU_MOSI 
12  MISO           ARDU_MISO 
13  SCK            ARDU_SCK
 ---------------------------------- 
*/

Quindi della scheda utilizzeremo i sei PIN Analogici (che leggono la tensione da 0V a 5V) che verranno indicati con le costanti ARDU_ANALOG0X (con X da 0 a 5). Degli altri PIN verranno utilizzati i PIN 0 e 1 come canali di trasmissione seriale (ARDU_RX e ARDU_TX) per la RS485 insieme al PIN 9 per commutare il canale RS485 in lettura o scrittura (ARDU_CTRL). I PIN 2 e 3 verranno utilizzati come IRQ per poter gestire due contatori da 32 bit (ARDU_COUNTER00 e ARDU_COUNTER01). I PIN da 4 a 8 verranno utilizzati come PIN digitali di ingresso o uscita e verranno indicati con la costante ARDU_DIGITAL0X (con X da 0 a 4). Gli altri PIN, da 10 a 13, verranno utilizzati dallo shield Ethernet nella configurazione TCP.
Insieme a questa mappatura dei PIN della scheda esistono delle altri costanti di configurazione:

  • ARDU_IRQ_COUNTER00 e ARDU_IRQ_COUNTER01 che definiscono gli IRQ da utilizzare per i due counter.
  • ARDU_READ_ANALOG_MS che definisce l’intervallo di lettura (in ms) degli ingressi analogici.

Tutte queste costanti sono interne al modulo ArduHelper. Il software che utilizza questo modulo deve invece far riferimento alla definizione delle costanti e dei metodi all’interno del namespace MBArduHelper.
Per referenziare gli ingressi analogici, i counter e gli I/O digitali abbiamo definito le seguenti costanti:

enum ANALOGS { ANALOG00=0, ANALOG01=1, ANALOG02=2, ANALOG03=3, ANALOG04=4, ANALOG05=5, ANALOGS_COUNT=6 };
enum COUNTERS { COUNTER00=0, COUNTER01=1, COUNTERS_COUNT=2 };
enum DIGITALS { DIGITAL00=0, DIGITAL01=1, DIGITAL02=2, DIGITAL03=3, DIGITAL04=4, DIGITALS_COUNT=5 };

E per la configurazione dell’interfaccia seriale:

// Serial Parameter - RS485
enum { 
    SERIAL_PARITY_NONE = 0x00, // default 
    SERIAL_PARITY_EVEN = 0x40, 
    SERIAL_PARITY_ODD = 0x80, 
    SERIAL_BIT_STOP1 = 0x10, // always 
    SERIAL_BIT_STOP2 = 0x20, // not implemented 
    SERIAL_BIT_DATA5 = 0x05, // not implemented 
    SERIAL_BIT_DATA6 = 0x06, // not implemented 
    SERIAL_BIT_DATA7 = 0x07, // not implemented 
    SERIAL_BIT_DATA8 = 0x08 // always 
};

I metodi definiti all’interno del namespace servono per gestire Arduino. Elenchiamo di seguito questi metodi con le relative spiegazioni di utilizzo.

void ardu_init();

Inizializza la scheda Arduino e va richiamato prima di utilizzare la scheda stessa. Tale metodo inizializza tutti i PIN con settaggi e valori di default (disattiva tutti i contatori portandone il valore a 0, setta tutti gli I/O digitali in modalità INPUT, setta la trasmissione RS485 come TX) e inizializza il Timer2 della scheda (utilizzando la libreria MsTimer2) per la lettura periodica degli ingressi analogici. Questa inizializzazione viene realizzata richiamando la funzione interna _activateAnalogIRQ() che configura il Timer2 in modo che ogni ARDU_READ_ANALOG_MS venga richiamato l’IRQ handler __irqReadAnalogValue:

void _activateAnalogIRQ() {
    // Set interrupt timer and function for analog input 
    MsTimer2::set(ARDU_READ_ANALOG_MS, __irqReadAnalogValue); 
    MsTimer2::start(); 
}
unsigned getAnalogValue(ANALOGS analog);

Permette di leggere il valore dell’ingresso analogico (valore da 0 a 1023).

unsigned long getCounterValue(COUNTERS counter); 
void setCounterValue(COUNTERS counter, unsigned long value);

Queste funzioni permettono di leggere il valore di un counter oppure di settarne il valore iniziale prima di attivarlo.

unsigned getCountersConf(); 
void setCountersConf(unsigned value);

Le funzioni permettono di leggere la configurazione dei counter oppure di settarla. Al momento del setting di uno o di entrambi i counter essi vengono attivati o disattivati, a seconda del valore dei settaggi. La mappatura dei valori è descritta di seguito:

// Counter Configuration bit mapping 
/* 
  Possible IRQ trigger: 
  - LOW to trigger the interrupt whenever the pin is low
  - CHANGE to trigger the interrupt whenever the pin changes value
  - RISING to trigger when the pin goes from low to high
  - FALLING for when the pin goes from high to low. 
  4 bit mapping: 
  XY00: LOW 
  XY01: CHANGE 
  XY10: RISING 
  XY11: FALLING 
  X=0 counter is disabled, X=1 counter is enabled
  Y=0 increase counter, Y=1 decrease counter 
*/ 
// bit 15-8: always 0 
// bit 7,6,5.4: COUNTER01 
// bit 3,2,1,0: COUNTER00
int getDigitalValue(DIGITALS digit); 
void setDigitalValue(DIGITALS digit, int value);

Queste funzioni permettono di leggere o settare il valore di un PIN digitale, secondo la configurazione.

unsigned getDigitalsConf(); 
void setDigitalsConf(unsigned value);

Le funzioni permettono di leggere la configurazione dei PIN digitali oppure di settarla. Al momento del setting di uno o più PIN essi vengono configurati, a seconda del valore dei settaggi. La mappatura dei valori è descritta di seguito:

// Digital Configuration bit mapping 
/* 
  Possible Value: 
  - INPUT (default): Pins configured this way are said to be in a high-impedance state
  - OUTPUT: Pins configured as OUTPUT are said to be in a low-impedance state
  - INPUT_PULLUP: There are 20K pullup resistors built into the Atmega chip that can be accessed from software. The value of this pullup depends on the microcontroller used. On most AVR-based boards, the value is guaranteed to be between 20k and 50k. On the Arduino Due, it is between 50k and 150k. 
  2 bit mapping: 
  00: INPUT 
  01: OUTPUT 
  10: INPUT_PULLUP
*/ 
// bit 8, 9: DIGITAL4 
// bit 6, 7: DIGITAL3 
// bit 4, 5: DIGITAL2 
// bit 2, 3: DIGITAL1 
// bit 0, 1: DIGITAL0
void activateSerial(unsigned int baud = 9600L, byte ppssdddd = 0x18); 
void writeEnableSerial(); // writing enabled 
void readEnableSerial(); // reading enabled

Sono le funzioni per settare l’interfaccia Seriale e per comandare la RS485 in trasmissione o ricezione.
La configurazione della seriale (il byte ppssdddd) è descritto di seguito:

// PPSSDDDD Parity, Stop bits, Data bits 
// Parity: 00 No - 01 Even - 10 Odd 
// Stop bits: 01 1bit - 10 2bits --- always 01 
// Data bits: 0101 5bits - 0110 6bits - 0111 7bits - 1000 8bits --- always 1000 
// In this library version the byte must be --- PP011000, only parity can be change.

Inoltre abbiamo definito alcune funzioni di utilità per la gestione dello storage persistente (EEPROM) di Arduino:

// EEPROM function
byte eepromGetByte(int addr);
void eepromPutByte(int addr, byte val);
word eepromGetWord(int addr);
void eepromPutWord(int addr, word val);
dword eepromGetDWord(int addr);
void eepromPutDWord(int addr, dword val);

Nel seguente articolo parleremo del modulo di mapping dei PIN di Arduino con l’indirizzamento Modbus.

Download section

Libreria MBArduino contenente i file descritti nell’articolo. La libreria va inserita all’interno del folder libraries all’interno della directory di lavoro Arduino.
"Download (9,6 kB)"

Per testare la libreria abbiamo inserito due sketch Arduino, uno che utilizza la libreria (test_arduhelper) per leggere i valori iniettati da un altro sketch (tester_arduhelper) che genera delle onde quadre secondo le seguenti frequenze/tempi e PIN:

#define PIN_50Hz  2
#define PIN_25Hz  3
#define PIN_T1    4
#define PIN_T2    5
#define PIN_T3    6
#define PIN_T4    7
#define PIN_T5    8

Bisogna poi unire il tester con la scheda di test come in figura.

tester

Sketch per il test della libreria.
"Download (1,4 kB)"

Sketch tester per lo sketch di test.
"Download (820,0 B)"

 

Modbus ed Arduino

Oggi inauguriamo una serie di articoli sul protocollo Modbus e la sua implementazione come SLAVE con Arduino UNO.

In primo articolo, Modbus Overview, è introduttivo sul protocollo Modbus nella sua implementazione su Seriale e su Ethernet. Inizieremo comunque ad impostare la implementazione che effettueremo su Arduino.

Nel secondo articolo (ancora non pubblicato) illustreremo alcune librerie di ausilio all’utilizzo di Arduino come SLAVE in una rete Modbus.

Nel terzo (anch’esso ancora non pubblicato) implementeremo effettivamente il protocollo di base e le sue peculiarità su comunicazione Seriale e TCP.

Infine discuteremo di possibili evoluzioni, eventualmente su altre schede della famiglia Arduino o di altre famiglie (es. Raspberry PI).

A presto per le successive evoluzioni.

Claudio.

1. Modbus overview

Le informazioni trattate in questa pagina sono derivate dalla documentazione originale che si può trovare sul sito The Modbus Organization, in particolare dai documenti MODBUS Protocol Specification e MODBUS TCP/IP.

Modbus è un protocollo applicativo che si basa sul paradigma client/server, in cui il server è generalmente chiamato SLAVE ed il client MASTER, per lo scambio di semplici messaggi di tipo request/reply.
Lo SLAVE offre servizi specificati da codici funzione (Function Code) definiti inviati dal MASTER nella richiesta.
Diverse sono le implementazioni del protocollo in termini di trasporto utilizzato e nei progetti illustrati in questo sito tratteremo principalmente le due più importanti:

  • Modbus over TCP/IP su Ethernet
  • Modbus over Serial RS485 e RS232

01-layer

Ovviamente nella realtà potrebbero esistere architetture miste in cui vi sono oggetti che hanno la funzione di gateway, cioè trasformano, ad esempio, il colloquio su rete Ethernet in un colloquio asincrono su seriale (vedremo come ultimo esempio anche un’implementazione di questo tipo di oggetto).

02-hybrid

Descrizione del protocollo

Modbus definisce una semplice struttura del messaggio che viene scambiato dal MASTER verso lo SLAVE e viceversa. Tale struttura è chiamata Protocol Data Unit (PDU) ed è indipendente dal mezzo di trasmissione. Il frame che ogni implementazione del protocollo utilizza (ed al cui interno è presente il PDU) viene invece chiamato Application Data Unit (ADU). Nella figura seguente si può vedere lo schema di PDU e il suo utilizzo nel caso di ADU per trasmissione seriale asincrona e di ADU per trasmissione Ethernet.

03-pdu-adu

Il campo FUNC (Function Code) del PDU è grande 1 byte. Codici validi per questo campo sono numeri da 1 a 255, di cui i numeri da 1 a 127 sono riservati a codici funzione mentre quelli da 128 a 255 sono riservati ed utilizzati in caso di eccezione nella risposta, inoltre il codice 0 non è valido. Vedremo in seguito come verranno codificate le funzioni e quali saranno i codici delle funzioni standard e quali quelli definiti dall’utente. Alcune funzioni prevedono sub-codici, da inserire comunque nel campo DATA.
Nel campo DATA si trovano anche i parametri della funzione, come gli indirizzi dei registri da leggere o scrivere. Se la richiesta può essere servita dallo SLAVE, allora la risposta conterrà i valori richiesti.
Definendo i seguenti frame di richiesta, risposta ed eventuale risposta con eccezione, in figura si può vedere la schematizzazione del colloquio tra client (MASTER) e server (SLAVE):

mb_req_pdu = FUNC_CODE(1 byte), DATA(n bytes)
 
mb_rsp_pdu = FUNC_CODE(1 byte), DATA(m bytes)
 
mb_excep_rsp_pdu = EXC_FUNC_CODE(FUNC_CODE+0x80), EXCEP_CODE(1 byte)

05-mimic.001

Originariamente il protocollo nacque per una connettività seriale asincrona (RS485 o RS232 ad esempio), in cui fu imposto che tutto il messaggio ADU poteva essere grande al massimo 256 byte. Come conseguenza di questo limite il PDU del messaggio è grande al massimo 253 bytes, cioè 1 byte per il campo FUNCTION e 252 byte per il campo DATA (dai 256 byte massimi totali abbiamo sottratto il byte iniziale, che rappresenta l’indirizzo dello SLAVE, e i 2 byte finali del CRC, come definito nell’ADU del protocollo nel caso di seriale asincrono).
Nella sua implementazione TCP/IP il protocollo ha mantenuto il limite massimo di 253 byte per la grandezza del PDU, ma l’ADU, in questo caso, può diventare grande al massimo 260 byte, 253 byte al massimo per la parte PDU + 7 byte di header TCP/IP chiamato MBAP – ModBus Application Protocol.

MBAP Header

Item Size Description Request Response
Transaction Identifier 2 bytes Identification of a MODBUS transaction Initialize by MASTER Recopied by SLAVE
Protocol Identifier 2 bytes Always 0x0000 Initialize by MASTER Recopied by SLAVE
Lenght 2 bytes Number of following bytes Initialize by MASTER Initialize by SLAVE
Unit ID 1 byte Identification of a remote SLAVE connected on a serial Initialize by MASTER Recopied by INPUTSLAVE

La porta standard TCP riservata su cui lo SLAVE risponde al MASTER è la 502.

Data Model

I tipi di oggetti referenziati all’interno del protocollo possono essere:

Object Size Type
DISCRETE INPUTS Single bit RO
COILS Single bit RW
INPUT REGISTERS 16bits word RO
HOLDING REGISTERS 16bits word RW

Gli oggetti con dimensione 16 bit hanno un encoding di tipo BIG-ENDIAN, cioè viene trasmesso prima il byte più significativo della word e poi il byde meno significativo. Così se la word da trasmettere è 0x1234, verrà trasmesso prima il byte 0x12 e poi 0x34.
Ogni oggetto Modbus, può essere referenziato con un indirizzo a 16 bit, da 0 a 65535 (0x0000 – 0xFFFF). Diversi tipi di oggetto potrebbero far riferimento al medesimo valore (overlapping: ad esempio un DISCRETE INPUT da 16 bit potrebbe essere letto anche come una successione di COILS).
Il protocollo quindi effettua un mapping logico tra le risorse dell’hardware (ad esempio valori di grandezze fisiche come temperatura, umidità, tensioni, correnti, etc…) e gli indirizzi logici che arbitrariamente il protocollo assegna a tali risorse per poterle leggere e/o gestire.

04-blocks

Negli esempi si utilizzeranno le schede Arduino, in particolare si inizierà con la scheda Arduino UNO, scheda su cui si implementerà il protocollo nella sua versione SLAVE.
Il mapping di Arduino UNO (o anche Arduino Leonardo e Arduino Duemilanove) che verrà utilizzato in seguito è il seguente:

MB-Map2    arduino-uno

Utilizzando tutti i PIN disponibili della scheda Arduino UNO abbiamo effettuato le seguenti scelte per il mapping sul protocollo Modbus:

  • I PIN con input analogici verranno mappati come Input Register a 16 bit. Tali register avranno indirizzamento da 0x0000 a 0x0005 e conterranno il valore in mV della tensione letta sul relativo PIN.
  • I PIN digitali numerati sulla scheda Arduino con 4, 5, 6, 7 e 8 verranno mappati come COIL con indirizzamento da 0x0020 a 0x0024. Sono quindi oggetti da utilizzare sia in lettura che in scrittura secondo la configurazione impostata sull’Holding Register chiamato COILS_SET, che ha indirizzo Modbus 0x00F0.
  • I PIN 2 e 3 sono PIN che sulla scheda arduino possono essere gestiti con IRQ, e quindi verranno utilizzati come counter. Il conteggio potrà avvenire sui fronti positivi (switch da 0 a 1 logici dell’ingresso digitale), su quelli negativi (da 0 a 1 logici) o su entrambi secondo la configurazione impostata sull’Holding Register chiamato COUNTERS_SET, che ha indirizzo Modbus 0x00E0. L’hardware permette di mantenere questo conteggio in una variabile di 32 bit, per cui ogni counter avrà associati 2 Holding Register da 16 bit ognuno, uno per la parte più significativa della variabile a 32 bit ed uno per la parte meno significativa. Il tipo di registro Modbus è in lettura e scrittura così che è possibile impostare un valore iniziale al conteggio.
  • I rimanenti PIN serviranno per la comunicazione seriale asincrona (PIN 0, 1 e 9) o per la comunicazione attraverso lo shield Ethernet aggiuntivo (PIN 10, 11, 12 e 13).
  • Nell’elenco, peraltro già mensionati, vi sono anche altri due registri che non corrispondono direttamente a letture o scritture di PIN della scheda, ma servono solamente per impostare i vari ingressi ed uscite nella maniera più opportuna (COILS_SET e COUNTERS_SET).

Modbus Transaction

Nella implementazione del protocollo seguiremo le linee guida che si trovano nella documentazione ufficiale sul sito The Modbus Organization, in particolare seguiremo, per ogni transazione, il seguente schema:

mb-transaction

Lo SLAVE (Arduino) si porrà in attesa di un messaggio. Se il messaggio è ad esso indirizzato (ad esempio lo SLAVE ID coincide, in caso di trasmissione seriale, oppure è stata aperta una connessione TCP) l’algoritmo, per costruire la risposta, effettuerà alcuni controlli che potrebbero portare ad una risposta di eccezione nel caso che le alcuni controlli diano esito negativo. I controlli che verranno effettuati sono nell’ordine:

  1. Validazione del codice di funzione: verrà generato un messaggio di errore nel caso il codice funzione non sia stato implementato dallo SLAVE.
  2. Validazione degli indirizzi dei registri: verrà generato un messaggio di errore nel caso gli indirizzi dei registri siano fuori range rispetto i registri validi per la funzione specificata.
  3. Validazione dei dati ricevuti: verrà generato un messaggio di errore se i dati non sono validi per i registri indirizzati, ad esempio se un registro supporta un valore da 0 a 1000 non può essere infasato con un valore maggiore di 1000.
  4. Validazione della esecuzione della funzione: verrà generato un messaggio di eccezione, che nel nostro caso si limiterà ad un errore di indisponibilità della lettura su Arduino.

Se tutte le verifiche hanno esito negativo, allora verrà costruito il messaggio da inviare al MASTER che ha effettuato la richiesta.

Function Code

Il Function Code ha la grandezza di un byte e, come abbiamo già riportato, non tutti i valori da 0 a 255 sono validi. In particolare il valore 0 non è valido, i valori da 1 a 127 sono codici funzione validi, mentre i valori da 128 a 255 sono invece valori riservati per le eccezioni. In pratica se c’è un’eccezione il messaggio di risposta conterrà il codice funzione per l’eccezione composto dal codice funzione della richiesta più 128 (ad esempio se ho un’eccezione per la funzione 5 la risposta conterrà come codice funzione 5+128, cioè 133).
Dei codici funzione da 1 a 127, alcuni sono definiti dallo standard Modbus, altri sono User Defined, cioè a discrezione di chi li implementa e possono essere utilizzati per funzioni dedicate non previste dallo standard.

mb-func mb-func-code

 Alcuni dei codici funzione standard sono riportati nella tabella precedente, e sono divisi in 4 grandi categorie:

  • Codici funzione per la gestione dei registri di un bit.
  • Codici funzione per la gestione dei registri di 16 bit.
  • Codici funzione per la lettura e scrittura di streaming di dati.
  • Codici funzione per diagnostica.

Nei nostri progetti implementeremo solo alcuni dei codici funzione standard e qualcuno dei codici funzione definiti dall’utente.

Claudio.