Resolving DAQFactory Modbus CRC Failed on Function 16 Writes

Daniel Price13 min read
ModbusOther ManufacturerTroubleshooting
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

Where does the write request stop between DAQFactory and the slave?

The slave at address 01 accepts an 11-byte function 16 frame from its own configuration software.CRC failed. The fault is in the PDU the master assembles, not on the wire.

The request passes through these hops:

  1. The DAQFactory Modbus RTU driver builds the frame.
  2. The PC serial port carries it out. The port monitor taps the traffic here.
  3. The cable or converter delivers it to the slave firmware.
  4. The slave sends its response back along the same path.
  5. DAQFactory checks the CRC of that reply.

The port monitor sees TX exactly as it leaves the PC, so a byte-for-byte diff of the two TX captures shows what the master does differently. The RX capture shows how the slave reacted.

RX observed after DAQFactory write Meaning Next step
No bytes Slave discarded the frame (unsupported quantity, function, or address range) Diff TX against the vendor tool frame
Bytes present, DAQFactory shows CRC failed Reply is not a valid RTU frame for the request (non-standard or truncated answer) Log the RX length and the last two bytes
01 10 75 7F 00 0N + CRC Normal FC16 acknowledgement (echoes start address and quantity) Write accepted. Verify by reading back

Check before moving on: in one port monitor session, capture TX and RX for one write from the vendor software and one from DAQFactory. Use the same register and the same value for both.

What does each byte of the vendor tool's function 16 frame carry?

The vendor software sends 01 10 75 7F 00 01 02 00 CF C9 0C. Here is the decode against the Modbus RTU Write Multiple Registers layout:

Byte (0-based) Value Field Decoded
0 01 Slave address 1
1 10 Function code 16, Write Multiple Registers
4-5 00 01 Quantity of registers 1
6 02 Byte count 2 = 2 × quantity

The pair that looked like it "doubles" in DAQFactory's frame is the quantity low byte and the byte count. Counted 1-based, those are bytes 6 and 7. The byte count is always twice the quantity because every holding register is 2 bytes wide, so 01 02, 02 04, 03 06 and 04 08 are all valid pairs. DAQFactory's 00 02 04 means two registers and four data bytes. That is a request for twice as much data, not a formatting bug.

A function 16 frame is 9 + 2N bytes long for N registers: 11 bytes for one register, 13 for two, 15 for three, 17 for four.

Check: every captured FC16 frame should satisfy byte count = 2 × quantity and total length = 9 + 2N. If a frame fails that test, the capture is truncated or merged with the next frame.

Which DAQFactory I/O type produced which frame on register 30079?

DAQFactory's frame was 01 10 75 7F 00 02 04 ...because the selected output type is 32 bits wide. The 16-bit types use function 6, Write Single Register. That frame has no quantity or byte count: the value follows the address directly. Captured frames writing 210 (0xD2) to address 30079:

I/O type FC Registers Data bytes on wire Notes
S16 / U16 06 1 00 D2 Full frame 01 06 75 7F 00 D2 22 43
S32word / U32word 10 2 00 00 00 D2 High word first
floatwords 10 2 00 00 43 52 Words swapped
floatRbytes 10 2 00 00 52 43 All four bytes reversed
skip2 10 2 43 52 00 00 Same bytes as float in this capture

None of the 32-bit variants can reproduce the vendor frame, because the vendor frame carries one register. If the slave accepts a 2-register write, the adjacent parameter gets overwritten with half of your 32-bit value.

Check: read 30080 before any test write and record it. After each experiment, read it again. A changed value means a 32-bit type reached the device.

How do I force a single-register write with function 16?

