Nordic nRF Connect SDK Integration Guide
This guide walks through integrating the Hubble Network dual-stack Satellite and BLE application with the nRF Connect SDK (NCS). NCS is Nordic Semiconductor’s production-ready SDK built on top of the Zephyr RTOS, and is the recommended platform for Nordic nRF52, nRF53, and nRF54 Series devices.
By the end of this guide you will know how to:
Integrate the Hubble Satellite and Terrestrial (BLE) network stacks into an NCS application.
Obtain ephemeris data and use pass prediction to schedule satellite transmissions.
Transmit data to the Hubble satellite network from a Nordic device.
Supported NCS Version and Boards
The Hubble Device SDK currently supports NCS v3.4.0. If you require support for a different version, contact us.
Nordic nRF52, nRF53, and nRF54 Series SoCs are supported. The SDK includes board API implementations for the following development kits out of the box:
Board |
Target |
SoC Family |
|---|---|---|
nRF21540 DK |
|
nRF52 |
Nordic Thingy:53 |
|
nRF53 |
nRF54L15 DK |
|
nRF54 |
For custom boards, refer to the board bring-up section.
Prerequisites
Before starting, ensure you have the following:
A supported development kit or custom board with a PA or FEM capable of at least +20 dBm transmit output power. The Hubble satellite link budget requires this minimum output to reach the network reliably.
An antenna tuned to the Hubble satellite frequency band, connected to the RF output of the PA or FEM.
A Hubble account and API key to fetch ephemeris data for pass prediction. See Create your Hubble Account below.
ADALM-PLUTO SDR (optional, recommended for custom board bring-up): used to verify RF output at the physical layer. Available from common distributors such as DigiKey and Mouser. See the ADALM-PLUTO product page for details, and Next Steps for the verification workflow.
Create your Hubble Account
A Hubble account is required to access the Hubble Dashboard and generate an API key for fetching ephemeris data used in pass prediction.
Create an account at the Hubble Dashboard.
Once logged in, follow the Hubble Platform API documentation to authenticate and generate your API key.
Keep your API key accessible. It is used later in this guide when fetching orbital parameters for pass prediction.
SDK Setup
Install the NCS Toolchain
Follow the NCS installation guide to install the NCS toolchain and all required dependencies before proceeding.
Add Hubble Network to an Existing NCS Project
If you already have an NCS workspace, add the Hubble Device SDK as a West
module by including the following in your project’s sub-manifest or
west.yml:
manifest:
remotes:
- name: hubble
url-base: https://github.com/HubbleNetwork
projects:
- name: hubblenetwork-sdk
repo-path: hubble-device-sdk
revision: main
path: modules/lib/hubblenetwork-sdk
remote: hubble
Then run:
west update hubblenetwork-sdk
Initialize a New Workspace
To start a fresh workspace with Hubble Network as the manifest repository,
use the west-ncs.yml to import NCS:
west init -m git@github.com:HubbleNetwork/hubble-device-sdk.git \
--mf west-ncs.yml ~/hubblenetwork-workspace
cd ~/hubblenetwork-workspace
west update
Fetch the Satellite Radio Blobs
The satellite radio port for Nordic SoCs relies on prebuilt libraries distributed as West blobs. These are required for the satellite stack to build and must be fetched explicitly:
west blobs fetch hubblenetwork-sdk
The blobs are placed under modules/lib/hubblenetwork-sdk/zephyr/blobs/
and linked automatically when CONFIG_HUBBLE_SAT_NETWORK=y is set for a
supported Nordic board.
Note
If you skip this step, the build will fail with a missing library error
when CONFIG_HUBBLE_SAT_NETWORK=y is enabled.
Board Bring-Up
If you are using one of the supported development kits from the Supported NCS Version and Boards section, skip ahead to Project Configuration.
For custom boards, this section walks through implementing the satellite board API using the nRF21540 DK as a concrete example. If your board does not yet have a Zephyr board definition, follow the Zephyr board porting guide first before proceeding.
The full board API contract and integration checklist are documented in Supporting New Boards. This section focuses on the NCS/Zephyr path.
Note
nRF53 (dual-core) boards: The nRF5340 has a dedicated network core (Cortex-M33) for radio activity and an application core for application logic. The satellite and BLE radio stacks run on the network core, while your application runs on the application core. Communication between the two cores requires IPC.
When building for an nRF53 target, use sysbuild to compile both cores’
firmware in a single build invocation. For a complete dual-core project
structure with IPC wiring and application/network core split, see the
Hubble Thingy:53 reference application.
Implementing the Board API
Create a .c file for your board and implement the four functions declared
in port/zephyr/sat_board.h. The nRF21540 DK implementation at
port/zephyr/boards/nordic/nrf52/nrf21540dk.c is used as the reference
throughout.
Note
A common FEM implementation uses generic two-pin PA/LNA control, such as
a Skyworks SKY66112-11 or similar. See
port/zephyr/boards/nordic/nrf52/fem.c for a reference on how these
routines are implemented.
hubble_sat_board_init()
Called once during SDK initialization. Initialize your board hardware here: GPIOs, PA/FEM configuration, and any static power defaults:
int hubble_sat_board_init(void)
{
/* Initialize your board hardware here, e.g. PA/FEM GPIOs. */
hubble_board_fem_setup();
return 0;
}
hubble_sat_board_enable()
Called before each transmission window. Enable your board’s RF components, setting power to the right level (+20 dBm) before handing off to the SoC layer:
int hubble_sat_board_enable(void)
{
/* Enable your board hardware here */
return hubble_sat_soc_enable();
}
hubble_sat_board_disable()
Called after each transmission window. Disable your RF components and return the board to its idle state:
int hubble_sat_board_disable(void)
{
/* Disable your board hardware here */
return hubble_sat_soc_disable();
}
hubble_sat_board_packet_send()
Called by the SDK for each transmission, including retries. Enable the PA/FEM path before transmission and put it back to sleep/bypass afterwards:
int hubble_sat_board_packet_send(const struct hubble_sat_packet_frames *packet)
{
int ret;
/* Enable your PA/FEM before transmitting. */
board_fem_enable();
/*
* Optional: default power for each SoC is 0 dBm,
* however, the board may and can set the SoC radio power
* to a higher level supported by the SoC based on the gain
* of the PA/FEM to reach +20 dBm output.
* e.g. nrf_radio_txpower_set(NRF_RADIO, desired_power_level);
*/
board_fem_set_power_level(desired_power_level);
/* Call the SoC layer to handle packet transmission */
ret = hubble_sat_soc_packet_send(packet);
if (ret != 0) {
/* Handle error */
}
/* Optional: if power level was set, restore previous power level */
board_fem_set_power_level(previous_power_level);
/* Return PA/FEM to sleep/bypass after transmission. */
board_fem_sleep();
return ret;
}
Register the Board in the Build System
Your board source file, which includes the PA/FEM setup routines and the
board API implementation above, must be registered with the Zephyr build
system. This can be done from within the SDK’s port/zephyr/boards/
directory or from within your application’s own source tree as an out-of-tree
module.
Conditioned on your board’s Kconfig symbol, add the following to the relevant
CMakeLists.txt. Using the nRF21540 DK as an example:
if(CONFIG_BOARD_NRF21540DK_NRF52840)
zephyr_include_directories(
${ZEPHYR_BASE}/subsys/bluetooth/controller/ll_sw/nordic/hal/nrf5/radio/
)
zephyr_library_sources(fem.c)
zephyr_library_sources(nrf21540dk.c)
endif()
Replace CONFIG_BOARD_NRF21540DK_NRF52840 with the Kconfig symbol for your
board (CONFIG_BOARD_<YOUR_BOARD>), and list your FEM and board source
files accordingly. The prebuilt satellite radio blob for the SoC family is
linked automatically by the existing SDK SoC CMakeLists.txt.
Project Configuration
Enable the Hubble dual-stack by adding the following to your prj.conf:
CONFIG_HUBBLE_BLE_NETWORK=y
CONFIG_HUBBLE_SAT_NETWORK=y
# Set to your oscillator's PPM rating (check your crystal datasheet)
CONFIG_HUBBLE_SAT_NETWORK_DEVICE_TDR=10
CONFIG_HUBBLE_SAT_NETWORK_DEVICE_TDR sets the clock drift rate of your
oscillator in parts per million (PPM). Configuring this correctly ensures the
SDK adds the right number of retransmissions to compensate for accumulated
clock drift since the last time sync. An incorrect value reduces satellite
delivery probability. See Clock Drift Compensation for a detailed
explanation.
For the full set of available options, see Configuration Options.
Other common options for a dual-stack application:
# Bluetooth
CONFIG_BT=y
CONFIG_BT_PERIPHERAL=y
# Satellite pass windows can be 12+ hours away,
# so we need a 64-bit timeout to avoid overflow.
CONFIG_TIMEOUT_64BIT=y
# Optional: enable FPU if supported
CONFIG_FPU=y
Preparing for Satellite Transmission
The satellite stack requires three inputs before it can schedule and transmit:
Unix time: used for pass prediction and key derivation
Device location: latitude and longitude, used to compute satellite passes
Orbital parameters: satellite ephemeris data describing each satellite’s orbit
How these are provisioned is up to the application.
Time
Many approaches work: BLE provisioning from a phone, NTP over Wi-Fi, GPS, an RTC, or reading from persistent storage across reboots. See Time Management for best practices and trade-offs.
Location
If the device is deployed at a fixed location, latitude and longitude can be hard-coded directly in firmware:
struct hubble_sat_device_pos device_pos = {
.lat = 47.6,
.lon = -122.3,
};
For mobile devices, location can be obtained from an onboard GPS module if present, or provisioned at runtime from a companion app. For example, delivered over BLE from a phone/gateway that has GPS access.
Orbital Parameters
Orbital parameters describe each satellite’s orbit and are used by
hubble_sat_next_pass_get() to predict when a satellite will be
visible from the device location.
Since Hubble satellites are station-keeping, orbital parameters are stable
enough to be baked into firmware at build time. Use the
tools/orbital_params_fetch.py helper to fetch current parameters from the
Hubble API and generate a sat_params.c file ready to compile into your
application:
export HUBBLE_API_TOKEN=<your-api-token>
python tools/orbital_params_fetch.py path/to/output
See Orbital Parameters (Satellites information) for details on the generated format and how to register the array with the SDK.
Initializing the Hubble Device SDK
Once time, location, and orbital parameters are available, initialize the SDK before calling any other Hubble API:
/*
* At this point unix_time_ms, device_pos, and orb_params are assumed to be
* valid. Either baked into firmware or received via BLE provisioning.
*/
err = hubble_init(unix_time_ms, master_key);
if (err != 0) {
LOG_ERR("Failed to initialize Hubble Device SDK (err %d)", err);
return err;
}
err = hubble_sat_satellites_set(orb_params, orb_params_count);
if (err != 0) {
LOG_ERR("Failed to set orbital parameters (err %d)", err);
return err;
}
hubble_init() takes the current Unix time in milliseconds and a
pointer to the master key. hubble_sat_satellites_set() registers the
orbital parameters array with the SDK.
Warning
The key buffer MUST remain valid for the lifetime of SDK usage. The SDK stores the pointer directly and does not copy the key. Do not use a stack or temporary buffer.
Unix time must be non-zero. Passing
0inCONFIG_HUBBLE_COUNTER_SOURCE_UNIX_TIMEmode returns an error.The orbital parameters array MUST remain valid for as long as pass prediction is used. The SDK stores a pointer and does not copy the array.
The Pass Prediction Loop
With the SDK initialized, the application enters the main dual-stack loop: compute the next satellite pass, beacon over BLE while waiting, stop BLE, transmit to the satellite, then repeat.
Compute the Next Pass
/*
* Before this loop, make sure that the Bluetooth stack has been
* properly initialized by calling bt_init()
*/
for (;;) {
now_ms = hubble_time_get();
err = hubble_sat_next_pass_get(now_ms, &device_pos, &pass_info);
if (err != 0) {
LOG_ERR("Failed to get next pass (err %d)", err);
return err;
}
/* If the pass has already started, find the next one. */
if (pass_info.start <= now_ms) {
err = hubble_sat_next_pass_get(
pass_info.start + pass_info.duration,
&device_pos, &pass_info);
if (err != 0) {
LOG_ERR("Failed to get next pass (err %d)", err);
return err;
}
}
hubble_sat_next_pass_get() returns the soonest pass visible from the
device location. If pass_info.start is already in the past, a pass is in
progress, skip it and compute the next one by searching from after its end
(pass_info.start + pass_info.duration).
Beacon over BLE While Waiting
Start a timer for the pass window and begin BLE advertising while the device waits:
k_timer_start(&sat_timer, K_MSEC(pass_info.start - now_ms),
K_NO_WAIT);
/*
* Enable Bluetooth by calling bt_enable(), get the Hubble beacon
* payload via hubble_ble_advertise_get() and start BLE advertising
* with bt_le_adv_start().
*/
start_ble_advertising();
/*
* Block and wait until the pass timer fires. Common approaches are
* a semaphore, task notification, or event flag.
*/
block_until_timer_expires();
The timer callback signals the waiting thread when the pass window is overhead.
Transmit to the Satellite
Before transmitting, the BLE stack must be fully stopped to release the radio.
Call bt_le_adv_stop() to stop advertising, then bt_disable() to
disable the Bluetooth stack entirely. The satellite radio requires exclusive
access to the radio hardware:
/* Stop advertising and disable BLE to release the radio. */
bt_le_adv_stop();
bt_disable();
err = hubble_sat_packet_get(&packet, NULL, 0);
if (err != 0) {
LOG_ERR("Failed to build packet (err %d)", err);
return err;
}
/* Blocking call, retries are handled internally by the SDK */
err = hubble_sat_packet_send(&packet, HUBBLE_SAT_RELIABILITY_NORMAL);
if (err != 0) {
LOG_ERR("Failed to send packet (err %d)", err);
return err;
}
} /* end while loop, back to compute the next pass, re-enable bluetooth, and beacon */
hubble_sat_packet_send() is blocking. It returns only after the full
transmission sequence completes, including all retries. See
Power Consumption and Reliability for guidance on reliability modes and
their effect on power consumption.
Building and Flashing
Build your application with the ncs-dual-stack snippet. This snippet is
required for all NCS applications using the Hubble dual-stack. It enables
the Multiprotocol Service Layer (MPSL) for BLE and satellite radio coexistence:
west build -p -b <your_board> <your_app> -S ncs-dual-stack
west flash
Verifying the Application
Enable the Hubble Device SDK logging by adding the following to prj.conf:
CONFIG_LOG=y
CONFIG_HUBBLE_LOG_LEVEL_DBG=y
Open a serial terminal to the device at 115200 baud (e.g. nrfutil,
minicom or any serial terminal application).
Expected SDK Log Output
After a successful hubble_init() call, the SDK logs:
<inf> hubblenetwork: Hubble Device SDK initialized
At debug level, once pass prediction runs and a transmission is scheduled, you will see the retry count computed from your TDR setting and the time elapsed since the last sync:
<dbg> hubblenetwork: Time drift since last sync: 20000 ms
<dbg> hubblenetwork: Number of additional retries due TDR: 1
<dbg> hubblenetwork: Number of retries: 9 - interval: 20 seconds
After hubble_sat_packet_send() completes the full transmission
sequence:
<inf> hubblenetwork: Hubble Satellite packet sent
If this line appears without any preceding <err> or <wrn> from the
hubblenetwork module, the device has successfully transmitted to the
satellite network. For RF-level verification using an SDR, see
Next Steps.
Troubleshooting
Build fails with missing library
Symptom: Linker error referencing a missing hubblenetwork or satellite
radio library.
Cause: West blobs were not fetched.
Fix:
west blobs fetch hubblenetwork-sdk
hubble_init returns an error
Symptom: <wrn> hubblenetwork: Failed to set Unix Epoch time
Cause: unix_time_ms passed to hubble_init() is 0.
The SDK requires a valid non-zero Unix timestamp.
Fix: Ensure time is provisioned (over BLE, NTP, GPS, or RTC) before
calling hubble_init(). See Preparing for Satellite Transmission.
—
Symptom: <wrn> hubblenetwork: Failed to set key
Cause: The key buffer is NULL, zero-length, or the wrong size for the
configured key type (CONFIG_HUBBLE_NETWORK_KEY_128 or
CONFIG_HUBBLE_NETWORK_KEY_256).
Fix: Verify the key is correctly decoded and its length matches
CONFIG_HUBBLE_KEY_SIZE.
Pass prediction returns an error
Symptom: <wrn> hubblenetwork: Hubble Satellite next pass get: no satellites configured
Cause: hubble_sat_satellites_set() was not called before
hubble_sat_next_pass_get().
Fix: Call hubble_sat_satellites_set() with a valid orbital
parameters array immediately after hubble_init().
—
Symptom: <wrn> hubblenetwork: Hubble Satellite next pass get: no pass found
Cause: No satellite pass is visible from the given location within the search window. Most commonly caused by incorrect device coordinates or a stale Unix timestamp.
Fix: Verify that device_pos.lat and device_pos.lon are correct
and that unix_time_ms reflects current wall-clock time.
—
Symptom: Firmware appears to hang or stall inside
hubble_sat_next_pass_get().
Cause: The pass prediction algorithm iterates forward orbit-by-orbit until
it finds a pass. If the input time is near zero (e.g. Unix time was passed in
seconds instead of milliseconds), or if device_pos.lat
and device_pos.lon are invalid, the loop can spin
through thousands of orbits before returning.
Fix: Confirm unix_time_ms is in milliseconds and that
device_pos.lat and device_pos.lon hold the actual device coordinates.
Transmission fails
Symptom: <wrn> hubblenetwork: Hubble Satellite packet transmission failed
Cause: The board API returned an error from
hubble_sat_board_packet_send(), or the BLE stack was still active
when hubble_sat_packet_send() was called.
Fix: Ensure bt_le_adv_stop() and bt_disable() are called before
hubble_sat_packet_send(). If the error comes from the board API,
check your PA/FEM GPIO sequencing in
hubble_sat_board_packet_send().
—
Symptom: <err> hubblenetwork: Hubble Satellite Network initialization failed
Cause: hubble_sat_board_init() returned an error during SDK
initialization, common cause is the board init failed.
Fix: Check the return value of hubble_sat_board_init() in your
board implementation.
Next Steps
RF Verification with an SDR
Before waiting for a live satellite pass, you can verify that your device is transmitting a valid Hubble packet at the physical layer using an ADALM-PLUTO product page and the pyhubblenetwork Python library.
Install the library:
pip install pyhubblenetwork
Connect the ADALM-PLUTO near the device antenna and run the scanner with your device key to decode captured packets in real time:
hubblenetwork sat scan --key "<your-device-key>"
A successfully decoded packet confirms the RF output, packet framing, channel hopping sequence, and PA/FEM sequencing are all correct. For the full list of available commands, run:
hubblenetwork --help
See the pyhubblenetwork repository for detailed usage and setup instructions.
Viewing Upcoming Passes
Use the Hubble Pass Explorer on the Hubble Dashboard to see when the next satellite pass is predicted for your location. This is useful to cross-check pass prediction results from the device and to plan test windows.
Dashboard Verification
Once a live satellite pass has occurred and hubble_sat_packet_send()
returned without error, after the downlink data is successful, log into the
Hubble Dashboard to confirm the packet was received by the network. A packet
appearing on the dashboard is end-to-end proof that the device is operational
on the Hubble satellite network.
Further Reading
Satellite Network Overview: satellite protocol details, reliability modes, and power trade-offs.
Pass Prediction: choosing pass options, interpreting pass results, and computing multiple upcoming passes.
Configuration Options: full Kconfig reference for all
CONFIG_HUBBLE_*options.Time Management: time management best practices for devices with and without a real-time clock.
Supporting New Boards: full board API contract and integration checklist.