Configuring P3000 Unit ID Bits for Shared Modbus TCP Code

Brian Holt9 min read
AutomationDirectModbusTechnical Reference
Licensed PE Working through this on a live machine? A Maine-licensed engineer can take it from here — included with IMD hardware, by the hour for everything else. Book an engineer

The situation: three Productivity P3000 CPUs run the same process. Each one writes the same calculated block, MODBUS_ARRAY, to a different Modbus TCP slave: 192.168.0.1, 192.168.0.2 and 192.168.0.3. You want one project you can load on any of the three. The ladder needs a way to learn which CPU it is running on and pick the matching slave. The checks below cover the quick fixes that fail and the build that holds up.

Check 1: Look for an IP or MAC system tag, then move on

The obvious first try is to read the CPU's own IP address or MAC ID in ladder and compare it against three presets. Open the system tag list in your Productivity Suite version and search for anything that exposes the Ethernet port address or hardware ID.

  • Not there: this is the usual result. There is no commonly used ladder-readable tag that returns the CPU's IP or MAC. Go to Check 2.
  • Found in your version: you can use it. Still read Check 3 first, because an IP-based identity breaks if the CPU address comes from the project file.

A MAC-based identity has a second weakness. It belongs to the CPU hardware, not to the machine. Swap a failed CPU at 2 a.m. and the replacement has a new MAC. The program then either finds no matching preset or you have to edit presets on the spot. The identity should stay with the panel wiring so a spare CPU drops straight in.

Check 2: Rule out the other quick fixes

Quick fix Why it fails on shift Verdict
Three separate, nearly identical projects Every logic change has to go into three files. Sooner or later one gets missed, and the units drift apart without anyone noticing. Works, but costs maintenance time forever
IP/MAC comparison in ladder No readable tag (Check 1). MAC changes when a CPU is swapped. Don't build on it
Retentive "unit number" register set from HMI or data view The value lives in CPU memory. A memory clear, battery loss, or a download that writes initial values can reset it, and then the unit talks to the wrong slave. Acceptable as a backup, not as the primary identity
Hardwired ID inputs packed on first scan The identity is in the panel wiring. It survives downloads, memory clears and CPU swaps. Use this

Get it running, then fix it properly. If production is down right now, load a known-good per-unit project to restore comms. Build the shared version below on the bench afterwards.

Check 3: Confirm where each CPU's own IP address comes from

This check is separate from the slave addresses, and people skip it. Open the project's hardware configuration and look at the CPU Ethernet port settings.

  • The CPU address is stored in the project: downloading one project to three CPUs gives all three the same address. That is a duplicate IP on the network, and comms to every unit become intermittent. You have two options:
    • put the CPUs on isolated network segments where the duplicate does no harm, or
    • keep the CPU port setting as the one per-unit difference, and change it at download time.
    Stop here if you can't resolve this. A shared program is no use while the CPUs collide on the wire.
  • The CPU address is set on the CPU and kept across downloads: go to Check 4.

Before any download, ping each CPU and each slave from a laptop on that network. Write down which address answers from which panel.

Check 4: Count spare inputs and choose the ID code

Each ID bit takes one spare discrete input. Reserve code 0 as invalid. An all-off pattern is what you see with a missing jumper, a blown input common, or a pulled terminal block, so it must never select a slave.

Units sharing the program Minimum ID bits (0 reserved) Suggested bits Valid codes
3 (this case) 2 3 (third bit as parity or growth) 1, 2, 3
4 to 7 3 4 1 to 7
8 to 15 4 5 1 to 15

Minimum bits = the smallest n where 2n − 1 ≥ number of units. The extra bit is optional. Used as a parity bit, it lets the logic catch one failed input. Without it, a single failed input can turn a valid code into another valid code. For example, unit 3 (binary 11) losing a bit reads as unit 1 or unit 2, and then it writes to another unit's slave.

  • Enough spare DI: go to Check 5.
  • Not enough spare DI: fall back to the retentive-register method from Check 2. Add an HMI display of the active unit number and the target slave IP so operators can see a wrong value.

Check 5: Wire the jumpers and read them live

  1. Choose the same input points on all three CPUs, for example the last two or three points of one input module. Label them "UNIT ID" on the drawings and on the terminal strip.
  2. On each panel, jumper the needed ID inputs to the input supply so they are ON. Unit 1 = bit 0 on. Unit 2 = bit 1 on. Unit 3 = bits 0 and 1 on. If you use parity, set the parity bit so the total count of ON bits is odd.
  3. Take the jumper supply from the same source that powers the rest of that input module. If the module loses power, the whole code drops to 0 (invalid), not to a different valid code.
  4. Go online with data view and read the ID input points on each CPU. Compare them against the drawing before loading any comms logic.
  • Readings match the drawing: go to Check 6.
  • A bit reads OFF when it should be ON: check the jumper, the terminal torque and the input common. Don't continue until all three units read correctly.

Check 6: Pack, validate and latch the ID on first scan

