SDK for TESAIoT Dev Kit
API reference & tutorials (ModusToolbox)
Loading...
Searching...
No Matches
I1 — BLE bring-up and the single-RF rule
Variant
mtb-mpy and mtb-only

The BLE bring-up block in proj_cm33_ns/main.c is independent of BENTO_HAS_MPY. The MicroPython guard and bindings shown as text are mtb-mpy only.

Warning
Group prerequisite — read before anything else. The entire Bento Buddy / BLE module is compiled out of the default build: ENABLE_PAGE_BENTO_BUDDY ?= 0 (proj_cm33_ns/Makefile:64, :305), and the whole BLE block including the [boot] prints sits inside #if ENABLE_PAGE_BENTO_BUDDY (main.c:36, :325). First step: make getlibs in proj_cm33_ns (fetches btstack and the BT firmware), then rebuild with ENABLE_PAGE_BENTO_BUDDY=1. Without the libraries the build halts with the Makefile $(error) at :353-359 — that error is itself the taught observable for the missing-prerequisite state: ENABLE_PAGE_BENTO_BUDDY=1 requires btstack-integration. Run 'make getlibs' to fetch the AIROC BLE host stack. Nothing in this chapter or I2 — The NUS protocol surface is observable on a default build.

Learning goal

Bring the BLE NUS stack up in the two-layer order the firmware uses, understand why the chip-power task must own ble_nus_init, and see the single-RF exclusivity rule refuse WiFi from Python — and the one build in which that rule is skipped.

Real firmware sequence

Layer 1 — the chip-power task. WL_REG_ON is asserted after a 1.5 s settle, then 50 ms for the HCI controller to power up, then bento_buddy_request_start():

static void chip_power_then_ble_task(void *arg)
{
(void)arg;
vTaskDelay(pdMS_TO_TICKS(1500));
printf("[boot] Asserting WL_REG_ON (P%u.%u) for CYW55513...\r\n",
CYBSP_WIFI_WL_REG_ON_PORT_NUM, (unsigned)CYBSP_WIFI_WL_REG_ON_PIN);
Cy_GPIO_Write(CYBSP_WIFI_WL_REG_ON_PORT, CYBSP_WIFI_WL_REG_ON_PIN, 1U);
/* Murata 2FY datasheet: WL_REG_ON to module-ready ≈ 5 ms. Give 50 ms
* for the HCI controller firmware to finish its internal power-up. */
vTaskDelay(pdMS_TO_TICKS(50));
printf("[boot] WL_REG_ON asserted — bringing up BLE NUS stack\r\n");
extern int bento_buddy_request_start(void);
printf("[boot] bento_buddy_request_start rc=%d\r\n", rc);
vTaskDelete(NULL);
}

bento_buddy_request_start returns 0 = newly started, 1 = already running, −1 = init failed or mutex timeout (ble_nus_lazy.c:152-156). It takes a mutex with a 1000 ms timeout (:161) — FreeRTOS task only, never main() or an ISR. The boot-time owner of ble_nus_init must be this dedicated task: calling it from any other context fails with state=ERROR (main.c:326-334).

Layer 2 — the radio scheduler, initialised after the auto-start is queued so it does not race AIROC init; set_on_state is a bare assignment and must precede init, which fires the first state transition (radio_scheduler.c:319):

#if ENABLE_PAGE_BENTO_BUDDY
/* Two-layer BLE bring-up:
*
* 1. bento_buddy_auto_start_install — the legacy 3-s-delayed task
* that brings up the AIROC BLE host stack. Proven path: kept as
* the boot-time owner of ble_nus_init. Smoke tests showed that
* calling ble_nus_init from any other context (notably the
* radio_scheduler worker) fails with state=ERROR even with the
* same 3-s delay — the original task's stack/priority is what
* the AIROC HCI bring-up actually needs.
*
* 2. radio_scheduler — runtime arbiter for BLE↔Wi-Fi mode switches.
* Initialised AFTER auto_start so it doesn't race the AIROC init.
* Phase 1 only services the verbs (radio.status / radio.switch /
* wifi.set_creds) and persists creds; the actual swap-radio path
* is exercised by user action, not at boot. Saved-creds auto-Wi-Fi
* moves to Phase 2 once the persistence hooks (LFS boot_mode +
* LCD long-press) are wired. */
{
/* Replace bento_buddy_auto_start_install with our chip-power-then-BLE
* variant — the legacy task skipped the WL_REG_ON / WCM init step,
* which CYW55513 needs for BLE controller bring-up. */
install_chip_power_then_ble();
extern void nus_radio_emit_state_event(const struct radio_status_s *st);
.persist_boot_mode = NULL,
.load_boot_mode = NULL,
};
(void)radio_scheduler_init(&cfg);
}
#endif

radio_scheduler_init is legal pre-scheduler (static mutex/queue + xTaskCreate); it returns true on success or on double-init, false only when xTaskCreate fails; persist_boot_mode/load_boot_mode may be NULL (fallback RADIO_BOOT_AUTO). Its worker delays 3000 ms before servicing the queue. nus_radio_emit_state_event is used only as a function pointer — never called directly.

The CM33-side IPC receiver must be armed before the first CM55 tap; on mtb-mpy it is registered at boot in mpy_main.c:604-623 (ipc_bento_buddy_rx_init(), printing [MPY] bento_buddy IPC RX init: OK), and is also lazily self-registered inside bento_buddy_request_start (ble_nus_lazy.c:183-186) — double registration is documented and tolerated.

