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.
API reference
void core_ble_set_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@5149237.

