Zephyr Project Board Porting Guide#

Texas Instruments™

2 hours read

Introduction#

Welcome to this lab, which will teach you how to add Zephyr support to a custom board. Make sure to pay attention to all of the required hardware and software and to follow the steps carefully to complete your setup.

We’ve seen in a previous SimpleLink Academy training how to build and flash the Blinky project onto our board. When building the Blinky project, we used the command west build -p always -b {BOARD_IDENTIFIER} samples/basic/blinky. The two most important parts of this command are the path to the sample to build, and the board identifier. This board identifier indicates to Zephyr which board configuration should be used to building the firmware.

The list of all supported boards from all supported silicon makers can be found by running west boards. But one may notice that for Texas Instruments™, only LaunchPads are supported by Zephyr. If we want to use Zephyr running on a SimpleLink MCU on a custom board, we need to create our own custom board configuration.

The goal of this lab is to help you create your own board configuration based on the board configuration for Texas Instruments’s LaunchPads. We will first explain how Zephyr builds firmwares in order to understand what is the role of the board configuration, its link with the MCU configuration and with the drivers. Then we will see how a board configuration is structured using devicetrees to describe the hardware specificities of the board. Finally, we will create a new board configuration for a custom board, and blink the LED of this custom board.

Prerequisites#

  • For this lab, a host computer running Linux is needed, preferably Ubuntu as this is what this lab will use.

  • You should already have setup the Zephyr development environment for SimpleLink boards. If this is not already done, you can follow the SimpleLink Academy training about setting up the Zephyr environment.

  • In order to flash a firmware onto the board, two options are possible :

    • XDS110 LaunchPad™ development kit debugger, which should include a USB-C to USB-A cable for connecting the XDS110 to a computer.

    • J-Link Debug Probe, from SEGGER. The J-Link Plus debug probe was used for development and testing, but other J-Link debug probes might work too.

      Warning

      Older versions of J-Link debug probes hardware are not supported because they do not support dormant mode handling. While version 12.0 is known to be working, version 10.1 is known to be not supported.

  • You will need a development board which is supported by Zephyr. Here is the list of all boards officially supported by Texas Instruments™ :

Warning

Other Texas Instruments™ boards are supported in Zephyr, but are
maintained by the Zephyr community instead of Texas Instruments™. While we recommend using one of the boards mentioned above, you could also use one of these boards, but your mileage may vary :

Understanding how Zephyr builds software#

The Device model#

One of the most important selling point of Zephyr is that it is silicon vendor independent. The way Zephyr code manages to be independent from the hardware running it is by having a common API for all boards. Let’s use the example of the Blinky sample.

main.c::main – Code of the Blinky sample#
/*
 * Copyright (c) 2016 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>

/* 1000 msec = 1 sec */
#define SLEEP_TIME_MS   1000

/* The devicetree node identifier for the "led0" alias. */
#define LED0_NODE DT_ALIAS(led0)

/*
 * A build error on this line means your board is unsupported.
 * See the sample documentation for information on how to fix this.
 */
static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

int main(void)
{
	int ret;

	if (!gpio_is_ready_dt(&led)) {
		return 0;
	}

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
		return 0;
	}

  ...

  ret = gpio_pin_toggle_dt(&led);

  ...
}

In this code, we are calling the gpio_pin_toggle_dt(&led) function to toggle the GPIO value for the led. This function then calls a GPIO driver, which will write in the correct register to toggle the GPIO. However, toggling a GPIO on a Texas Instruments™ MCU is a different process from toggling a GPIO on a MCU from another silicon vendor. This means that we need to have different drivers for each manufacturer.

The way Zephyr manages to conciliate all silicon vendors is by using Devices. A Zephyr device can be roughly simplified as the following :

  • Configuration information

  • A driver implementation for a specific subsystem.

Note

In reality a device is much more than those two elements. A device also has a name field and a state field. They also contain optional fields like dependencies to other devices, power management resources or metadata. Since these fields are not essential for this training, we will ignore them, but it’s good to be aware that Zephyr devices are much more powerful than driver implementations and configuration information.

The subsystem can be something like GPIO, SPI, UART, sensors, and more. Zephyr will use devices to know the specificities of vendor-specific hardware, allowing it to know the correct configuration information and the correct driver for each subsystem should be used at build time.

For example, let’s take a GPIO device for the CC2340R53 LaunchPad, like a LED. This device has a driver implementation for all functions of the GPIO driver API. This device also has configuration information, such as :

  • The GPIO register

  • The number of total GPIOs

  • The DIO line of the GPIO

  • The initial value for the GPIO

  • Etc …

If we go back to the example of the Blinky sample, the led variable used in the instruction gpio_pin_toggle_dt(&led) is the specification of a device. This allows the gpio_pin_toggle_dt to know which driver implementation should be called, and with which parameters.

gpio.h – Code of the gpio_pin_toggle_dt and associated functions#
static inline int gpio_pin_toggle_dt(const struct gpio_dt_spec *spec)
{
	return gpio_pin_toggle(spec->port, spec->pin);
}

static inline int gpio_pin_toggle(const struct device *port, gpio_pin_t pin)
{
	[...]
	return gpio_port_toggle_bits(port, (gpio_port_pins_t)BIT(pin));
}

static inline int gpio_port_toggle_bits(const struct device * port, gpio_port_pins_t pins)
{
	[...]
	return z_impl_gpio_port_toggle_bits(port, pins);
}

static inline int z_impl_gpio_port_toggle_bits(const struct device *port,
					       gpio_port_pins_t pins)
{
	const struct gpio_driver_api *api =
		(const struct gpio_driver_api *)port->api;

	return api->port_toggle_bits(port, pins);
}

As you can see from the functions called by gpio_pin_toggle_dt, they culminate in a function called z_impl_gpio_port_toggle_bits. This function then uses the driver API for our LED device, and calls a function from this API, with the configuration information as the parameters for this function call. The driver function called is a function specific to the device, which is therefore specific to the silicon vendor given in the build parameters.

We now understand that Zephyr uses devices to tell the firmware which driver functions to use and what data to give them. This means that the only thing we have to do to add Zephyr support to our custom board is to create a list of devices that are specific to our board, and which can be used by developers when calling the Zephyr subsystem APIs. This list of devices in Zephyr are known as Devicetrees.

Zephyr devicetrees#

A Zephyr devicetree is a hierarchical data structure that describes the hardware we are using. The two most important hardware that we will describe are the SoC and the board. Devicetrees are written in a devicetree grammar which is very similar to the devicetree grammar from Linux. These devicetrees will then be transformed into C header files that can be included and used inside a Zephyr project.

Here is an example of a very minimal devicetree :

Example devicetree with a system clock device.#
/dts-v1/;

/ {
	sysclk: system-clock {
		compatible = "fixed-clock";
		clock-frequency = <48000000>;
		#clock-cells = <0>;
	};
};

Since devicetrees are hierarchical data structures, a devicetree defines nodes, which can be the children of another node. You can see all the nodes in the devicetree as different devices. In this example, we describe two nodes :

  • The root node /

  • The node named system-clock, which is the child of the root node

As we said previously, we will abstract a device to be only configuration information and a driver implementation. In Zephyr devicetrees, the driver implementation is defined by the compatible field of the node. From the driver’s point of view, the driver implementation defines a DT_DRV_COMPAT macro with a unique string, and all devicetree nodes with their compatible field being equal to this DT_DRV_COMPAT will be associated with this device. This string usually is the brand of the silicon vendor and the name of the driver. For example, here is how the Bluetooth device on the CC2340R53 LaunchPad board gets associated to the right Bluetooth HCI driver.

hci_ti.cDT_DRV_COMPAT macro definition in the TI Bluetooth HCI driver.#
[...]
#define DT_DRV_COMPAT ti_bt_hci
[...]
lp_em_cc2340r53.dts – Bluetooth HCI device for the CC2340R53 LaunchPad board.#
[...]

/ {
  [...]

	ti_bt_hci: ti_bt_hci {
		compatible = "ti,bt-hci";
		status = "okay";
	};
};

