In the previous article, we ran a minimal Hello World application. The original code is simply
// Copyright (c) 2024 Qingdao IotPi Information Technology Ltd., All rights Reserved
#include <zephyr/kernel.h>
int main(void){ printk("Hello World! %s\n", CONFIG_BOARD); return 0;}
which can output “Hello World” without any hardware configuration. This also reflects the hardware abstraction capability of ZephyrRTOS: hardware-related configurations are achieved by calling specific drivers through device tree configuration files.
Thus, the same code can achieve the same functionality on different boards by executing
west build -p auto -b nrf52840dk_nrf52840 .
west build -p auto -b nrf5340dk_nrf5340 .
west build -p auto -b stm32f4_disco .
west build -p auto -b nucleo_f411re .
Here, -b xxxxx specifies the target board for compilation and execution. In Zephyr RTOS, there are some community-maintained standard development boards: https://docs.zephyrproject.org/latest/boards/index.html
In the original code, you can check the boards supported by your current repository version by looking at zephyr/boards/<arch> (for example, SoCs with ARM architecture can be found in zephyr/boards/arm). You can also define your own development boards in your code.
To get started with device tree syntax, refer to https://docs.zephyrproject.org/latest/build/dts/intro-syntax-structure.html or search for more detailed tutorials.
I am developing using the nRF5340DK, so the board definition is in zephyr/boards/arm/nrf5340dk_nrf5340/nrf5340dk_nrf5340_cpuapp_ns.dts. Since the nRF5340 has both an application core and a network core, here cpuapp corresponds to the application core, and _ns corresponds to the non-secure mode.
Of course, this file is just the entry point for the board definition. It includes multiple layers of other dtsi include files. For example, it includes two files: nrf5340_cpuapp_common.dtsi and nordic/nrf5340_cpuapp_qkaa.dtsi. The former is a file in the same directory, while the latter is a SoC file defined in zephyr/dts/arm/nordic.
In nrf5340_cpuapp_common.dtsi, the following definition exists:
Here, zephyr,console = &uart0 specifies which uart is used for console output,
In the application code directory: app1, create a directory boards and save a file named: nrf5340dk_nrf5340_cpuapp_ns.overlay with the following content
/ {
chosen {
zephyr,console = &uart1;
};
};
&uart1 {
status = "okay";
};
Here, zephyr,console changes the default uart device used for console, and the following section &uart1 enables the device driver.
After refreshing the firmware and running it, output will be displayed on ttyACM0 (uart1 corresponds to ttyACM0, and uart0 corresponds to ttyACM2) showing the printk output.
By using device tree to define different hardware, the same code can be executed correctly on different hardware. Additionally, hardware information can be flexibly defined as needed. For example, I am currently controlling the air780eg modem, and I can easily change SoCs without modifying the source code, as long as I define different boards separately, which greatly reduces the cost of hardware porting.