Read the inputs once, in a first-scan task, and pack the bits into a word. After that, the running logic uses only the latched word, never the live inputs. If a jumper vibrates loose mid-shift, the unit keeps talking to its own slave instead of switching targets while running. Keep a separate live comparison for alarming.

// Example logic, generic ladder pseudocode. Tag names are examples.
// FIRST-SCAN TASK
UnitID      := pack(ID_Bit0, ID_Bit1)      // bit0 -> weight 1, bit1 -> weight 2
ParityOK    := (ID_Bit0 XOR ID_Bit1 XOR ID_Parity) = 1   // odd parity, if 3rd bit used
UnitIDValid := (UnitID >= 1) AND (UnitID <= 3) AND ParityOK

// CONTINUOUS TASK
LiveID      := pack(ID_Bit0, ID_Bit1)
IDMismatch  := LiveID <> UnitID                // alarm only; do not re-select slave

// Calculate and store data in MODBUS_ARRAY (common to all units)

EnWrite1 := UnitIDValid AND (UnitID = 1) AND CommsPermissive
EnWrite2 := UnitIDValid AND (UnitID = 2) AND CommsPermissive
EnWrite3 := UnitIDValid AND (UnitID = 3) AND CommsPermissive

// Modbus TCP write block A: target 192.168.0.1, source MODBUS_ARRAY, enable EnWrite1
// Modbus TCP write block B: target 192.168.0.2, source MODBUS_ARRAY, enable EnWrite2
// Modbus TCP write block C: target 192.168.0.3, source MODBUS_ARRAY, enable EnWrite3

Alarm_BadUnitID := NOT UnitIDValid

How the gating works: all three write blocks are in every copy of the program, but on any CPU only one of them is ever enabled. The two disabled blocks send nothing, so they create no connection attempts, no timeouts and no error bits on that unit. Keep CommsPermissive as a single rung for whatever other conditions you already need (network healthy, process running). The ID bits should add a condition, not replace existing interlocks.

If your write instruction takes the slave IP from a tag, you can use one write block and load the target address from a small lookup indexed by UnitID. Check in the instruction help that the address field accepts a tag before you build it this way. Three fixed blocks are easier to read online at night, and nothing can change the target while running.

Verify the build before it goes to all three panels

Symptom after download Likely cause Next reading
No Modbus traffic at all, bad-ID alarm on Code 0 or out of range: jumper missing, input common dead, parity wrong Data view on the ID inputs; meter the input common
Data arrives at another unit's slave Jumper pattern wired for the wrong unit, or a failed bit without parity Compare latched UnitID with the panel label
IDMismatch alarm while running Loose jumper or intermittent input since power-up Terminal torque, input LED, recent maintenance on that strip
Intermittent comms on all units after the shared download Duplicate CPU IP from the project hardware config (Check 3) Ping sweep; check for duplicate-address warnings on the switch
Unit identity changed after a memory clear Identity still coming from a retentive register, not the inputs Confirm the first-scan task reads the physical points
  1. On the bench or on one panel, download the shared project. Confirm that UnitID, UnitIDValid and exactly one enable bit match that panel.
  2. Watch the slave's register map or comms counters. Only the intended slave should receive MODBUS_ARRAY updates.
  3. Pull one ID jumper and power-cycle. The bad-ID alarm must come on (with parity) and no write block should be enabled. Refit the jumper and power-cycle again.
  4. With the unit running, pull one ID jumper. IDMismatch must alarm, and the target slave must not change until the next power-up.
  5. Repeat steps 1 and 2 on the other two panels. Record the ID pattern and slave IP on each panel's drawing set.

Keep it maintainable

  • Put the ID-to-slave table (code, jumper pattern, slave IP) in a rung comment next to the write blocks and in the panel drawings.
  • Add a new unit by wiring the next free code and adding one write block. Do not reuse a retired code on a different slave without updating every copy of the project.
  • Keep one master project file under version control. The only per-panel differences should be the jumpers, plus the CPU port address if Check 3 found it stored in the project.

FAQ

How do I make one Productivity P3000 program know which CPU it is running on?

Wire two or three spare discrete inputs as ID jumpers, with a different pattern on each panel. Read and pack them into a word in a first-scan task, and treat code 0 as invalid. Use that latched word as an enable condition on each Modbus TCP write block, so only one slave is targeted per unit.

How do I stop a failed ID input from sending data to the wrong Modbus slave?

Reserve code 0 as invalid and add a parity bit. Then a single failed input produces an invalid code that inhibits all writes, instead of selecting another valid unit. Latch the ID on first scan and alarm on any mismatch with the live inputs, rather than changing targets while running.

When should I stop troubleshooting and call AutomationDirect support?

Stop if the ID inputs read correctly and only one write block is enabled, but the slave still gets no data or error bits keep toggling. Also stop if you can't find out whether the CPU's own IP address is stored in the project. Contact AutomationDirect technical support with the Productivity Suite version, CPU firmware, and the write instruction's error or status values.

Back to blog