Indexing a CODESYS STRING returns the character code as a BYTE, so a hex-pair converter must take BYTE inputs. Loop from 0 to nBytes - 1 with nBytes = LEN / 2, convert 'AFDA0909090909' (14 characters) into 7 bytes, and rebuild '444446FFFF' from 5 bytes through SHR.
A hex string such as 'AFDA0909090909' is 14 characters and converts to 7 bytes: 16#AF, 16#DA, 16#09, 16#09, 16#09, 16#09, 16#09. The loop meant to produce that array often refuses to compile with Cannot convert type 'BYTE' to type 'STRING(1)', because indexing a CODESYS STRING hands back the character's code as a BYTE while the pair converter takes STRING(1). The fix is to give the converter BYTE inputs, loop once per byte from 0 to nBytes - 1 with nBytes = LEN / 2, and run the same two functions in reverse with SHR to rebuild the string. Strings like this usually arrive one line at a time from a meter, a drive or a gateway; if yours comes off a serial port, build it first with the technique in accumulating multiline serial reads into variables in CODESYS.
Why the loop refuses to compile
The single-pair call works: ASCIIstring_TO_BYTE('A','F') returns 16#AF, because both arguments are string literals and the function declares its inputs as STRING(1). Inside the loop there are no literals. MyStr[0] of 'AFDA0909090909' is the code 16#41, the ASCII value of 'A', held in a BYTE. That mismatch is what the compiler reports as Cannot convert type 'BYTE' to type 'STRING(1)'.
The second message from the same attempt, Function STRING_TO_BYTE not resolved, says there is no built-in conversion of that name to reach for. A type cast moves a value between types bit for bit; it never turns the two codes 16#41 and 16#46 into the nibble values 10 and 15. The job needs arithmetic or a library function.
Symptoms and what each points to
| Symptom | What it points to | Next move |
|---|---|---|
Cannot convert type 'BYTE' to type 'STRING(1)' |
An indexed string element, a BYTE, is landing on a STRING(1) parameter | Give the converter BYTE inputs |
Function STRING_TO_BYTE not resolved |
No built-in conversion parses hex digits | Use the Util library function or the local one below |
| Compiles, but the last byte is garbage | The bound runs past the string: 0 TO 10 on 10 characters is 11 iterations | Derive the bound from LEN |
| 14 values come out of a 14-character string | One conversion per character, so each result holds one nibble | Convert per pair, positions Index2 and Index2+1 |
| A valid-looking pair returns 16#00 | A lowercase or non-hex character falls through an uppercase-only ladder | Ranged subtraction with a 16#FF marker |
| The rebuilt string has extra or missing characters | The reverse loop's bound was written for a different byte count | Bound of byte count minus 1, then check LEN |
Take the characters as BYTE codes
The Util library in CODESYS carries HEXinASCII_TO_BYTE, documented as converting one hex character's code into one byte of value, and BYTE_TO_HEXinASCII for the way back. OSCAT's HEX_TO_BYTE covers the forward direction as well; WORD_AS_STRING in the same utilities does the opposite job, value out to characters, so it is the wrong tool for string to bytes.
The forward loop, with illustrative variable names around the documented library and operator names:
VAR
MyStr : STRING(255) := 'AFDA0909090909'; (* 14 characters *)
MyBytes : ARRAY[0..99] OF BYTE;
Index : INT;
nBytes : INT;
END_VAR
nBytes := LEN(MyStr) / 2; (* 14 / 2 = 7 bytes *)
FOR Index := 0 TO nBytes - 1 DO (* 0 TO 6 *)
MyBytes[Index] := SHL(HEXinASCII_TO_BYTE(MyStr[Index * 2]), 4)
+ HEXinASCII_TO_BYTE(MyStr[Index * 2 + 1]);
END_FOR
HEXinASCII_TO_BYTE, SHL and LEN are the library and operator names; MyStr, MyBytes, Index and nBytes are illustrative. The high-nibble character sits at Index2, the low-nibble character at Index2+1. The same pair arithmetic scales up when a protocol field is wider than a byte: converting four ASCII hex digits into a WORD carries that wider case through step by step.
Set the loop bound from LEN
The failing snippet ran FOR iCount := 0 TO 10 over a 10-character string. That is 11 iterations: index 10 already reads the string terminator, and the paired call also touches index 11, which is where the garbage in the last byte comes from. Two smaller faults share the same snippet. The output index iCountTT is never reset to 0, so a second call keeps writing from where the first stopped. And the IF (iCount MOD 2) = 0 gate spends an iteration on every character while converting on every other one.
Loop over byte index and compute the character positions from it, as the loop above does. A fixed-length input can keep a fixed bound: 'DFAFFF0001' holds 10 characters, so 0 TO 4 fills bytes 0 to 4. Anything variable takes nBytes := LEN(MyStr) / 2 and 0 TO nBytes - 1. Reject an odd length before the loop, because the final pair is missing its second character, and compare nBytes with the array's last index before the first write.
Write the nibble function when no library fits
The attempted ladder compares the input character against each of the sixteen hex characters in separate IF blocks and leaves the state at its initial 0 for anything else. A lowercase 'a' or a stray 'G' therefore produces a silent nibble of 0, and nothing tells you the input was bad. Arithmetic on the character code covers all three valid ranges and flags the rest:
FUNCTION HexChar_TO_NIBBLE : BYTE
VAR_INPUT
c : BYTE; (* character code, e.g. MyStr[Index] *)
END_VAR
IF c >= 16#30 AND c <= 16#39 THEN (* '0' to '9' *)
HexChar_TO_NIBBLE := c - 16#30;
ELSIF c >= 16#41 AND c <= 16#46 THEN (* 'A' to 'F' *)
HexChar_TO_NIBBLE := c - 16#37;
ELSIF c >= 16#61 AND c <= 16#66 THEN (* 'a' to 'f' *)
HexChar_TO_NIBBLE := c - 16#57;
ELSE
HexChar_TO_NIBBLE := 16#FF; (* invalid character *)
END_IF
The ranges are the ASCII codes for the digits, the uppercase letters and the lowercase letters; HexChar_TO_NIBBLE and c are illustrative names to compile on your target. Call it with the indexed characters as they come: HexChar_TO_NIBBLE(MyStr[Index * 2]). Test each return for 16#FF before combining, because a marker pushed through SHL becomes a plausible byte that hides the bad character.
Example: 'AFDA0909090909' to 7 bytes and back to '444446FFFF'
The inputs: a status frame hands you the 14-character string 'AFDA0909090909', and a second job needs the reverse direction, rebuilding a string from the 5 bytes 16#44, 16#44, 16#46, 16#FF, 16#FF held in MyBytes[0..4]. The character codes come from the ASCII table: '0' to '9' are 16#30 to 16#39, and 'A' to 'F' are 16#41 to 16#46.
The rule in words: nBytes = LEN / 2, and each byte is 16 times the high-nibble value plus the low-nibble value, where a nibble value is the character code minus 16#30 for a digit and minus 16#37 for an uppercase letter.
Byte 0: 'A' is 16#41, so the high nibble is 16#41 - 16#37 = 16#0A = 10. 'F' is 16#46, so the low nibble is 16#46 - 16#37 = 16#0F = 15. The byte is 10 x 16 + 15 = 175 = 16#AF. Byte 1: 'D' gives 16#44 - 16#37 = 13 and 'A' gives 10, so 13 x 16 + 10 = 218 = 16#DA. Bytes 2 to 6 are five copies of the pair '0','9': 0 x 16 + 9 = 9 = 16#09. MyBytes[0..6] now holds 16#AF, 16#DA, 16#09, 16#09, 16#09, 16#09, 16#09, and MyBytes[7] still holds whatever it held before the run, which shows the bound stopped at 6.
The reverse loop uses the documented return shape of BYTE_TO_HEXinASCII: one byte in, a word out, the input's higher bits carried in the higher byte, so TO_BYTE keeps the low byte and SHR by 4 moves the high nibble down first.
FOR Index := 0 TO 4 DO (* 5 bytes, 10 characters *)
MyStr2[Index * 2 + 1] := TO_BYTE(BYTE_TO_HEXinASCII(MyBytes[Index]));
MyStr2[Index * 2] := TO_BYTE(BYTE_TO_HEXinASCII(SHR(MyBytes[Index], 4)));
END_FOR
Byte 16#44: the high nibble is SHR(16#44, 4) = 16#04 = 4, giving character 16#30 + 4 = 16#34 = '4'; the low nibble is 16#44 - 4 x 16 = 4, giving '4' again. Byte 16#46 gives '4' then '6'. Byte 16#FF gives 16#37 + 15 = 16#46 = 'F' twice. Five bytes at 0 TO 4 produce '444446FFFF', and LEN(MyStr2) reads 10 characters. Start MyStr2 as an empty string before the loop, because writing characters by index leaves the terminator wherever the runtime keeps it.
Decision: accept a conversion when the length is even and every forward nibble value is 16#0F or lower; a 16#FF return or an odd length means reject the frame and raise your own error flag. The threshold is one comparison per nibble.
Test the converter with these vectors
- Forward: run 'AFDA0909090909' once and confirm MyBytes[0..6] reads 16#AF, 16#DA, 16#09, 16#09, 16#09, 16#09, 16#09. If MyBytes[7] changed, the bound is one too high.
- Reverse: start from 16#44, 16#44, 16#46, 16#FF, 16#FF and confirm the string reads '444446FFFF' with LEN = 10.
- Round trip: forward-convert the rebuilt string and compare the result byte for byte against the array you started from.
- Bad input: feed 'AFDA09G9'. 'G' is 16#47, outside every valid range, so the range guard must stop it; feed a 7-character string and the odd-length check must stop it before the loop. If either input comes back with an array and no complaint, the sentinel and length checks are missing.
Put the three range comparisons around the loop whichever converter sits inside it, so the reject decision stays in your code.
Which converter to use where
Recommendation: Use the Util library pair, HEXinASCII_TO_BYTE forward and BYTE_TO_HEXinASCII back, when that library is already on your runtime; keep the local HexChar_TO_NIBBLE function with its 16#FF marker when the library is missing or the device sends lowercase hex; and derive every loop bound from LEN in both directions, rejecting odd lengths and any forward nibble that returns 16#FF.
If the rebuilt string is on its way out to a display or a drive as a command, each position still carries a character code; serial command byte encoding for display control works a complete frame through with the same codes.
Questions
How big must the byte array be for a STRING(255) input?
Each pair needs one byte, so 255 characters ask for 128 bytes in the worst case. An ARRAY[0..99] OF BYTE covers inputs up to 200 characters. Either raise the array to ARRAY[0..127] OF BYTE or reject any string with LEN above 200 before the loop; the guard is one comparison between nBytes and the array's last index.
What does the converter do with a character like 'G'?
'G' is code 16#47, outside 16#30 to 16#39, 16#41 to 16#46 and 16#61 to 16#66, so the local nibble function returns the 16#FF marker. A genuine nibble tops out at 16#0F, so in the forward direction a 16#FF return can only be that marker: reject the record. In the reverse direction 16#FF is ordinary data and converts to the two characters 'FF'.
Can the old ASCIIstring_TO_BYTE function stay in the project?
Keep it for the single-pair calls where you already hold two one-character strings, such as ASCIIstring_TO_BYTE('A','F'), which returns 16#AF. The loop cannot use it, because string indexing hands over BYTE codes. To serve both callers with one function, change both inputs from STRING(1) to BYTE and replace the sixteen comparisons with the three ranged subtractions shown above.
Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.