Map PLC tags
Assign Modbus addresses to vBuilder tags before the PC application attempts to read or write them.
Custom PLC integration
Use the legacy Visual Studio project, current Modbus specifications, and practical frame examples to build a custom USB connection to an Ace or Branch PLC.
Example scope
Ace and Branch PLCs expose Modbus communication over their USB connection. The legacy example uses a C# Windows Forms application to open the Velocio virtual COM port and exchange Modbus RTU-style request and response frames.
Assign Modbus addresses to vBuilder tags before the PC application attempts to read or write them.
Find the VelocioComm port and make sure vBuilder or another application is not already using it.
The sample covers coils and 16-bit values, then shows how to extend the pattern to 32-bit data.
Append the Modbus CRC to requests and verify the CRC, unit ID, function, length, and errors in responses.
Video walkthrough
The video demonstrates the project workflow. The CRC helper and 32-bit examples documented below were added after the video was produced.
What you need
Connection setup
Give each tag used by the PC application a Modbus address. Mark tags writable when the application needs to change them. Record the PLC station ID; the preserved example uses station 1.
Connect the PLC over USB, open Windows Device Manager, expand Ports (COM & LPT), and note the COM number shown beside VelocioComm.
Close any vBuilder connection or other application using the same port. The legacy project opens the selected port at 115200 baud, eight data bits, no parity, one stop bit, and no handshaking.
Build a frame from unit ID, function code, address, and payload; append CRC low byte then high byte. On receipt, reject short frames, CRC failures, unexpected unit IDs, and Modbus exception responses.
The wire-level starting address is zero-based. In the preserved example, an address displayed as 1 in vBuilder is sent as offset 0. Confirm this mapping with a bench test before writing live machine values.
Useful C# details
These concise examples preserve the behavior of the legacy project without reproducing the entire Windows Forms application.
var port = new SerialPort(portName, 115200, Parity.None, 8, StopBits.One);
port.Handshake = Handshake.None;
port.ReadTimeout = 1000;
port.WriteTimeout = 1000;
port.DataReceived += OnDataReceived;
port.Open();A serial port is exclusive. Handle access-denied and disconnect errors, and always close and dispose the port when the application exits.
byte[] request = { 0x01, 0x03, 0x00, 0x00, 0x00, 0x02 };
byte[] frame = AppendModbusCrc(request);A 32-bit integer or float occupies two 16-bit registers. Only read contiguous registers as one request when the vBuilder address map is intentionally contiguous.
static ushort ModbusCrc(ReadOnlySpan<byte> data)
{
ushort crc = 0xFFFF;
foreach (byte value in data)
{
crc ^= value;
for (int bit = 0; bit < 8; bit++)
crc = (ushort)((crc & 1) != 0 ? (crc >> 1) ^ 0xA001 : crc >> 1);
}
return crc;
}The sender calculates the CRC across the request, appends the least-significant CRC byte first, and verifies received frames before using their data.
byte[] littleEndian = { response[4], response[3], response[6], response[5] };
float value = BitConverter.ToSingle(littleEndian, 0);The response places the high byte first inside each Modbus register. The preserved example swaps the bytes in each word before using BitConverter.
short value = unchecked((short)((response[3] << 8) | response[4]));The same byte-order rule applies to unsigned 16-bit values. Validate the byte count before indexing the response buffer.
byte[] valueBytes = BitConverter.GetBytes(value);
byte[] request = { stationId, 0x10, 0x00, 0x00, 0x00, 0x02, 0x04, valueBytes[1], valueBytes[0], valueBytes[3], valueBytes[2] };
byte[] frame = AppendModbusCrc(request);Bench-test word and byte order with a known value. Different devices can represent multi-register data differently even when each 16-bit register follows Modbus byte order.
SerialPort.DataReceived runs outside the UI thread. Buffer complete frames and marshal UI updates with the application framework’s dispatcher or invocation mechanism. Never assume one receive event contains exactly one complete Modbus frame.
Protocol reference
The original documentation lists the following functions. Confirm support against the exact PLC model and firmware used in your installation.
0x01Read Coils0x02Read Discrete Inputs0x03Read Holding Registers0x04Read Input Registers0x05Write Single Coil0x06Write Single Register0x0FWrite Multiple Coils0x10Write Multiple Registers0x17Read/Write Multiple RegistersVerified resources
The source archive is preserved for historical value. The protocol links point to the Modbus Organization, and the serial API link points to Microsoft documentation.