nRF Connect SDK Tutorial - old (nRF9160 DK)

10/3/2019: Updated tutorial to support nRF Connect SDK (NCS) v1.0.0

This tutorial is outdated and might get removed in the near future, please check out the nRF Connect Tutorial Series, which is more up to date. It works with the nRF9160 DK and covers the same material++.

This tutorial will cover the basics of building an application with the nRF9160 Development Kit (DK) using the nRF Connect SDK. It will demonstrate how to create an application from scratch, and how to build and flash it using west and SEGGER Embedded Studio. You will get familiar with the west tool and how to manage a multi repository project. Eventually, you will learn how to add a peripheral to your project and how to manage the configurations and the device tree. The tutorial will not cover anything related to the modem.

Before starting on this tutorial you should follow the Getting Started Assistant in nRF Connect for Desktop, it is recommended before reading the theory and mandatory before starting on the practical part (creating an application from scratch). You should use tag v1.0.0, since that is the tag this tutorial is based upon. In order to follow this tutorial you will need the nRF9160 DK.


1. Theory

1.1 About NCS

The nRF Connect SDK (NCS) is hosted by Nordic Semiconductor and contains a set of open source projects that makes it possible to develop an application for nRF9160 Cellular IoT devices. NCS consists of several repositories (repos), including the nrf repo, the nrfxlib repo, the MCUboot repo and the Zephyr Project

The figure below visualizes the toolchain in NCS, which is based on the Zephyr toolchain. You can see a set of different tools, each playing a role in the creation of an application, from configuring the system to building it.

Kconfig generates definitions that configures the whole system, while the Device Tree describes the hardware. Cmake then uses the information from Kconfig and the device tree to generate build files, which Ninja (comparable to make) will use to build the program. The gcc compiler system is used to create the executables. The upcoming chapters will take a closer look at some of these tools.

1.2 West

This section requires the reader to have a basic understanding of how git works.

The Zephyr project includes a tool called west, which makes it possible to manage multiple repositories. This tool is quite useful if your application uses libraries and features from folders that are cloned from different repositories/projects, since it keeps control of what commit to use from the different projects. It also makes it fairly simple to add and remove modules. Take a look at the image below, which gives you a good intuition of how it works.

The image shows the content of a west installation, which contains one manifest repository and multiple projects, where the manifest repository controls what commit to use from the different projects. The two most important commands in west are west init and west update, where the former will initialize a new west installation and the latter will update the projects depending on the information in the manifest repository. Take a look at the Zephyr documentation for a more in depth understanding.

Let's take a closer look at the ncs folder, to get a better perspective of how west works. If you have followed the "nRF Connect Getting Started Guide" correctly the west installation should look as followed:

The file .west/config determines the manifest repo and the Zephyr base of the west installation, and as seen in the code snippet below it is set to respectively the nrf folder and the Zephyr folder. This implies that the nrf folder determines what commits to check out for the different projects when running west update. The Zephyr base is a variable that will be used to refer to the Zephyr folder when an application is built.

path = nrf
base = zephyr

If you open the west manifest (the nrf folder), you will see that it contains a west.yml file. This file contains the the revision (tag, branch or SHA) for all the project that is associated with the current nrf revision, and you can see that the projects corresponds to the folders in the image above. The west.yml file shows the URL of where the projects are cloned from. E.g. the projects zephyr, mcuboot and nrfxlib are cloned from Nordic Playground on Github. These projects are maintained by Nordic Semiconductor, and you can read more about them in the NCS documentation. Revisit the image at the top of this section, to better visualize how it works.

It is possible to add your own commands to west in addition to the ones managing the multiple git repositories, and the approach is explained in the Zephyr documentation. If you look at the file <sourcecode_root>ncs\zephyr\scripts\west-commands.yml, you can see the extension commands added to west by Zephyr. Later in this tutorial it will be shown how to build and flash an application using these commands.

1.3 Master vs. Tag

The nrf folder contains west.yml, which turns it into the powerful repository that controls the content of the whole NCS. This makes updates to NCS fairly simple, since all you have to do is to fetch/pull a reliable version of the nrf repository before updating the rest of the projects through west.

