System designTroubleshooting

BLE.active(True) hangs on an embedded vision board

Why a BLE.active(True) call hangs on an embedded vision board: four causes ranked by probability and the firmware-and-example update that fixes most of them.

LumaSpec Vision8 min read Share on LinkedIn

On this page 10 sections
  1. 01Isolate the blocking call in one minute
  2. 02What BLE.active(True) must do before it returns
  3. 03Cause 1: firmware and examples from different releases
  4. 04Cause 2: a build without the radio's driver
  5. 05Cause 3: a controller wedged by an earlier crash
  6. 06Cause 4: the supply rail sags during radio power-up
  7. 07Symptom to fix at a glance
  8. 08When BLE should not carry your inspection data
  9. 09Prevention checklist
  10. 10Questions

A wireless MicroPython board that freezes inside BLE.active(True) looks like broken hardware, and it usually is not. The print before the call appears, the print after never does, and Ctrl-C does nothing. That signature points at one of four causes, ranked here by how often each turns out to be true. You will isolate the blocking call in under a minute with two print markers, fix the version mismatch behind most cases by updating firmware and examples as one package, and decide whether BLE belongs in your inspection path at all.

Isolate the blocking call in one minute

Wrap the suspect call with prints and a timer before touching anything else, and run the test in the REPL rather than inside the full example:

import time
import bluetooth

ble = bluetooth.BLE()   # documented MicroPython API
t0 = 0                  # illustrative timer variable, int
t0 = time.ticks_ms()
ble.active(True)        # the call under test
print("active ok:", time.ticks_diff(time.ticks_ms(), t0), "ms")

A healthy build prints the line within a second, and the number itself typically lands between tens and a few hundred milliseconds. Treat 1,000 ms as the failure threshold and 10 s as proof the call is not coming back. If Ctrl-C cannot interrupt it, the block sits inside the C radio driver: MicroPython can only raise KeyboardInterrupt between bytecodes, so a driver that never returns also never yields. A missing helper file such as ble_advertising.py fails differently, with an ImportError at the import line before any hang, so a missing file cannot cause this symptom.

What BLE.active(True) must do before it returns

The call powers up the Bluetooth controller over the board's HCI link and waits for the controller to answer. Everything it needs lives in the firmware image: the bluetooth module and the HCI transport for the board's exact radio are compiled into the binary at build time. No script or example folder can add them afterwards; the pure-Python aioble layer runs on top of that C stack, and the MicroPython project publishes the library as part of its micropython-lib package collection. Every fix below therefore changes the image, not the script. Linux vision boards hide the same trap: when a sensor driver never probes, media0 exists with no video0 node, and no user-space setting brings it back.

Cause 1: firmware and examples from different releases

This is the most common cause. Examples change with each release, and the internal calls they make drift between releases; a current example on older firmware, or the reverse, can wedge the controller during power-up, exactly at active(True).

Two checks confirm it. Read the build banner the firmware prints at boot, and note which release the example folder came from. A banner and an example folder that name different releases are the fault, and updating only one half reproduces it.

Fix it as one package: flash the current firmware from the board vendor's own download, then re-copy the examples from that same release onto the board. Rerun the two-line test afterwards and expect the millisecond number within a second. The same pairing rule clears the camera-side version of this failure, where firmware and library updates land as a set — see camera firmware version mismatch: fix detection failures safely. If a flash goes wrong and the board stops enumerating, the DFU recovery steps for a bricked camera apply here too.

Cause 2: a build without the radio's driver

Generic and community builds often ship with the Bluetooth module compiled out, or without the HCI transport this board's radio needs. The example is then perfectly correct and still cannot run. Probe the stack directly:

import bluetooth        # fails if the module is compiled out
ble = bluetooth.BLE()   # raises an availability error if no stack is built in

If the import fails or construction raises, the image has no stack and no script will fix it. If construction succeeds but the two-line test hangs after every power-cycle, the stack is present and the transport to the radio is not answering. Either way, the fix is the board vendor's own image, built for your exact board revision.

Cause 3: a controller wedged by an earlier crash

The radio controller runs independently of the Python VM, so a crash in a previous run can leave state that survives a soft reset but not a loss of power. The pattern is a hang that appears once, after a failed run, and then outlives every Ctrl-D.

Test it: unplug USB, wait 10 s, reconnect, and rerun the two-line test. If it now passes, the stack is fine and the earlier run wedged it. Carry out the cause 1 update anyway, because a one-time wedge on an install that has drifted between releases is usually the mismatch announcing itself.

Cause 4: the supply rail sags during radio power-up

Radio init bursts current, and the port matters more than the cable. The USB limits are specification values: 100 mA before enumeration, 500 mA from a configured USB 2.0 port, 900 mA from a USB 3.0 port.

peak current = board idle current + radio burst current port margin = port current limit − peak current

Worked example: a small wireless board idling at 120 mA whose radio adds 200 mA bursts at init peaks at 120 + 200 = 320 mA. An unconfigured port leaves a deficit of 320 − 100 = 220 mA, enough to brown out the controller mid-init. A USB 3.0 port leaves a margin of 900 − 320 = 580 mA. The board currents are illustrative; the port limits are not.

