Problem Overview: CX-Server Lite OCX Redistribution Failures
Custom Visual Basic (VB) applications that target Omron CS1/CJ1 series PLCs are typically built on top of the CX-Server Lite communications stack, exposed through ActiveX controls such as CX-Comms OCX, CX-Cmms OCX, and CX-Drive OCX. While these controls are documented in the CX-Server Lite User Manual and accelerate development on a single engineering workstation, they introduce a recurring deployment problem: the OCX runtime cannot be silently redistributed, and missing-dependency errors appear on target machines that never had CX-Server Lite installed.
Typical runtime failures observed when copy-deploying a compiled VB executable that references CX-Server Lite OCX controls:
-
429 - ActiveX component can't create objectat startup when the OCX is registered but license keys are absent. -
Component 'CXCMMSO.OCX' or one of its dependencies not correctly registeredwhen the OCX is missing or 32/64-bit mismatch occurs. -
Class not licensed for useon systems where the OCX is present but the design-time license token (.LIC) cannot be located. - Silent failure of inline help files when the deployment language differs from the development language (a documented myOMRON Europe behaviour, see myOMRON KB #230).
CX-Server Lite Licensing Model and Distribution Reality
CX-Server Lite is offered as a single-seat developer licence. According to the Omron Europe software registration portal, the same site that lists CX-Compolet and CX-Position downloads (Software Registration & Downloads) also distributes CX-Server Lite v2.2 update packages. The licence terms cover:
- One (1) developer installation per licence serial number.
- Use of the OCX controls from a single engineering workstation.
- No royalty-free runtime redistribution clause.
For multi-end-user deployment, the documented commercial paths are:
- Purchase a CX-Server Lite licence per end-user PC (cost-prohibitive at fleet scale).
- Upgrade to CX-Compolet ActiveX controls (SYSMAC Compolet), which ship as a separate component set and follow their own licence model.
- Replace the OCX layer entirely with direct FINS UDP communication from VB using Winsock - one licence, no runtime distribution dependency.
Option 3 is the only path that produces a single-instance-per-licence deployment model with no per-target runtime install, no OCX registration, and no design-time licence token to embed.
Why FINS UDP on Ethernet Is the Correct Replacement
CS1 and CJ1 PLCs equipped with an ETN21 Ethernet module, a built-in CS1W-ETN01/ETN11, CS1D-ETN21D, or any CJ1W-ETN21 variant, expose a UDP port for the Omron proprietary FINS (Factory Interface Network Service) protocol. By default this is UDP/9600, configurable in the Ethernet Unit's Routing Table.
FINS is a request/response protocol with deterministic completion codes. Because it sits directly on UDP, no OCX, no COM runtime, no third-party OPC server, and no IFR (Instruction Frame Routine) is needed - the application speaks FINS through a standard Windows Sockets control. The same VB6 Winsock control (MSWINSCK.OCX, freely redistributable with most VB6 runtimes) handles all transport work.
Key advantages over the OCX path:
| Attribute | CX-Server Lite OCX | FINS UDP + Winsock |
|---|---|---|
| Runtime distribution | Per-target install required | None - bundled in EXE |
| Licence footprint | Developer + runtime licence | Developer licence only |
| OS dependency | COM registration, MDAC | Winsock 2 (all Win32+) |
| Network path | CX-Server routing table | Direct UDP to PLC IP |
| Latency overhead | COM marshalling | Single datagram |
| Troubleshooting | Vendor trace tools | Wireshark dissector (omron-fins) |
FINS UDP Frame Architecture
A FINS/UDP datagram consists of two concatenated structures: the FINS/UDP header (10 bytes) and the FINS command frame (variable length). No payload-level integrity check is performed by FINS itself; reliability is delegated to the application's request/timeout/retries discipline.
FINS Header Field Definitions
| Offset | Field | Width | Typical Value | Meaning |
|---|---|---|---|---|
| 0 | ICF | 1 | 0x80 | Information Control Field - command frame; bit 0 = response required |
| 1 | RSV | 1 | 0x00 | Reserved - always 0x00 |
| 2 | GCT | 1 | 0x02 | Permitted gateway count (0..7); 2 supports a single router hop |
| 3 | DNA | 1 | 0x00 | Destination Network Address |
| 4 | DA1 | 1 | 0x01..0x7F | Destination Node Number (CS1 default = 1) |
| 5 | DA2 | 1 | 0x00 | Destination Unit Address (0 = CPU unit) |
| 6 | SNA | 1 | 0x00 | Source Network Address |
| 7 | SA1 | 1 | client IP last octet | Source Node - must be unique within the network |
| 8 | SA2 | 1 | 0x00 | Source Unit Address |
| 9 | SID | 1 | 0x00..0xFF | Service ID - incremented for each new request |
CS1 PLC Ethernet Unit Pre-Configuration
Before any VB client can talk FINS/UDP, the PLC side must be set up. Connect with CX-Programmer and configure the ETN module parameters:
- Set the Ethernet module's IP address, subnet mask, and default gateway (e.g.
192.168.250.1 / 255.255.255.0). - In the TCP/IP tab, enable FINS/UDP port. Default is 9600; record the value if changed.
- In the FINS Network tab, assign a Network Number (e.g.
0) and the Ethernet module's Node Number (must match the FINS header's DA1 in the client request). - In the CPU Bus Unit Settings, set Node Address Setting (NA!) to a value matching DA1.
- Add a FINS Routing Table entry: local network = 0, relay node = PC IP last octet, relay agent = CPU.
- Transfer the settings to the PLC and cycle power or restart the ETN unit (CX-Programmer can do this hot).
ping to verify L3 reachability before debugging FINS itself.VB6 Winsock Skeleton for FINS/UDP
Drop a Winsock control (MSWINSCK.OCX) onto the form as udpPLC, set Protocol = sckUDPProtocol, and bind to a local port (any free port > 1024). The minimal send/receive skeleton:
' --- Module-level constants
Private Const PLC_IP As String = "192.168.250.1"
Private Const PLC_FINS_PORT As Long = 9600
Private Const LOCAL_NODE As Byte = 100 ' must be unique on the network
Private Const PLC_NODE As Byte = 1 ' DA1 value for CS1
' --- Build a 10-byte FINS/UDP header
Private Function BuildFinsHeader(ByVal sid As Byte) As Byte()
Dim h(0 To 9) As Byte
h(0) = &H80 ' ICF - command
h(1) = &H00 ' RSV
h(2) = &H02 ' GCT
h(3) = &H00 ' DNA - destination network
h(4) = PLC_NODE ' DA1 - destination node
h(5) = &H00 ' DA2 - destination unit (CPU)
h(6) = &H00 ' SNA - source network
h(7) = LOCAL_NODE ' SA1 - source node
h(8) = &H00 ' SA2 - source unit
h(9) = sid ' SID
BuildFinsHeader = h
End Function
' --- Memory Area Read (MRC=0101, SRC=0101)
' Reads 'count' words starting at 'address' from 'areaCode'
Private Function FinsReadWords(ByVal areaCode As Byte, _
ByVal address As Long, _
ByVal count As Integer) As Byte()
Dim sid As Byte: sid = (sidCounter + 1) And &HFF
sidCounter = sid
Dim pkt() As Byte
ReDim pkt(0 To 17) ' 10 header + 2 cmd + 4 params + 2 count
Dim h() As Byte: h = BuildFinsHeader(sid)
Dim i As Integer
For i = 0 To 9: pkt(i) = h(i): Next i
pkt(10) = &H01 ' MRC - memory area
pkt(11) = &H01 ' SRC - read
pkt(12) = areaCode ' area
pkt(13) = (address \ 256) And &HFF ' address high
pkt(14) = address And &HFF ' address low
pkt(15) = &H00 ' bit position = word access
pkt(16) = (count ") And &HFF ' items high
pkt(17) = count And &HFF ' items low
FinsReadWords = pkt
End Function
Async Receive Handler
Private Sub udpPLC_DataArrival(ByVal bytesTotal As Long)
Dim buf() As Byte
udpPLC.GetData buf, vbArray + vbByte, bytesTotal
' Validate FINS echo: SID at offset 9 must match the last SID sent
If buf(9) <> lastSid Then
Debug.Print "SID mismatch - dropped frame"
Exit Sub
End If
' Completion code is the first two bytes of the FINS response body
Dim mainCode As Byte: mainCode = buf(10)
Dim subCode As Byte: subCode = buf(11)
If mainCode <> &H0 Or subCode <> &H0 Then
Debug.Print "FINS error "; Hex(mainCode); " "; Hex(subCode)
RaiseEvent FinsError(mainCode, subCode)
Exit Sub
End If
' Parse word data starting at offset 12
Dim wordCount As Integer: wordCount = (bytesTotal - 12) \ 2
ReDim wordData(0 To wordCount - 1) As Integer
Dim i As Integer
For i = 0 To wordCount - 1
wordData(i) = buf(12 + i * 2) * 256 + buf(13 + i * 2)
Next i
RaiseEvent FinsDataReady(wordData)
End Sub
FINS Area Codes and Common Commands for CS1
The CS1 supports a wide set of memory areas through FINS. Word-access codes (used when bit position = 0x00):
| Code (hex) | Area | Symbol | Range (words) |
|---|---|---|---|
| 0x80 | CIO Area | CIO | 0..6143 |
| 0x81 | Work Area | WR | 0..511 |
| 0x82 | Holding Area | HR | 0..511 |
| 0x83 | Auxiliary Area | AR | 0..959 (read-only above 447) |
| 0x84 | Data Memory | D | 0..32767 |
| 0x90..0x9F | Extended Data Memory | E0..E15 | 0..32767 each bank |
| 0x98 | EM Current Bank | E$ | 0..32767 |
Bit-access codes (bit position 0x00..0x0F) use the same area base plus 0x80: e.g. 0xB0 = CIO bit, 0xB4 = D bit. 0xFF as bit position explicitly requests word access regardless of the area code.
Most-Used FINS Commands (CS1/CJ1)
| MRC | SRC | Command | Typical Use |
|---|---|---|---|
| 0x01 | 0x01 | Memory Area Read | Read words/bits from any area |
| 0x01 | 0x02 | Memory Area Write | Write words/bits |
| 0x01 | 0x04 | Memory Area Fill | Bulk-fill DM/EM with a constant |
| 0x03 | 0x04 | Memory Area Transfer | Server-side copy between areas |
| 0x05 | 0x01 | CPU Unit Information Read | Read model code, version |
| 0x06 | 0x01 | CPU Unit Status Read | Operating mode, fatal flag |
| 0x21 | 0x01 | Bit Set / Bit Reset | Force bits (debug only) |
| 0x23 | 0x01 | Forced Set/Reset Cancel | Release forced bits |
| 0x26 | 0x01 | Name Read | Resolve symbolic tag to address |
| 0x28 | 0x01 | Message Read/Clear | FINS message log |
FINS Completion Codes (Error Mapping)
Every FINS response begins with a 2-byte completion code. The main code categorises the failure; the sub code gives the precise cause. Treat any non-zero pair as an exception in the client.
| Main (hex) | Category | Sub Codes (hex) | Likely Cause / Remedy |
|---|---|---|---|
| 0x00 | Normal completion | 0x00 | Success |
| 0x01 | Source / destination address error | 0x03, 0x05, 0x06 | Area code or address out of range; verify D range <= 32767 |
| 0x02 | Command length error | 0x01 | FINS frame too short/long; recompute byte count |
| 0x04 | Address range error | 0x01, 0x02 | Start + count exceeds area end |
| 0x05 | Data length error | 0x01, 0x02 | Word count byte boundary; bit reads must specify bit position |
| 0x10 | PLC parity / checksum | 0x01..0x05 | Hardware issue; cycle power |
| 0x20 | CPU Unit error | 0x02, 0x03, 0x04 | PLC in PROGRAM/HALT mode or fatal error |
| 0x40 | Service aborted | 0x01..0x04 | Process interrupted; retry |
| 0x50 | Routing table error | 0x01, 0x02, 0x03 | ETN module has no route to destination network |
| 0x60 | Command format error | 0x01, 0x02 | Unknown MRC/SRC for CS1 firmware |
Verification and Commissioning Procedure
After building the Winsock-based FINS client, run the following four-step verification on every deployment target before declaring the application production-ready:
-
L3 reachability.
ping 192.168.250.1from the target PC. A successful ICMP echo confirms that any later FINS failure is a protocol/routing issue, not a network issue. -
L4 reachability. Open a command prompt and run
netstat -an | findstr :9600after the VB app has sent its first datagram, or usenc -u 192.168.250.1 9600with Wireshark filtering onudp.port == 9600to confirm packets leave the host. - FINS round-trip. Send a CPU Unit Information Read (MRC=0x05, SRC=0x01). A non-zero-length reply with completion code 0x0000 confirms the entire stack - Winsock, UDP, ETN module routing table, CS1 CPU - is healthy.
-
Application poll. Issue a Memory Area Read against a known D register (e.g.
D0) and confirm the value matches what CX-Programmer online view shows. This catches swapped byte-order bugs (FINS is big-endian) and bit/word addressing mistakes.
Troubleshooting Matrix
| Symptom | Capture Point | Likely Cause | Corrective Action |
|---|---|---|---|
| No UDP traffic from PC | Wireshark on switch SPAN port | Windows Firewall blocking outbound 9600 | Add inbound/outbound rule for UDP 9600; disable firewall temporarily to confirm |
| UDP sent, no response | Wireshark shows outgoing only | ETN routing table has no return entry; SNA/SA1 mismatch | Set SA1 to PC IP last octet; verify FINS node address in CX-Programmer |
| Response received, completion 0x5002 | FINS body decode | Destination network unreachable | Re-check ETN routing table network numbers match DNA field |
| Response received, completion 0x0103 | FINS body decode | Area code/address out of range | Validate D address < 32768 and area code matches table above |
| Response received, completion 0x2002 | FINS body decode | PLC in PROGRAM mode | Switch to MONITOR/RUN; verify CPU LED |
| Winsock error 10054 on remote reset | VB error trap | Previous ICMP destination unreachable | Wrap send in retry/backoff; rebind local port |
| OCX 429 on legacy code path | Application log | License token missing | Switch code path to FINS/Winsock - this article |
| Spanish-language install, blank help | CX-Server Lite UI | myOMRON KB #230 known issue | Install English pack or upgrade to v2.2 from Omron registration portal |
Edge Cases and Field-Proven Caveats
-
Multi-homed PC. If the Windows host has more than one active NIC,
Winsock.LocalIPmay bind to the wrong interface. UseudpPLC.Bind localPort, "192.168.250.100"explicitly to force the FINS network interface. - ETN21 firmware < v2.0. Older ETN21 modules reject FINS frames whose SID is 0x00 on retransmission. Always increment SID per request.
- NAT in path. FINS UDP traverses NAT only if a static UDP mapping is configured. Carrier-grade NAT between PC and PLC will break the protocol because the SID/SA1 reflection becomes impossible to predict.
- Polling rate. A single CS1 CPU can service roughly 50-100 FINS exchanges per second before ladder scan is impacted. Avoid polling loops > 50 ms period per tag; instead, use one multi-word FINS read for an entire region.
- CX-Compolet interop. If a future project adopts CX-Compolet SYSMAC controls (listed on the Omron registration portal), the same FINS framing logic can stay as a fallback path; CX-Compolet can be configured to talk to a SYSMAC Gateway, allowing a graceful migration.
-
Firewalls. Windows Firewall on Windows 7+ blocks unsolicited inbound UDP by default, but FINS responses are allowed if the PC initiated the request. Still, explicitly allow
udpPLC.LocalPortinbound. -
Endianness. FINS commands always use big-endian for multi-byte integers. The byte-order swap in the receive handler above (
buf(12 + i*2) * 256 + buf(13 + i*2)) converts to little-endian VB Integer.
Migrating an Existing OCX Application
For teams that already shipped a VB6 application referencing CX-Comms OCX, the migration to FINS/UDP does not require a rewrite. Wrap each OCX method call in an adapter class that translates ReadDevice into a FINS read request and back:
- Audit the current OCX call surface - typically
ReadDevice(area, address, count),WriteDevice(...),Connect,Disconnect. - Replace
Connectwith a singleWinsock.Bindcall. - Implement a synchronous request/response state machine on a 500 ms timer with a configurable retry count (default 3).
- Map the OCX area enumerators (e.g.
AR_DM,AR_CIO) to the FINS area codes in the table above. - Keep the public method signatures of the adapter identical to the OCX so downstream forms do not change.
This preserves the application's UI layer and isolates the protocol change to a single class, dramatically reducing regression risk.
FAQ
Do I really need a CX-Server Lite licence per end-user PC to redistribute an OCX-based VB application?
Yes. The CX-Server Lite OCX controls are licensed per developer workstation and require a registered, licensed install on every runtime target. They cannot be silently redistributed as part of a setup package - copy-deploying the OCX yields "class not licensed" or "missing dependency" errors. The redistribution-free path is to talk FINS/UDP directly through Winsock.
What is the default UDP port for FINS on a CS1 ETN module?
9600. It is configurable in the Ethernet Unit's TCP/IP tab via CX-Programmer. If changed, both the client (Winsock RemotePort) and the PLC must be updated together.
Is the FINS/UDP frame big-endian or little-endian?
Big-endian (network byte order). All multi-byte fields - addresses, counts, and data values - must be byte-swapped to little-endian when interpreting responses in VB's native Integer/Long types.
How many FINS exchanges per second can a CS1 sustain?
Approximately 50-100 exchanges per second before ladder scan time is noticeably affected. Prefer a single multi-word read over many single-word reads, and use a polling period of at least 50 ms per tag.
What is the correct area code for DM (D) memory on CS1?
0x84 for word access. For bit access, use 0xB4 and set the bit position (0x00..0x0F). For EM banks, use 0x90 (E0) through 0x9F (E15), or 0x98 for the current EM bank selected in the CPU.
Can CX-Compolet replace CX-Server Lite for redistribution?
CX-Compolet (SYSMAC Compolet) is a separate ActiveX component set that follows its own licence model and is available from the Omron Europe software registration portal. It is the recommended upgrade path when an OCX-based architecture must be retained; for fully licence-free distribution, the direct FINS/UDP + Winsock approach remains the lowest-friction option.