You have two choices when developing with NCS. You can work with master or a particular tag. If you choose master, you will use the newest version of the nrf repository, and consequently an up to date ncs folder. The tags represent versions of master at a particular moment in time, and is considered reliable. There is not a correct answer of what to choose, as it depends on your particular need. If reliability is important, and you can't afford unexpected behavior and bugs, then it is recommended to use the latest tag. If it is more important that your project has the newest features, than having a reliable solution, then master is the choice to go for.

Take a look at the nRF repository, where you find master and all the tags. The image below shows the available tags of this repository at the time this tutorial was written. The tags are enclosed by red and green rectangles, where the green color represent the latest tag, which is most up to date.

1.4 The Device Tree

The device tree describes the hardware of a board, everything from the gpio configurations of the LED’s on the nRF9160 DK to the memory location of the ADC peripheral. Device trees gives the developer more flexibility and makes it easier to modify the hardware. The device tree uses a particular format consisting of nodes connected together, where each node contains a set of properties. Read more about the Device Tree format in the Device Tree Specification. The Zephyr documentation gives a more in-depth explanation of the Device Tree in the context of Zephyr. 

The image below gives an overview of how the different nRF9160 dts/dtsi files includes each other. The file that the arrow is pointing away from, includes the file that the arrow is pointing towards. You can see that the dts/dtsi files are organized in a hierarchical manner, where the top files (in the blue area) describes the higher level hardware like th UART baud rate, I2C SDA and SCL pins and GPIO configuration of LEDs and buttons. The bottom files (in the green area) describes lower level hardware, like the peripheral's interrupt priority and memory location, base address of SRAM and CPU related information.

The image below shows what happens with the device tree when building an nRF9160 application. The dts file is first compiled into a compiled dts file (nrf9160_pca10090. dts_compiled), where all the defines and macros are resolved, before a python script transforms the hardware information into a header file (generated_dts_board.h). Both nrf9160_pca10090.dts_compiled and generated_dts_board.h are added to the project's build folder. The drivers will then use this information. The .overlay file is used to modify the device tree. It comes in handy when your project uses external devices, such as a sensor using SPI. Read more about overlay files in the Zephyr documentation.

Let’s go through a specific example, in order to get a better intuition of how this works. Inside <sourcecode_root>\ncs\zephyr\dts\arm\nordic\nrf9160_common.dtsi, there is a node describing the ADC peripheral, and the image below shows how this node is transformed into a set of defines in the header file generated_dts_board_unifixed.h.

The defines may then be used by drivers or other part of the compiled code. E.g. DT_NORDIC_NRF_SAADC_ADC_0_BASE_ADDRESS is used by the file nrfx_config_nrf9160.h, which will map the base address of the SAADC peripheral such that it is accessible by the nrfx drivers. See the code snippet below.

#define NRF_SAADC \

1.5 Configurations

In Zephyr, Kconfig files are used to configure the whole system. Specifically, the files give a description of what hardware should be used and how it should be used. The configuration files determines what drivers should be used to interface with the hardware, and also makes it possible to pick and choose specific kind of libraries, software and other features. The configuration system makes is possible to choose Nordic specific drivers to control peripherals, enable logging and build additional samples such as the Secure Partition Manager sample. There are different kinds of configuration files, including Kconfig and prj.conf files. Read more about them in the Zephyr documentation in Application Configuration and Setting Configuration Values.

The illustration seen below shows what happens to the systems configurations when an application builds. All the configurations from the Kconfig and the prj.conf files are merged together into a .config file before being transformed into a header file which the nordic specific .c drivers can use.

Let’s go through a specific example to get a deeper understanding of how this process works. Take a look at the configuration I2C_2_NRF_TWIM inside <sourcecode_root>/ncs/zephyr/drivers/i2c/Kconfig.nrfx. Whether it is enabled or not depends on other configurations, such as HAS_HW_NRF_TWIM0, I2C_NRFX and CONFIG_SOC_FAMILY_NRF. The image below shows the transformation of the configuration from Kconfig format to a #define in a header file. Be aware that the build folder may not be present for you, since it is first made when a project is built.

The autoconf.h file is then included into the project and the code snippet below shows how the define is used by the nordic specific Zephyr driver i2c_nrfx_twim.c.


1.6 Secure vs. nonsecure

The new TrustZone technology that comes with the Arm Cortex-M33 processor makes it possible to create two environments that can run simultaneously using the same CPU; a secure environment and a non-secure environment. This makes the application more protected from attackers, with the drawback of increased complexity. This section will try to give you a better understanding of this new feature, and how it affects the development process.