Fix: power the board from a USB 3.0 port or a powered hub, take other devices off the same bus, and rerun the test. If the hang tracked the port, it disappears.

Symptom to fix at a glance

Symptom Most likely cause Fix
No second print; Ctrl-C dead; power-cycle clears it once 1, then 3 Flash current firmware and examples together, power-cycle, retest
import bluetooth fails or bluetooth.BLE() raises 2 Vendor image built for this board revision
Construction works; active(True) hangs after every power-cycle 1 or 2 Vendor image at the current release
Hangs only on an unconfigured or unpowered port 4 USB 3.0 port or powered hub

When BLE should not carry your inspection data

BLE earns its place on a vision board for beacons, set-up and small telemetry, where its usable rate of a few hundred kilobits per second is plenty. It cannot carry frames: GigE moves 1,000 Mbit/s and USB3 5,000 Mbit/s, thousands of times the payload BLE can push. Production machines put imaging on a wire — a board-level camera over GigE or USB3, driven by a vision controller sized for the port count.

Two wired board cameras from the catalog:

Board-level cameras6.3 MP board-level camera, IMX178, 1/1.8" rolling shutter, 18 fps (LS-BCM-0020)LS-BCM-0020
Sensor format
1/1.8"
Lens mount
CS-mount
$290.00View part
Board-level cameras3.1 MP USB3 board-level camera, IMX252, 1/1.8" global shutter, 120 fps (LS-BCM-0003)LS-BCM-0003
Sensor format
1/1.8"
Lens mount
C-mount
$690.00View part

The MV-CB016-10GM-M-W suits a compact station: a 1.6 MP, 1/2.9" mono sensor with a global shutter behind an M12 mount, talking GigE. Board level camera selection for embedded vision machines walks the sensor, shutter and mount choice that comes before this one.

Board-level camerasHikrobot MV-CB016-10GM-M-W 1.6 MP GigE board-level camera, IMX296, Mono, M12 lens mount, WTB GigE portMV-CB016-10GM-M-W
Sensor format
1/2.9"
Lens mount
M12
Interface
GigE
Resolution
Up to 3 MP
$559.00View partBuild a system with it

On the host side, the MV-VC2140-128G40-1T pairs 8 GB of memory and a 128 GB SSD with two USB 3 ports, three to four LAN ports and four built-in light channels, so one box drives camera, lighting and logic.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

Matching a board-level camera to its USB3 bridge before you stream is the wired-world twin of this article's rule: host and device from one matched, tested pair. Filter by interface, mount and shutter in the camera finder, or start from the camera step of the system configurator.

Build the whole station

The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.

Start with the camera

Prevention checklist

  • One release, always. Flash firmware and copy examples from the same release; a mixed pair is the top cause of this fault.
  • Re-copy examples after every update. An old example folder beside new firmware rebuilds the mismatch.
  • Two-line test at board intake. Construct bluetooth.BLE(), call active(True) with prints around it, and record the milliseconds before the board enters service.
  • 900 mA of supply on the bench. Radio init bursts exceed what an unconfigured port guarantees.
  • Power-cycle after any BLE crash. Soft reset leaves controller state behind; only removed power clears it.
  • BLE for telemetry, wire for images. Keep frames on GigE or USB3.

Run the two-line test on the bench board today. When the program moves to production hardware, give the imaging a wire, and if a wired build needs a sanity check before you order, the /contact/ page reaches us directly.

Questions

How long should BLE.active(True) take to return?

On a working build, tens to a few hundred milliseconds. Treat anything over 1,000 ms as a hang and remove power at 10 s. The call powers the radio controller and waits for it to answer, so it is short but never instant. If Ctrl-C cannot interrupt it, the block is inside the C driver, which points at the firmware build rather than at your script.

Can I add Bluetooth support with a Python library?

No. The bluetooth module and the HCI transport for the board's radio are compiled into the firmware image, and no package can add them later. The pure-Python aioble library runs on top of that C stack and needs it present. If import bluetooth fails or bluetooth.BLE() raises an availability error, the only fix is flashing an image that includes the stack for this exact board.

Why did a soft reset not clear the hang?

The radio controller and its state live outside the Python VM, so Ctrl-D restarts your script but can leave the controller wedged from an earlier crash. Remove power for about 10 s, then rerun a two-line test that constructs bluetooth.BLE() and calls active(True) with prints around it. If it passes after the power cycle, update firmware and examples together, because a one-off wedge usually starts as a version mismatch.

How do I check which firmware build the board runs?

Read the banner the firmware prints at boot; it names the build and the board. Compare that name with the release the example folder came from. Different releases on the two sides are the classic mismatch that wedges BLE.active(True). After flashing, re-copy the examples from the same release before you test, because the Python side and the C stack must agree on the internal calls.

Is BLE fast enough to stream inspection images?

No. A BLE link moves a few hundred kilobits per second of application data on a good day, while GigE carries 1,000 Mbit/s and USB3 5,000 Mbit/s. Keep BLE for beacons, set-up and small telemetry, and put frames on a wired board-level camera driven by a vision controller, which also gives deterministic timing for machine handshakes.