[...]

In this example, the value of the compatible field is ti,bt-hci, and it is successfully matched against the DT_DRV_COMPAT value of the TI Bluetooth HCI driver. The build toolchain of Zephyr will therefore build the hci_ti.c file and use it as the Bluetooth HCI driver for the CC2340R53 LaunchPad board.

Note

In the example, the compatible field string is a little bit different than the DT_DRV_COMPAT values. This is because the DT_DRV_COMPAT value should not have any special characters, and so all special characters from the compatible field are replaced by underscores.

Now that we know how the drivers are associated with devices, we can see how the devices are associated with configuration data. Using the previous example of a very minimal devicetree :

Example devicetree with a system clock device.#
/dts-v1/;

/ {
	sysclk: system-clock {
		compatible = "fixed-clock";
		clock-frequency = <48000000>;
		#clock-cells = <0>;
	};
};

In this example, clock-frequency and #clock-cells are fields that can be used in the driver as configuration data. But this data on its own is useless, it needs additional information to be correctly interpreted. How can the driver know the type of the #clock-cells field ? Is it a float, a double or an integer ? Is it signed or not ? The solution to this problem is Zephyr devicetree binding.

Zephyr devicetree bindings#

Zephyr devicetree bindings are files that describe rules for the configuration data needed for the drivers of a device. These usually include properties like their type, or a human-readable description, but can be more specific. For example, here is the Device bindings file for the fixed-clock driver :

fixed-clock.yaml – Device Bindings for the fixed-clock driver#
# Copyright (c) 2019 Derek Hageman <hageman@inthat.cloud>
# SPDX-License-Identifier: Apache-2.0

description: Generic fixed-rate clock provider

compatible: "fixed-clock"

include: clock-controller.yaml

properties:
  clock-frequency:
    type: int
    description: output clock frequency (Hz)
    required: true

  "#clock-cells":
    const: 0

In this example of the device bindings for the fixed-clock driver, we find the two properties that were defined in the device tree that used this driver. The property clock-frequency has a type int, a description, and is required to use the driver.

This is useful for two reasons :

  • The bindings create rules that the devicetrees have to follow, and leaves no room for interpretation of what the devicetree actually describes. Thanks to the rules created by the bindings, the syntax and properties of the devicetrees can be verified at compile time instead of causing an error at runtime.

  • The rules and the properties extracted from the bindings allow the devicetrees to be transformed into useful C code, that can be used by driver functions in Zephyr.

The second point is the most important. The way that Zephyr creates C code that can change from a silicon maker to another is by converting the devicetree of the board into a large C header file called devicetree-generated.h. You can find this file after a successful build under zephyrproject/zephyr/build/zephyr/include/generated/zephyr/devicetree_generated.h. The Zephyr devicetree documentation provides a good visual representation of how this header file is generated. You can ignore the devicetree overlays, as they are project-specific.

../../_images/zephyr_dt_inputs_outputs.svg

Schema of how the Zephyr devicetree-generated.h file is generated.#

The devicetree-generated.h file contains a lot of C macros with a specific format. These macros are then used by the Zephyr driver functions and macros in order to use the properties that were defined in the devicetree. Here is an example of what such a devicetree-generated.h would look like for the system-clock node of our previous example devicetree.

devicetree-generated.h – Header file generated from the example device tree#
/*
 * Devicetree node: /system-clock
 *
 * Node identifier: DT_N_S_system_clock
 *
 * Binding (compatible = fixed-clock):
 *   $ZEPHYR_BASE/dts/bindings/clock/fixed-clock.yaml
 *
 * (Descriptions have moved to the Devicetree Bindings Index
 * in the documentation.)
 */

/* Node's full path: */
#define DT_N_S_system_clock_PATH "/system-clock"

/* Node's name with unit-address: */
#define DT_N_S_system_clock_FULL_NAME "system-clock"

/* Node parent (/) identifier: */
#define DT_N_S_system_clock_PARENT DT_N