A firmware image can be built as Secure or Non-Secure. If a firmware image is built as Secure it will run in the Secure domain and has access to resources (peripherals, memory areas etc..) configured as Secure. Conversely a Non-Secure firmware can only access Non-Secure resources.

The System protection unit (SPU) peripheral in nRF9160 makes it possible to manage the CPU access to peripherals and memory regions. It does so by configuring the permissions of specific RAM and flash regions and setting the security attributes of peripherals. If a RAM or Flash region’s permission is set as secure, only Secure firmware has access. If a peripheral is configured as Non-Secure, it is mapped to the Non-Secure peripheral address space (0x4xxx_xxxx), and a Secure configuration maps the peripheral to the Secure address space (0x5xxx_xxxx). At reset, all the peripherals are set to Secure.

In order to run a non-secure application, one have to use the SPU to configure the resources as Non-Secure. Luckily, there is already a library with functions that simplifies this process, the Secure Partition Manager (SPM) library. The library is located in <sourcecode_root>/ncs/nrf/subsys/spm. Its API consists of two functions: spm_config() and spm_jump().The function spm_config() will set the security attribute of the peripherals depending on the default configurations in its associated Kconfig file. It will set the flash regions after the SPM location, as Non-Secure, while the RAM regions after the first 64 kB are set as Non-Secure. The function spm_jump() will make the application jump to a Non-Secure partition (e.g. the non-secure user application). The Secure Partition Manager sample uses this library and runs the mentioned functions. Its location is <sourcecode_root>ncs/nrf/samples/nrf9160/spm. By building and flashing this sample in addition to the main application, the main application will run in the non-secure domain.

2. Creating an application from scratch

This chapter will take you through the steps required for creating an application in NCS for nRF9160.

The first section (Your first "Hello World") will show you how to create a minimal working example which function is to log "Hello World", both using Segger Embedded Studio (SES) and the Zephyr extension commands in west. At the end of the section I will show you how to manage a multi repository environment using west, specifically how to update NCS and how to preserve you application when going from one revision of nrf to another.

The second section (Create a light intensity controller) demonstrates how to add peripherals to your application, specifically the PWM and the SAADC peripheral. Adding a peripheral requires the developer to add configurations and modify the device tree, which this section will cover thoroughly. You will also learn how to create a Non-secure application.

As mentioned in the introduction of this tutorial, you should follow the Getting Started Assistant in the nRF Connect for Desktop and update NCS to tag v1.0.0 before starting on this part. Check out Getting Started with nRF9160 DK for guidance how to install and follow the Getting Started Assistant and how to how to update NCS to tag v1.0.0. You only need to go through the chapters "Installing nRF Connect for Desktop, Getting St..." and "Setting Up nRF Connect SDK Environment" . You don't need to install the LTE Link Monitor for this tutorial.

Lastly, before starting the development, make sure you have installed the nRF Command Line tools. This includes the nrfjprog executable, which is required to program the nRF9160 SiP. 

2.1 Your first “Hello World”

2.1.1 Set it up

  • Start by creating a folder named hello_world inside <sourcecode_root>\ncs\nrf\samples\nrf9160.
  • Then, create these inside the hello_world folder:
    • A folder named src with main.c inside
    • CMakeLists.txt
    • prj.conf

It should look like this:

  • Next up, copy these lines into CMakeLists.txt

cmake_minimum_required(VERSION 3.13.1)
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
target_sources(app PRIVATE src/main.c)

  • Inside prj.conf, add these configurations


These lines are required in order to see the logging from a serial terminal and will enable the serial driver and the UARTE peripheral. However, it is actually not necessary to enable these, as they are already set inside nrf9160_pca10090_defconfig. I included them anyways to make you familiar with the prj.conf file.

The main.c file should look like this:

#include <zephyr.h>
#include <misc/printk.h>

void main(void)
	printk("Hello World!\n");


2.1.2 Build and flash - using west

In order to build the application using west, some environment variables needs to be set, specifically the toolchain and the Zephyr base. However, the west build command sets the Zephyr base automatically and you don't have to worry about it.

If you follow the Zephyr documentation you'll be told to set environmental variables through the cmd files zephyr-env.cmd and zephyrcc.cmd. This is a nice way of doing it if you are working with different projects, and don't want the env. variables to interfere, but you'll have to run the cmd file every time you open the command line.

For this tutorial, let's add the environmental variables to the whole system instead. Follow these steps:

  • Open cmd, running as administrator

  • Run the following commands
    • setx -m ZEPHYR_TOOLCHAIN_VARIANT "gnuarmemb"
    • setx -m GNUARMEMB_TOOLCHAIN_PATH c:\gnuarmemb

C:\WINDOWS\system32>setx -m ZEPHYR_TOOLCHAIN_VARIANT "gnuarmemb" && setx -m GNUARMEMB_TOOLCHAIN_PATH c:\gnuarmemb

SUCCESS: Specified value was saved.

SUCCESS: Specified value was saved.

  • Reopen the command line and check that the env. variables are set correctly:

C:\Nordic_SDK\ncs\nrf\samples\nrf9160\hello_world>echo %GNUARMEMB_TOOLCHAIN_PATH% & echo %ZEPHYR_TOOLCHAIN_VARIANT%

After the toolchain is set, the application is ready to get built.  

  • Open the command line inside the project folder, <sourcecode_root>\ncs\nrf\samples\nrf9160\hello_world
  • Run the following command to build the application with west:

west build -b nrf9160_pca10090

If the build process was successful, you should see a folder named build inside the hello_world folder:

The build folder should contain the following files and folders:

The last step remains, flashing the project onto the nRF9160 chip.
  • Connect the nRF9160 DK to your computer and turn it on. Make sure SW5 is set to nRF91
  • The flashing is done by running the command west flash, either from the project folder or the build directory.
  • Then, download, install and start a terminal (e.g. Termite) to show the serial output
    • To check what COM port to use: Press the Windows key + Q, search for "Device Manager" and open it. The COM ports are listed under "Ports (COM & LPT)"
    • Open three terminals with the COM ports you found and choose the following Port configurations

After resetting the board, you should see the following log inside one of the terminals:

***** Booting Zephyr OS v1.14.99-ncs2 *****
Hello World!

2.1.3 Build and flash - using SEGGER Embedded Studio

  • Open SEGGER Embedded Studio (Nordic Edition) V4.20 from the location it was extracted in step 3 of the Getting Started Assistant. Click on <path_of_choice>\arm_segger_embedded_studio_v420a_win_x64_nordic\bin\emStudio.exe to open a new session.
  • First, the Zephyr base and the toolchain needs to be set. If you have followed the Getting Started Assistant in the nRF Connect for Desktop you have already done this, but for the record, let's do it again. Click on Tools→Options, and you will see the following window:

  • Choose nRF Connect, and set the GNU ARM Embedded Toolchain Directory and the Zephyr base as followed:

  • In order to open the hello_world application in SES, click on File → Open nRF Connect SDK Project...

  • Choose the following nRF Connect Options:

  • Before running the application, erase the content of the chip. This can be done by running the command nrfjprog --eraseall (using the nRF5 Command Line Tools) or by using the Programmer app in the nRF Connect app desktop version.
  • To build and flash the application, choose Build → Build and Run

  • Verify that everything works by checking that the terminal outputs "Hello World!"

2.1.4 Manage your project with west

Here I will show you how to manage a multi-repository environment, using the west tool. 

How to update NCS to another tag/master

  • If you run git status you can see that you are in the tag v1.0.0

C:\Nordic_SDK\ncs\nrf>git status
HEAD detached at v1.0.0
Untracked files:
  (use "git add <file>..." to include in what will be committed)


nothing added to commit but untracked files present (use "git add" to track)

  • Then, open the command line anywhere inside the nrf folder and checkout the master by running git checkout master 

C:\Nordic_SDK\ncs\nrf>git checkout master

  • By running git status again, you can see that you are now in the master

C:\Nordic_SDK\ncs\nrf>git status
On branch master
Your branch is up to date with 'origin/master'.

Untracked files:
  (use "git add <file>..." to include in what will be committed)


nothing added to commit but untracked files present (use "git add" to track)

  • When changing tag/master, the west.yml file may change, in order to update the NCS accordingly, type in the command west update.

