RelayDuino-Modbus
The RelayDuino is a swell little device made by some dudes in Australia. It's based on an Arduino and it's got some nice relays, a few analog inputs and even some opto-isolated discrete inputs. It even has an RS485 port on it.
What more could you want?
I'll tell you what more I want, Sally! I wants Modbus!
Now they do make a Modbus version of this thing, but it's a few bucks more and I'll be damned if I'm giving them any more money for the exact same hardware. The Aussies are all criminals, every last one of them. You know it, I know it. Let's just try to put this ugliness behind us, ok?
Good.
Now, I wouldn't have gone to all this trouble if Mango, would support adding your own serial device with a custom protocol. Seems like a no-brainer, right? There's a jillion oddball serial devices out there and you'd think an open-source SCADA system would be easy to hack your own device into right? I mean, come on!
Wait, do you think these Mango guys are from Australia? It would be just like those crafty bastards.
Regardless, Mango + Modbus seemed like the easiest way to go.
I took the Arduino Modbus Slave library from here: http://sites.google.com/site/jpmzometa/arduino-mbrt/arduino-modbus-slave
Register Map
The modbus slave library is pretty minimal, it only supports register reads and writes. So everything is mapped to a regsiter. You may notice that the map bears a striking resemblance to that of the aforementioned KTB-224
Register | Name | read/write | Description |
---|---|---|---|
0 | Counter 0 | R/W | Value increments when corresponding opto input goes from 0 to 1. |
1 | Counter 1 | ||
2 | Counter 2 | ||
3 | Counter 3 | ||
4 | Analog-input 0 | R | |
5 | Analog-input 1 | ||
6 | Analog-input 2 | ||
7 | Opto-input 0 | R | |
8 | Opto-input 1 | ||
9 | Opto-input 2 | ||
10 | Opto-input 3 | ||
11 | |||
12 | watchdog | R/W
The lower byte of each of these registers is kept in EEPROM. |
Value for the watchdog timer. The box will reset after N seconds unless it receives a valid read or write.
N==0 disables the watchdog. |
13 | Slave ID | The Modbus Slave ID is read from this location at boot time. | |
14 | baud rate | The Modbus baud rate.
12 = 1200 24 = 2400 48 = 4800 96 = 9600 192 = 19200 | |
15 | parity | 0 = none 1 = odd 2 = even | |
16 - 31 | more EEPROM | Store your phone number in here, see if I care. | |
32 | Relay 0 | R/W | Any true (>0) value activates the relay. |
33 | Relay 1 | ||
34 | Relay 2 | ||
35 | Relay 3 | ||
36 | Relay 4 | ||
37 | Relay 5 | ||
38 | Relay 6 | ||
39 | Relay 7 | ||
40 - 60 | |||
61 | soft reset | W | Writing the magic number 0xDEAD to this location causes a reset. Baudrate and Slave ID will be re-read from EEPROM |
62 | software version | R | Version of this Arduino application |
63 | free running counter | R | the lower two bytes returned by millis() |
Code
Version 0.2
#include <EEPROM.h> #include <ModbusSlave.h> //#define DEBUG #ifdef DEBUG #include <SoftwareSerial.h> #define DEBUG_PRINTLN(x) debugSerial.println(x) #define DEBUG_PRINT2(x, y) debugSerial.print(x, y) #define DEBUG_PRINT(x) debugSerial.print(x) #else #define DEBUG_PRINTLN(x) ; #define DEBUG_PRINT2(x, y) ; #define DEBUG_PRINT(x) ; #endif typedef unsigned int uint; #define SOFTWARE_VERSION 0x0002 #define SOFT_RESET_COMMAND 0xDEAD void softReset(void); #ifdef DEBUG SoftwareSerial debugSerial(10, 11); #endif /* First step MBS: create an instance */ ModbusSlave mbs; #define ANIN1 6 // Analog 1 is connected to Arduino Analog In 6 #define ANIN2 7 // Analog 2 is connected to Arduino Analog In 7 #define ANIN3 0 // Analog 3 is connected to Arduino Analog In 0 int Analogs[3] = {ANIN1, ANIN2, ANIN3}; #define REL1 2 // Relay 1 is connected to Arduino Digital 2 #define REL2 3 // Relay 2 is connected to Arduino Digital 3 PWM #define REL3 4 // Relay 3 is connected to Arduino Digital 4 #define REL4 5 // Relay 4 is connected to Arduino Digital 5 PWM #define REL5 6 // Relay 5 is connected to Arduino Digital 6 PWM #define REL6 7 // Relay 6 is connected to Arduino Digital 7 #define REL7 8 // Relay 7 is connected to Arduino Digital 8 #define REL8 9 // Relay 8 is connected to Arduino Digital 9 PWM int Relays[8] = {REL1, REL2, REL3, REL4, REL5, REL6, REL7, REL8}; int PWMAble[4] = {2, 4, 5, 8}; #define OI1 15 // Opto-Isolated Input 1 is connected to Arduino Analog 1 which is Digital 15 #define OI2 16 // Opto-Isolated Input 2 is connected to Arduino Analog 2 which is Digital 16 #define OI3 17 // Opto-Isolated Input 3 is connected to Arduino Analog 3 which is Digital 17 #define OI4 18 // Opto-Isolated Input 4 is connected to Arduino Analog 4 which is Digital 18 int Optos[4] = {OI1, OI2, OI3, OI4}; #define TXENPIN 19 // RS-485 Transmit Enable is connected to Arduino Analog 5 which is Digital 19 const char parity_table[] = {'n', 'o', 'e'}; /* EEPROM map */ #define EE_WATCHDOG 0 #define EE_SLAVEID 1 #define EE_BAUD 2 #define EE_PARITY 3 /* slave registers */ union reg_struct_t { int reg[]; struct { uint counter[4]; // 0 - 3 uint analog[3]; // 4 - 6 uint reserved0; uint opto[4]; // 8 - 11 uint eeprom[20]; // 12 - 31 uint relay[8]; // 32 - 39 uint reserved1[21]; // 40 - 60 uint soft_reset; // 61 uint software_vers; // 62 uint clock; // 63 }; } regs; #define REG_COUNT (sizeof(regs) / sizeof(int)) #define EEPROM_COUNT (sizeof(regs.eeprom) / sizeof(int)) #define RELAY_COUNT (sizeof(regs.relay) / sizeof(int)) unsigned long wdog = 0; /* watchdog */ unsigned long wdog_limit = 0; /* previous time*/ unsigned char wdog_enable = 0; /* enable flag */ void setup() { int i; #ifdef DEBUG debugSerial.begin(9600); #endif for (i=0;i<8;i++) { pinMode(Relays[i], OUTPUT); // declare the relay pin as an output } for (i=0;i<4;i++) { pinMode(Optos[i], INPUT); // declare the relay pin as an output } softReset(); } void softReset() { int i; unsigned char ee_value; DEBUG_PRINTLN("Reset..."); for (i=0;i<RELAY_COUNT;i++) { regs.relay[i] = 0; } for(i=0; i<REG_COUNT; i++) { regs.reg[i] = 0; } for(i=0; i<EEPROM_COUNT; i++) { regs.eeprom[i] = EEPROM.read(i); } /* the Modbus slave configuration parameters */ unsigned char slave_id = EEPROM.read(EE_SLAVEID); DEBUG_PRINT("slave_id = "); DEBUG_PRINT2(slave_id, HEX); long baudrate = EEPROM.read(EE_BAUD) * 100; DEBUG_PRINT(" baudrate = "); DEBUG_PRINT2(baudrate, DEC); char parity = parity_table[EEPROM.read(EE_PARITY)]; DEBUG_PRINT(" parity = "); DEBUG_PRINT2(parity, BYTE); /* Second step MBS: configure */ mbs.configure(slave_id, baudrate, parity, TXENPIN); /* initialize the watchdog timer */ //wdog_limit = EEPROM.read(EE_WATCHDOG); wdog_limit = 0; wdog_limit *= 1000; wdog_enable = 0; // on reset, the watchdog is always disabled // it gets enabled on the first valid transaction DEBUG_PRINT("wdog_limit = "); DEBUG_PRINT2(wdog_limit, DEC); DEBUG_PRINTLN("ms"); regs.soft_reset = 0xBEEF; } void loop() { int i; unsigned char ee_value; for (i=0 ; i<4 ; i++) { if (digitalRead(Optos[i])==LOW) { regs.opto[i] = 1; } else { regs.opto[i] = 0; } } for (i=0 ; i<8 ; i++) { if (regs.relay[i]) { digitalWrite(Relays[i],HIGH); } else { digitalWrite(Relays[i],LOW); } } for (i=0 ; i<3 ; i++) { regs.analog[i] = analogRead(Analogs[i]); } for(i=0; i<EEPROM_COUNT; i++) { ee_value = regs.eeprom[i] & 0xFF; if(EEPROM.read(i) != ee_value) { EEPROM.write(i, ee_value); } } if(regs.soft_reset == SOFT_RESET_COMMAND) { softReset(); } if(wdog_limit != 0 && wdog_enable) { if(millis() - wdog > wdog_limit) { softReset(); } } regs.software_vers = SOFTWARE_VERSION; regs.clock = millis(); /* lastly, we update the modbus registers from the master */ i = mbs.update((int*)®s, REG_COUNT); if(i>4) { DEBUG_PRINT("sent "); DEBUG_PRINT(i); DEBUG_PRINTLN(" byte reply"); wdog_enable = 1; wdog = millis(); } }