/* Node's index in its parent's list of children: */
#define DT_N_S_system_clock_CHILD_IDX 0

[...]

/* Generic property macros: */
#define DT_N_S_system_clock_P_clock_frequency 48000000
#define DT_N_S_system_clock_P_clock_frequency_EXISTS 1

In this example of the devicetree-generated.h, we can find the clock frequency property that we defined in our devicetree. It’s now usable from the C code by including the <devicetree.h> header in our driver implementation or in other places to extract this property. Since this header file is generated for each project and each board, this allows Zephyr to be completely silicon-vendor independent, because as long as every silicon vendor adds the clock-frequency to the system-clock device in their devicetree, they can build any project that uses a system clock.

Adding Zephyr support to a new board#

Now that we understand how Zephyr builds firmware from devicetrees, we will add Zephyr support for a new board. For this we will start from the board files for the CC2340R53 LaunchPad board, and we will transform the devicetree and board files to add Zephyr support to a custom board. The custom board used will be a Bluetooth Low Energy (BLE) mouse using a CC2340R53 MCU.

Task 1 : Creating a copy of the CC2340R53 LaunchPad board#

First of all, we will copy the CC2340R53 LauchPad board files. The files for this board are located under zephyrproject/zephyr/boards/ti/lp_em_cc2340r53. Lets copy this folder, paste it in the ti folder, and rename it cc2340r53_mouse.

Error

In this training, we will use the CC2340R53 LaunchPad as the source board files, because our board will use the CC2340R53. If your board uses a different MCU, it’s better to copy the board files from the LaunchPad of this MCU instead.

Warning

In this training, we will call our board cc2340r53_mouse. But you can replace cc2340r53_mouse by another string more adapted to your board. Be careful however to always replace the cc2340r53_mouse string by your board’s name. Your board name must have no space and must not have any special characters, except dashes and underscores.

The files in this folder can be split into multiple categories :

  • Documentation files

  • Board metadata files

  • Flash and Debug support files

  • Kconfig files

  • Devicetree files

Documentation files#

The documentation files are human-readable information about the board. These files are located under the cc2340r53_mouse/doc folder. It contains an index.rst file that acts as documentation for the board, and an img folder for images for the board. While those files are not necessary to build a firmware on our custom board, they help other readers to understand what the board is. If you plan on committing this board to the official Zephyr repository, those files are mandatory.

Board metadata files#

The board metadata files are files that give extra information about the board, but unlike the documentation files, those files are usually in YAML format to be easily read by a build script. The board metadata files include board.yml and lp_em_cc2340r5.yaml.

board.yaml#

board.yaml defines the board at a high level. This includes the SoC, board variants, and board revisions. We will change the board identifier but not the SoC name, since we are using the SoC CC2340R53 for our board.

board.yml – Ported board.yml board file for the mouse board#
board:
  name: cc2340r53_mouse
  vendor: ti
  socs:
    - name: cc2340r53
<board_name>.yaml#

lp_em_cc2340r53.yaml is similar to board.yml but goes more in depth and defines important information such as the ram and flash size, the name and the identifier of the board. This identifier is the one used by the west build command to identify which devicetrees to use. For adding Zephyr support to our board, we will rename the file, and change the identifier and name. If other values are different for your board, feel free to change them.

cc2340r53_mouse.yaml – Ported lp_em_cc2340r5.yaml board file for the mouse board#
identifier: cc2340r53_mouse
name: BLE Mouse Using CC2340R53
type: mcu
arch: arm
ram: 36
flash: 512
toolchain:
  - zephyr
  - gnuarmemb
  - xtools
supported:
  - gpio
vendor: ti

Flash and Debug support files#

The only file that adds support for Flash and debug is the board.cmake file. This gives build information to Zephyr to add support for flashing and debugging. For example the board.cmake file for the CC2340R53 LaunchPad gives the additional --device=CC2340R53 flag to J-Link. Unless you are sure you know what you are doing, you should not modify this file.

Kconfig files#