The vendor software always uses function 16, even for one register. Many slaves implement only one of the two write functions, and this slave's behaviour matches a device that expects FC16 for every write. DAQFactory provides two Set Register S16 I/O types for this reason. The standard one emits function 6. The one labelled S16 (16) forces function 16 with quantity 1.

  1. Test function 6 once with the standard S16 output type. If the slave echoes 01 06 75 7F 00 D2 + CRC and the read-back matches, the device supports FC6 and needs no further change.
  2. If FC6 gets no reply or an exception (01 86 xx), change the output channel's I/O type to Set Register S16 (16). Keep address 30079 and slave 1.
  3. Set the channel to 207 and capture TX. It must read 01 10 75 7F 00 01 02 00 CF C9 0C, which matches the vendor tool byte for byte, including the CRC.
  4. For script writes, use SetRegisterS16() only where its function selection suits the slave. It sends function 6 with one value and function 16 with more than one value. For single-register FC16 writes from script, set the value of an output channel configured as S16 (16) instead of calling the function with one value.

Watch the width of array arguments. Device.MyDevice.SetRegisterS32(1,30079,{0,210}) does not write two registers. It writes two 32-bit values, which is four registers, and produces 01 10 75 7F 00 04 08 00 00 00 00 00 D2 00 00 F0 1D. That overwrites 30079 through 30082. The array length multiplies by the register width of the type: 1 for S16/U16, 2 for S32/U32/float.

Check: the TX capture from DAQFactory and the vendor tool are identical for the same value, and the slave returns the 8-byte FC16 acknowledgement, with no CRC failed in the DAQFactory log.

How do the 3-register and 10796 writes map to DAQFactory calls?

The vendor tool issues two more function 16 writes. Here they are in DAQFactory terms:

Captured frame Start address Qty Register values DAQFactory equivalent

The byte 0x30 is ASCII'0', so the first two registers hold the text "0000", packed two characters per register high byte first, followed by a zero register. That layout points to a string field terminated or padded with nulls. Treat it as text when you write it from DAQFactory. Pack the characters into 16-bit words in the same order instead of sending a numeric value.

The three-value SetRegisterS16() call picks function 16 automatically because it carries more than one value. No forced I/O type is needed.

Check: capture the DAQFactory TX for the 30100 call. It must end in 50 DE, and the slave must acknowledge with 01 10 75 94 00 03 + CRC.

Is function code 0xFA Modbus, and how do I reach it from DAQFactory?

The vendor software opens the session and polls with frames that use function code 0xFA:

Frame Header Bytes 8-9 Bytes 10-11 Last 2 bytes

0xFA is not a public Modbus function code. Request function codes run from 1 to 127. A set high bit (0x80 and above) is reserved to flag an exception in a response, so a standards-compliant master never sends 0xFA and DAQFactory's Modbus driver cannot generate it. The frame still uses Modbus RTU framing: slave address first, CRC-16 last. That is typical of a vendor extension riding on a Modbus stack.

The payload pattern is a working hypothesis to confirm with the manufacturer. Bytes 8-9 fall in the same address ranges as the Modbus registers above (0x75xx, 0x2Axx region), and bytes 10-11 behave like a count. Get the frame definition from the manufacturer's protocol document.

To integrate the vendor DLL instead, you need the DLL plus its header (.h) file or its API manual. The DLL alone does not expose function signatures.

To speak 0xFA from DAQFactory, create a user-defined serial device on the same COM port settings and assemble the frames in script.

Check: replay one captured 0xFA frame byte for byte from DAQFactory with the vendor tool closed. The reply must match the reply the vendor tool receives for the same frame.

How do I send the raw 0xFA frame and turn the reply into a number?

Build the request as a string with chra(), append the CRC, write it, read the reply, and slice the bytes you need. Count the header carefully. The captured frame carries six 0x00 bytes between FA and the address. An array with five zeros produces a different frame and a different CRC.

private string out = chra({0x01,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x75,0x30,0x00,0x3E})
out = out + CalcCRC(out)          // appends 0x20 0xC9
device.MyDevice.Write(out)
private string datain = device.MyDevice.Read(100)
MyChannel.AddValue(To.urWord(asca(mid(datain,15,2))))