C:\Nordic_SDK\ncs\nrf>west update
=== updating zephyr (zephyr):
--- zephyr: fetching changes
remote: Enumerating objects: 7605, done.
remote: Counting objects: 100% (7605/7605), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 13998 (delta 7601), reused 7603 (delta 7600), pack-reused 6393
Receiving objects: 100% (13998/13998), 21.85 MiB | 8.68 MiB/s, done.
Resolving deltas: 100% (11313/11313), completed with 2536 local objects.
   6b9a1e0ed6..d6e67554cf  master     -> refs/west/master
--- zephyr: checked out 4493a423a645cb54de2ef2720ebb7c403774ae11 as detached HEAD
=== updating nffs (modules\fs\nffs):
--- nffs: fetching changes
--- nffs: checked out bc62a2fa9d98ddb5d633c932ea199bc68e10f194 as detached HEAD
=== updating segger (modules\debug\segger):
--- segger: fetching changes

This will clone the appropriate versions of the projects (mcuboot, nrfxlib, zephyr etc..) into NCS. After this command has completed, you have successfully updated NCS to the master. The approach is exactly the same when updating to another tag, you just run git checkout <ncs_tag> instead. Take a look at nRF Connect SDK Release Notes to see the available tags.

If you open the folder <sourcecode_root>/ncs/nrf/samples/nrf9160, you can see that the hello_world project is present. This is is because we haven't committed the changes, and any uncommitted changes that does not cause a conflict will be brought along. If you commit your changes, or create them in a separate branch, you have to do some more work in order to bring them along into master (or another tag). The next section will show you how to do this.

  • Move back to tag v1.0.0 by running git checkout v1.0.0, and run west update to update the projects

How to preserve your application

I will now show you how to merge your application with another tag/master when you have committed your changes.

  • Make sure you are on tag v1.0.0 and the hello_world sample is present in ..samples/nrf9160
  • Add and commit your changes by typing in git add . and git commit -m "Added hello world sample"

C:\Nordic_SDK\ncs\nrf>git add . && git commit -m "Added hello world sample"
[detached HEAD edf908a] Added hello world sample
 Committer: xyzxyz <>
Your name and email address were configured automatically based
on your username and hostname. Please check that they are accurate.
You can suppress this message by setting them explicitly. Run the
following command and follow the instructions in your editor to edit
your configuration file:

    git config --global --edit

After doing this, you may fix the identity used for this commit with:

    git commit --amend --reset-author

 4 files changed, 18 insertions(+)
 create mode 100644 samples/nrf9160/hello_world/CMakeLists.txt
 create mode 100644 samples/nrf9160/hello_world/prj.conf
 create mode 100644 samples/nrf9160/hello_world/sample.yaml
 create mode 100644 samples/nrf9160/hello_world/src/main.c

  • In order to merge the hello_world application into master you have to do the following
    • Create a new branch from where you are: git branch hello_world_branch
    • Switch to master: git checkout master
      • If you open ..samples\nrf9160 you can see that the hello_world project is gone
    • Merge the application into master: git merge hello_world_branch
      • If you open ..samples\nrf9160 you can see that the hello_world project is present

C:\Nordic_SDK\ncs\nrf>git branch hello_world_branch

C:\Nordic_SDK\ncs\nrf>git checkout master
Previous HEAD position was 5d61511 Added a hello world sample
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

C:\Nordic_SDK2\ncs\nrf>git merge hello_world_branch
Merge made by the 'recursive' strategy.
 samples/nrf9160/hello_world/CMakeLists.txt | 4 ++++
 samples/nrf9160/hello_world/prj.conf       | 0
 samples/nrf9160/hello_world/sample.yaml    | 6 ++++++
 samples/nrf9160/hello_world/src/main.c     | 8 ++++++++
 4 files changed, 18 insertions(+)
 create mode 100644 samples/nrf9160/hello_world/CMakeLists.txt
 create mode 100644 samples/nrf9160/hello_world/prj.conf
 create mode 100644 samples/nrf9160/hello_world/sample.yaml
 create mode 100644 samples/nrf9160/hello_world/src/main.c

In this case the merging went quite smoothly, but in other circumstances where the NCS has undergone significant changes, the merging may be more troublesome.

  • Move back to tag v1.0.0 by running git checkout v1.0.0, and run west update to update the projects

Create your own repo