Kconfig is a way to select configuration settings at build time. Configuration options (often called symbols) are defined in Kconfig files, which also specify dependencies between symbols that determine what configurations are valid. Symbols can be grouped into menus and sub-menus to keep the interactive configuration interfaces organized. The goal is to support configuration without having to change any source code.

Note

The Kconfig configuration system is used by Zephyr but is also present in the Linux kernel.

When building the firmware, Zephyr will aggregate the configuration settings from the different Kconfig files, into one large header file. This header file is called autoconf.h, and can be found under zephyrproject/zephyr/build/zephyr/include/generated/zephyr/autoconf.h. The Kconfig files used to generate this file are present at different levels :

  • SoC level

  • Board level

  • Project level (prj.conf file of a project)

The Kconfig files at the board level are Kconfig, Kconfig.defconfig, Kconfig.<board_name> (Kconfig.lp_em_cc2340r53 in our case) and <board_name>_defconfig (lp_em_cc2340r53_defconfig in our case).

Kconfig.<board_name>#

The Kconfig.lp_em_cc2340r53 file is a Kconfig file to select the SoC of the board and other SoC settings. We need to rename the file to the right name, Kconfig.cc2340r53_mouse. We also need to change the content of the file since the Kconfig board name symbol has changed. Here is the new content of the Kconfig.cc2340r53_mouse file :

# Copyright (c) 2024 Texas Instruments Incorporated
# Copyright (c) 2024 BayLibre, SAS
#
# SPDX-License-Identifier: Apache-2.0

config BOARD_CC2340R53_MOUSE
	select SOC_CC2340R53

config BOARD_USE_LF_XOSC
	bool "Use external low-frequency crystal"
	default y
Kconfig.defconfig#

The Kconfig.defconfig file is the default configuration values for our board. This selects Kconfig symbols that are mandatory for our board and SoC. These Kconfig values cannot be changed by a user in the project level Kconfig files. For example, the Kconfig.defconfig for the CC2340R53 LaunchPad sets the Bluetooth heap size with BT_LL_HEAP_SIZE. Changing those values might cause problems with the bluetooth stack, so a sane default is set in this Kconfig board file. The only value that is safe to change is the BT_COMPANY_ID value, because it sets the Bluetooth Company ID to the Texas Instruments™ ID. We need to change the content of the file since the Kconfig board name symbol has changed. Here is the new content of the Kconfig.defconfig file :

# Copyright (c) 2024 Texas Instruments Incorporated
#
# SPDX-License-Identifier: Apache-2.0

rsource "../lpf3_common/Kconfig.defconfig"

if BOARD_CC2340R53_MOUSE

if BT

config ZEPHYR
  bool
  default y

config BT_LL_HEAP_SIZE
  hex
  default 0x1880

config BT_BUF_EVT_RX_SIZE
  default 255

config BT_BUF_ACL_RX_SIZE
  default 255

config BT_BUF_ACL_TX_SIZE
  default 251

config BT_BUF_CMD_TX_SIZE
  default 255

config BT_COMPANY_ID
  default 0x000D

choice BT_RECV_CONTEXT
  bool
  default BT_RECV_WORKQ_BT if BT_MESH
  default BT_RECV_WORKQ_SYS
endchoice

endif # BT

endif # BOARD_CC2340R53_MOUSE
<board_name>_defconfig#

The <board_name>_defconfig Kconfig file is very similar to the Kconfig.defconfig, except the selected Kconfig symbols can be changed by a user in the project level Kconfig files. They are not mandatory values, but suggested default values. For example, the line CONFIG_PM=y enables the Power Manager in every project that use the board. While most project would benefit from this, the user can choose to disable it, this is not a mandatory configuration option. We will not change the content of this file. We will only rename the file to cc2340r53_mouse_defconfig.

Kconfig#

The Kconfig file can add Kconfig settings which are specific to the current board. For the CC2340R53 LaunchPad board, these settings are common to every cc23xx MCU, so you should always at least keep the rsource "../lpf3_common/Kconfig" line to include those common settings. In this guide we will not change this file.

Devicetree files#

The devicetree files are used to create the final devicetree for the board. We will change these files a lot because they are the one that describe what devices are there on the board.

