During application development, it can be useful to customize aspects of the nRF Connect SDK for your specific application, without modifying the SDK files themselves.
In this exercise, we will customize our application by adding custom files, configurations, and modifying the devicetree.
Use the previous exercises as your starting point. Copy the previous completed exercise to a new directory and name it l3_e2
.
Adding custom files
1. Create a .c
file and a .h
file in the same location as the main.c
file, and call them myfunction.c
and myfunction.h
.
2.1 Define the function sum()
in the
file and make sure to include the header file.myfunction.c
#include "myfunction.h"
int sum(int a, int b){
return a+b;
}
C2.2 Then declare the function in the myfunction.h
header file.
#ifndef MY_FUNCTION_H
#define MY_FUNCTION_H
int sum(int a, int b);
#endif
CThe first two lines and the last lines are called include guards.
Include guards: A construct, in this case, a macro, that is used to avoid the problem of double inclusion, which happens if the header file is included twice, thereby rendering the contents invalid.
3. Include the custom files in the build.
To include the custom files in the build, we use the CMake function target_sources()
. Add the following line to CMakeLists.txt
target_sources(app PRIVATE src/myfunction.c)
CMake4. Include the header file for the custom function.
Now that the files are included in the build, we just need to include the header file in main.c
to run the function.
#include "myfunction.h"
C5. Replace main()
to run the custom function.
Alter main()
to run the function sum()
that we have defined.
int main(void)
{
int a = 3, b = 4;
while(1){
printk("The sum of %d and %d is %d\n", a, b, sum(a,b));
k_msleep(1000);
}
}
C6. Build and flash the application to your board.
Open a terminal output and you should see the following output.
*** Booting nRF Connect SDK v2.8.0-preview1-11645184a54d ***
*** Using Zephyr OS v3.7.99-adcffa835a8e ***
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
TerminalThe issue with including source code through target_sources()
is that we will include this file in our build regardless of whether we will use it. In the next paragraph, we will learn how to control the inclusion of source code using Kconfig symbols and target_sources_ifdef()
.
Adding custom configurations
6. Define a custom Kconfig for enabling myfunction
.
Now, let’s define our own config that will determine if our custom files get included in the build or not. To do this, create a file called Kconfig
in the application directory (the same location as CMakeLists.txt
and prj.conf
).
Make sure the file does not have a file extension. Depending on your editor, an extension may be added by default and has to be removed manually.
source "Kconfig.zephyr"
config MYFUNCTION
bool "Enable my function"
default n
KconfigThe first line sources Kconfig.zephyr
, which is necessary when defining a new Kconfig file to source Zephyr RTOS configurations. The next three lines define the configuration CONFIG_MYFUNCTION
as a boolean variable and sets its default value to n
.
If you are interested in learning more about creating menus in Kconfig, here is a link to documentation with more details.
7. Make the addition of custom files conditional on the Kconfig.
In CMakeLists.txt
, we want the addition of the custom files to be conditional. Comment out the line from step 3 and add the following line using the function target_sources_ifdef()
, like this:
target_sources_ifdef(CONFIG_MYFUNCTION app PRIVATE src/myfunction.c)
CMakeThe build will now only include the custom file myfunction.c
if CONFIG_MYFUNCTION
is enabled.
This strategy is used intensively in nRF Connect SDK to only include the source code of libraries that you plan to use in your project.
This is to limit the size of your application. Modules and subsystems are only included in the build when you enable the relevant configuration, allowing you to keep the application as small as you wish.
8. Enable the Kconfig for myfunction
.
Enable the config by adding the following line to prj.conf
:
CONFIG_MYFUNCTION=y
Kconfig9. Update your main.c file so that it can check if the Kconfig symbol is enabled/disabled
9.1 Check CONFIG_MYFUNCTION
before including the header file.
#ifdef CONFIG_MYFUNCTION
#include "myfunction.h"
#endif
C9.2 Check CONFIG_MYFUNCTION
before calling sum()
.
int main(void)
{
while (1) {
#ifdef CONFIG_MYFUNCTION
int a = 3, b = 4;
printk("The sum of %d and %d is %d\n", a, b, sum(a, b));
#else
printk("MYFUNCTION not enabled\n");
return 0;
#endif
k_msleep(1000);
}
return 0;
}
C10. Build and flash the application to your board and you should see the following output.
*** Booting nRF Connect SDK v2.8.0-preview1-11645184a54d ***
*** Using Zephyr OS v3.7.99-adcffa835a8e ***
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
The sum of 3 and 4 is 7
Terminal11. Try disabling the Kconfig symbol in prj.conf
.
11.1 In the prj.conf
file, change the first line to CONFIG_MYFUNCTION=n
11.2 Build and flash the application as you have done previously.
You should see the following log output.
*** Booting nRF Connect SDK v2.8.0-preview1-11645184a54d ***
*** Using Zephyr OS v3.7.99-adcffa835a8e ***
MYFUNCTION not enabled
Terminal11.3 Reenable the CONFIG_MYFUNCTION
symbol (CONFIG_MYFUNCTION=y
) as we need it for the next section, “Modifying the devicetree”.
Modifying the devicetree
In this section, we will modify the devicetree of the device’ only for this application by changing the baud rate (seed) at which information is sent to the console. Changing the hardware description (devicetree) for a specific application is done using devicetree overlay files.
12. Create an overlay file in the application directory with the name of the board you’re using.
In the application directory (the same location as CMakeLists.txt
and prj.conf
), create a directory named boards
. In the boards
directory, create an overlay file with the name of the board you’re using, in our case nrf52833dk_nrf52833.overlay
. The main rule is that the file name must be the same name as the target board, and the file must end with .overlay
, e.g <board_target>.overlay
. There are other ways to define overlays explained in nRF Connect SDK documentation.
Note that if you are using a device with multiple targets (e.g nRF54L15 DK, nRF9151 DK), make sure you use the board target, not the board name. For example nrf54l15dk_nrf54l15_cpuapp.overlay
, not nrf54l15dk_nrf54l15dk.overlay
(which will not work).
In nRF Connect for VS Code, there is an option to create an overlay file with the same board name used for the build as shown in the image below. Make sure to select the application context first (1). Then open Config files -> Devicetree to reveal the option to + Create overlay file (2).
This GUI also lets you view all the nodes in the devicetree and will help you with tips on the correct syntax to use.
13. Change the baud rate for the UART node.
We want to change the baud rate for the UART instance used when printing to console, which is UART0 (UART20 for the nRF54L15). The baud rate is the property current-speed
in the nrf-uart
binding, which is defined and specified in nrf-uart-common.yaml
. Add the following to the overlay file, which can be found in the root directory of the application, to change this property:
uart0
is used for all Nordic DKs except the nRF54L15 DK.
&uart0 {
current-speed = <9600>;
};
Devicetreeuart20
is used for the nRF54L15 DK.
&uart20 {
current-speed = <9600>;
};
DevicetreeOther common things to change in the devicetree are the pins used by peripherals (rx
, tx,
cts
, etc.), and the status (okay
, disabled
).
There are different methods to set devicetree overlay files. More details can be found here: Set devicetree overlays.
14. Do a pristine build and flash the application to the board.
In the Actions window, select this icon to the right of the Build action to do a pristine build.
If this icon does not appear for you, you can alternatively invoking the command palette of VS Code (Ctrl + Shift + P in Windows) and search for Pristine Build to trigger it.
Pristine build: Creates a new build directory. All byproducts from previous builds have been removed.
To confirm that the devicetree was changed, we can view the compiled devicetree output.
This can be done by invoking the command palette of VS Code (Ctrl + Shift + P in Windows) and typing the command nRF DeviceTree: Open Compiled Output. Inside the Compiled devicetree file, search for the node-name uart0
(1) and find the node in the devicetree (2). Here we can see that the current-speed
has been changed to the value set in the overlay file (3).
This file can also be found at the path l3_e2/build/l3_e2/zephyr/zephyr.dts
You could also open the DeviceTree Visual Editor and navigate to soc-> uart0 to examine this node’s properties. For a multi-core SoC, it will be soc->peripheral->uart0. The Devietree Visual Editor needs a compiled application to process the project’s devicetree structure.
If the DeviceTree window is not visible in VS Code, it means you don’t have the nRF DeviceTree extension installed in VS Code. Make sure you install it by going, to Extensions, and searching for nRF Devicetree. The nRF DeviceTree is also part of the nRF Connect for VS Code Extension Pack.
15. Observe that the serial terminal doesn’t show any output.
This is because we changed the baud rate in the application to 9600 baud/sec while the terminal is still using the default baud rate of 115200 baud/sec. Which means, the serial terminal is trying to read the log output at the wrong speed.
16. Change the baud rate in the serial terminal.
1. Close and open a new terminal.
2. Change the baud rate to 9600.
For the remaining settings:
3. Observe the log output.
1. Change the baud rate to 9600.
2. Connect to the device.
Select Connect to port to connect to the device using the current settings
3. Observe the log output.
17. Devicetree Visual Editor (further reading).
Once we have a devicetree overlay for an application and a build application, we can now use the Devicetree Visual Editor, which allows us to edit devicetree structure using an intuitive GUI.
Through the DeviceTree visual editor, we can also explore the available properties in each node and modify entries directly using the visual editor. It is important to know that the Devicetree visual editor and devicetree text editor methods are interchangeable. Therefore, it is a great tool for learning Devicetree syntax.
We can access the Devicetree visual editor from the Actions view:
See the illustration below showing how the Devicetree Visual Editor is used to set the following UART configurations:
Note that the Devicetree visual editor is populating the Devicetree overlay file for you instead of having to remember the syntax of Devicetree.
The solution for this exercise can be found in the GitHub repository, l3/l3_e2_sol
of whichever version directory you are using.