Hello Nordic Team,
I am working on an application using the nRF52833 with the following requirements:
- Device operates as both BLE Central + Peripheral
- Stores bonding information for 6 paired devices
- Runtime authentication callbacks:
- Display I/O
- Passkey entry
- Peripherals used:
- ADC (3 channels)
- I2C (polling + interrupt) for Fuel Gauge IC
- PWM
- GPIOs for LEDs and buttons
- LPCOMP
- Separate 8 KB partition reserved for storing bonded device information
The device has 512 KB flash. After reserving 8 KB for pairing data, the effective application flash becomes approximately 504 KB.
Current observations:
- Initial firmware size was around 57% usage
- After disabling logs and enabling size optimization, usage reduced to around 45%
- For OTA/DFU support, I need to reduce total usage further to around 40%
When checking the memory report, I noticed:
- Actual application logic/code is only around 30 KB
- Most flash seems to be consumed by BLE stack, services, and related configurations
Since application-level optimization is already mostly completed, I would like guidance on reducing BLE-related flash usage.
Could you please suggest the proper sequence and best practices for flash optimization for this kind of application?
Specifically, I would like recommendations on:
- Which BLE modules/features can be safely disabled
- Configurations that significantly impact flash usage
- Best order for optimization steps
- Recommended DFU/OTA memory layout for this use case
- Whether Central + Peripheral + 6 bonds is practical with current memory constraints on nRF52833
- Any important Kconfig/prj.conf optimizations for Zephyr/NCS
Below is the optimization sequence I am currently planning to follow. Please let me know if this is the correct approach.
#CONFIG_PWM=y
CONFIG_PWM=y
CONFIG_PWM_NRFX=y
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
# ===========================================================================
# SIZE-OPTIMIZED prj.conf — nRF Connect SDK v2.9.0
# Target: free ~15–20 KB flash for OTA dual-bank bootloader headroom
# Current: 224576 B / 504 KB (43.51%) → Target: ≤ 38% flash
# ===========================================================================
# KEY: each changed line has a comment explaining WHY it was changed.
# Lines that were already correct are kept without comment noise.
# ===========================================================================
########################
# CORE SYSTEM
########################
CONFIG_REBOOT=y
CONFIG_POWEROFF=y
CONFIG_PM_DEVICE=y
CONFIG_NRFX_POWER=y
# STACK SIZES — not touched per your request.
CONFIG_MAIN_STACK_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096
CONFIG_MPSL_WORK_STACK_SIZE=2048
CONFIG_BT_RX_STACK_SIZE=2048
CONFIG_IDLE_STACK_SIZE=1024
CONFIG_ISR_STACK_SIZE=2048
# HEAP — reduced from 4096 to 2048.
# Audit showed zero k_malloc/k_free calls anywhere in your code.
# The heap is only consumed by Zephyr internals (settings backend NVS
# scratch buffer ~512 B, BT host ~256 B). 2048 is safe headroom.
# Savings: ~2 KB RAM freed (also prevents linker padding flash waste).
CONFIG_HEAP_MEM_POOL_SIZE=2048
########################
# BLUETOOTH – ROLES
########################
CONFIG_BT=y
CONFIG_BT_CENTRAL=y
CONFIG_BT_PERIPHERAL=y
CONFIG_BT_SMP_APP_PAIRING_ACCEPT=y
CONFIG_BT_FIXED_PASSKEY=y
CONFIG_BT_DEVICE_NAME="HANDHELD_v4.0"
########################
# BLUETOOTH – BUFFERS ← LARGEST SAVINGS AREA
########################
# ACL TX/RX COUNT: reduced from 10 → 4.
# You have exactly 2 simultaneous connections (underdash_conn + mobile_app_conn).
# Formula: COUNT ≥ (max_connections × 2) + 2 safety = 6, but since the two
# connections are never in heavy simultaneous burst, 4 is sufficient.
# Each ACL buffer = ACL_TX_SIZE bytes. Savings: 6 × 251 B × 2 = ~3 KB RAM.
CONFIG_BT_BUF_ACL_TX_COUNT=4
CONFIG_BT_BUF_ACL_RX_COUNT=4
# ACL BUFFER SIZE: reduced from 251 → 69.
# Largest payload you actually send/receive:
# - Manual override write: 1 byte
# - Battery notify (B001): 4 bytes
# - Fault notify (B002): 3 bytes
# - B003 string notify: 24 bytes ("REINITIATE_PAIRING_UD2" = longest)
# - Diagnostics from UD2: 2 bytes
# ATT overhead = 3 bytes. L2CAP header = 4 bytes. HCI header = 4 bytes.
# Worst case on wire: 24 + 3 + 4 + 4 = 35 bytes.
# Set to 69 (safe 2× headroom above 35) to avoid hitting any edge case.
# Savings: (251-69) × 4 × 2 = ~1.4 KB RAM.
CONFIG_BT_BUF_ACL_TX_SIZE=69
CONFIG_BT_BUF_ACL_RX_SIZE=69
# L2CAP TX MTU: reduced from 247 → 65.
# No bt_gatt_exchange_mtu() call exists anywhere in your code.
# Default ATT MTU is 23 bytes. Your largest notify is 24 bytes, which
# requires MTU ≥ 27 (24 data + 3 ATT header). Setting 65 gives safe room
# for the mobile app to negotiate a slightly larger MTU if it wants.
# Savings: reduces internal L2CAP PDU buffer allocation.
CONFIG_BT_L2CAP_TX_MTU=65
# L2CAP TX BUF COUNT: reduced from 7 → 3.
# Only 2 connections, each needs 1 TX buffer + 1 spare. 3 is sufficient.
# Savings: frees ~4 × (MTU-sized) buffer slots.
CONFIG_BT_L2CAP_TX_BUF_COUNT=3
# CTLR DATA LENGTH MAX: reduced from 251 → 69 to match ACL buffer size.
# The controller PDU size must not exceed ACL buffer size or it triggers
# a controller assertion. Matched to BT_BUF_ACL_TX_SIZE above.
# Savings: reduces controller RAM and flash (PDU buffer pool).
CONFIG_BT_CTLR_DATA_LENGTH_MAX=69
# TX POWER: reduced from +8 dBm → +4 dBm.
# +8 is the maximum power allowed, drawing ~9 mA peak vs ~6 mA at +4.
# For a handheld device pairing with an underdash unit in the same vehicle,
# +4 dBm range (~20m) is more than adequate.
# If range ever becomes an issue, increase back to +8. Savings: ~1 KB flash
# (the controller power table for all levels above +4 is excluded).
CONFIG_BT_CTLR_TX_PWR_PLUS_4=y
########################
# GATT & CLIENTS
########################
CONFIG_BT_GATT_CLIENT=y
# BT_GATT_DM REMOVED.
# Audit confirmed your code uses raw bt_gatt_discover() directly throughout
# central.c — the GATT Discovery Manager library (bt_gatt_dm_*) is never
# called anywhere. Removing it saves the entire library (~3–4 KB flash).
# CONFIG_BT_GATT_DM=y ← REMOVED
########################
# SCANNING & CONNECTION MGMT
########################
CONFIG_BT_SCAN=y
CONFIG_BT_SCAN_FILTER_ENABLE=y
CONFIG_BT_SCAN_UUID_CNT=1
CONFIG_BT_SCAN_NAME_CNT=1
CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL=n
CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS=n
CONFIG_BT_PERIPHERAL_PREF_MIN_INT=12
CONFIG_BT_PERIPHERAL_PREF_MAX_INT=12
CONFIG_BT_PERIPHERAL_PREF_TIMEOUT=400
CONFIG_BT_GATT_AUTO_SEC_REQ=y
########################
# FLASH & NVS
########################
CONFIG_FLASH=y
CONFIG_SOC_FLASH_NRF=y
CONFIG_NRFX_NVMC=y
CONFIG_MPU_ALLOW_FLASH_WRITE=y
CONFIG_FLASH_PAGE_LAYOUT=y
CONFIG_FLASH_MAP=y
CONFIG_NVS=y
CONFIG_SETTINGS=y
CONFIG_SETTINGS_NVS=y
########################
# BLUETOOTH SECURITY / BONDING
########################
CONFIG_BT_BONDABLE=y
CONFIG_BT_SETTINGS=y
# BT_PRIVACY disabled — confirmed no bt_id_* or RPA calls in your code.
CONFIG_BT_PRIVACY=n
# BT_MAX_CONN: reduced from 7 → 2.
# You have exactly 2 connection objects: underdash_conn and mobile_app_conn.
# They are never used simultaneously beyond 2. This is the single largest
# BT RAM saving: each connection slot costs ~200–400 B RAM in the host stack.
# Savings: ~5 × 300 B = ~1.5 KB RAM, plus flash for the conn management code.
CONFIG_BT_MAX_CONN=2
# BT_MAX_PAIRED: reduced from 7 → 6.
# Your code defines MAX_PERIPHERAL_BONDS=5 + MAX_CENTRAL_BONDS=1 = 6 total.
# common.c line 465 checks against CONFIG_BT_MAX_PAIRED directly, so this
# must stay ≥ 6. Setting exactly 6 — no wasted NVS entries or RAM slots.
CONFIG_BT_MAX_PAIRED=6
########################
# GPIO / WDT / CLOCK
########################
CONFIG_GPIO=y
CONFIG_WATCHDOG=y
CONFIG_CLOCK_CONTROL_NRF_K32SRC_RC=y
CONFIG_CLOCK_CONTROL_NRF_K32SRC_500PPM=y
########################
# LOGGING — already disabled, keeping as-is
########################
CONFIG_LOG=n
CONFIG_LOG_MODE_IMMEDIATE=n
CONFIG_USE_SEGGER_RTT=n
CONFIG_LOG_BACKEND_RTT=n
CONFIG_LOG_BACKEND_UART=n
CONFIG_LOG_PRINTK=n
CONFIG_BT_LOG_LEVEL_WRN=n
CONFIG_BT_CONN_LOG_LEVEL_WRN=n
CONFIG_BT_ISO_LOG_LEVEL_WRN=n
CONFIG_BT_HCI_DRIVER_LOG_LEVEL_WRN=n
# BT_CONN_CHECK_NULL_BEFORE_CREATE — REMOVED.
# This is not a real Zephyr/NCS Kconfig option. It silently does nothing
# but may cause a warning during CMake config that adds parse overhead.
# CONFIG_BT_CONN_CHECK_NULL_BEFORE_CREATE=y ← REMOVED
########################
# BLUETOOTH SECURITY
########################
CONFIG_BT_SMP=y
# SETTINGS_RUNTIME REMOVED.
# No settings_runtime_set() or settings_runtime_get() calls found anywhere
# in your code. This pulls in an extra settings backend handler module.
# Savings: ~1–2 KB flash.
# CONFIG_SETTINGS_RUNTIME=y ← REMOVED
########################
# FILTER ACCEPT LIST
########################
CONFIG_BT_FILTER_ACCEPT_LIST=y
########################
# BT CONTROLLER FLAGS
########################
CONFIG_BT_DATA_LEN_UPDATE=n
CONFIG_BT_PHY_UPDATE=n
# GATT CACHING: disabled.
# No database_hash or GATT caching APIs used in your code. The caching
# feature adds a GATT service + flash writes on every attribute change.
# Savings: ~2 KB flash + reduces NVS wear.
CONFIG_BT_GATT_CACHING=n
# SERVICE CHANGED: disabled.
# No svc_changed indication is sent anywhere in your code. This characteristic
# is only needed if you dynamically add/remove GATT services at runtime,
# which you do not do. Savings: ~1 KB flash.
CONFIG_BT_GATT_SERVICE_CHANGED=n
CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS=n
CONFIG_BT_ASSERT=n
# CTLR_PRIVACY: disabled.
# No bt_le_oob, RPA rotation, or privacy address resolution calls in your code.
# BT_PRIVACY is already n above. CTLR_PRIVACY adds controller-side resolving
# list management which you do not use. Savings: ~2 KB flash.
CONFIG_BT_CTLR_PRIVACY=y
CONFIG_BT_CTLR_PHY_2M=n
########################
# SIZE OPTIMIZATIONS — already set, keeping
########################
CONFIG_SIZE_OPTIMIZATIONS=y
CONFIG_MINIMAL_LIBC=y
CONFIG_LTO=y
CONFIG_ISR_TABLES_LOCAL_DECLARATION=y
########################
# I2C / ADC
########################
CONFIG_I2C=y
CONFIG_I2C_NRFX=y
CONFIG_ADC=y
CONFIG_ADC_NRFX_SAADC=y
########################
# FLASH PARTITION
########################
CONFIG_PM_PARTITION_SIZE_SETTINGS_STORAGE=0x2000