nRF Connect SDK Intermediate – [Lesson 3] – Exercise 1 – Custom board for single-core SoC – v2.9.0 – v2.7.0

In this exercise, we will create a custom board definition for a board based on a single-core Nordic SoC. This board will not support TF-M support, and will have a single board target. We will use the nRF52833 SoC as an example, but the instructions will be applicable to other single-core SoCs.

The hardware for which we will develop the custom board is based on the nRF52833 DK. We assume that the new board will have almost the exact schematics, with the main exception being the absence of the Arduino shield. It will be kind of like a small-factor version of the Development Kit. We will use the DK’s Hardware Schematics as our starting point.

Note

If you are using a different development kit for this course, use the hardware schematic of your board as a starting point. They can be downloaded here:

nRF52833 DK schematics

Exercise steps

1. Create a new directory on your root directory and name it my_boards.

In this directory (for example C:\my_boards), we will store our custom board definitions.

2. Open VS Code, and from the Welcome page, click on Create a new board.

2.2 Select the nRF Connect SDK version to associate with the board.

2.3 You will be prompted with five steps:

  • Board name: This is the string that will be used in the board target. We will input devacademyl3e1.
  • Description: This is the human-readable name that we discussed in Creating board files. We will name our board DevAcademy L3E1.
  • Vendor name: Provide your company name (do not use whitespace). In our case, we will pass nordic for this exercise.
  • SoC: Select the SoC used by your custom board. This is a very critical step, and we need to select the exact SoC number (nRF52840, nRF52832, etc.) and variant used (QIAA, CIAA, etc.) on the custom board. We will be using the nRF52833 QIAA.
  • Board root: Set the root directory where your board will be defined. We will pass the directory we defined in step 1: C:\my_boards

3. Point the build system to the custom board root directory.

By default, the build system in nRF Connect SDK looks at specific folders for board definitions, specifically <SDK Installation Path>/zephyr/boards/ and <SDK Installation Path>/nrf/boards/.

We need to tell the build system to look at the directory we created in Step 1.

Navigate to the Settings of the nRF Connect Extension (File -> Preferences -> Settings -> Extensions -> nRF Connect -> Board Roots -> Add Item), then add the directory defined in Step 1.

More on this

Alternatively, one can specify this at build time. Specify the location of the custom board information with the -DBOARD_ROOT parameter to the build system.

This can be done both using CLI (west)

west build -b <board target> -- -DBOARD_ROOT=<path to boards>

or using the Add Build Configuration GUI, by passing -DBOARD_ROOT=<path to boards> under Extra CMake arguments.

When using -DBOARD_ROOT=<board-root>, both absolute and relative paths can be used. Relative paths are treated relative to the application directory.

4. Open the custom board definition folder in VS Code.

In VS Code, head to File -> Open Folder, then navigate to the custom board definition folder we created in the previous steps then select the folder of the newly created board. This will open the board’s files in the Explorer of VS Code.

5. Add Kconfig options to devacademyl3e1_defconfig

5.1 Open the file devacademyl3e1_defconfig.

This file is a Kconfig fragment that is merged as-is into the final build of any application built for the specified board. In addition to the default Kconfig symbols in this file, we need to add any Kconfig symbols we want to be enabled for any application built for our board. In the case of the DevAcademyL3E1, since the schematic contains an interface MCU with RTT support and USB/Serial converter, we want to add UART, RTT. We will also enable GPIO support.

5.2 Add the following lines to the end of the file

# Enable RTT
CONFIG_USE_SEGGER_RTT=y

# enable GPIO
CONFIG_GPIO=y

# enable uart driver
CONFIG_SERIAL=y

# enable console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y

6. Create a new application based on the Hello World sample.

Create an application based on the Hello World sample, save it somewhere on your disk (do not save it in the boards directory). Then build the application for the new custom board, as shown in the screenshot below. The sample will not be runnable at the beginning. The purpose here is to build the sample so that the DeviceTree Visual Editor will be able to parse the project.

7. Go to the DeviceTree Visual Editor.

Once the build is complete, press on the DeviceTree in the ACTIONS Panel.

The DeviceTree Visual Editor provides an overview of the devicetree context and a visual representation of the devicetree structure of your project. We will use it along with the devicetree text editor to seamlessly populate the devicetree files.

