After Blinky: Your First BLE Peripheral on STM32WBA

You’ve blinked an LED. Congratulations. You’ve proven you can set up a toolchain, compile code, and flash a board. But here’s the uncomfortable truth: you could have done that with an Arduino in four minutes. The reason you chose an STM32, whether you’ve articulated it yet or not, is that you want to build something real. Something wireless. Something that talks to a phone.
And right now, there’s a wall between you and that goal. You may have already hit it: you opened STM32CubeMX’s BLE configuration panel, saw 40+ parameters you didn’t understand, and quietly closed the tab. GAP, GATT, ATT, L2CAP, advertising intervals, UUIDs, service declarations. It feels like you need a degree in Bluetooth just to send a byte.
You don’t. Not today.
In this STM32WBA BLE tutorial, we’re going to build the absolute smallest useful BLE project: a peripheral that advertises a custom service and accepts a connection from your phone. No data exchange. No read/write characteristics. Just your STM32WBA announcing itself to the world, and your phone finding it. You’ll verify success with your own eyes: your device name appearing in a scanner app.
That’s it. And it’s enough to prove the toolchain works, the BLE stack runs, and this whole wireless thing is not magic.
What you need:
- A NUCLEO-WBA55CG board
- STM32CubeIDE 1.15+ (with integrated STM32CubeMX)
- The STM32CubeWBA firmware package (CubeIDE will prompt you to install it)
- A smartphone with nRF Connect installed (free, available on iOS and Android)
BLE Concepts You Actually Need Right Now
Forget the Bluetooth spec. You need exactly three concepts, and here’s the analogy that will carry you through:
Your STM32WBA is a shop on a street.
Advertising is the sign hanging outside. It broadcasts the shop name and a list of what’s sold inside. Anyone walking down the street (any phone scanning for BLE devices) can read the sign without stepping inside. Your device continuously shouts “I’m here, my name is MyBLEDev, and I offer this service,” about 10 times per second.
Connecting is when a customer walks through the door. A one-to-one link forms between the phone (the central) and your board (the peripheral). The shop stops shouting; it’s busy with a customer.
Services are the categories of what the shop offers. Today, we’re hanging a sign that says “I offer one custom service,” but the shelves inside are empty. There are no characteristics (individual data points to read or write). That’s fine. The sign alone is a working BLE peripheral.
┌──────────────┐ ┌──────────────┐
│ STM32WBA │ ))) → │ Phone │
│ (Peripheral)│ │ (Central) │
│ │ │ │
│ Advertising: │ │ Scanning... │
│ "MyBLEDev" │ │ Found it! │
│ Service: 0x1234... │ │
└──────────────┘ └──────────────┘
│ │
│◄──── Connection ──────►│
│ (no data exchange) │One more thing: services are identified by UUIDs, unique 128-bit numbers. Standard services (like Heart Rate) have short, predefined UUIDs from the Bluetooth SIG. Our custom service will use a randomly generated 128-bit UUID. CubeMX will generate one for you.
That’s all the theory you need. Let’s build.
Project Setup in STM32CubeMX
Open STM32CubeIDE. We’re creating the project from scratch.
Step 1: Create a new STM32 project.
Go to File → New → STM32 Project. In the board selector tab, search for NUCLEO-WBA55CG and select it. Click Next, give your project a name (e.g., BLE_FirstPeripheral), and click Finish. When prompted to initialize all peripherals with their default mode, click Yes. This ensures the clocks and pins are configured correctly for your specific board. This matters more than usual because BLE is picky about clock sources.
Step 2: Enable the BLE middleware.
In the CubeMX perspective (the .ioc file should open automatically), navigate to:
Pinout & Configuration → Middleware and Software Packs → STM32_WPAN
Enable BLE. STM32_WPAN is ST’s wireless protocol stack, containing the entire BLE software stack that runs on the radio coprocessor. You don’t need to understand its internals. CubeMX will generate the initialization code.
Step 3: Configure BLE settings.
With STM32_WPAN/BLE selected, you’ll see configuration tabs. Here’s what to set:
| Step | Navigation Path | What To Set |
|---|---|---|
| Enable BLE | Middleware → STM32_WPAN → BLE | Check “Enabled” |
| Set Device Name | BLE → General → Local Name | Enter MyBLEDev |
| Application | BLE → Application → BLE Application Type | Select Custom Template (or Custom P2P) |
| Add Custom Service | BLE → Custom Application → Add Service | Click to add one service |
When you add the custom service, CubeMX will auto-generate a 128-bit UUID. Give the service a name (e.g., My_Service). Don’t add any characteristics. The default advertising interval (around 100ms) is fine. This controls how often your device broadcasts its “sign.” Shorter intervals mean phones find you faster but use more power.
Step 4: Leave the clocks alone.
BLE on the STM32WBA requires a 32 MHz HSE crystal and a 32.768 kHz LSE crystal for precise radio timing. Since you selected the NUCLEO board in Step 1, CubeMX already configured these correctly. Do not manually change clock settings. Seriously.
Step 5: Generate code.
Project → Generate Code(or pressAlt+K)
CubeMX will create a substantial amount of code. Don’t panic when you see new folders appear. Let’s make sense of them.
A Map of the Generated Code (So You Don’t Get Lost)
You don’t need to read every file. You need to know where to look and what not to touch.
BLE_FirstPeripheral/
├── Core/
│ ├── Src/
│ │ └── main.c ← Your entry point (familiar from Blinky)
│ └── Inc/
├── STM32_WPAN/
│ ├── App/
│ │ ├── app_ble.c ← BLE initialization, advertising start
│ │ ├── custom_app.c ← Your custom service logic (empty for now)
│ │ └── custom_stm.c ← Service/characteristic definitions
│ └── Target/
│ └── hw_ipcc.c ← Inter-processor communication (don't touch)
└── Middlewares/ ← The BLE stack itself (don't touch)Here’s what happens when the chip boots: main() initializes the hardware (clocks, GPIOs, the usual), then initializes the BLE stack through a call chain that lands in app_ble.c. That file configures the GAP layer (advertising parameters, device name) and registers your custom service. Then it starts advertising. Your board is now broadcasting its name into the void, waiting for a phone to hear it.
The file you’ll care most about going forward is app_ble.c. When you eventually add characteristics and handle data exchange, custom_app.c becomes your workspace.
One thing that might puzzle you: the hw_ipcc.c file. The STM32WBA has a Cortex-M33 core for your application and a separate radio coprocessor that handles the actual Bluetooth RF work. These two processors talk to each other over IPCC (Inter-Processor Communication Controller). You never need to touch this. Just know it exists so you aren’t confused by the file.
Build, Flash, and Verify Your BLE Peripheral
Build it.
In STM32CubeIDE, hit Ctrl+B (or Project → Build All). The first build takes a while because the BLE middleware is substantial. You may see a few warnings from the auto-generated stack code. As long as you see 0 errors in the console, you’re good. Warnings in Middlewares/ or STM32_WPAN/ folders are from ST’s code, not yours.
Flash it.
Connect your NUCLEO-WBA55CG via USB. Hit Run → Debug (or F11), then Resume (or F8) to let it run. Alternatively, use Run → Run to flash without debugging. The onboard ST-LINK handles everything.
The fun part.
- Open nRF Connect on your phone.
- Tap Scan in the top right.
- Watch the device list populate. Within a few seconds, you should see “MyBLEDev” appear.
Take a moment. That’s your device. That name came from your firmware. Your STM32 is transmitting a radio signal that your phone just picked up.
- Tap Connect next to your device.
- The app will show a “Connected” state and list the services. You’ll see your custom service UUID (the long 128-bit one that CubeMX generated), along with a couple of default services (Generic Access and Generic Attribute, which the BLE stack adds automatically).
- Notice there’s nothing to interact with under your custom service. No characteristics, no read/write buttons. That’s expected. The shelves are empty, remember?
Connection is the finish line for this tutorial.
If Things Go Wrong
| Symptom | Likely Cause | Fix |
|---|---|---|
| Device not visible in scanner | BLE not enabled in CubeMX | Re-open the .ioc file, verify STM32_WPAN/BLE is enabled, regenerate |
| Device not visible in scanner | Wrong firmware on the board | Verify active build configuration matches your project, re-flash |
| Connection drops immediately | Clock misconfiguration | Delete custom clock changes, regenerate project with board defaults |
| Build errors in WPAN files | Firmware package version mismatch | Help → Manage Embedded Software Packages → update STM32CubeWBA |
If your device isn’t appearing and everything looks correct in CubeMX, try power-cycling the board (unplug and replug USB). Also make sure your phone’s Bluetooth is on and location services are enabled. Android requires location permission for BLE scanning (annoying, but true).
What Just Happened (And What Didn’t)
Let’s be precise about what you accomplished:
- Initialized a full BLE 5.4 stack on a Cortex-M33 microcontroller
- Configured GAP advertising parameters
- Registered a custom BLE service with a 128-bit UUID
- Started advertising, making your device discoverable
- Accepted a connection from a central device (your phone)
That’s a real BLE peripheral. It’s minimal, but it’s not a toy. This is the same initialization sequence that every BLE product goes through, from fitness trackers to industrial sensors.
What you didn’t do: expose any data. Your service has no characteristics, so there’s nothing for the phone to read, write, or subscribe to. The shop is open, the sign is up, but the shelves are empty. Filling those shelves is the next step.
This was intentionally minimal. The point was to prove, to yourself, that the toolchain works, the board works, and BLE is not an impenetrable wall. It’s a stack of software, configured through a GUI, that you can build incrementally.
What to Build Next
The natural next step: add a single read-only characteristic to your custom service. Have it return a hardcoded value, say, the number 42. Then open nRF Connect, connect, tap the characteristic, and read it. You’ll see 0x2A show up on your phone. That’s data crossing the air from your microcontroller to your hand.
From there, the path opens up. Replace the hardcoded value with a sensor reading. Add write characteristics so your phone can control the board. Enable notifications so data streams automatically. Each step builds on what you did today.
For reference, ST’s AN5928 application note covers the full BLE configuration flow for STM32WBA in detail. The STM32_WPAN user manual (UM2967) documents every function in app_ble.c and custom_stm.c. Both are dense but useful once you have the working foundation you just built.
You went from blinking an LED to running a Bluetooth radio. The wall wasn’t as high as it looked.
Hubble Network connects BLE devices to satellite networks—no gateways, no infrastructure changes. See how it works →