Read from the same device object you wrote to. A read against a different device name polls a different port definition. Read(100) waits for 100 bytes or the port timeout. If the reply is shorter, each poll costs the full timeout. Count the reply length from a capture and read exactly that many bytes.

The reply offsets are 0-based in mid():

Offset 0 1 2 3 4 5 6 7 8 9 10 11-14 15 16
Byte 01 FA 00 02 00 C8 18 E3 7C 00 51 00 00 00 00 01 E9

asca(mid(datain,15,2)) returns the array {1,233}, which is two separate bytes. To.urWord()To.uWord()TheTo. function family converts binary byte arrays into numeric values. Use the r (reversed, big-endian) variants for Modbus-style register data.

Check: with the reply above, MyChannel logs 489, not {1,233} and not 59649.

How do I compute the Modbus CRC16 in DAQFactory script and order its bytes?

The slave's CRC is standard CRC-16/Modbus: initial value 0xFFFF, reflected polynomial 0xA001. It only looks reversed because of how online calculators display it. A calculator prints the CRC as a number, for example 0x3751, while Modbus RTU transmits the CRC low byte first, 51 37. Register data inside a Modbus frame is big-endian, but the CRC trailer is little-endian. The two rules are independent, and mixing them up is the usual source of the "reversed CRC" confusion.

This is the bit-wise routine used in DAQFactory's Modbus driver. Here it returns the two CRC bytes already ordered for the wire. Create a function named CalcCRC and paste:

function CalcCRC(string buffer)
private crc = 0xffff
private raw = asca(buffer)
for (private x = 0, x < GetLength(buffer), x++)
   crc = crc # raw[x]
   for (private j = 0, j < 8, j++)
      if (crc & 0x0001)
         crc = crc >> 1
         crc = crc # 0xA001
      else
         crc = crc >> 1
      endif
   endfor
endfor
return(chra(from.uWord(crc)))

In DAQFactory script, # is XOR and & is bitwise AND. buffer is the frame assembled so far, without CRC, passed as a string because the serial Write() takes a string. from.uWord() splits the 16-bit result little-endian, low byte first, which is the CRC order on the wire. from.urWord() splits big-endian and would put the bytes in the wrong order for the trailer. Keep it as a function rather than inline sequence code so the Modbus writes, the 0xFA polls and any other frame builder share one implementation.

Test input (hex) Expected numeric CRC Expected wire bytes
01 FA 00 00 00 00 00 00 75 30 00 3E 51488 = 0xC920 20 C9

If the routine returns something else, such as 0x947E where 0xC920 is expected, the input string differs from the captured frame. Compare the byte count first, then each byte. A single dropped or extra byte changes the whole CRC.

Check: in the Command/Alert window, evaluate ? asca(CalcCRC(chra({0x01,0xFA,0x00,0x00,0x00,0x00,0x00,0x00,0x75,0x30,0x00,0x3E}))). It must return {32,201} (0x20, 0xC9).

Why does the CRC script throw C1127, and how do I make it fast?

The bit-wise loop can stop with C1127 Infinite loop detected. 100 steps executed in 0.014146 seconds. The error comes from DAQFactory's infinite loop check, which trips on any tight loop. The CRC loop is not infinite. Disable the check under File → Preferences. It is a deprecated feature and normally defaults to inactive.

Do not put delay() inside the bit loop to dodge the check. With delay(0.09) per bit iteration, one CRC costs bytes × 8 × 0.09 s. Delays shorter than 0.09 s brought the error back, so the delay fixes nothing and caps the poll rate at one transaction every several seconds.

Removing loop iterations matters more in DAQFactory script than in compiled C, where the serial link is the bottleneck. A table-driven CRC does one table lookup per byte instead of eight shift-and-test steps. Build the 256-entry table once with the same reflected-polynomial loop, for example in a sequence that runs at startup:

global crcTable
for (private i = 0, i < 256, i++)
   private c = i
   for (private j = 0, j < 8, j++)
      if (c & 0x0001)
         c = (c >> 1) # 0xA001
      else
         c = c >> 1
      endif
   endfor
   crcTable[i] = c
endfor

Then compute the per-frame CRC with one pass over the message:

function CalcCRCFast(string buffer)
private crc = 0xFFFF
private raw = asca(buffer)
for (private x = 0, x < GetLength(buffer), x++)
   crc = (crc >> 8) # crcTable[(crc # raw[x]) & 0xFF]
endfor
return(chra(from.uWord(crc)))

The alternative is the classic two-half byte table (high-byte table followed by low-byte table, 512 entries typed in as constants), iterating index = crc_high # raw[x], crc_high = crc_low # table[index], crc_low = table[index+256]. It works, but the hand-typed constants are easy to corrupt. The generated table cannot contain a typo. With the byte-table form, one variable ends up holding the byte transmitted first. Confirm which one against a known frame instead of guessing from the variable names.

Check: CalcCRCFast() and CalcCRC() return identical bytes for all three test vectors in the previous section, and the table version completes without a visible delay.

How do I prove the full write and poll path end to end?

Run this sequence with the vendor software closed and the port monitor capturing TX and RX:

  1. Read registers 30079 and 30080 and record both values as the baseline.
  2. Write 207 to 30079 through the S16 (16) output channel. Confirm TX = 01 10 75 7F 00 01 02 00 CF C9 0C and RX begins 01 10 75 7F 00 01, with no CRC failed in DAQFactory.
  3. Read 30079 back and confirm 207. Read 30080 and confirm it still equals the baseline, which proves no 32-bit type reached the device.
  4. Run . Confirm TX = 01 10 75 94 00 03 06 30 30 30 30 00 00 50 DE and an FC16 acknowledgement with quantity 00 03.
  5. Write 117 to 10796 through an S16 (16) channel. Confirm TX ends 00 75 EB D9.
  6. Send the 0xFA poll from the user-defined serial device. Confirm the appended CRC bytes are 20 C9, the reply arrives within the port timeout, and MyChannel logs 489 from reply offsets 15-16.
  7. Leave the Modbus channels and the 0xFA sequence polling together for several cycles. The DAQFactory alert log must stay free of CRC failed and C1127, and every TX in the capture must have a matching RX.

FAQ

How do I make DAQFactory write a single Modbus register with function 16 instead of 6?

Set the output channel's I/O type to Set Register S16 (16). It sends function 16 with quantity 1 and byte count 2, for example 01 10 75 7F 00 01 02 00 CF C9 0C. The standard S16/U16 types and a one-value SetRegisterS16() call send function 6.

Why does DAQFactory send 00 02 04 when I write one Modbus register?

The selected type is 32-bit (S32, U32, or one of the float variants), so it writes two registers (quantity 00 02) and four data bytes (byte count 04). The second word lands in the next register. Switch to a 16-bit type to write exactly one register.

How do I calculate Modbus CRC16 in DAQFactory script?

XOR each byte into a 0xFFFF accumulator and shift right 8 times per byte, XORing 0xA001 whenever the LSB was 1. Append the result with chra(from.uWord(crc)) so the low byte goes first. For 01 FA 00 00 00 00 00 00 75 30 00 3E the result is 0xC920, sent as 20 C9.

How do I convert two received bytes into a 16-bit value in DAQFactory?

Use the To. functions: To.urWord(asca(mid(data,15,2))) combines the bytes big-endian, so 01 E9 becomes 489. To.uWord() is the little-endian variant, and mid() offsets are 0-based.

How do I stop the C1127 infinite loop error in a DAQFactory CRC loop?

Disable the infinite loop check under File → Preferences instead of adding delay()inside the loop. A precalculated 256-entry lookup table cuts the loop to one iteration per byte.

Back to blog