Skip to main content

Ring Light and Camera Lighting Setup in OpenPnP

A ring light on a desktop pick and place machine is not something you switch on and forget. OpenPnP treats the LED strip as an actuator and fires it inside the capture event: on before the camera settles, off afterwards if you asked for that. Keeping the light under software control is what makes the 1 mm dot in the middle of the datum board read at the same gray level on the last capture of a job as on the first.

Two cameras means two strips, and on most builds one G-code command drives both. The wiring, the firmware parameters and the four switches that decide when each strip fires are documented in three separate places: an Opulo test page, the OpenPnP wiki, and the Java that does the switching. Putting them side by side is useful, because the faults people run into are usually mismatches between those three.

What the strips accept​

The ring lights on this platform are NeoPixel strips, so the command is Marlin's M150. Opulo's build check gives the two lines used to test them on a fresh machine:

M150 P255 R255 U255 B255 ;both ring lights on, full brightness, white
M150 P0 ;both ring lights off

That page also states the lights come up together on the one command, with R, U and B setting colour and P setting brightness. On a Vertex 4 the same two lines are the quickest way to prove the strips, the cable and the 24 V rail all work before you open OpenPnP at all.

Marlin accepts more terms than Opulo uses in that test. From the M150 reference:

TermRangeReference note
R, U, B0 to 255red, green, blue components
W0 to 255white, needs RGBW_LED or NEOPIXEL_LED compiled in
P0 to 255brightness, needs NEOPIXEL_LED
I0 to pixel count - 1single pixel index, added in 2.0.6
S0 or 1strip index, added in 2.0.6.1, needs NEOPIXEL2_SEPARATE
Kflagleave the values you did not name unchanged

S is the interesting one. Our own FAQ lists M150 P 255 S 0 as the top strip and M150 P 255 S 1 as the bottom one, which only works if the firmware was built with NEOPIXEL2_SEPARATE and the two rings are wired as two strips. Send S 0 and S 1 in turn on your machine and watch. If both lines light the same ring, your build drives both strips from one output, and the shared-actuator route further down is the one that applies to you.

Where the actuator lives​

Opulo's ring light guide puts the actuator here:

Machine Setup > Drivers > GcodeDriver > Gcode tab
Head Mountable: Actuator [No Head] LED
Setting: ACTUATE_BOOLEAN_COMMAND
Value: {True:M150 P255 R255 U255 B255}{False:M150 P0}

Everything in that command string is editable, and this is where brightness is set for real work. Opulo recommends full brightness with the camera exposure kept low, on the grounds that a dim strip forces an exposure high enough to let whatever else is lighting the room into the frame. Some owners report better repeatability from a lower P value, so treat full brightness as the starting point rather than a law.

The camera then has to be told which actuator is its light. Under the camera's own branch in Machine Setup there is a Light panel with a Light Actuator dropdown. Head-mounted actuators are the only ones offered for a head-mounted camera until you tick Allow Machine Actuators?, which exists for the case where a machine-level actuator is doing the switching.

Two things go wrong at this step. A camera with no actuator assigned captures with whatever the room happens to be doing, which is how a fiducial that resolves in the morning stops resolving at dusk. And an actuator whose Default ON and Default OFF values are missing or identical stops the capture with this message:

Actuator <name> has Default ON/OFF values not properly configured.

That string comes from OpenPnP's actuator base class, and it is thrown the moment a capture tries to switch the light. A {False:...} half that is empty or identical to the {True:...} half will produce it.

The four switches​

Four checkboxes decide when the strip fires, and the shipped defaults are not the four you would guess. These are the defaults in the source, with the labels exactly as OpenPnP spells them:

SwitchShips asWhat it does
Before Capture?enabledlight on before the camera captures for vision, ahead of settling
User Camera Action?enabledlight on when you move or use the camera yourself
After Capture?disabledlight off once the capture finishes
Anti-Glare??disabledthis light off before any opposite-looking camera captures

The one that surprises people is After Capture?. It ships disabled, so the strip stays lit after every vision pass. That is deliberate for a machine running continuous jobs, and it is also why the head and the board under it stay warm and why a frame taken later can catch light you did not expect. Tick it and the strip goes dark between captures.