Notice that it used the default devacademyl3e1.dts as a starting point. It also pulls in the SoC devicetree, since it’s included in the devacademyl3e1.dts. The SoC devicetree has some nodes enabled, but most are disabled (For example, the GPIOs are disabled). Therefore, we will be enabling these nodes in the board-level devicetree file devacademyl3e1.dts .

8. Edit the board’s devicetree.

8.1 Enable GPIOs and GPIOTE.

The first thing we need to enable in the devicetree is the GPIOs and GPIOTE. Open the file devacademyl3e1.dts and add the following lines at the end of it.

&gpiote {
	status = "okay";
};

&gpio0 {
	status = "okay";
};

&gpio1 {
	status = "okay";
};
C

8.2 Define LEDs and Buttons

The Buttons and LEDs on the schematics are connected to the DevAcademyL3E1 custom board as shown below:

8.2.1 Adding LEDs

The GPIOs connected to the four LEDs are P0.13, P0.14 , P0.15, and P0.16, and the LEDs are connected to VDD on the other end. Therefore, we need to configure the GPIOs as active low. We will use add this inside text editor to do that, as shown below:

	leds {
		compatible = "gpio-leds";
		led0: led_0 {
			gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
			label = "Green LED 0";
		};
		led1: led_1 {
			gpios = <&gpio0 14 GPIO_ACTIVE_LOW>;
			label = "Green LED 1";
		};
		led2: led_2 {
			gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
			label = "Green LED 2";
		};
		led3: led_3 {
			gpios = <&gpio0 16 GPIO_ACTIVE_LOW>;
			label = "Green LED 3";
		};
	};
C

You need to add the above Devicetree nodes inside the root node, as shown below:

8.2.2 Adding buttons

The GPIOs connected to the four buttons are P0.11, P0.12, P0.24, and P0.25, and the buttons are connected to the ground on the other end. Therefore, we must pull up these GPIOs and make them active low. This is done as illustrated below:

8.2.2.1

Add the following #include at the top of the file

#include <zephyr/dt-bindings/input/input-event-codes.h>
C

as shown below:

8.2.2.1 Add the below Devicetree nodes inside the root node, below the leds, as shown below:

	buttons {
		compatible = "gpio-keys";
		button0: button_0 {
			gpios = <&gpio0 11 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Push button switch 0";
			zephyr,code = <INPUT_KEY_0>;
		};
		button1: button_1 {
			gpios = <&gpio0 12 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Push button switch 1";
			zephyr,code = <INPUT_KEY_1>;
		};
		button2: button_2 {
			gpios = <&gpio0 24 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Push button switch 2";
			zephyr,code = <INPUT_KEY_2>;
		};
		button3: button_3 {
			gpios = <&gpio0 25 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
			label = "Push button switch 3";
			zephyr,code = <INPUT_KEY_3>;
		};
	};
C

8.3 Adding Serial Console / UART

Pins P0.05, P0.06, P0.07, and P0.08 on the nRF52833 SoC are connected on the schematic to an Interface MCU that does USB<->UART conversion.

8.3.1 Enable the UART node, set the default baud rate, and the pinctrl states. Add the following code at the end of the devacademyl3e1.dts file

&uart0 {
	compatible = "nordic,nrf-uarte";
	status = "okay";
	current-speed = <115200>;
	pinctrl-0 = <&uart0_default>;
	pinctrl-1 = <&uart0_sleep>;
	pinctrl-names = "default", "sleep";
};