They are divided into two files extensions : .dtsi and .dts files. The .dtsi files are devicetree files that are only meant to be included inside .dts files. The three devicetree files in the board folder are boosterpack_connector.dtsi, lp_em_cc2340r53-pinctrl.dtsi and lp_em_cc2340r53.dts.

boosterpack_connector.dtsi#

The boosterpack_connector.dtsi file describes the mapping between the pin connectors and the DIO lines. For example, pin 9 of the connector is mapped to DIO24 on the board.

../../_images/boosterpack_connector.jpg

The boosterpack connector of the CC2340R53 LaunchPad.#

Since your custom board is not a boosterpack, you can safely remove this file in your custom board folder.

lp_em_cc2340r53-pinctrl.dtsi#

The lp_em_cc2340r53-pinctrl.dtsi file describes the pinmux mapping for the drivers. Instead of having the pinmux values spread all around the devicetree, we made the choice to put everything in one file. The configuration values for the pinmux are stored inside devices, which can later be referenced by their name in the final devicetree file. Unlike the other files, the name of the file is not very important, but we still rename this file to cc2340r53_mouse-pinctrl.dtsi for coherence with the other files naming convention.

lp_em_cc2340r53.dts#

The lp_em_cc2340r53.dts is the main devicetree file. It starts with include macros to include the dtsi files from the folder, but also the dtsi files for the CC2340R53 MCU. This allows this devicetree file to only focus on the specifics of the board, and to put all the MCU-specific values in common for all boards that need them. We need to rename the file to cc2340r53_mouse.dts. Since we deleted the boosterpack_connector.dtsi file and renamed the lp_em_cc2340r53-pinctrl.dtsi, we also need to change the content of this file. Here’s the changed includes for this file :

/*
 * Copyright (c) 2024 Texas Instruments Incorporated
 * Copyright (c) 2024 BayLibre, SAS
 *
 * SPDX-License-Identifier: Apache-2.0
 */

/dts-v1/;

#include <ti/cc2340r53.dtsi>
#include "cc2340r53_mouse-pinctrl.dtsi"
#include <zephyr/dt-bindings/gpio/gpio.h>
#include <zephyr/dt-bindings/pwm/pwm.h>
#include <zephyr/dt-bindings/input/input-event-codes.h>

[...]

Testing the changes#

After all those changes our board folder should look like this :

../../_images/board_folder.png

Final files in the new board folder.#

We should now be able to see our board by running west boards. We can filter for our board by running west boards | grep mouse. If this command returns the name of our board, it is successfully detected !

We can try then to build a sample for our new board, and run it on a CC2340R53 LaunchPad. To try this, we can move to the zephyr folder, run west build -p always -b cc2340r53_mouse samples/basic/blinky, and flash the resulting firmware. If we can see the led blinking, then we have successfully created a new board in Zephyr. For now, this board almost exactly the same board as the CC2340R53 LaunchPad, but we will change this in the next task.

Task 2 : Changing the devicetree to fit our custom schematics#

Now that we have a new working board folder, we can start changing the devicetree of this board to adapt it for our board. In this task we will show the example of changing the DIO line of the led0 device, so that the blinky sample will work on our custom board.

For this, we first need to take a look at the hardware schematics of our board.

../../_images/mouse_schematics_io_block.png

The schematics of the IO block for the CC2340R53 with RGE packaging for the BLE mouse board#

../../_images/mouse_schematics_led.png

The schematics of the LED for the BLE mouse board#

The LED of our mouse board is activated using GPIO with the DIO 12. If we look at the devicetree in cc2340r53_mouse.dts we currently have for the mouse, we can see the following led devices :

/ {
	model = "LP_EM_CC2340R53";
	compatible = "ti,lp_em_cc2340r53";

	[...]

	aliases {
		led0 = &led0;
		led1 = &led1;
		led0 = &led0;
		sw0 = &btn0;
		sw1 = &btn1;
		pwm-led0 = &pwm_green;
		pwm-led1 = &pwm_red;
		watchdog0 = &wdt0;
		mcuboot-led0 = &led1;
		mcuboot-button0 = &btn1;
		[...]
	};

	leds {
		compatible = "gpio-leds";
		led0: led_0 {
			gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
			label = "Green LED";
		};
		led1: led_1 {
			gpios = <&gpio0 14 GPIO_ACTIVE_HIGH>;
			label = "Red LED";
		};
	};

  [...]

};

