You are putting camera control on the controller itself: an RTOS target such as QNX 7.1 next to the PLC, and a GigE Vision camera on an Ethernet port. Two questions decide whether the port succeeds, and both have firm answers. First, what does a GigE Vision control stack need to build under the qcc cross-toolchain? Three libraries, and that list holds. Second, why does the camera connect by IP yet never appear in the device list? Almost always the discovery broadcast is leaving on the wrong interface, and the fix is a socket change, not a configuration hunt. This guide gives both answers with the arithmetic to size the link.
What the port actually needs
A GigE Vision stack does discovery, register access, GenICam feature control and GVSP streaming, and it needs exactly three ported libraries on top of a C toolchain. zlib inflates the compressed GenICam XML file the camera serves over the control channel; libxml2 parses that file into the feature and register map; glib supplies the sockets, threads and event loop. glib adds two small companions of its own: libffi, which its object layer requires, and iconv if the C library does not provide it. Nothing else — no GUI toolkit and no camera-side SDK, because every GigE Vision camera speaks the same wire protocol on UDP port 395.
That is the feasibility answer: if zlib, libxml2 and glib build and run on your target, the vision stack builds too. The scope line is the transport. This covers GigE Vision only; a USB3 Vision camera needs the host USB stack and a different transport library, which is a separate project.
Every camera in the GigE area scan cameras range works through the same port, because the feature list comes from the camera's own XML rather than from the library. A 12 MP GigE model such as the LS-CMR-0053 makes a good first target: one C-mount lens line, and frame sizes that fit the bandwidth arithmetic below.
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- 12 MP
Cross-compiling for QNX 7.1
QNX 7.1 ships GCC 8.3 as qcc, and every library in the chain builds with Meson, so the whole port is one cross-file and five builds in the right order.
- Source the SDP environment script (
qnxsdp-env.sh) so qcc, the target sysroot and the headers are on the path for every build. - Write one cross-file and use it for all five builds: under
[binaries]setc = 'qcc'; under[host_machine]setsystem = 'qnx',cpu_family = 'aarch64'or'x86_64',endian = 'little'. One file keeps the flags identical across the chain. - Build in dependency order — zlib, libffi, libxml2, glib, then the vision stack — installing each into a single target prefix such as
/system, and pointPKG_CONFIG_PATHat that prefix so each build finds the previous one. - Switch off what an RTOS target does not carry: glib's tests, docs and dbus options off; libxml2's extra bindings off. Fewer host tools invoked, fewer surprises.
- Expect a few Linux-only calls inside glib — file-descriptor paths and
/procwalks are the usual ones. Guard them behind an OS check; they sit on convenience paths, not on the protocol path.
Targets below a POSIX RTOS — a microcontroller rather than a computer — face the same cross-build discipline at the kernel level; porting ThreadX on a TMS320F28379D microcontroller shows what that smaller port looks like.
Why the device list stays empty with several NICs
The camera sits on one interface — wm3 in this example — and the host address on that interface is inside the camera's subnet. Connecting by IP works on the first try and frames stream. Call the enumeration function, though, and it returns zero devices. Ping works, streaming works, the list is empty.
Discovery is a UDP broadcast to port 395. A stack that opens one socket and sends to 255.255.255.255 hands the datagram to the routing table, and the routing table sends broadcasts out the default interface — wm1, on a target whose default route faces the plant network. The camera on wm3 never hears the request, so it never answers, and the enumeration call times out with an empty list. Connect-by-IP behaves differently because it routes to one specific address, which the table resolves onto wm3.
Rule out the single-interface case first: a GigE Vision camera firewall that lets ping through but blocks discovery, or the failure modes in the Linux GigE Vision discovery walkthrough. Several interfaces, and the routing explanation above is the one that fits.
The fix: one bound socket per interface
Make discovery deliberate instead of routed. In the socket-list constructor — the function that builds the discovery sockets — replace the single broadcast socket with this:
- Walk the interface list with
getifaddrs(orSIOCGIFCONF) and drop loopback and any interface without an address. - Open one UDP socket per interface and
bind()it to that interface's local address. - Compute the directed broadcast for each subnet — for a
/24on 192.168.10.x that is 192.168.10.255 — andsendto()the discovery datagram there, on port 395, from the matching socket. - Collect replies on the same sockets for about a second; a camera on a healthy link answers in tens of milliseconds.
Four interfaces, four sockets, four broadcasts. The reply arrives on the socket bound to the network the camera lives on, and the device list fills. This is a dozen lines in one constructor, and it belongs in the port rather than in a start-up script.
If you cannot change the stack, fall back to addressing: give the camera a persistent IP from the bench, then connect by address and skip enumeration. It works — the machine simply is not discoverable, so record the address plan on the electrical drawing.
The numbers that matter once the link is up
The 0.85 factor covers IP, UDP and GVSP headers, inter-frame gaps, and the leader and trailer packets every frame carries. On a 1 Gb/s link: 1,000,000,000 bits/s ÷ 8 = 125 MB/s of line rate, × 0.85 ≈ 106 MB/s usable for image data.
Take the 6 MP mono case at 8 bits per pixel. Frame size: 6,000,000 px × 1 B = 6 MB. Frame-rate ceiling: 106 MB/s ÷ 6 MB = 17.7, so about 17 fps. Frame period: 1000 ms ÷ 17.7 ≈ 56 ms, which sets the exposure budget — a 1 to 10 ms exposure leaves the link, not the exposure, as the limiter. Two such cameras on one port: 106 ÷ (2 × 6) = 8.8, about 8 fps each.
Packet size sets CPU load, not bandwidth. At full line rate, 1500-byte packets mean 125,000,000 ÷ 1500 ≈ 83,000 packets per second; 9000-byte jumbo packets mean 125,000,000 ÷ 9000 ≈ 14,000 — six times fewer interrupts and scheduler wake-ups for the same image data. On an RTOS that is the difference between a quiet core and a saturated one. Raise the MTU to 9000 on the NIC, the switch and the camera, and set the packet size before acquisition starts; the order is the same on QNX as on Linux.
Give the receive socket room for scheduling jitter: SO_RCVBUF at two frames, 2 × 6 MB = 12 MB, absorbs a slow control tick without dropping GVSP packets. If frames still arrive torn, the method in GigE camera buffer timeouts and socket buffer tuning and diagnosing GigE Vision stream packet loss carries across, because the counters to read are the same.
Choosing the hardware around the port
One camera, one port: any GigE controller the QNX driver binds will carry the 106 MB/s. PoE earns its card slot — 802.3af delivers 12.95 W at the camera and removes a power cable and connector at the head. Count camera ports against the card's power budget when you host several.
PoE cards from the catalog, side by side:
| Specification | MV-GE2002P | LS-FGB-0004 |
|---|---|---|
| Price | $249.00 | $359.00 |
Values as printed in each product's data sheet; open a part for its drawing and full specifications.
For port-per-camera layouts, assigning cameras to ports reliably in a multi-camera system keeps the address plan sane as the machine grows. When the arithmetic says one GigE port is too slow, a 10GigE camera with a matching card from frame grabbers raises the ceiling tenfold.
On the cable side, take screw-lock connectors for vibration, drag-chain ratings where the head moves, and stay under the 100 m segment limit of the Ethernet standard. The LS-CBL-0199 is a 3 m screw-lock GigE cable built for drag-chain duty; more lengths and connector families sit under cables.
Checks before the port ships
- The zlib, libxml2 and glib test programs run on the target — a library that builds but not runs dies at the first XML parse.
- A 30-line test program reads a camera register over UDP port 395 by address, proving driver, NIC and IP plan before the stack enters.
- Enumeration returns the camera on every interface after the socket change — run it with the plant cable fitted, not only the bench one.
- A 9000-byte ping crosses NIC, switch and camera in both directions.
- A ten-minute stream shows zero dropped or resent packets with
SO_RCVBUFat two frames. - The frame-rate arithmetic above fits the cycle time with margin — 17 fps is comfortable against a 2 s takt and impossible against 40 ms.
When the usual answer is wrong
The three-library answer assumes you can ship LGPL components. glib is LGPL 2.1, libxml2 is MIT, and zlib carries its own permissive license; dynamic linking keeps a closed application compliant. If your product policy forbids LGPL outright, the honest alternatives are a commercial GigE Vision SDK or a hand-written control layer: a GVCP register read is two datagrams of a few dozen bytes, but GVSP streaming with packet resend is weeks of work, not days.
Re-arranging routes so the broadcast leaves on the camera's interface is the wrong fix. It drags plant traffic with it and breaks at the next network change. The per-interface socket is the durable answer.
Discovery does not cross routers. If the camera is not on a shared L2 segment — behind a routed link to another cabinet — skip enumeration and connect by persistent IP; enumeration is a convenience, not a requirement for control or streaming.
And when the port itself is the bottleneck, no socket tuning will fix it. Two 6 MB cameras sharing one port cap near 8 fps each, so give each camera a port or move to 2.5GigE or 10GigE parts.
The porting checklist
- Three libraries ported: zlib, libxml2, glib, plus libffi and iconv under glib.
- One Meson cross-file, five builds, one target prefix.
- Discovery: one bound socket per interface, directed broadcast, port 395, one-second wait.
- MTU 9000 end to end, packet size set before acquisition.
-
SO_RCVBUFat two frames; acquisition thread pinned to its own core. - Persistent addresses recorded; force-IP confined to the bench.
Start the hardware side in the camera finder, wire the whole chain in the system configurator, and if the port stalls, bring the interface names and a bench capture to the contact page — the two failure signatures above cover most field cases.
Questions
Does a GigE Vision camera on QNX need a frame grabber card?
No. The camera is a network device; any Ethernet controller the RTOS driver binds will do, and control traffic is a few datagrams on UDP port 395. A purpose-built card earns its slot when you want port count or PoE: a dual-port PoE card such as the MV-GE2002P carries one camera's data and power on one cable, and a 4-port card like the LS-FGB-0004 hosts four cameras in one PCIe slot.
Why can I connect by IP but the camera never appears in the device list?
Because connect-by-IP routes to one exact address, while discovery is a broadcast that the routing table sends out the default interface. With the camera on wm3 and the default route on wm1, the datagram leaves on wm1 and nothing on wm3 hears it. The cure is one bound socket per interface, sending that subnet's directed broadcast on UDP port 395. On a single-NIC host with the same symptom, check the host firewall first, since UDP 395 is a common filter.
Is zlib really required, or is libxml2 enough?
Both are required, and zlib comes first. The camera serves its GenICam register description as a compressed XML file; zlib inflates it and libxml2 turns it into the feature and register map used for every later call. The failure signature is distinctive: discovery succeeds, then the first connect aborts while reading the register description — that is the missing zlib, not a camera problem.
How much CPU does GigE Vision streaming cost on an RTOS?
Packet rate decides it, not frame rate. At a 1 Gb/s line rate, 1500-byte packets land about 83,000 packets per second; 9000-byte jumbo packets cut that to about 14,000. Set the MTU to 9000 end to end before acquisition, size the receive buffer to two frames, and pin the acquisition thread to its own core. Watch the interrupt rate in the system monitor before and after the MTU change — the drop is immediate.
Can I run two cameras on one GigE port?
Yes, when the frame rate allows it. Usable GVSP payload on 1 Gb/s is about 106 MB/s, and two 6 MP mono cameras at 6 MB per frame give 106 ÷ 12 = 8.8, so about 8 fps each. That suits presence checks and slow takt lines. For faster cycles, give each camera its own port, or move the pair to a 2.5GigE or 10GigE camera and card.
What license does the stack carry into a shipped product?
glib is LGPL 2.1, libxml2 is MIT, and zlib carries its own permissive license. Dynamic linking keeps a closed application compliant with the LGPL, and only the libraries themselves are covered — your application object code stays closed. If policy forbids LGPL outright, the alternatives are a commercial GigE Vision SDK or a hand-written GVCP control layer, with the streaming side being the expensive part.

