Profile Real BLE Battery Drain on the LP-EM-CC2340R5

The CC2340R5 datasheet quotes 150 nA in standby. Your beacon, advertising once a second with a 9-byte payload, will pull closer to 8 µA average. That’s a 50x gap, and it’s the difference between a 10-year coin cell and a 3-year one.
Datasheet currents are floors, measured with peripherals off, GPIOs configured ideally, and no real workload running. Your firmware is none of those things.
This is a hands-on workflow for measuring what your beacon actually draws on the LP-EM-CC2340R5 using EnergyTrace++, plus 5 optimizations that go past what TI’s SWRA478 covers. Scope is non-connectable advertising only (sensor beacon mode).
Disclaimer: Numbers in this article are directionally accurate from our bench setup. Your silicon revision, SDK version, ambient temperature, and supply voltage will shift the absolute values. Always verify on your hardware before committing to a battery budget.
Hardware and Tooling Setup
You need:
- LP-EM-CC2340R5 evaluation module
- LP-XDS110ET debug probe (or the BoosterPack adapter board, MMB0 / LP-EM-ADAPTER-EVM)
- Code Composer Studio Theia or CCS 12.x
- SimpleLink Low Power F3 SDK 8.40 or newer
The LP-EM is a target-only module. It doesn’t carry its own debugger, so you wire it to the XDS110 over Serial Wire Debug.
Jumper config on the adapter:
| Signal | Jumper | State |
|---|---|---|
| 3V3 | JP1 | Closed (through EnergyTrace shunt) |
| GND | JP2 | Closed |
| RXD / TXD | JP3, JP4 | Closed (optional, for UART logs) |
| RESET | JP5 | Closed |
| TCK / TMS | JP6, JP7 | Closed |
| 5V isolation | JP8 | Open |
The 5V isolation jumper is the one that catches people. Leave it closed and the XDS110 back-powers the target through a path that bypasses the EnergyTrace shunt. Your readings will look fine and be wrong.
Measurement path:
[XDS110] --SBW--> [LP-EM-CC2340R5]
| |
+--EnergyTrace++-----+
(shunt on 3V3 rail)EnergyTrace++ (the double-plus matters) does fine-grained current and program-counter correlation. Plain EnergyTrace gives you average current only. You want the ++ mode, and it requires an active debug session, not just the CCS power profile view.
The Reference Workload: A Minimal Sensor Beacon
Strip your project to one job: advertise a manufacturer-specific payload once a second, non-connectable, non-scannable.
// Manufacturer-specific beacon payload
static uint8_t advData[] = {
0x02, GAP_ADTYPE_FLAGS, GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,
0x09, GAP_ADTYPE_MANUFACTURER_SPECIFIC,
0xFF, 0xFF, // Company ID (test)
0x01, // Sensor type
0x00, 0x00, // Temp placeholder
0x00, 0x00 // Battery mV placeholder
};
GapAdv_params_t advParams = GAPADV_PARAMS_LEGACY_SCANN_NONCONN;
advParams.primIntMin = 1600; // 1000 ms (units: 0.625 ms)
advParams.primIntMax = 1600;
advParams.primPhy = GAP_ADV_PRIM_PHY_1_MBPS;Why each field matters for power:
GAPADV_PARAMS_LEGACY_SCANN_NONCONN: no scan response, no connection handling. Smallest possible event.primIntMin/Max == 1600: 1 second between events. Linear lever on average current.primPhy = 1 Mbps: shortest on-air time per byte for a beacon-sized payload. Coded PHY makes this longer, not shorter (more on that below).
Call GapAdv_enable(advHandle, GAP_ADV_ENABLE_OPTIONS_USE_MAX, 0) once after init. Don’t poll a sensor every event. Gate it:
if ((adv_event_count++ % 10) == 0) {
read_temperature(); // every 10th event = 10 s cadence
}Sensor reads are usually cheaper than radio events, but waking the I2C bus on every advertising event still adds up.
If you’re sending these payloads through Hubble’s network, the BLE advertising packet format used by the Hubble Device SDK is worth a look before you finalize your byte layout.
Capturing the Trace in EnergyTrace++
- Plug in the LP-EM through the XDS110, open your project in CCS, start a debug session.
- Window > Show View > EnergyTrace. Select EnergyTrace++ Mode (not the basic mode).
- Hit Run. Let it capture for at least 30 seconds. You want 25 to 30 advertising events to average over.
- Stop and inspect.
What you should see:
Current (mA)
8 | _ _ _
6 | | | | | | | <- TX bursts (3 channels)
4 | | | | | | |
2 | | | | | | |
0.001|__| |__| |__| |__ <- standby between events
+------------------> time
^1s^ ^1s^Each advertising event fires 3 bursts, on channels 37, 38, and 39, all within roughly 2 to 4 ms. Between events you should drop into standby, somewhere between 1 and 5 µA depending on what else you’ve left running.
Pull these 4 numbers from the EnergyTrace UI:
- Peak current during TX (typically 6 to 8 mA at 0 dBm)
- Event duration (start of channel 37 to end of channel 39)
- Average current over the whole capture
- Standby current between events
Sanity check:
I_avg ≈ (Q_event × f_adv) + I_standbyWhere Q_event is the integrated charge per advertising event (in µC), and f_adv is the advertising frequency in Hz. If your back-of-envelope number is more than 20% off the measured average, something is wrong. Either your standby isn’t really standby, or you’re missing events in the capture window.
Five Optimizations Beyond the App Notes
TI’s SWRA478 covers the basics. These 5 are CC2340R5-specific and came out of actual bench measurements on a baseline beacon pulling 14.2 µA average at 1 s interval, 1 Mbps, 0 dBm.
1. Trim the advertising payload. Every byte adds roughly 8 µs of TX time per channel, times 3 channels, times your advertising frequency. We dropped from 31 bytes to 14 bytes by removing unused manufacturer fields and saw a 17% cut.
2. Disable the secondary advertising set. Recent SDK versions create a secondary set on legacy PHY by default in some examples. Check every GapAdv_create() call. We had one running at 100 ms interval that nobody asked for. Removing it cut another 20%.
3. Audit GPIO state before sleep. When we walked the pin map after a board bringup, one unconfigured input was leaking a steady 4 µA, enough on its own to wreck the budget. Floating inputs leak, so configure every unused pin:
GPIO_setConfig(CONFIG_GPIO_UNUSED_0, GPIO_CFG_IN_PD);
GPIO_setConfig(CONFIG_GPIO_UNUSED_1, GPIO_CFG_IN_PD);
// repeat for every pin not driven by application
Pulldown or pullup, just don’t leave it floating.
4. Drop TX power if your link budget allows.
HCI_EXT_SetTxPowerCmd(HCI_EXT_TX_POWER_MINUS_20_DBM);Peak TX current drops from ~7.5 mA to ~3.5 mA at -20 dBm. Average impact is smaller than you’d think (TX is only 2 to 4 ms out of every 1000 ms), but it’s free if range is fine. Ours dropped 6%.
5. Skip the Coded PHY for short beacons. Coded PHY (S=8) gives you sensitivity, but at 8x the on-air time per bit. For a 14-byte payload to a fixed-position gateway 5 m away, you’re paying for range you don’t need. 1 Mbps is shorter and cheaper.
Cumulative results from the bench:
| Optimization | Before (µA avg) | After (µA avg) | Δ |
|---------------------|-----------------|----------------|-------|
| Baseline | 14.2 | - | - |
| Trim payload | 14.2 | 11.8 | -17% |
| Disable 2nd adv set | 11.8 | 9.4 | -20% |
| GPIO pulldowns | 9.4 | 8.1 | -14% |
| TX power -20 dBm | 8.1 | 7.6 | -6% |Caveat on each: payload trim assumes you control the receiver. Disabling the second adv set assumes nothing in your stack depends on it. GPIO pulldowns assume nothing external is driving the pin. TX power assumes link budget. PHY choice assumes range tolerance. Verify each on your hardware.
Translating µA to Battery Life
Life (years) = Capacity (mAh) × 1000
÷ (I_avg (µA) × 24 × 365)Worked example: CR2032 rated 220 mAh, measured 8 µA average:
220 × 1000 / (8 × 24 × 365) = 3.14 yearsThen derate:
- Coin cell self-discharge: 1 to 2% per year
- Cold temperature capacity loss: up to 30% below 0°C
- High pulse currents (your 7.5 mA TX bursts): voltage sag, effective capacity drops another 10 to 15% on a CR2032
Realistic: 2.5 to 2.8 years for that 8 µA workload on a CR2032 at room temp. Not 10.
Dividing capacity by standby current alone gives you “battery life if the radio never turns on,” a number that exists only in datasheets.
Run the Workflow on Your Bench
- Wire LP-EM to XDS110, open the 5V isolation jumper.
- Flash the minimal beacon. Confirm 1 s advertising on a phone scanner.
- Capture 30 s in EnergyTrace++ mode. Pull peak, event duration, average, standby.
- Walk the 5 optimizations. Re-measure after each.
- Plug the final average into the battery life formula. Derate.
Profiling a connectable peripheral is a separate exercise, where connection interval, slave latency, and supervision timeout open a different set of levers. If you’re integrating beacon traffic into a wider terrestrial network, the Hubble terrestrial device SDK reference covers the API surface you’ll build against once your power numbers are locked.
Hubble Network lets BLE devices reach the cloud directly from satellite, so the power budget you just measured is the power budget you ship with—no gateways in the loop. See how it works →