Skip to content

fix: BLE advertising/TX-power/LED/button parity polish#8

Merged
jonasniesner merged 6 commits into
OpenDisplay:mainfrom
balloob:fix/ble-peripheral-polish
Jul 8, 2026
Merged

fix: BLE advertising/TX-power/LED/button parity polish#8
jonasniesner merged 6 commits into
OpenDisplay:mainfrom
balloob:fix/ble-peripheral-polish

Conversation

@balloob

@balloob balloob commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

What

A set of small parity fixes bringing the nRF54 BLE peripheral in line with the nRF52840 reference firmware (OpenDisplay-Firmware, Arduino/Bluefruit). Each fix was verified against the cited reference code. No behavioral guesses — where the reference has no producer, that is documented rather than invented.

Both CI environments (seeed-xiao-nrf54l15, seeed-xiao-nrf54lm20a) build green.

Per-fix summary

# Fix Before (nRF54) After (nRF54) Reference
1 Advertising interval floor Fixed 1000 ms — MIN=MAX=BT_GAP_ADV_SLOW_INT_MIN (opendisplay_ble.c:32-33) 160 ms floor / 1000 ms ceiling — OD_ADV_INTERVAL_MIN=256u (opendisplay_ble.c:37) MIN=256/MAX=1600 (ble_init.cpp:48-49,63)
2 MSD status bit2 connectionRequested Hard 0, noted "reserved" (opendisplay_ble.c:145-149) Bit2 sourced from a flag + public setter opendisplay_ble_set_connection_requested() (opendisplay_ble.c:157,344; opendisplay_ble.h) ((connectionRequested & 0x01) << 2) (display_service.cpp:1296)
3 Apply power_option.tx_power Parsed, never applied HCI VS Write_Tx_Power_Level for advertising (init + config reload) and per-connection (apply_tx_power, opendisplay_ble.c:366) Bluefruit.setTxPower(power_option.tx_power) (ble_init.cpp:90)
4 LED 8-level PWM brightness ramp 2-step ramp (od_flash_led) Full 7-slice software-PWM ramp, thresholds 7,1,6,2,5,3,4 (R/G) + 3,1,2 (B) (opendisplay_led.c:82) 7 delayMicroseconds(100) slices in flashLed (device_control.cpp:471-499)
5 Buttons: interrupts + pulldowns Poll only; pull-up-or-none hardwired (opendisplay_button.c:69 passed false) Both-edges GPIO interrupt via new nrf54_gpio_configure_interrupt() (ISR sets a pending flag, no I2C/BLE); polling kept as fallback; honors pulldowns mask (nrf54_gpio.c:98, opendisplay_button.c:80,86) CHANGE interrupt + debounce, honors pulldowns (device_control.cpp:589-605)
6 0x0052 deep-sleep response parity ACKs {0x00,0x52} then no-ops No response, log only (opendisplay_pipe.c:1177) nRF52840 build recognizes but sends no response (device_control.cpp:691-705)
7 Device-name chip-id width 6 hex digits %06lX (opendisplay_ble.c:102) Verified — no change. Width matches. getChipIdHex() pads to 6 hex digits (encryption.cpp:725-737)

Notes on individual fixes

Fix 2 — In the reference, connectionRequested is declared and read into the status byte but is never set anywhere (main.h:127, "Reserved for future features"). So there is no producer to port, on this branch or the reference. This PR adds the structural hook (a static flag + opendisplay_ble_set_connection_requested(bool) in the public header) so a future feature / sibling branch can drive it; bit2 stays 0 until then, matching the reference exactly.

Fix 3 — On Zephyr + SoftDevice Controller there is no stable bt_le_* runtime TX-power API, so this uses the standard HCI vendor-specific Write_Tx_Power_Level command (the approach in Zephyr's hci_pwr_ctrl sample), gated on CONFIG_BT_HCI_VS (now enabled explicitly in prj.conf). The controller clamps the requested dBm to its supported set and returns the value it actually selected, which is logged (clamp + log, no hand-rolled table). The config's tx_power byte is interpreted as a signed int8 dBm, matching the reference's direct pass-through. Note: this Zephyr version renamed bt_hci_cmd_create() to bt_hci_cmd_alloc() (opcode/len now set by bt_hci_cmd_send_sync); the code uses the new name.

Fix 6 — This composes with the separate DFU-honesty question: 0x0051 (ENTER_DFU) still falsely ACKs on nRF54; that is intentionally left for a separate change and not touched here.

Fix 7 — Verified only, no code change: both sides emit exactly 6 uppercase hex digits (the reference from DEVICEID[1] & 0xFFFFFF, nRF54 from hwinfo masked to 24 bits via %06lX). Value source differs by chip, but the width — the thing clients key device names on — matches.

Hardware testing

I have no hardware, so nothing here was runtime-tested — everything below is compile-verified only (both CI envs green). A hardware smoke test should confirm:

  • Fix 3 (TX power): the [OD] tx_power … requested=X selected=Y dBm log line appears at boot and on config reload, selected is sane for the nRF54L radio, and RSSI at a fixed distance tracks the configured value. Also confirm the per-connection apply on connect does not disturb the link.
  • Fix 5 (button interrupts): presses register with interrupts attached (not just via the polling fallback), the ISR path does no blocking work, and a pulldowns-configured input reads correctly.
  • Fix 1 (adv interval): discovery latency improves (scanner sees the tag faster) with no regression in connection stability.
  • Fix 4 (LED ramp): the 8-level brightness gradation is visually smooth and colors match the reference.

Stacking & conflicts

🤖 Generated with Claude Code

https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR

balloob and others added 6 commits July 8, 2026 01:28
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR
CMSIS 6 (framework-zephyr 3.40400 for the LM20A) gates DSP intrinsic
wrappers on __ARM_FEATURE_DSP; forcing the macro exposes __sxtb16/__sxtab16
which g++ never declares as builtins, failing every C++ TU. No code in this
tree uses DSP intrinsics, and the compiler predefines the macro itself when
the target arch actually supports DSP.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR
Restore nRF52840-reference parity in the nRF54 BLE peripheral:
- Advertising interval floor 160 ms (was fixed 1000 ms)
- MSD status bit2 connectionRequested flag + setter hook
- Apply power_option.tx_power via HCI VS Write_Tx_Power_Level (adv + conn)
- LED flash: full 8-level software-PWM brightness ramp (was 2-step)
- Buttons: both-edges GPIO interrupt + pending flag, honor pulldowns mask
- 0x0052 deep sleep: no response (match reference nRF52840 build)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012g2e8mr132vcizx92WsgiR
@jonasniesner jonasniesner merged commit 1a848ad into OpenDisplay:main Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants