BLE
On the wireless Core, core_ble is a peripheral-mode Bluetooth Low Energy API: advertise a name, define a GATT server with read / write / notify characteristics, and react to connections — all in a few C calls over the ST radio stack.
Overview
BLE_ENABLED=1 and the HSE clock. The C API is Tier 1 (handle-based, C-callback driven). You do not have to write it by hand: a project can declare its GATT contract instead and have the builder, a typed API, and the publishing generated for it.The shape of a BLE app: register a service builder before init, bring up the stack, advertise, then pump core_ble_process() from your main loop so the radio gets serviced.
#include "core.h"
int main(void) {
core_init();
core_ble_set_services(app_services); // builder, defined below
core_ble_enable_pairing(); // optional: Just Works pairing
core_ble_init();
core_ble_advertise("Core.ST.W5");
while (1) {
core_ble_process(); // must run continuously
}
}Advertising
Advertising is what makes the device discoverable. Start and stop it by name, and tune the interval and transmit power to trade discovery latency against battery:
core_ble_set_adv_interval(100, 150); // min/max ms (20–10240)
core_ble_set_tx_power(1); // 0 low (-20 dBm), 1 medium (0 dBm), 2 high (+10 dBm)
core_ble_advertise("Core.ST.W5");
// ... core_ble_stop_advertise();GATT server
A GATT server is services, each holding characteristics — the values a phone or hub reads, writes, or subscribes to. Build them in your service-builder function. UUIDs are generated from the names you give:
static core_ble_char_t led_char, count_char;
static void on_led_write(const uint8_t *data, uint16_t len, void *ctx) {
if (data[0]) core_led_on(); else core_led_off();
}
void app_services(void) {
core_ble_svc_t led = core_ble_add_service("LED Control");
led_char = core_ble_add_char(led, "LED State", CORE_BLE_RW, CORE_BLE_BOOL,
on_led_write, NULL);
core_ble_svc_t ctr = core_ble_add_service("Counter");
count_char = core_ble_add_char(ctr, "Count", CORE_BLE_READ | CORE_BLE_NOTIFY,
CORE_BLE_UINT8, NULL, NULL);
}Push a new value with core_ble_set_value; if the characteristic is notify-enabled and the central subscribed, core_ble_notify sends it:
uint8_t n = ++counter;
core_ble_set_value(count_char, &n, 1);
core_ble_notify(count_char);Declaring a contract
Writing the builder by hand is the escape hatch, not the default. A project can declare its services and characteristics in config.json and have coregen generate ble_contract.c: the builder above, a typed ble_<name>_set() per value, and a weak ble_<name>_on_write() you override to receive writes.
"ble": {
"enabled": true,
"name": "My Board",
"contract": [
{ "name": "Counter", "id": "0xFE40",
"characteristics": [
{ "name": "Count", "id": "0xFE41", "type": "uint16",
"access": ["read", "notify"],
"source": { "var": "count" } }
] }
]
}Ids are declared, never generated, so inserting a service cannot renumber the ones after it and break a client you have already shipped. Use "sig" instead of "id" for a Bluetooth SIG UUID that generic scanners can name on sight.
The source field is the part that saves the most work. Bind a characteristic to a variable and it publishes itself: assign count anywhere in your program and the new value reaches the phone, with nothing added to your loop.
int count; /* not static — the generated publisher reads it */
int main(void) {
core_init();
core_ble_init();
core_ble_advertise(BLE_DEVICE_NAME);
while (1) {
core_ble_process(); /* publishes bound values as a side effect */
count++;
core_delay_ms(100);
}
}"publish": { "hz": 1 } on the characteristic to change the rate. In hand-written code, core_ble_subscribed() answers the same question so you can gate your own publishing the same way.Leave source out and you get the setter and nothing else, which is the hand-written path above. Say "source": "code" to say you meant it, and coregen stops warning that nothing publishes the value.
Connections
Poll core_ble_connected(), or register callbacks to react the moment a central connects or drops:
static void on_connect(void *ctx) { core_led_on(); }
static void on_disconnect(void *ctx) { core_led_off(); }
core_ble_on_connect(on_connect, NULL);
core_ble_on_disconnect(on_disconnect, NULL);Pairing is optional Just Works — call core_ble_enable_pairing() before init and the phone shows a one-time pair prompt, then reconnects automatically. Central/scanner mode and bonded keys aren’t implemented yet.
Cross-architecture support
BLE is hardware-verified on the wireless Core; the other ST Cores have no radio. The Nordic (nRF54) family will bring BLE to a second architecture behind the same intent.
See the implementation status for the full matrix.
Known gaps
What the SDK itself lists as missing here, straight from the header:
DSL coverage landed: a project declares a contract, gets palette blocks to publish and receive, and can bind a characteristic to a variable so coregen generates the publisher. What is still missing is the simulator side, so a DSL program that uses BLE cannot be exercised without flashing a board.
core_ble_on_connect() and core_ble_on_disconnect() take a callback pointer rather than overriding a weak symbol, so coregen has no seam to emit a dispatcher against. Every other event in the DSL arrives through that weak-symbol pipeline. Until this is wired the only way to notice a connection from the DSL is to poll.
A `bytes` characteristic can be declared and published from C, but the DSL has no type that carries a buffer and a length, so it gets neither a publish block nor a write handler, and it cannot be bound to a variable. Scalars and strings both work.
Peripheral-only today. No scanning, no central-role connections, no GATT-client reads/writes. Tracked as its own initiative — most Bergsonne use-cases are peripheral-role (sensor advertising to a phone). Apps that need central-role drop into the WBA BLE stack.
core_ble_enable_pairing() does Just Works *legacy* pairing with bonding: characteristics require an encrypted link and the keys persist in flash NVM, so a bonded host reconnects without re-prompting. Not yet exposed: LE Secure Connections (numeric-comparison / passkey MITM protection) and directed advertising for fast reconnect to a known bonded central.
Narrower than it used to be. A contract pins its own 16-bit ids, custom (placed in the Bergsonne base UUID) or SIG-adopted, and core_ble_add_service_id() / _sig() take them verbatim, so ids never shift and deployed clients keep working. What is still missing is a fully arbitrary 128-bit UUID, which is what interop with an existing app that expects some other vendor's base requires.
The WBA radio supports LE Audio (LC3), extended advertising / 2M PHY / coded PHY, and multi-link (multiple simultaneous connections). None of that is exposed.
From the @studio unsupported notes in core_ble.h — tiles@6af026f.
API reference
void core_ble_set_services(void);int core_ble_add_services(void);void core_ble_init(void);int core_ble_advertise(const char * name);int core_ble_stop_advertise(void);void core_ble_process(void);core_ble_svc_t core_ble_add_service(const char * name);core_ble_svc_t core_ble_add_service_id(const char * name, uint16_t id);core_ble_char_t core_ble_add_char(core_ble_svc_t svc, const char * name, uint8_t access, uint8_t type, core_ble_write_cb on_write, void * ctx);core_ble_char_t core_ble_add_char_id(core_ble_svc_t svc, const char * name, uint16_t id, uint8_t access, uint8_t type, core_ble_write_cb on_write, void * ctx);core_ble_svc_t core_ble_add_service_sig(const char * name, uint16_t uuid16);core_ble_char_t core_ble_add_char_sig(core_ble_svc_t svc, const char * name, uint16_t uuid16, uint8_t access, uint8_t type, core_ble_write_cb on_write, void * ctx);int core_ble_set_value(core_ble_char_t ch, const void * data, uint16_t len);int core_ble_notify(core_ble_char_t ch);int core_ble_subscribed(core_ble_char_t ch);int core_ble_connected(void);void core_ble_on_connect(void * ctx);void core_ble_on_disconnect(void * ctx);void core_ble_set_tx_power(uint8_t level);void core_ble_set_adv_interval(uint16_t min_ms, uint16_t max_ms);void core_ble_set_conn_params(uint16_t min_ms, uint16_t max_ms, uint16_t latency, uint16_t timeout_ms);void core_ble_enable_pairing(void);Generated from core_ble.h — tiles@452b1b1.

