/* rio-8-mirror.c (c) 2009 Coyote DataCom Gre7g Luterman <gre7g.luterman@gmail.com> This user program allows a RIO-28 to operate as a non-traditional RIO-8 mirror master. RIO-8's are Modbus devices with 4 digital inputs and 4 digital outputs. In addition, they can be configured to work in a mirror mode such that the inputs on a master unit are replicated on the outputs of slave unit(s). Likewise, the inputs on the slave unit(s) are replicated on the outputs of the master. The mirroring system allows a lot of flexibility, but it is not without limitation. For example, each input can only be replicated on a single output. This example illustrates how a RIO-28 can act as a master unit and communicate with RIO-8 slaves, but without all the limitations. For this design, we will use a RIO-28 with Modbus address of 254 as the master. Since we will be writing all of the mirroring code as a user program, the RIO-28 will not need to be configured as a mirror master. The program will, however, respect the failsafe settings saved in holding registers 53 & 54. We will also use six RIO-8's with Modbus addresses of 1 - 6. Each slave will be configured as a mirror slave and can be configured for failsafe in the traditional way. The master's outputs (DO1 - DO4) will replicate values sampled from slave #1's inputs (DI1 - DI4). The master's inputs (DI1 - DI4) will be replicated on the outputs of EVERY slave 1 - 6 (DO1 - DO4), as illustrated below: +----------+ |Master DO1| <--------------\ | DO2| <------------\ | | DO3| <----------\ | | | DO4| <--------\ | | | +----------+ | DO5| --E | | | \------------ |DI1 Slave1| | DO6| --E | | \-------------- |DI2 | | DO7| --E | \---------------- |DI3 | | DO8| --E \------------------ |DI4 | | Addr | | Addr | | 254 DI1| ---------------+-----------> |DO1 1 | | DI2| -------------+-|-----------> |DO2 | | DI3| -----------+-|-|-----------> |DO3 | | DI4| ---------+-|-|-|-----------> |DO4 | | DI5| --X | | | | +----------+ | DI6| --X | | | | | DI7| --X | | | | +----------+ | DI8| --X | | | | X-- |DI1 Slave2| +----------+ | | | | X-- |DI2 | | | | | X-- |DI3 | | | | | X-- |DI4 | | | | | | Addr | | | | +-----------> |DO1 2 | | | +-|-----------> |DO2 | | +-|-|-----------> |DO3 | +-|-|-|-----------> |DO4 | | | | | +----------+ | | | | . | | | | : | | | | +----------+ | | | | X-- |DI1 Slave6| | | | | X-- |DI2 | | | | | X-- |DI3 | | | | | X-- |DI4 | | | | | | Addr | | | | +-----------> |DO1 6 | | | +-------------> |DO2 | | +---------------> |DO3 | +-----------------> |DO4 | +----------+ This program will respect other mirror-configuration holding registers, such as MirRepeat and MirTimeout. Finally, before we can create any code, it is important to understand how the RIO-8 slaves interact with what they presume is a RIO-8 master. RIO-8 slaves do this in two important ways: 1. On occassion, the slave will send a broadcast (it doesn't know the master's Modbus address) that indicates its input state. The broadcast is as follows: 0x00 - Broadcast 0x70 - Custom slave broadcast command 0x03 - Bytes to follow (not including CRC) MBAd - Slave's Modbus address (0x01 - 0x06) 0x04 - Number of DIs to follow DIs - DI data in low 4 bits of byte CRC - 2-byte CRC code The slave will continue to repeat this broadcast periodically until it is acknowledged by a master. To acknowledge the broadcast, the master must read (any number of) discrete inputs on the slave. The master must do this, even if it does not need this information. 2. To prevent the slave's outputs from going into failsafe mode (and blinking rapidly to indicate an error), the slave must receive either writes to the coils, or a broadcast from the master. The broadcast is as follows: 0x00 - Broadcast 0x71 - Custom master broadcast command BCnt - Bytes to follow (not including CRC) NDig - Number of digital nodes to follow 0x04 - Number of digital outputs per node listed 0x00 - Number of analog nodes to follow 0x00 - Number of analog outputs per node listed / MBAd - Modbus address of the slave to be updated \ DOs - New DO data in low 4 bits of byte CRC - 2-byte CRC code The offset section is repeated for every slave needing to be set. */ // Constants: const int BLINK_ON_TIME = 10; // During a timeout "error" condition, blink const int BLINK_DUTY = 60; // LEDs on for 10ms and off for 50ms. const int HOLD_PORT_REPLY_TIMEOUT = 36; // Modbus ports 37, 51, 52, & 53 const int HOLD_PORT_MIR_REPEAT = 50; // Remember, C is zero-based, so all of const int HOLD_PORT_MIR_TIMEOUT = 51; // these port numbers are offset by const int HOLD_PORT_MIR_FAILSAFE = 52; // one. // Variables: timer LostCommTimer, T; int i, j, Temp, PrevDIs; // Reset the outputs DO5 - DO8. DO[4] = DO[5] = DO[6] = DO[7] = 0; // A short delay at the beginning of your program is always a good idea - // especially when your code accesses the serial port. This will prevent the // uploader from getting confused. T.wait(1000); // Start in "error" mode. Do not presume we have a connection to the slaves // until one has been established. ERROR: /* When in the timeout (error) condition, we would like to rapidly blink the output LEDs to indicate that - due to communication problems - we are unable to mirror slave #1's inputs. However, user programs cannot control the LEDs independently from the outputs. So to blink the LEDs, we need to toggle the outputs. To avoid any problems, we will respect the failsafe configuration and instead toggle the DO5 - DO8 outputs instead. Since these extra outputs are not connected to anything, rapidly toggling them will be harmless. */ // Set the outputs to the failsafe values, if present Temp = Count[HOLD_PORT_MIR_FAILSAFE]; // Tricky way to access hold. reg. 53 for (i = 0; i < 4; i += 1) { if (Temp & 0x0100) // Masked? DO[i] = Temp & 0x0001; Temp = Temp / 2; } // Broadcast the values of DI1 - DI4 to each of the slaves PrevDIs = DI[0] + (DI[1] * 2) + (DI[2] * 4) + (DI[3] * 8); SerialOut("\x00\x71\x10\x06\x04\x00\x00\x01" + chr(PrevDIs) + "\x02" + chr(PrevDIs) + "\x03" + chr(PrevDIs) + "\x04" + chr(PrevDIs) + "\x05" + chr(PrevDIs) + "\x06" + chr(PrevDIs) + CRC); // Wait a while by blinking a few times for (i = 17; i; i -= 1) { // Blink the DO5 - DO8 outputs by pulsing them Pulse[4] = Pulse[5] = Pulse[6] = Pulse[7] = BLINK_ON_TIME; T.wait(BLINK_DUTY); } // Try to read the values of DI1 - DI4 from the slave at address 1 for (i = 0; i < 4; i += 1) { Temp = MBGetDI(1, i); if (MBGetErr()) goto ERROR; // On a successful read, apply the values read to our outputs DO1 - DO4 DO[i] = Temp; } // On success, leave the error mode // In normal mode, we will not blink the outputs to indicate an error and we // will not resort to failsafe values. We will occassionally broadcast our // values, and we will need to respond to broadcasts from the slaves. NORMAL: // Set a timeout timer LostCommTimer = Count[HOLD_PORT_MIR_TIMEOUT] * 1000; LOOP: // Count down to our next broadcast for (i = Count[HOLD_PORT_MIR_REPEAT]; i; i -= 1) { // Listen for a broadcast from a slave T = 1000; Temp = SerialIn(T, "\x00\x70\x03" + ord(&i) + "\x04" + ord(&j) + CRC); // Respond to the broadcast by reading a DI from that slave if (Temp < 2) { MBGetDI(i, 0); // Don't bother storing the result // Was that broadcast from the unit we're mirroring, Modbus address 1? if (i == 1) { // Yes, that's the one. Output the value. DO[0] = j & 1; DO[1] = j & 2; DO[2] = j & 4; DO[3] = j & 8; } } // Have our inputs changed? Temp = DI[0] + (DI[1] * 2) + (DI[2] * 4) + (DI[3] * 8); if (Temp != PrevDIs) { // Yes, break out now and send the new values PrevDIs = Temp; break; } } // Yes, broadcast the values of DI1 - DI4 to each of the slaves SerialOut("\x00\x71\x10\x06\x04\x00\x00\x01" + chr(PrevDIs) + "\x02" + chr(PrevDIs) + "\x03" + chr(PrevDIs) + "\x04" + chr(PrevDIs) + "\x05" + chr(PrevDIs) + "\x06" + chr(PrevDIs) + CRC); // Now we must pause. If there was no gap between the broadcast and the MBGetDI // that we're about to do, it will look like one big command instead of two // shorter ones. T.wait(Count[HOLD_PORT_REPLY_TIMEOUT]); // Try to read the values of DI1 - DI4 from the slave at address 1 j = 0; for (i = 0; i < 4; i += 1) { Temp = MBGetDI(1, i); // On a successful read, apply the values read to our outputs DO1 - DO4 if (not MBGetErr()) { DO[i] = Temp; j += 1; } } // On success, loop back to reset the timeout timer if (j == 4) goto NORMAL; // Has the timeout timer expired? if (LostCommTimer == 0) // Yes, enter the error mode goto ERROR; goto LOOP;
Right-click on this link and select "Save target as..." to store a copy of the source code.