If you’ve ever built something with Wi-Fi—an LED blinking on an ESP32, a sensor sending data to a dashboard—you’ve already tasted the power of connected devices.
But Wi-Fi has limits. It depends on routers, passwords, and range.
Cellular IoT is different.
It’s what lets a tracker work in the mountains.What keeps a parcel locker online in the middle of a city.What allows machines to talk across countries—without asking for Wi-Fi.
This is your first step into that world.
In this tutorial, you won’t just blink an LED. You’ll create a tiny IoT device reaching out into the invisible infrastructure around you—finding a cell tower, negotiating with it, and joining the internet.
And when it succeeds, LED will light up.
Step 1 — Preparing the HardwareBefore anything happens in code, something important happens physically: your device gets its identity.
The setup:
When you unbox the Nordic nRF9151 DK, you’ll find the board itself along with two SIM cards: one from Onomondo and one from Conexa (Wireless Logic).
Plus, you get a nice sticker.
That sticker went straight to the sticker graveyard on my laptop lid.
If you just unboxed your nRF9151 DK, you might have noticed it comes with two different SIM cards: one from Onomondo and one from Wireless Logic (Conexa).
As a beginner, you might think, "A SIM is a SIM, right? I just plug it in and it connects."
Actually, these two SIMs represent two completely different philosophies in cellular IoT. Understanding the difference between them will save you hours of debugging! Here is what you need to know:
The "Magic" SIM: Onomondo
Onomondo is a modern, cloud-native cellular network designed specifically to make IoT development as frictionless as possible.
How it works: Onomondo uses a feature called "APN Override." An APN (Access Point Name) is basically the gateway and password your device uses to talk to the cell tower.
The Developer Experience: If your code forgets to send an APN, sends a blank APN, or even sends a typo like my_broken_apn, the Onomondo network catches the mistake, ignores it, and connects you to the internet anyway.
Verdict: It is completely Plug-and-Play. You don't need to configure any passwords in your firmware. It is the absolute best SIM to use for your very first "Hello World" project.
The "Enterprise" SIM: Conexa (Wireless Logic)
Conexa is a highly secure, enterprise-grade network. If you look at the paperwork that came with this SIM, you will notice it assigns your board a Static Private IP Address (e.g., 100.120.x.x).
How it works: Because Conexa puts your device on a secure, private network, it enforces Strict Authentication. When your nRF9151 tries to talk to a local cell tower, the tower demands the exact APN, a Username, and a Password.
The Developer Experience: If you try to connect without providing these credentials, the tower will instantly reject your device. Your nRF9151 will blink its "Searching" LED forever, and you might think your board is broken.
Verdict: This is exactly what you want for a massive fleet of secure commercial devices, but it requires a little extra setup in your code.
Both SIMs put your device on a secure, private network with a Static IP. But for beginners, start your journey with the Onomondo SIM to get your first successful connection effortlessly.
Once you are comfortable, swap to the Conexa SIM to learn how to securely authenticate an IoT device using strict credentials!
Connect Onomondo SIM to the dashboardTake the SIM card in your hand for a second.
Note: I will begin with the Onomondo setup, followed by a similar test using Conexa.
Check Coverage Link
Unlike Wi-Fi credentials, this tiny chip is your device’s passport. It tells cellular networks: “This device is allowed to exist.”
Insert the Onomondo nano-SIM into the slot on the board.
Connect the board via USB, and flip the power switch.
Now visit Onomondo app page and register card. I guess you can use 'N/A' for the company and URL fields, and just state that you found them through Hackster.
Onomondo Registration Includes:
- 50MB total data (10MB initial + 40MB additional)
- Free 60-day platform trial
- Access to real-time network insights
- Granular control over device network access
Once you access the portal dashboard, your SIM status should appear as Active, indicated by a green icon.
Congratulations!
You have successfully connected your nRF9151 to the Onomondo network and your device is now online.
Click View to see detailed information about your SIM and its data usage. Feel free to explore the rest of the Onomondo portal to see what else is available.
Although the nRF9151 device has an IP address, pings will fail because the SIM is inside protected network. To access your device via its IP, you must install the OpenVPN with configuration file on your computer.
Navigate to openvpn.net and download the OpenVPN desktop version compatible with your operating system.
Launch OpenVPN Connect.
For this tutorila you no need to have VPN to access your sim via private IP, so it is not critical to execute steps 4A or 4B.
Click on your email address in the top-right corner. Select "API docs" from the dropdown list.
Now you will get to the docs page
Select the OpenVPN tab from the list.
Download the onomondo.ovpn file from the page.
Since you already have the configuration file, go to the bottom of the screen, select Upload file, and navigate to the location where you saved it on your computer.
Confirm file import.
After importing file click Connect.
To connect, log in using the same credentials you used for the Onomondo portal.
Log in using the same credentials you used for the Onomondo portal to establish the connection.
With your VPN tunnel active, you now have a direct line to your nRF9151. You're no longer just sending data into a void—you can actively manage, ping, and communicate with your hardware from anywhere.
Now, let’s get to the real fun: writing some code.If you decide to go with the Conexa SIM instead, the process is very similar but uses the SIMPro and DevicePro portals.
Step 2B — Register Conexa SIM & Set Up DevicePro portalConexa SIM card is using the (VF-NL) IMSI, which means that it does not support NB-IoT coverage; only LTE-M connectivity is available.
Check Coverage Link
Currently, the only verified network launch for VF-NL in Poland (my case) is with the Orange network. Because of this, there is no specific steering profile applied to this SIM card, so it will automatically connect to the best available network in the area at any given time.
Note: Safari is not supported by SimPro dashboard, so please switch to Chrome or Firefox to ensure the portal functions correctly.
Carefully pop the nano-SIM out of its plastic holder.
Flip the plastic card over and scan the QR code to be redirected to the registration page, where you can claim your free data.
After registering your SIM, you will receive 12 months of access to the Conexa network and the SIMPro connectivity management platform. You will also get an additional 45MB of data on top of the initial 5MB included with your SIM.
And yeah, once you submit the registration form, it might take a little while to process, so just heads up. While I'm waiting for access to the SIM Pro platform, let's start building the code with Onomondo.
Comparing to Onomondo this SIM has PIN code and PUK code,
Step 4B — Installing Wireless Logic VPN Configuration Files for ConexaOnce you have access to the Device Pro Portal, download the .ovpn configuration file from the main page .
The 'SIM-Tunnel' enables the secure transmission of all ports and protocols to the SIM card, such as SSH, FTP, Telnet, HTTP/S, RTSP etc. Read more about this on the official Wireless Logic website here
Note: I'm waiting for access to the Device Pro platform from Wireless Logic owner of the Conexa. It’s a real struggle to get their systems working, so I prefer Onomondo for their ease of use and quick setup.
For this tutorila you no need to have VPN to access your sim via private IP, so it is not critical.
Step 4 — The Software Environment nRF Connect SDKCellular IoT is not Arduino-style simplicity. It’s closer to real embedded systems used in production.
That’s why we use:
- nRF Connect SDK
- Zephyr RTOS
- Visual Studio Code
Install nRF Connect for Desktop.
nRF Connect for Desktop serves as the centralized cross-platform gateway for Nordic Semiconductor’s specialized development tools. While the actual coding and compilation occur within VS Code, this desktop suite provides a graphical "app-store" interface for hardware-specific utilities that operate independently of the IDE.
By installing this hub, the developer gains access to a modular suite of applications designed for real-time hardware diagnostics and optimization:
- Cellular Monitor: A high-level diagnostic tool used for real-time modem tracing and AT command evaluation, replacing the legacy LTE Link Monitor.
- Programmer: A visual interface for inspecting memory layouts and flashing compiled
.hexbinaries directly to the SoC. - Power Profiler: An essential utility for analyzing current consumption when used with the Power Profiler Kit (PPK2), critical for low-power Zephyr applications.
- Bluetooth Low Energy: A comprehensive tool for scanning, advertising, and testing GATT services on BLE-enabled devices.
The installation of nRF Connect for Desktop completes the environment by bridging the gap between the Firmware (Zephyr/NCS) and the Physical Hardware, providing the visibility needed to debug cellular connectivity and power efficiency that a standard text editor cannot provide.
Open Toolchain Manager and install Quick Start.
During installation, you will be prompted to name your board. In the third step, you will need to choose which firmware to upload; select AT commands.
The programming process will now begin.
The system will then verify your board.
Then, Quick Start will remind you of the default SIM variants included in the box.
Then you can evaluate AT commands by opening Cellular Monitor.Then, you can evaluate AT commands by opening Cellular Monitor.
Explore the learning resources to find out more.
Now you need to install the SDK. Since I am developing using the VS Code IDE, I will choose Option 1.
Open VS Code with extension.
After the extension, SDK, and toolchain have been successfully installed.
You will then have the correct build configuration within VS Code.
Congratulations! You have successfully finished setting up your development environment.
Now that everything is installed, let's open Cellular Monitor.
Since you have the environment ready, this is where you'll start interacting directly with the hardware.
To get a real-time look at how your device is interacting with the towers, open the Cellular Monitor app. This is the most powerful tool for troubleshooting connection issues or verifying roaming status.
Connect to the device detected by the system by clicking its name in the top-left corner.
Click start to trace.
Then, you will be able to see the tracing process in real-time.
For more detailed debugging install Programmer, Cellular Monitor, Power Profiler if needed.
When you use Nordic’s Cellular Monitor, the dashboard can be a bit deceiving. A green checkmark next to "LTE CONNECTION" on the left side does not mean you are connected to the internet!
To prove you actually bypassed the cell tower's "bouncer" and got an IP address, you need to look at the LTE Network panel in the center of the screen.
Here are the 3 fields you must check to confirm a true connection:
EPS Network Registration Status (The Magic Number)
This is the most important number on your screen.
Status 2: Searching / Attaching. If you are stuck on 2, the tower sees you, but it is actively deciding whether to let you in (or silently dropping you).
- Status 2: Searching / Attaching. If you are stuck on 2, the tower sees you, but it is actively deciding whether to let you in (or silently dropping you).
- Status 3: Registration Denied. The tower explicitly rejected your SIM card.
- Status 5 (or 1): Registered Roaming (or Home). This is the goal! In the Onomondo screenshot, you can see it says 5. This means the network authenticated the SIM, accepted the APN, and gave the device a seat at the table.
Activity Status
Right below the RRC status, look at the Activity Status text.
- Bad: Not registered, attaching or searching. (You are locked out).
- Good: Registered, roaming. (You are officially online!).
With Conexa, you'll encounter situations like this because you have to manually provide the APN and password.
Open serial terminal
You are now communicating directly with the modem.
Enter the APN provided in the Conexa email.
AT+CGDCONT=0,"IP","eapn1.net"Check the result; it should return 'OK'.
Now, enter your username and password, then press Enter.
AT+CGAUTH=0,1,"NordicSe","NordicSe"similar, must return OK.
Now, close the serial terminal.
Check the PDN tab at the top right to verify that your APN is registered.
tbc. as Conexa has issues with roaming.
Step 5 — Setting up a Zephyr RTOS environmentEssentially, the Zephyr RTOS is already installed as part of the nRF Connect SDK (NCS) setup, so there is no need to install it separately—unless you plan to develop applications independently of the Nordic ecosystem.
In my case on macOS, Zephyr was installed at:
/opt/nordic/ncs/v3.2.4/zephyrNow you’re ready to create something real.
Step 6A — Building Your First nRF9151 Application Using Zephyr RTOS and OnomondoOpen VS Code, navigate to the nRF Connect Extension, and click Create a New Application.
Open VS Code and navigate to the nRF Connect extension. Click Create a new application, where you can choose to copy from an existing sample or browse the nRF Connect SDK Add-on index for external repositories.
While I highly recommend exploring the various samples available in the SDK to understand the breadth of the Zephyr RTOS and Nordic SDK, we will focus specifically on the blank application for now.
Detailed walkthroughs of other samples will be covered in a separate tutorial.
Name your application as you wish and press 'Enter' to finalize the project structure.
You will be prompted to scan for kits; click 'Search'
Once complete, you will be provided with a project template containing all the necessary files for development.
Open the prj.conf file in your project, and paste these lines:
# Enable GPIO (for the LED)
CONFIG_GPIO=y
# Enable the Modem and LTE Link Control
CONFIG_NRF_MODEM_LIB=y
CONFIG_LTE_LINK_CONTROL=y
CONFIG_LTE_NETWORK_MODE_LTE_M=yNow for the code. This is the absolute simplest way to connect to a cell tower in Zephyr.
Open src/main.c, delete the default code, and paste this exact program. It is heavily commented so you know exactly what is happening:
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <modem/nrf_modem_lib.h>
#include <modem/lte_lc.h>
#include <stdio.h>
// Grab LED 0 from the Devicetree
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
int main(void)
{
printf("Starting Cellular Blink!\n");
// 1. Setup the LED pin
if (device_is_ready(led.port)) {
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
}
// 2. Initialize the Modem
printf("Initializing modem...\n");
nrf_modem_lib_init();
// 3. Connect to the LTE Network (e.g., Orange/Play/Plus in Poland)
printf("Connecting to Onomondo LTE-M network. This may take a minute...\n");
// Note: This is a "blocking" function. The code pauses here until the network attaches!
lte_lc_connect();
// 4. Success! Turn on the LED to prove we are online.
printf("CONNECTED! Turning on LED.\n");
gpio_pin_set_dt(&led, 1);
// Keep the main thread alive
while (1) {
k_sleep(K_SECONDS(1));
}
}Click on the nRF Connect icon in the Activity Bar. In the Applications panel at the bottom, locate your project and click '+ Add build configuration' to define your hardware targets.
In the Build Configuration menu, ensure that the SDK and Toolchain are both set to the nRF Connect versions you installed.
Then, set the Board target specifically to nrf9151dk/nrf9151/ns to match your hardware.
Scroll to the bottom of the Build Configuration panel and click 'Build Configuration'.
The terminal will open automatically, allowing you to monitor the progress as it compiles your project.
When the build successfully finishes—as it should for nRF Connect SDK v3.2.4 —you will receive a notification in the status bar, and your binary image will be ready to flash to the hardware.
You are now ready to flash the board. Navigate to the Actions tab in the nRF Connect sidebar and click 'Flash' to load the compiled image onto your nRF9151.
The flashing process will now begin using West, Zephyr’s meta-tool. You can monitor the progress in the terminal as it erases, programs, and verifies the binary on your nRF9151.
Navigate to the Connected Devices tab in the nRF Connect sidebar. Here, you will see your nRF9151 DK listed, confirming that the toolchain has a solid connection to your hardware.
Click the name of your nRF9151 DK to expand the dropdown list and view the available serial ports and hardware details.
Click the name of your nRF9151 DK to expand the dropdown list. This will reveal the available serial ports VCOM and specific hardware details, allowing you to interface directly with the board.
- VCOM0: Usually the Application Port. This is where your Zephyr
printkmessages (like "Connecting to Onomondo...") will appear. - VCOM1: Usually the Modem/AT Port. If you want to manually ask the modem
AT+CEREG?to see your registration status, this is the port you'd use.
Locate VCOM0 in the dropdown list and click the 'Plug' (Fork) icon next to it. This will open the Serial Terminal at the bottom of your screen, where you can monitor the application logs in real-time.
Once the port is open, you will receive real-time logs in the terminal. This allows you to monitor the device's boot sequence, modem initialization, and the live connection status as it attaches to the Onomondo network.
If the terminal is blank or you missed the initial startup messages, press the physical RESET button on your nRF9151 DK to refresh the output and view the full boot sequence from the beginning.
Once the device successfully connects to the LTE-M Onomondo network, you will see a confirmation message in the terminal, and LED on the board will light up to indicate an active connection.
Okay, so where’s the blink? The LED will blink while searching for a signal and switch to a stable, solid light once successfully connected to the Onomondo network.
Edit main.c code:
Because the original lte_lc_connect() function is blocking (it completely pauses the code until the modem attaches to the network), you cannot easily blink an LED in the main thread while it runs.
To fix this, we need to switch to lte_lc_connect_async(). This function kicks off the connection process in the background and returns immediately, allowing your main loop to continue running. It takes an event handler (a callback function) that will notify us when the network successfully connects.
Here is the updated code:
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <modem/nrf_modem_lib.h>
#include <modem/lte_lc.h>
#include <stdio.h>
// 1. Setup LED and our "Blink" worker
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
static struct k_work_delayable blink_work;
// Worker ONLY handles blinking. It runs every 500ms until told to stop.
static void blink_handler(struct k_work *work)
{
gpio_pin_toggle_dt(&led);
k_work_reschedule(&blink_work, K_MSEC(500));
}
// 2. Network Event Handler
static void lte_handler(const struct lte_lc_evt *const evt)
{
if (evt->type == LTE_LC_EVT_NW_REG_STATUS) {
bool connected = (evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME ||
evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING);
if (connected) {
printf("\nCONNECTED! Turning LED solid.\n");
// Stop the blinking task, and turn the LED ON
k_work_cancel_delayable(&blink_work);
gpio_pin_set_dt(&led, 1);
} else {
printf("\nSearching for network...\n");
// Start (or restart) the blinking task
k_work_reschedule(&blink_work, K_NO_WAIT);
}
}
}
// 3. Main Application
int main(void)
{
printf("Starting Smart Cellular Blink!\n");
// Initialize LED and Worker
if (!device_is_ready(led.port)) return -1;
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
k_work_init_delayable(&blink_work, blink_handler);
// Turn on Modem
if (nrf_modem_lib_init() != 0) {
printf("Modem init failed!\n");
return -1;
}
// Connect in the background (triggers lte_handler on changes)
lte_lc_connect_async(lte_handler);
// Put the main thread to sleep forever. The background worker handles the rest!
k_sleep(K_FOREVER);
return 0;
}Rebuild application
and Flash it again
Open the Serial Terminal, and you will see the log output—similar to what is shown in the video—displaying the real-time status of your connection.
The board’s LED will blink while the modem is searching for a signal; once the connection is established, it will turn solid green to indicate a successful attach to the Onomondo network.
For a more sophisticated implementation, we use a worker function. Here nrf_modem_lib_init() handles the background initialization automatically, allowing you to simply call lte_lc_connect_async() to begin the connection process without blocking your main application.
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <modem/nrf_modem_lib.h>
#include <modem/lte_lc.h>
#include <stdio.h>
// Grab LED 0 from the Devicetree
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_ALIAS(led0), gpios);
// Declare a delayable work item and a connection state flag
static struct k_work_delayable blink_work;
static volatile bool is_connected = false;
// Worker function: This handles the LED logic without blocking threads
static void blink_work_handler(struct k_work *work)
{
if (is_connected) {
// We are connected: Set LED to stable ON
gpio_pin_set_dt(&led, 1);
// Notice we DO NOT reschedule the worker here.
// This stops the background task and saves power!
} else {
// Not connected: Toggle the LED to blink
gpio_pin_toggle_dt(&led);
// Reschedule this exact worker to run again in 500 milliseconds
k_work_reschedule(&blink_work, K_MSEC(500));
}
}
// Event handler for LTE link control
static void lte_handler(const struct lte_lc_evt *const evt)
{
switch (evt->type) {
case LTE_LC_EVT_NW_REG_STATUS:
if ((evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_HOME) ||
(evt->nw_reg_status == LTE_LC_NW_REG_REGISTERED_ROAMING)) {
is_connected = true;
printf("\nCONNECTED! Setting LED solid.\n");
// Trigger the worker immediately to apply the "solid ON" state
k_work_reschedule(&blink_work, K_NO_WAIT);
} else {
// If the state changes to not registered (searching/disconnected)
if (is_connected) {
printf("\nDisconnected. Searching again...\n");
}
is_connected = false;
// Restart the blinking worker
k_work_reschedule(&blink_work, K_MSEC(500));
}
break;
default:
break;
}
}
int main(void)
{
int err;
printf("Starting Cellular Blink with Zephyr Worker!\n");
// 1. Setup the LED pin
if (!device_is_ready(led.port)) {
printf("LED device not ready\n");
return -1;
}
gpio_pin_configure_dt(&led, GPIO_OUTPUT_INACTIVE);
// 2. Initialize the Delayable Worker
k_work_init_delayable(&blink_work, blink_work_handler);
// Start the worker immediately to begin the "searching" blink
k_work_reschedule(&blink_work, K_NO_WAIT);
// 3. Initialize the Modem
printf("Initializing modem...\n");
err = nrf_modem_lib_init();
if (err) {
printf("Failed to initialize modem library, error: %d\n", err);
return err;
}
// 4. Connect asynchronously
printf("Connecting to Onomondo LTE-M network. This may take a minute...\n");
// --> CHANGED HERE: We now just use lte_lc_connect_async() <--
err = lte_lc_connect_async(lte_handler);
if (err) {
printf("Failed to connect to LTE, error: %d\n", err);
return err;
}
// 5. Suspend main thread. The Worker and LTE Callback will handle everything asynchronously.
k_sleep(K_FOREVER);
return 0;
}Step 6B — Building Your First nRF9151 Application Using Zephyr RTOS and ConexaYou are using a Wireless Logic (Conexa-LD) SIM card. Unlike standard IoT SIMs that auto-configure, this specific SIM requires a private APN, a Username, and a Password to attach to the network. If the modem doesn't provide these credentials during the handshake, the cell tower will reject the connection.
Once you have completed registration in Step 2B, Wireless Logic will email you an Excel attachment containing your APN details.
The good news is that you do not need to change the C code we just wrote. You only need to tell Zephyr's Packet Data Network (PDN) library to use your credentials by adding a few lines to your prj.conf file.
# Cellular and LTE Settings
CONFIG_LTE_LINK_CONTROL=y
CONFIG_LTE_NETWORK_MODE_LTE_M=y
# Wireless Logic Conexa SIM APN Settings
CONFIG_PDN=y
CONFIG_PDN_DEFAULTS_OVERRIDE=y
CONFIG_PDN_DEFAULT_APN="eapn1.net"
CONFIG_PDN_DEFAULT_USERNAME="your-username-here"
CONFIG_PDN_DEFAULT_PASSWORD="your-password-here"
# Set authentication type (PAP is standard for Wireless Logic)
CONFIG_PDN_DEFAULT_AUTH_PAP=ytbc. still trying to get Conexa sim online with nRF9151... not so user friendly sim, just use Onomondo.


_UoqlmTWtmc.png?auto=compress%2Cformat&w=48&h=48&fit=fill&bg=ffffff)




Comments