8.3.2 The pin mappings of the various nodes (except for LEDs and buttons) must be done in a separate file called the pinctrl file(<board_name>-pinctrl.dtsi). Add the following pinctrl definitions inside the &pinctrl in devacademyl3e1-pinctrl.dtsi

	uart0_default: uart0_default {
		group1 {
			psels = <NRF_PSEL(UART_TX, 0, 6)>,
				<NRF_PSEL(UART_RTS, 0, 5)>;
		};
		group2 {
			psels = <NRF_PSEL(UART_RX, 0, 8)>,
				<NRF_PSEL(UART_CTS, 0, 7)>;
			bias-pull-up;
		};
	};

	uart0_sleep: uart0_sleep {
		group1 {
			psels = <NRF_PSEL(UART_TX, 0, 6)>,
				<NRF_PSEL(UART_RX, 0, 8)>,
				<NRF_PSEL(UART_RTS, 0, 5)>,
				<NRF_PSEL(UART_CTS, 0, 7)>;
			low-power-enable;
		};

8.3.3 Add the following three lines inside the node named chosen in devacademyl3e1.dts.

		zephyr,console =  &uart0 ;
		zephyr,shell-uart =  &uart0 ;
		zephyr,uart-mcumgr = &uart0;

The chosen node’s properties are used to configure system- or subsystem-wide values. So, these values are referenced by the different software modules you configured, usually through the DT_CHOSEN() macro. See the example of the UART Console and the UART MCUmanager and the shell UART backend.

Note that there are other “chosen” properties related to Bluetooth LE boards. Namely: zephyr,bt-mon-uart and zephyr,bt-c2h-uart if you plan to use the Host monitor or HCI over UART (Expose the Bluetooth LE controller to external MCU), respectively.

8.4 Adding I2C, SPI, and PWM Peripherals

8.4.1 I2C

The P0.26 and P0.27 GPIOs are used for I2C peripheral. On the nRF52833, there are two I2C peripherals , we will enable i2c0 and connect it to these two pins.

8.4.1.1 Add the following in devacademyl3e1-pinctrl.dtsi inside the &pinctrl node

	i2c0_default: i2c0_default {
		group1 {
			psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
				<NRF_PSEL(TWIM_SCL, 0, 27)>;
		};
	};

	i2c0_sleep: i2c0_sleep {
		group1 {
			psels = <NRF_PSEL(TWIM_SDA, 0, 26)>,
				<NRF_PSEL(TWIM_SCL, 0, 27)>;
			low-power-enable;
		};
	};

8.4.1.2 Enable the I2C0 node by setting its status to “okay” and set its pinctrl . Add the following code in devacademyl3e1.dts

&i2c0 {
	compatible = "nordic,nrf-twi";
	status = "okay";
	pinctrl-0 = <&i2c0_default>;
	pinctrl-1 = <&i2c0_sleep>;
	pinctrl-names = "default", "sleep";
}; 

8.4.2 SPI

We will use the dedicated SPI peripheral (spi1) on the nRF52833 SoC. Note that spi0 is shared with i2c0 as a serial unit so we can use only one at a time.

8.4.2.1 Add the following in devacademyl3e1-pinctrl.dtsi

	spi1_default: spi1_default {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 31)>,
				<NRF_PSEL(SPIM_MOSI, 0, 30)>,
				<NRF_PSEL(SPIM_MISO, 1, 8)>;
		};
	};

	spi1_sleep: spi1_sleep {
		group1 {
			psels = <NRF_PSEL(SPIM_SCK, 0, 31)>,
				<NRF_PSEL(SPIM_MOSI, 0, 30)>,
				<NRF_PSEL(SPIM_MISO, 1, 8)>;
			low-power-enable;
		};
	};

8.4.2.2 Enable the SPI1 node by setting its status to “okay” and set its pinctrl . Add the following code in devacademyl3e1.dts

&spi1 {
	compatible = "nordic,nrf-spi";
	status = "okay";
	pinctrl-0 = <&spi1_default>;
	pinctrl-1 = <&spi1_sleep>;
	pinctrl-names = "default", "sleep";
};

8.4.3 PWM

Let’s enable a PWM peripheral. There are 4 dedicated PWM peripherals on the nRF52833 SoC. We will use PWM0 and connect it’s output to LED0.

8.4.3.1 Add the following in devacademyl3e1-pinctrl.dtsi for the pin assignment.

pwm0_default: pwm0_default {
	group1 {
		psels = <NRF_PSEL(PWM_OUT0, 0, 13)>;
		nordic,invert;
	};
};

pwm0_sleep: pwm0_sleep {
	group1 {
		psels = <NRF_PSEL(PWM_OUT0, 0, 13)>;
		low-power-enable;
	};
};

8.4.3.2 Enable the PWM0 node by setting its status to “okay” and set its pinctrl. Add the following code in devacademyl3e1.dts

&pwm0 {
	status = "okay";
	pinctrl-0 = <&pwm0_default>;
	pinctrl-1 = <&pwm0_sleep>;
	pinctrl-names = "default", "sleep";
};
Devicetree

