Blog
FoE (File over EtherCAT) Firmware Update Explained
October 7, 2026
A protocol-level look at packet structure, opcodes, and error handling
Most explanations of File over EtherCAT (FoE) stop at “the master sends the file, the slave acknowledges each packet.” That's true, but it skips the part that actually matters when you're implementing or debugging FoE at the byte level: the exact PDU structure, the opcode set, how reads differ from writes, and what the error codes actually mean when a transfer goes wrong. This guide goes into that protocol-level detail.
FoE is one of the mailbox protocols carried inside the EtherCAT mailbox mechanism, alongside CoE, EoE, SoE, and AoE. Mailbox communication runs over the same EtherCAT frame structure as process data but is logically separate — it's used for acyclic, one-off transfers like configuration access and file transfer, rather than the cyclic process data exchange that carries real-time control values. FoE was deliberately kept simple and TFTP-like precisely because firmware transfer doesn't need to be real-time; it needs to be reliable and simple enough to implement in a small bootloader footprint.
Every FoE message is carried inside a standard EtherCAT mailbox header, followed by an FoE-specific header whose first byte identifies the opcode. The FoE-specific portion of the header looks like this:
| Bytes | Content |
|---|---|
| Byte 0 | OpCode (1 byte) |
| Byte 1 | Reserved (1 byte) |
| Bytes 2-5 | Password / Packet Number / Error Code (4 bytes, meaning depends on OpCode) |
| Bytes 6+ | File Name / Data / Error Text (variable length, depends on OpCode) |
That 4-byte field at bytes 2-5 is the part that trips people up when reading a raw capture, because it means three different things depending on which opcode is in byte 0 — a password on a Read/Write Request, a packet number on Data, and an error code on an Error message.
| OpCode | Name | Direction | Purpose |
|---|---|---|---|
| 1 | FoE Read Request (RRQ) | Master → Slave | Master requests to read (upload) a file from the slave |
| 2 | FoE Write Request (WRQ) | Master → Slave | Master requests to write (download) a file to the slave — this is the opcode used to start a firmware update |
| 3 | FoE Data (DATA) | Both directions | Carries a sequential chunk of file data, tagged with a packet number |
| 4 | FoE Acknowledgement (ACK) | Both directions | Acknowledges receipt of a Data packet or a Request, referencing the packet number being acknowledged |
| 5 | FoE Error (ERR) | Both directions | Reports a failure and aborts the transfer, carrying an error code and optional human-readable error text |
| 6 | FoE Busy (BUSY) | Slave → Master | Slave signals it needs more time before it can accept/process the next packet (e.g., while erasing flash) without aborting the session |
This is the sequence a firmware update over FoE follows, opcode by opcode:
FoE isn't only for pushing firmware down to a device — OpCode 1 (Read Request) lets a master pull a file off a slave, which is commonly used for reading back configuration files, log files, or a currently running firmware image for verification. The flow mirrors the write sequence with direction reversed:
This asymmetry — write starts with an ACK before data flows, read starts with data immediately — is a common source of interoperability bugs when a master or slave stack implementation assumes both directions behave identically.
When a transfer can't continue, either side sends an ERR (OpCode 5) with an error code in the 4-byte field and, optionally, human-readable error text in the variable-length field. The error code values are drawn from a shared range with other EtherCAT mailbox error codes; the ones specific to FoE-relevant failure conditions are the ones actually seen in practice:
| Condition | Typical Cause |
|---|---|
| Not found | Requested filename doesn't exist on the slave (relevant to Read Request) |
| Access denied | Password check failed, or the slave restricts writes to that filename in its current state |
| Disk full | Slave's flash/storage region can't hold the incoming file |
| Illegal opcode | Received an opcode the slave's FoE implementation doesn't support or expect in the current state |
| Packet number wrong | Received Data packet number doesn't match the expected next sequence number — the classic “out of sync” failure |
| Already exists | Write target conflicts with an existing file the slave won't overwrite without an explicit condition being met |
| No user | No FoE session currently open, or session ownership conflict (e.g., two masters/tools attempting FoE simultaneously) |
| Program error | Generic catch-all for a device-specific internal failure during the write (e.g., flash write failure) |
A “Packet number wrong” error is worth calling out specifically: it's the single most common FoE error seen in real interoperability testing, and it almost always traces back to one side losing track of state — a retried Data packet reusing an old sequence number, or a master and slave disagreeing about whether packet numbering restarts at 0 or 1 for a given implementation quirk.
The BUSY opcode (6) exists because flash operations — especially sector erase — can take substantially longer than a normal mailbox round-trip, and the slave needs a way to say “not ready yet, but don't abort” rather than either blocking mailbox communication entirely or timing out the session. A slave sends BUSY (optionally with an estimated wait time) in place of the expected ACK, and a well-behaved master extends its timeout accordingly rather than treating the delay as a failure.
The common implementation mistake is on the master side: a master stack hard-coded to a fixed mailbox timeout with no BUSY handling will abort a perfectly healthy transfer the moment a slave pauses to erase a flash sector, producing an intermittent failure that only shows up on slower flash parts or larger erase block sizes — exactly the kind of bug that passes testing on a fast dev board and fails in the field on production hardware with slower flash.
Simma Software builds real-time protocol stacks and flash bootloaders across CAN, LIN, UDS, J1939, CANopen, XCP, and EtherCAT, with robust FoE handling — including BUSY/timeout management and dual-bank fallback — built in. Contact us to talk through your FoE or EtherCAT bootloader implementation.