Developing your application inside the nrf repo is not the best approach. The recommended way is to develop your application in your own repository ( ncs/<your_repo>).  This keeps your application development separate from the nrf repo, and may cause less trouble (no need to create a branch and merge your changes). Your repo will have its own west.yml file (with your apps dependencies) in addition to the application itself. In the future, west will support a multi-manifest feature, which makes it possible to import other west.yml files from your own west.yml. This will allow you to associate your project to a specific nrf revision, as you can point to the corresponding nrf/west.yml.

The tutorial will be updated in the near future to cover this approach.

2.2 Create a light intensity controller

This section will show you how to transform your nRF9160 DK into a light intensity controller, which can be achieved by using the PWM and the SAADC peripherals and a timer. By checking the input voltage of a pin using the SAADC peripheral, the intensity of an on board LED can be controlled by varying the pulse width of the PWM signal accordingly. A potentiometer may come in handy, but is not mandatory.

2.2.1 Tools and tips

Before we start creating the project, I would like to present you some useful tools and tips. If it gets confusing, don't worry, it will make more sense when you start developing.

See the configurations:

When developing with Zephyr, you are dealing with a lot of configurations. Here I will show you two ways getting an overview of all the configurations; through menuconfig or through SES.

  • In order to run menuconfig, type ninja menuconfig in the the command line from the build folder of hello_world.

C:\Nordic_SDK\ncs\nrf\samples\nrf9160\hello_world\build>ninja menuconfig

You should then see a window that looks as followed:

This will get the configurations from the file <..>\hello_world\build\zephyr\.config, and gives you a good overview of the systems configurations. If you modify a configuration, you will see that the .config file will be modified as well. E.g. if you navigate to "Nordic nRF Connect→SPM→Use Secure Partition Manager" and press enter followed by the key "S", this configuration will be enabled. By opening the .config file, you can see that CONFIG_SPM is set to y.

Let's see how this is done in SES.

  • Open the hello_world sample in SES
  • In order to see the configurations, click on Project→Configure nRF Connect SDK. The following window will appear:

Similar to menuconfig, you can modify the .config file from here, by clicking on Configure after the changes are made. Notice the search bar in the top right corner, which let's you search for particular configurations.

All the changes done to the .config file will be dismissed when the application is rebuilt. In order to make the configurations permanent, do the following:

  • Save a copy of the .config file
  • Enable the appropriate configurations, either through menuconfig or SES
  • Do a diff between the old and new .config files
  • Add the changes to prj.conf

See the device tree

  • SES gives you the possibility to see the device tree, which gives you a nice visual overview of it. Click on Project→Edit Device Tree

You should see the following window:

Useful tips for developing an application

  • If you are unsure of how to implement particular peripheral to your project, take a look at the examples and tests provided by Zephyr:
  • If you aren't sure what configurations and node properties a peripheral needs, you should look inside the peripheral's zephyr driver: <sourcecode_root>/ncs/zephyr/drivers/<peripheral>/<peripheral>_nrfx.c (The naming convention may differ slightly from this)
    • Search for "DT_" to see all the Device Tree specific defines the driver uses.
    • Search for "CONFIG_" to see all the configuration specific defines the driver uses.
  • Take a look at the Configuration Options in the Zephyr documentation, which gives you detailed information about all the configuration options from the Kconfig files in Zephyr

2.2.2 Set it up

Let's set up the light controller application, by creating the necessary files.

  • Make sure you are on the correct tag before continuing.
    • Run git status. If you're not on tag v1.0.0, update to it by following the steps in section 2.1.4
  • Start by creating a folder named light_controller inside <sourcecode_root>/ncs/nrf/samples/nrf9160 and add the files src/main.c, CMakeLists.txt and prj.conf to it, like we did when creating the hello world application.
  • Add the following content to CMakeLists.txt:

cmake_minimum_required(VERSION 3.13.1)
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)
target_sources(app PRIVATE src/main.c)

  • Add the following config to prj.conf


You may wonder why the configurations CONFIG_PWM_0 and CONFIG_PWM_NRFX aren't included, as they are required to build this application, for respectively using the PWM instance 0 and the Nordic pwm drivers. However they are enabled by default by the Kconfig system.

  • Copy and paste the following code into src/main.c

#include <nrf9160.h>
#include <zephyr.h>
#include <misc/printk.h>
#include <pwm.h>

