RIO-28 program created for a client with special mirroring needs:
This user program allows a RIO-28 to operate as a non-traditional RIO- 8 mirror master.
/* 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.

Questions? Discuss them with us.