ProgramFetchSynchronousError, logged with error code FC, is a processor fetch fault: the download finished, the program started, and the CPU fetched an instruction from an address that holds no executable code. Fix it in this order: an empty-project baseline, a target and library match against the runtime, a clean rebuild with a reset download, then halve the call list until one POU restores the fault.
ProgramFetchSynchronousError, logged with error code FC, is a processor fetch fault. The download finished, the program started, and the CPU fetched an instruction from an address that holds no executable code. The transfer is innocent; the code image or the platform is wrong. Fix it in this order: prove the platform with an empty project, match the target settings and libraries to the runtime, rebuild everything and download after a reset, then halve the call list until one POU restores the fault. On a safety controller programmed with CODESYS 2.3, the whole sequence fits in an afternoon, about 3 hours including downloads. One rule while the fault is live: full download every time, no online change.
What does ProgramFetchSynchronousError with error code FC mean?
It is a processor exception, and the name says so. Synchronous means the fault is reported at the instruction that caused it. Program fetch means the CPU tried to read an instruction from an address that holds no valid executable code.
Application faults carry their own names and table entries, a division by zero for instance. A fetch exception comes from the processor, so the log prints it under the code FC, and the FC entry in the device manual is the one to read. Before you change anything, write down the exact log line and the runtime version string. Copy the fatal-error dialog off the operator HMI before anyone clears it. That dialog is where the floor first sees the fault, and the text on it is the evidence vendor support will ask for.
Where in the download path does the fault happen?
At the last of four hops. Your engineering PC compiles the code, the programming link copies it, the controller stores it, and the CPU fetches it. A fault that appears at first execution points at the image or the platform, never at the transfer.
| Hop | What moves | What breaks the fetch |
|---|---|---|
| Engineering PC | Compiled code, target settings | A target file or memory layout that mismatches the runtime |
| Programming link | Code image, boot project | A truncated or stale image left from an earlier download |
| Controller memory | Code image at its load address | Code placed at an address the runtime does not expect |
| CPU | First cycle after download | A corrupt pointer, an unresolved library call, an overwritten return address |
A fault on the first cycle after download points at the last row. The code that runs on that cycle is the prime suspect: variable initialization, first-scan logic, and the first call of each function block.
Does an empty project download and run without the fault?
Test it first, because the empty project splits the problem in one download. If a project with no POUs, no libraries and no task configuration runs clean, the runtime, target file and hardware are sound and the fault lives in your application code.
- Create a new project for the same target entry as the real one.
- Leave it empty: no POUs, no libraries, no task configuration.
- Download, start it, and read the error log.
Budget about 10 minutes for this test, most of it the download. If the empty project reaches RUN with a clean log, the platform is sound; go on to the rebuild and the bisection. If the empty project faults as well, the target file, the runtime or the hardware is the problem. Check the target package against the runtime, then take the version string and the log line to vendor support.
Do the target settings and libraries match the runtime?
Compare them against the package the vendor ships for v01.00.07 161116. A target support package built for a different runtime release places code and data in a memory map the controller does not use, and the first jump into that code faults on fetch.
Open the target settings on the Resources tab and confirm the entry from the vendor's target support package. Compare the memory layout and code size fields against the same page in the empty project that runs; every difference is a suspect. List each library in the Library Manager and check its version against the release notes shipped with the package; replace anything older or newer than the paired version.
Install the development system and runtime updates only from the manufacturer's own download page, and the target package only from the controller vendor's download area. The comparison is the same discipline that clears a PLC runtime device mismatch on any controller, and if a package refresh stalls at upcoming installation, clearing a stuck CODESYS package install is its own fix.
Does a clean rebuild and a reset download clear the fault?
It does when stale code is the cause, and the check is cheap. An incremental build or a boot project left from an earlier download produces a code image that mismatches its own address and symbol tables, and a rebuild from zero removes that whole class.
- Run
Project > Clean all, thenProject > Rebuild all. Fix every warning about unresolved calls or missing libraries before you go on. - Reset the controller from the Online menu so nothing from the earlier download keeps running.
- Log in and do a full download. An online change on the first download after a fault leaves whatever the fault corrupted in place.
- Start the program and read the log. If the state on the panel leaves any doubt, checking whether a CODESYS runtime is running or stopped settles it in one look.
Which POU sends the program counter to an invalid address?
Find it by halving. If the empty project runs and the rebuilt full project still faults, disable half the calls in the task, rebuild, download and read the log. The half that restores the fault holds the culprit.
| Construct | How it produces a fetch fault | The check |
|---|---|---|
| Array index out of range | A write past the array overwrites adjacent data, return addresses included | Add a CheckBounds implementation and download again |
| Pointer use, address arithmetic | An uninitialized or stale pointer is dereferenced or called | Add a CheckPointer; search the project for ADR and every dereference |
| Large local arrays, deep call chains | A stack overflow overwrites return addresses | Move large arrays to global memory and flatten the calls |
| Library call unsupported on this target | The call resolves to an invalid address | Confirm each block against the library list for your controller |
| Task configuration | A task entry points at a POU that is missing or renamed | Compare the task list against the POUs that exist after Rebuild all |
Wire the Error outputs of library blocks to variables you can watch while you bisect. The vendor's function block documentation shows an input operand outside its permissible range reported as a 1 at the Error output, so a watched error variable gives you the offending block in one cycle. The bounds and pointer checks in this table are the same ones that run down a sporadic PLC access violation later in the machine's life, so what you add here keeps paying.
How does an example bisection run on a 24 POU project?
Five downloads: a task that calls 24 POUs halves to a single suspect in five steps. 24 to 12 to 6 to 3 to 2 to 1 is five halvings, and each halving is one rebuild, one download and one log read, about 10 minutes per step.
Take this case. The empty project reaches RUN clean. The rebuilt full project faults with FC on the first cycle. Step one: comment out 12 of the 24 calls, rebuild, download. The fault is gone, so the culprit sits in the 12 you disabled. Step two: enable 6 of those 12, with the known-good 12 still out. The fault returns, so the culprit is among the 6.
Step three: enable 3 of the 6; the fault stays away, so the culprit is in the 3 left out. Step four: enable 2 of the 3; the fault returns. Step five: enable 1 of the 2; the fault stays away, and the one still disabled is the culprit, a function block that writes a recipe array with an index typed on the operator screen. A CheckBounds added to the project logs index 41 against a limit of 40 entries on the next run. Clamp the index, restore all 24 calls, do a full download, and the program reaches RUN clean.
The arithmetic scales: each download halves the suspects, so 24 calls take 5 downloads and 100 calls take 7. That is why bisection beats reading code at random on any real project.
How do you prove the fix across a power cycle?
Download, run the full sequence, save the boot project and pull the power. A fetch fault can sit in the boot-project load path where a download never tests it, and the proof is a cold start that comes back to RUN with a clean log.
- Do a full download of the corrected project and confirm RUN with the log unchanged.
- Run the machine's whole sequence, every first-call branch and every state you disabled while bisecting.
- Save the boot project the way the device manual specifies.
- Cut the power, wait about 30 seconds, restore it, and confirm the controller comes back to RUN from the boot project with no new log entry.
The controller is a safety controller: run the validation procedure the device manual defines on the modified application before the machine runs. A program that reaches RUN fixes the download fault; the safety validation is a separate step and it stays required.
Recommendation: Work every FC fetch fault in this order and stop at the first step that clears it: empty-project baseline, target and library match against the installed runtime, Clean all with Rebuild all and a reset download, then halve the call list. When the empty project faults too, treat it as a platform fault and send the vendor the runtime version, the target file and the exact log line. When the bisection turns up a bounds or pointer defect, add the check function and leave it in the project after the fix.
Questions
Why does the fault appear after the download and not during the transfer?
The download only copies the code image into the controller. The fault fires on the first fetch from an invalid address, which happens on the first cycle after the program starts. A clean transfer followed by a fatal fault is exactly the signature of a bad code image or a platform mismatch, so treat the timing itself as a clue.
Why is ProgramFetchSynchronousError missing from the controller's error table?
The printed tables list application and communication errors, each with its own name and code. A fetch exception comes from the processor and reaches the log under code FC, so the FC entry in the device manual is the citable one. Send vendor support the exact log line, the runtime version and the target file you built against.
Why does an out-of-range array index stop the CPU with a fetch fault?
The write lands past the array and overwrites whatever sits after it. When that is a return address or a function pointer, the CPU later fetches an instruction from the overwritten value, which is data where code should be, and the fetch faults. A CheckBounds implementation that logs the index and the limit names the offending write on the cycle it happens.
How many downloads does a full bisection take?
Roughly the base-2 logarithm of the number of calls, rounded up: 24 calls take 5 downloads and 100 take 7, because each download halves the suspects. Add one confirmation run after you fix the defect, so plan six rebuild-and-download cycles for a 24 POU project.
Planning information, not legal or engineering advice. Rules differ by place: check codes and permits with your local authority and your licensed trades.