#if defined(CONFIG_BOARD_NRF9160_PCA10090NS) || defined(CONFIG_BOARD_NRF9160_PCA10090)


#error "Choose supported board or add new board for the application"

#define PWM_PERIOD 253

void main(void)
	printk("PWM Application has started!\r\n");
	u8_t pulse = 0;
	struct device *pwm_dev = device_get_binding(PWM_DEVICE_NAME);
	if (!pwm_dev) {
		printk("Cannot find %s!\n", PWM_DEVICE_NAME);
        while (1)
                pulse = pulse +10;
                if (pulse > PWM_PERIOD) {
					pulse = 0;
                if (pwm_pin_set_usec(pwm_dev, PWM_CH0_PIN, PWM_PERIOD, pulse)) {

At the top of main, under the include statement, I have added an #if macro, with some defines inside. The reason I did this is to make the code more general (easier to change architecture), which is always good practice. Instead of using the nRF9160 dts definitions directly, I remapped them to general definitions. You can see that the same approach is followed in many of the samples in the Zephyr Project, e.g. the servo_motor sample.

2.2.3 Build and flash it

  • Open a new nRF Connect SDK Project

  • Set the nRF Connect Options as followed:

  • Eventually build and run the application

If the application built and flashed successfully, led 1 should generate a sawtooth wave, by periodically increasing its intensity before starting back over again.

2.2.4 Create a Non-secure application

In order to build a Non-Secure firmware image, the option CONFIG_TRUSTED_EXECUTION_NONSECURE must be set, and to use the Secure Partition Manager, the config CONFIG_SPM must be enabled.

  • Open a new nRF Connect SDK Project, and choose these nRF Connect Options:

By setting the board name to nrf9160_pca10090ns (remember to add ns at the end!), NCS will set the configs mentioned above (TRUSTED_EXECUTION_NONSECURE and SPM)

  • Click OK
  • Open the configuration overview in SES and verify that the configs are set (Remember that you are able to search for configurations).

The process of building and flashing a multi-image project is covered in this link. Alternatively, follow the steps below:

  • Click on Build→Build Solution

  • To download the project, click on Target→Download zephyr/merged.hex

  • If the led blinks, you have successfully created a non-secure application

2.2.5 Modify the device tree

This section will show you how you can modify and add to the the device tree. As mentioned in the Device Tree theory, this can be achieved by using overlay files, which will override the fields set by dts/dsti files. Specifically, I will show how to use LED 4 instead of LED 1 for the PWM output.

  • Add a file named nrf9160_pca10090ns.overlay into <sourcecode_root>/ncs/nrf/samples/nrf9160/light_controller 


Be aware that the ns extension is added at the end of the file name, which should always be the case when building non secure applications. If you are building a secure application, the ns extension should be omitted.

  • Open the overlay file in notepad and add the following lines into it:

&pwm0 {
	ch0-pin = < 5 >;

The PWM0 node's base position is in the the file nrf9160_common.dtsi, which gives a low level description of the peripheral, and you can see that it is disabled by default. Since most of the peripherals are disabled by default (dppic, rtc0, rtc1, clock, power and wdt are the exceptions), the status field must be set to "ok" in an overlay file in order to be used. 

However for PWM0, this is not necessary, since the file nrf9160_pca10090_common.dts overwrites it by setting PWM0's status field to "ok". In the same file you can also see that the PWM channel 0 is mapped to pin 2 (LED 1 on the nRF910 DK). The overlay file we made will override this pin mapping to 5 (LED 4 on the nRF910 DK).

  • Open a new nRF Connect SDK project and select the same setting used in section 2.2.4
    • In order for the changes from the overlay file to take effect the project must be re-opend

The ch0-pin field is then transformed into the define DT_NORDIC_NRF_PWM_PWM_0_CH0_PIN inside <...>\light_controller\build_nrf9160_pca10090ns\zephyr\include\generated\generated_dts_board_unfixed.h and used by the driver pwm_nrfx.c.

  • Eventually, build flash the application as described in section 2.2.4
  • Verify that everything works, by checking if LED 4 blinks, reset the board if it doesn't work immediately.

2.2.6 Add the ADC peripheral

Let's complete the project and add the ADC related part to our project, in order to control the LED intensity.

  • Swap the content of <..>\light_controller\src\main.c with the code attached below.

#include <zephyr.h>
#include <device.h>
#include <gpio.h>
#include <adc.h>
#include <string.h>
#include <pwm.h>

#if defined(CONFIG_BOARD_NRF9160_PCA10090NS) || defined(CONFIG_BOARD_NRF9160_PCA10090)

/*ADC defines and includes*/
#include <hal/nrf_saadc.h>
#define ADC_GAIN ADC_GAIN_1_6
#define ADC_1ST_CHANNEL_ID 0  
#define ADC_2ND_CHANNEL_ID 2

/*PWM defines*/

#error "Choose supported board or add new board for the application"

#define PWM_MAX 253
#define BUFFER_SIZE 1

struct k_timer my_timer;
struct device *adc_dev;
struct device *pwm_dev;

static const struct adc_channel_cfg m_1st_channel_cfg = {
	.gain = ADC_GAIN,
	.reference = ADC_REFERENCE,
	.acquisition_time = ADC_ACQUISITION_TIME,
	.channel_id = ADC_1ST_CHANNEL_ID,
	.input_positive = ADC_1ST_CHANNEL_INPUT,

static s16_t m_sample_buffer[BUFFER_SIZE];

static int adc_sample(void)
	int ret;

	const struct adc_sequence sequence = {
		.channels = BIT(ADC_1ST_CHANNEL_ID),
		.buffer = m_sample_buffer,
		.buffer_size = sizeof(m_sample_buffer),
		.resolution = ADC_RESOLUTION,

	if (!adc_dev) {
		return -1;

	ret = adc_read(adc_dev, &sequence);
	if (ret) {
        printk("adc_read() failed with code %d\n", ret);

	for (int i = 0; i < BUFFER_SIZE; i++) {
                printk("ADC raw value: %d\n", m_sample_buffer[i]);
                float val = ((float)PWM_MAX/(float)568)*(float)m_sample_buffer[i];
                printf("Setting pulse to: %f\n", val);
                pwm_pin_set_usec(pwm_dev, PWM_CH0_PIN , PWM_MAX, val);

	return ret;

void adc_sample_event(struct k_timer *timer_id){
    int err = adc_sample();
    if (err) {
        printk("Error in adc sampling: %d\n", err);

void main(void)
    int err;

    /*PWM0 setup*/
    pwm_dev = device_get_binding(PWM_DEVICE_NAME);
    if (!pwm_dev) {
	    printk("device_get_binding() PWM0 failed\n");

    /*Timer setup*/
    k_timer_init(&my_timer, adc_sample_event, NULL);

    /*ADC0 setup*/
    adc_dev = device_get_binding(ADC_DEVICE_NAME);
	if (!adc_dev) {
        printk("device_get_binding ADC_0 failed\n");

    err = adc_channel_setup(adc_dev, &m_1st_channel_cfg);
    if (err) {
	    printk("Error in adc setup: %d\n", err);

	while (1) {

  • Then add the following configs to your prj.conf.


Similarly as the case for the PWM configuration, the CONFIG_ADC_0 and CONFIG_ADC_NRFX_SAADC aren't included. The reason is due to default values set in respectively the files <..>\nrf9160_pca10090\Kconfig.defconfig and <..>\adc\Kconfig.nrfx.

  • Build and flash your application like we did in section 2.2.4. Remember to reload the nRF Connect Options, in order for the changes in prj.conf to take effect.

If everything works as intended, you should see that the light intensity of LED 4 depends on the voltage applied on P0.13. You may attach a potentiometer to P0.13.

Check out the nRF Connect SDK Tutorial - Part 2, which is a continuation of this tutorial and covers other important aspects of NCS.

If you have any questions regarding this tutorial, please create a ticket in DevZone.

  • :  What version of NCS are you using? In v1.0.0, which this tutorial is based on, misc/printk has to be used. Please check out the nRF Connect SDK Tutorial series, where NCS v1.2.0 is used (when this was written) and is perfectly compatible with the nRF9160 DK. In that tutorial, sys/printk is used.

    I have added a comment on the top of this tutorial, where I address this.

    Best regards,


  • Hello,
    On "hello world" section, the main.c must be remplaced by:

    #include <zephyr.h>
    #include <sys/printk.h>
    void main(void)
    printk("Hello World!\n");

    Because printk.h has moved from the misc folder to the sys folder.

    I'm not sure, but with this changement, my code works well