[...]

The CC2340R53 LaunchPad board has two LEDs, one green and one red. The green LED uses DIO 15, and the red LED uses DIO 14. GPIO_ACTIVE_HIGH is a GPIO flag, it indicates that the physical level of the LED is high when the logical level is high. We want to keep this flag for the LED of our mouse board, because our LED will emit light when the physical level of the wire is high. The only thing we need to change is to delete the second led, and to change the DIO used for the LED.

Hint

Zephyr makes a distinction between the physical level and logical level of a pin. The physical level is the physical voltage of the pin (low if 0V, high if 3V), while the logical level is the level the application writes to the pin. Usually the physical level and logical level are the same for a LED, but for a button with a pull-up resistor or a “Chip Select” pin, those devices usually have their logical level be the opposite of the physical level.

We will therefore change the LED device description to the following :

/ {
	model = "CC2340R53_MOUSE";
	compatible = "ti,lp_em_cc2340r53";

	[...]

	aliases {
		led0 = &led0;
		sw0 = &btn0;
		sw1 = &btn1;
		pwm-led0 = &pwm_green;
		pwm-led1 = &pwm_red;
		watchdog0 = &wdt0;
		mcuboot-button0 = &btn1;
		[...]
	};

	leds {
		compatible = "gpio-leds";
		led0: led_0 {
			gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
			label = "LED";
		};
	};

  [...]

};

[...]

Warning

“Wait ! We changed the model property of the root node, but not the compatible property. Why did we not change the compatible property to something like compatible = "ti,cc2340r53_mouse"; ?”

The reason why we did not change the compatible property is because this property defines what drivers should be used. We want to use the drivers that are already made for the CC2340R53 MCU instead of re-creating all the drivers from the ground up. This is one of the strength of Zephyr, the device model allows us to change the property of the board but still re-use the drivers for the SoC.

We can try then to build the blinky sample for our mouse board, and run it the actual mouse hardware. To try this, we can move to the zephyr folder, run west build -p always -b cc2340r53_mouse samples/basic/blinky, and flash the resulting firmware.

Hint

If we can see the led blinking, then congratulations ! We have successfully ported the devicetree of the CC2340R53 LaunchPad to the Mouse board.

../../_images/mouse_blinking.jpg

The LED of the mouse board blinking. The mouse board is connected through JTAG to a XDS110#

Resources to go Further#

In this training we have seen how to create a new board folder for Zephyr, what the configuration files for this board are used for, and a practical example of changing the devicetree by changing the DIO line for the LED. However, this is only a hands-on introduction to the subject, and there is still a lot that can be said about them. This section is focused on resources from the official Zephyr documentation and the next steps one can take to learn more about board porting on Zephyr.

  • The Board Porting Guide gives a complete in-depth guide of porting your board to Zephyr. It explains all possible files in a board folder, the nomenclature between the different layers of the boards, and the good practices for porting a board.

  • The Devicetree Intro is a conceptual overview of devicetrees and how Zephyr uses them. It explains the syntax, the properties and more.

  • The Devicetree HOWTOs are a list of devicetree guides for Zephyr. They explain overlays, how to debug devicetrees, the file they generate and their link with device drivers.

  • The Device Driver Model page explains more in depth what is a driver, how can they be used and how to create your own drivers

  • The Devicetree Bindings page explains how Zephyr’s build system uses bindings, the syntax and good practice rules for bindings.

  • The Kconfig page gives more specific explanations to Kconfigs in Zephyr, such as preprocessor functions, interactive graphical interfaces for configuration and best practices for them.

  • Last but not least, the Kconfig Search Tool which allows to search for specific Kconfig symbols using keyworks or RegEx.