8.4.3.3 In the root node, add the node pwmleds of type pwm-leds and set the channel and the default period

	pwmleds {
	compatible = "pwm-leds";
	pwm_led0: pwm_led_0 {
		pwms = <&pwm0 0 PWM_MSEC(20) PWM_POLARITY_INVERTED>;
	};
};

8.5 The samples in nRF Connect SDK and Zephyr OS uses fixed aliases for devicetree nodes. Therefore, to ensure that samples find these aliases, it’s crucial to add aliases in the board devicetree file . Add the following inside the / root node.

	/* These aliases are provided for compatibility with samples */
	aliases {
		led0 = &led0;
		led1 = &led1;
		led2 = &led2;
		led3 = &led3;
		pwm-led0 = &pwm_led0;
		sw0 = &button0;
		sw1 = &button1;
		sw2 = &button2;
		sw3 = &button3;
		bootloader-led0 = &led0;
		mcuboot-button0 = &button0;
		mcuboot-led0 = &led0;
		watchdog0 = &wdt0;
	};

8.6 If you plan to use other peripherals on your device on all applications built for this board, for example, the USB device peripheral. You will also need to enable its node in the board devicetree.

Testing

9. Build and flash the application to your board.

In this part, we will build and flash different samples on the custom board devacademyl3e1/nrf52833 for testing purposes. You can see the custom board from the Add Build Configuration Window, as shown below.

9.1 Test Serial Console/UART.

Build the “Hello World” sample for the target devacademyl3e1/nrf52833 and flash it to your board. You should see the following output.

*** Booting nRF Connect SDK ***
Hello World! devacademyl3e1/nrf52833
Terminal

With this, we have validated that the serial Console / UART is working properly.

9.2 Test LEDs and buttons.

Build the “Button” Sample for the target devacademyl3e1/nrf52833 and flash it to your board . You should see that everytime you press Button 1 on the DK , LED 1 is turned on. Also you should see the following output on the terminal.

*** Booting nRF Connect SDK ***
Set up button at gpio@50000000 pin 11
Set up LED at gpio@50000000 pin 13
Press the button
Button pressed at 89700
Button pressed at 109857
Button pressed at 128976
Button pressed at 163524
Terminal

9.3 Test PWM.

Build the “PWM LED ” Sample for the target devacademyl3e1/nrf52833 and flash it to your board. You should see that LED 1 is turned on and off and then gradually start fading. Also, you should see the following output on the terminal.

*** Booting nRF Connect SDK 2.6.1-3758bcbfa5cd ***
[00:00:00.365,966] <inf> main: Testing LED 0 - no label
[00:00:00.371,826] <inf> main:   Turned on
[00:00:01.376,647] <inf> main:   Turned off
[00:00:02.381,530] <inf> main:   Increasing brightness gradually
[00:00:04.408,447] <inf> main:   Blinking on: 0.1 sec, off: 0.1 sec
Terminal

Note: Not all Cycle periods are supported on all hardware.

9.4 Test the Bluetooth LE Radio.

The radio node for the Bluetooth LE is already enabled “status = "okay" in the SoC devicetree file. Therefore you don’t need to do any changes in the board devicetree file. However, if you plan to use the IEEE 802.15.4 radio (for Zigbee or Thread), you will need to enable the ieee802154 and set the chosen property zephyr,ieee802154 = &ieee802154 in the board devicetree file.

Build the “BLE UART service ” or the “BLE LED Button Service” sample for the target devacademyl3e1/nrf52833 and flash it to your board and test the sample as specified in the document for the selected sample.

9.5 Test I2C and SPI.

To test I2C or SPI you would need external components. You could follow the instructions in these lessons for I2C and SPI.

The solution for this exercise can be found in the GitHub repository for this course.

Switch language?

Progress is tracked separately for each language. Switching will continue from your progress in that language or start fresh if you haven't begun.

Your current progress is saved, and you can switch back anytime.

Register an account
Already have an account? Log in
(All fields are required unless specified optional)

  • 8 or more characters
  • Upper and lower case letters
  • At least one number or special character

Forgot your password?
Enter the email associated with your account, and we will send you a link to reset your password.