Running Photon Feeders From a Non-LumenPnP Host
On a LumenPnP the feeder bus arrives solved. The motherboard talks RS-485 on a serial port you never configure, the connection kit carries data and 24 V to the slots, and OpenPnP finds feeders by scanning slot addresses. Put the same feeders on a different machine and you have to supply each of those three things yourself.
Opulo documents the options on one short page and calls the result non-standard: "This is not the standard way of communicating with the Photon feeders! ... this method of talking with feeders will require a bit of debugging and trial & error." That page leaves the settings and the framing to the schematic.
The three routes, and what they have in common
Whichever route you take, the end state is the same. A Marlin host with RS-485 support speaks M485 on one serial port. A 24 V pair sits on the rail next to the data lines. Each slot answers with the address stored in its own 1-Wire EEPROM.
The routes Opulo lists:
- A LumenPnP controller board plus a slot harness kit. The board is the intended host, so the protocol path is the stock one.
- LumenPnP emulation: an STM32F407 development board, an RS-485 transceiver, Marlin loaded onto the dev board, and your own 24 V bus for the feeders. Opulo's note here is that "aside from setting up a second serial port for communicating with feeders, all other configuration should be the same."
- A USB-to-RS-485 dongle. Opulo says direct RS-485 communication "has been done for debugging, but not for regular communication during a job," and it needs the reply regex in OpenPnP changed.
The dongle route is the one Opulo flags as debugging-only, because nothing on the bus side speaks the Photon framing in that setup: you write raw bytes and parse raw bytes with no host firmware in between. If the goal is a machine that runs a job, routes one and two are the practical ones.
Route one: an Opulo board on your own rail
The parts are worth checking before ordering, because the standalone page's own links have moved. The motherboard PCB kit link on that page returns a 404 as of October 2026. What the store lists instead is the LumenPnP Control Box at $249.99, which contains the motherboard and the machine's pneumatics, and the harness is sold as the Feeder Connection Kit at $155. The kit description covers four feeder slot blades numbered 1 through 50, the cable harnesses and clips, mounting hardware, a feeder programmer, and an adapter board for v2.x through v3.2.x machines, and it states it is meant for machines bought before v3.1 or for DIY scratch-built machines.
The wiring is the same as on a LumenPnP. The harness end labelled ← TO MOBO plugs into the 2x3 feeder port below the Y axis stepper driver, slots 1 to 25 go on the front rail, 26 to 50 on the back, slot 1 sits 85 mm from the front left leg, and the differently coloured slot 50 connects last in the chain. The order matters for the protocol, so do not renumber a rail to match a layout you prefer.
Nothing requires that board to move an axis. Used standalone it has to power up on 24 V, enumerate as a USB serial port, and answer M485, and OpenPnP reaches the cable through a driver exactly like it does on a full machine.
Boards ship flashed, so only update the firmware if you have a reason. Opulo's update path is STM32CubeProgrammer over DFU: hold BOOT, press RESET for ten seconds, release RESET, wait ten seconds, release BOOT, then connect with PID 0xdf11 and VID 0x0483, Read Unprotect (MCU) and TZEN Regression (MCU) unchecked, do a full chip erase, and program the .bin. A machine that does not appear as a serial port afterwards usually just needs another press of RESET.
Route two: an STM32F407 and a transceiver
This route is Marlin configuration work, and four settings decide whether the bus exists at all. They come from the LumenPnP REV4 configuration that Marlin ships:
#define MOTHERBOARD BOARD_OPULO_LUMEN_REV4withSERIAL_PORT -1andBAUDRATE 250000for the USB link to the host#define RS485_SERIAL_PORT 1, which is what definesHAS_RS485_SERIALin the first place#define M485_PROTOCOL 1, defaulted with the commentCheck your host for protocol compatibility#define RS485_BUS_BUFFER_SIZE 128
Two things surprise people porting this to a dev board. First, the RS-485 stack is not in the Marlin source tree. ini/features.ini resolves HAS_RS485_SERIAL to the PlatformIO library jnesselr/rs485@^0.0.9 and adds src/gcode/feature/rs485 and src/feature/rs485.cpp to the build, so a build that has never pulled that dependency has no M485 command to compile.
Second, the bus needs two direction pins. On the REV4 board they are RS485_TX_ENABLE_PIN PD11 and RS485_RX_ENABLE_PIN PD12 on the STM32F407VET6, driving the transceiver's enable lines. The bus library flips them per byte, which is why it can write a byte and then read it back as a check. A dev board with no equivalent pins defined compiles and then fails on the first packet.
The bus speed is not the machine baud rate. rs485_init() runs RS485_SERIAL.begin(57600), while the USB link to OpenPnP stays at 250000. Feeding commands go over the 57600 bus, so a serial console on the feeder side is opened at 57600, not at the machine's rate.
M485_PROTOCOL deserves a warning. Level 2 changes what the firmware prints around a reply, and the stock OpenPnP actuator regex expects rs485-reply: as the prefix. Set the macro to 2 and the bus keeps working while every OpenPnP read fails with No matching responses found. Leave it at 1 unless you also change the regex.
The packet, and a hand test with M485
Photon frames are <to><from><packetId><payloadLength><crc8><payload>, and the checksum is CRC8-107 computed over the first four bytes plus the payload. M485 takes the frame as a hex string argument and writes those bytes as they are: the library's writePacket() adds no checksum of its own. A frame typed by hand therefore has to carry its own CRC.
So the bring-up test is one command. To ask slot 1 for its UUID, with command GET_FEEDER_ID (0x01) and packet id 0x00:
M485 010000017001
That string is to 01, from 00, packet id 00, length 01, crc 70, payload 01. The checksum is checkable against Opulo's own worked example, where GET_FEEDER_ID to slot 2 with packet id 0x07 carries CRC 0xC0. Compute the same way for that example and you get 0xC0, which is how the value in the command above was derived rather than copied.
Three behaviours of M485 are worth knowing before you read a log:
- A valid reply prints as
rs485-reply:followed by the response frame in hex, two characters per byte. The reply to the command above is a 13 byte frame, so its hex string is 26 characters. - No reply inside 50 ms prints
rs485-reply: TIMEOUT.M485sets its own read timeout to 50000 microseconds with a comment that the packet should already be in the buffer, and OpenPnP decodes the literal stringTIMEOUTas no packet, not as an error. - If a command fails to go out, the error line is
RS485: Write failedfollowed by one ofinterrupted,buffer fullortimeout. Before writing anything,M485drains and prints whatever was already on the bus asrs485-unexpected-packet:.
Addresses on the bus are 0x00 for the host, 0xFF for broadcast, and the slot number for everything else, so a scan is one unicast frame per slot with an incrementing packet id. That is exactly what OpenPnP does when it looks for feeders, which makes a hand scan the cheapest way to separate a dead rail from a software problem.
The OpenPnP field that breaks standalone setups
Every packet on the bus goes through one OpenPnP actuator named PhotonFeederData. PhotonBus.send() calls photonActuator.read(hexString) and decodes the reply, so the actuator's driver is the whole route to your feeder controller.
OpenPnP creates that actuator for you the first time a Photon feeder needs it. It walks the machine's drivers, sets the read command M485 {Value} and the regex rs485-reply: (?<Value>.*) on the first GcodeDriver it finds, and stops looking. The actuator it creates has no driver saved. AbstractActuator.getDriver() then falls back to the first driver in the machine's driver list.
On a LumenPnP that is harmless, because there is one driver. On a standalone machine with your machine controller as driver one and the feeder controller as driver two, the read lands on the machine controller and OpenPnP reports:
Actuator "PhotonFeederData" read error: Driver configuration is missing ACTUATOR_READ_COMMAND or ACTUATOR_READ_REGEX.
That message reads like a broken bus and is not one. Check the actuator's driver in the machine configuration and save it against the feeder driver. OpenPnP's own migration comment notes that spreading an actuator's commands across two drivers is no longer supported, so keep both commands on the feeder driver.
When the read does reach the right driver and the reply is not a usable packet, the failure is quieter. A length mismatch, a CRC mismatch and a different packet id all decode to nothing, so the feeder simply does not appear. The feeder-level errors for that stage are tabulated on the feeder compatibility page, which is also where the status codes are mapped.
Wiring checks before software checks
The rail carries power and data on the same ribbon, so measure 24 V at a slot before you conclude anything about the protocol. A feeder with no indicator light at all has no reason to answer a scan.
Two more checks that cost less than a firmware rebuild:
- Slot 50 last. Opulo's harness instructions are explicit that the different-coloured slot must be connected last in the chain because that is what lets the feeders communicate. A rail assembled out of order produces slots that answer inconsistently.
- Read-back failures. Every byte the library writes is read back and compared against what was sent. A byte that comes back different, or does not come back, ends as
RS485: Write failed interrupted. Treat that line as a wiring or transceiver direction problem, and look at the A/B pair and the two enable pins rather than at OpenPnP.
Slots built by hand need their addresses written once each. OpenPnP ships the wizard for it under the Photon global settings, with the panel text "If you've built your own slots and need to program them, use this wizard." The wizard asks you to insert a feeder into the physical slot matching the number on screen, programs PROGRAM_FEEDER_FLOOR (0xC2) for that feeder, and increments the number for the next slot. It also raises the max feeder address property to match, which is the property that has to be raised if your rail is longer than 50 slots.

When the machine is the shorter route
Both controller routes end in the same bring-up work described above, and the parts list for the first one comes to $404.99 today (Control Box $249.99 plus Feeder Connection Kit $155) before the 24 V supply and the OpenPnP setup.
A Vertex 4 is $1,499 and arrives with a Marlin mainboard on 24 V, 50 slots, and a rail that takes the same Photon feeders, so the bus, the power and the slot addressing are already there. PikkoBot sells the same Photon feeder as a powered four-pack at $299 for 8 mm and 12 mm, $319 for 16 mm and $329 for 24 mm, which drops onto either rail.
If the standalone host is the point of the project, the sections above are the whole job. If the point is feeding tape, the difference between $404.99 in parts and a machine that answers a bus scan on first power-up is worth pricing in before ordering.
Related pages
- Feeder compatibility checks: firmware, host and slot checks before buying a feeder
- Photon feeder firmware recovery: what to do when a feeder stops answering
- OpenPnP feeder types: the three layers between a job and a tape reel
- Feeder overview: buttons, drive modes and the 50-slot budget
- Installing OpenPnP: the host side of the same chain