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.
Adding custom files
1. Create a .c
file and a .h
file in the same location as the main.c
file, let’s call them myfunction.c
and myfunction.h
.
2. 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;
}
CThen 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. 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)
CMakeThis line must go before target_sources()
.
4. 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"
CAlter main()
to run the function 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);
}
}
C5. 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
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. 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. In CMakeLists.txt
, we want the addition of the custom files to be conditional. Change the last line to use 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 only to include the source code of libraries that you plan to use in your project.
The reason for 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. Lastly, 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
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#ifdef CONFIG_MYFUNCTION
#include "myfunction.h"
#endif
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);
}
}
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
, then build and flash the application. You should see the following output:
11.1 In the prj.conf
file, change line 1 to CONFIG_MYFUNCTION=n
11.2 Build and flash the application as you have done previously.
*** 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 exercise, we will customize our device’s devicetree only for this application by changing the baud rate at which information is sent to the console. Changing the hardware description for a specific application is done through devicetree overlay files.
12. Create an overlay file in the application directory (the same location as CMakeLists.txt
and prj.conf
) 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
: <board_name>.overlay
. There are other ways to define overlays explained in nRF Connect SDK documentation.
In nRF Connect for VS Code, in the Details View, there is an option to create an overlay file with the same board name used for the build as show in the image below. Make sure to select the application context first. 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. 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
lines 16-38. 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.), the status (okay
, disabled
).
There are different methods to set devicetree overlay files. More details can be found here.
14. Do a pristine build and flash the sample to the board.
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 serial terminal is launched with 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. Close the window and open another session where you use the correct speed that was set in the overlay file, see images below. Now observe that the log is being printed like normal.
For the remaining settings, select 8 for data bits ,1 for stop bit, none for parity, and enable hardware flow control (rtscts: on).
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
of whichever version directory you are using.