The order OpenPnP switches the camera light around one capture, and the four Light switches with their shipped defaults

The order the sequence runs in is worth knowing, because it explains a fault that looks like a hardware problem. OpenPnP first walks every other camera on the machine, finds the ones marked Anti-Glare that are looking the opposite way, and switches their light off. Only then does it switch this camera's light on. The decision is made on the Looking field, not on camera names, so a head-mounted down-looking camera and a machine-mounted up-looking camera are the pair that interfere: bottom vision lit at full brightness is exactly what the top camera should not see reflected off the board.

After the light goes on comes settling, and then the capture. The sequence repeats for every look, which on a job means hundreds of times.

Light levels per pipeline​

The camera's light settings are the default. The ImageCapture stage at the head of each vision pipeline can override the level for that pipeline alone. It ships with defaultLight enabled, which means the capture uses the actuator's Default ON value like every other camera use. Switch defaultLight off and a light property appears on the stage, and its shape follows the actuator's Value Type: a checkbox for an actuator that only switches on and off, a number box for an intensity, or a profile selector for a multi-channel light.

That is the clean way to run one part class at lower brightness without touching what the rest of the machine does. A shiny part that clips under the full strip, or a MaskHSV pipeline keyed to a coloured nozzle tip, both belong on a per-pipeline value rather than on a global one. The stage also keeps the light on across the frames it averages when its Count is above 1, so raising Count does not multiply light switching.

Two strips on one output​

If the two rings share a controller output, two actuators pointing at it will fight. OpenPnP's light actuation is cached: it compares the requested value against the last value it actuated and skips the command when they match. Two actuators on one output each keep their own idea of the strip's state, and the strip ends up dark while a camera believes it is lit.

The wiki's recipe for that wiring: delete the LIGHT_TOP actuator, assign the LIGHT_BOTTOM actuator to the top camera as well, tick Allow Machine Actuators? so a machine actuator can be picked for a head camera, and untick Anti-Glare? on both cameras. One actuator, one state.

Testing, and the values to start from​

The fastest test needs no job. In Machine Controls the Actuators panel shows a button per actuator, so clicking LED should light the strip. Once an actuator is assigned to a camera, the camera view also grows a small light symbol in the upper right corner that toggles it. Vertex 4 owners who would rather send raw G-code can use the PikkoBot Debug Tool, which talks to the board over USB serial and can toggle the lights without OpenPnP running, which is useful when you are trying to work out whether a strip is dead or the configuration is.

Our own homing and fiducial checklist starts with the light, because a fiducial that is not lit is the second most common reason the dot is not found. If you are setting a machine up from scratch and the top camera frame is black, the light being off is worth ruling out before you touch exposure.

Starting values for the two cameras come from Opulo's camera connection step: top camera exposure around 100 pointed at the datum board, bottom camera exposure around 80 with a nozzle tip in view, white balance 4000, format 1920x1080 at 30 FPS MJPG. The datum board is the reference target for the top camera for the same reason it is used for the homing fiducial, and it is the surface the light has to make readable.

Colour cast lands on MaskHSV​

Cheap LEDs are not neutral, and the OpenPnP wiki is blunt about where that hurts. Camera white balance can remove a cast, but a large part of the cast usually comes from the lighting itself, which is why the wiki points at colour rendering index and says replacing the strips with higher CRI ones is the better fix. On a machine where the strip is what it is, OpenPnP's own white balance function is the fallback.

The pipelines that care are the ones keying on colour rather than brightness: MaskHSV on bottom vision with green Juki nozzle tips, BlindsFeeder vision, and PushPullFeeder vision. Turn off Auto on white balance, brightness, contrast and gamma in the device settings, set the values to the defaults, and keep Auto off on exposure too. Then run one of the white balance buttons against a scene with brushed metal, white paper and shadow in it. The auto buttons compute once and then hold, so the balance stays put for repeatable colour work. They never raise overall image brightness, which is the exposure slider's job.

If the part and the background sit at the same brightness after all of that, no colour balance fixes it. A diffuse light from the side does more than any slider, which is the same conclusion the bottom camera exposure page reaches from the other end. Both cameras share the strip, the exposure and the room, and the bottom camera page covers the focus and position side of the same setup.