Stop is soft. bento_buddy_request_stop() (ble_nus_lazy.c:227-245) is void with no error signal; the AIROC stack stays resident, so a later start takes ble_nus_rearm_advertising() (:176) rather than re-init. The MicroPython bindings are modbentobuddy.c:29-44 (bento_buddy.start() raises OSError on −1; stop() returns None).

The single-RF rule. CYW55513 has one RF path. The canonical guard is the [ble_radio_scheduler_single_rf_guard] marker region at modwifi.c:54-81 (mtb-mpy), quoted here with one elision, marked:

#if defined(BENTO_HAS_BLE_NUS) && (BENTO_HAS_BLE_NUS == 1) \
&& !(defined(BENTO_HAS_DUAL_BAND) && (BENTO_HAS_DUAL_BAND == 1))
// ... elided: 14-line rationale comment, modwifi.c:56-69 — no COEX
// arbiter in this build, so WiFi while BLE advertises means RF
// contention and eventually a WHD HardFault; the DualBand variant
// (BENTO_HAS_DUAL_BAND=1) time-slices on the on-die COEX block and
// skips this guard ...
#include "../ble_nus/radio_scheduler.h"
bool wifi_permitted = (rm == RADIO_MODE_WIFI_ACTIVE
|| rm == RADIO_MODE_WIFI_FAILED /* retry path */);
if (!wifi_permitted) {
mp_raise_msg(&mp_type_OSError,
MP_ERROR_TEXT("WiFi unavailable: BLE is active. Use the "
"Desktop Buddy or call bento_buddy.stop() "
"to switch radio mode first."));
}
#endif
radio_mode_t radio_scheduler_get_mode(void)
Lock-free mode read — the canonical single-RF Wi-Fi/BLE exclusivity guard.
radio_mode_t
Definition radio_scheduler.h:34
@ RADIO_MODE_WIFI_FAILED
Definition radio_scheduler.h:41
@ RADIO_MODE_SWITCHING_TO_WIFI
Definition radio_scheduler.h:38
@ RADIO_MODE_WIFI_ACTIVE
Definition radio_scheduler.h:39

(modwifi.c:54-81 less the elided comment, shown as text because that file is not shipped in the mtb-only package. Note the #include "../ble_nus/radio_scheduler.h" at modwifi.c:70 — code that copies this guard needs it.) radio_scheduler_get_mode() is a lock-free single-enum read, callable from any context, and returns RADIO_MODE_UNKNOWN before init. The exception: under BENTO_HAS_DUAL_BAND == 1 the guard is compiled out, because the on-die COEX arbiter time-slices WiFi and BLE.

Step by step

Step 0 — Build with the flag

cd proj_cm33_ns && make getlibs
make build -j ENABLE_PAGE_BENTO_BUDDY=1

What you should observe. Without getlibs, the build stops at the $(error) quoted above. With it, three cores report complete.

Step 1 — Watch the two-layer bring-up on the console

Flash, power-cycle, open the console at 115200.

What you should observe, in this order and exactly these strings:

  • [boot] Asserting WL_REG_ON (Pu.u) for CYW55513...
  • [boot] WL_REG_ON asserted — bringing up BLE NUS stack
  • [boot] bento_buddy_request_start rc=d — expect rc=0 on a cold boot.

On mtb-mpy, [MPY] bento_buddy IPC RX init: OK precedes them. The topbar BLE glyph follows the 500 ms ble_nus_get_state() poll (Chapter I2).

Step 2 — Trip the single-RF rule (mtb-mpy, non-DualBand build)

>>> import wifi
>>> wifi.connect("ssid", "pass")

What you should observe. OSError: WiFi unavailable: BLE is active. Use the Desktop Buddy or call bento_buddy.stop() to switch radio mode first. No [wifi-glue] line appears — the refusal is before app_wifi_init().

Step 3 — Switch modes and retry

>>> import bento_buddy; bento_buddy.stop()

What you should observe. The desktop receives a bento.radio.state event (Chapter I2 shows its shape) as the scheduler transitions; a subsequent wifi.connect() proceeds and [wifi-glue] retry lines appear (wifi_init.c:225-242). bento_buddy.start() afterwards takes the rearm path and returns 1 if the stack was still resident.

Traps

Warning
bento_buddy_auto_start_install is superseded. main.c:344-347 replaced it because "the legacy task skipped the WL_REG_ON / WCM init step"; it has no caller anywhere. Use install_chip_power_then_ble() as the model.
radio_scheduler_set_on_state after radio_scheduler_init misses the first transition.
bento_buddy_request_stop swallows its mutex timeout silently. Confirm with ble_nus_get_state(), not with the return.
Never assume DualBand. On the default AI-Kit Bento Buddy build the rule is enforced; only BENTO_HAS_DUAL_BAND==1 images may run both radios.

Variant box

mtb-mpy mtb-only
Bring-up block main.c:325-357, identical identical
Start/stop from user code bento_buddy.start()/stop() Your own task calling bento_buddy_request_start()/stop(); CM55 page buttons over IPC
Single-RF refusal Python OSError No Python path; the C app_wifi_* overrides in wifi_init.c are not guarded by the scheduler — your own code must consult radio_scheduler_get_mode()
Console [MPY] RX-init line + [boot] lines [boot] lines + [HB]