Zephyr Project Debugging Guide#

Texas Instruments™

2 hours read

Introduction#

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

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 this Zephyr Project Setup training on SimpleLink Academy.

  • 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 :

Task 1 : Debugging a Zephyr project with OpenOCD and VSCode#

We’ve seen how to build and flash the Blinky project onto our board. This writes the firmware onto the flash memory of the board, and instructs the board to reboot and use this firmware.

In this task, we will see how to debug a Zephyr project using OpenOCD and the TI Embedded Debug for VS Code extension on Visual Studio Code. Debugging is useful to find the reason for bugs in our software. Unlike flashing, it allows us to halt our firmware in the middle of it’s execution, read the memory, and to see the control flow of our firmware.

The TI Embedded Debug for VS Code extension allows us to use VSCode’s graphical interface to debug a Zephyr binary on a LaunchPad. If you do not already have Visual Studio Code installed on your computer, you can install it here. This extension uses OpenOCD as their debugging server, so a XDS110 LaunchPad™ development kit debugger is necessary.

The Zephyr project that we will use in this task as a reference is the Blinky sample from Zephyr. You can build this sample by running west build -p always -b {BOARD_IDENTIFIER} samples/basic/blinky. The {BOARD_IDENTIFIER} should be replace by the identifier of your board. You can find all possible board identifiers by running west boards.

Installing the TI Embedded Debug for VS Code extension and its dependencies#

The first step is to install TI Embedded Debug for VS Code extension on Visual Studio Code. You can either install it by going on VSCode’s marketplace web page for the extension, or directly in VSCode by clicking the Extensions button, searching for TI Embedded Debug for VS Code and clicking the Install button.

../../_images/vscode_extension_marketplace.png

The TI Embedded Debug for VS Code in the Extension tab of VSCode#

Note

If you get a warning about trusting Texas Instruments™ for the VSCode extension, you should click the “Trust & Install” button.

Once the extension is installed, a new button with the logo of Texas Instruments™ should appear on VSCode’s Activity Bar on the left. When clicking on this button, the interface for the extension opens. You need to click the “Install Dependencies” button to install the dependencies necessary for the extension to work.

../../_images/vscode_extension_button.png

The TI Embedded Debug button in the Activity Bar of VSCode#

If the installation of the dependencies was successful, you should see a notification in the bottom right of VSCode saying that the dependencies have sucessfuly been installed.

../../_images/vscode_extension_dependencies.png

The notification received upon successful installation of the dependencies#

Finally, on Linux systems, you will need to run a script to install udev rules specific to TI devices. This script automatically gets downloaded as part of the dependency installation, but needs to be run manually due to requiring sudo priviledges. To run the script, you can open a terminal and run :

sudo ~/.config/Texas\ Instruments/ti-embedded-debug/ti-udev-rules/1.0.0/ti_permissions_install.sh --install

Setting up a debug launch configuration#

To start debugging a project, we need to create a debug launch configuration. This launch configuration will provide to OpenOCD with the settings used for the debugging session. These settings include for example :

  • The location of our ELF file generated in the build chapter of the previous Zephyr SimpleLink Academy training

  • The OpenOCD configuration file for our board

  • The logging level wanted for the VSCode extension and the OpenOCD server

To create a debug launch configuration in VSCode, you can click on Run > Add Configuration... and selecting Cortex Debug.

../../_images/vscode_add_configuration.png

The Add Configuration... button in VSCode#

../../_images/vscode_cortex_debug.png

The Cortex Debug button in VSCode#

Clicking the Cortex Debug button will create a launch.json file in the .vscode folder of your project, and automatically open it in VSCode.

By default your launch configuration will look like this :

launch.json – Generated code by VSCode for the default debug launch configuration.#
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Cortex Debug",
            "cwd": "${workspaceFolder}",
            "executable": "./bin/executable.elf",
            "request": "launch",
            "type": "cortex-debug",
            "runToEntryPoint": "main",
            "servertype": "jlink"
        }
    ]
}

The example below shows what your launch configuration should look like for debugging a Zephyr project using the VSCode extension installed previously.

launch.json – Modified to add support for Zephyr debugging with OpenOCD.#
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Zephyr TI Embedded Debug",
            "cwd": "${workspaceFolder}",
            "executable": "zephyr.elf",
            "request": "launch",
            "type": "cortex-debug",
            "servertype": "openocd",
            "searchDir": [
                "~/.config/Texas Instruments/ti-embedded-debug/openocd/<version>/share/openocd/scripts"
            ],
            "configFiles": [
                "./interface/xds110.cfg",
                "./board/<board_filename>.cfg"
            ],
            "runToEntryPoint": "main",
            "showDevDebugOutput": "none",
            "deviceName": "my-device"
        }
    ]
}

Some fields of this configuration have templates (<board_filename>, <version>), and they should be replaced by their actual values. To find how to replace these values later, we can refer to this table that explains of all the configuration fields, so you can understand what to change :

Configuration field

Explanation

Example value / Possible values

name

The name given for your debug configuration. Can be anything like the name of your project.

"Zephyr TI Embedded Debug"

cwd

Current Working Directory, all commands will be executed from this folder. This is only useful to allow for relative path instead of absolute path in other fields.

"${workspaceFolder}" to point to your project folder, or an absolute path like "C:/Users/username/workspace/my_project/Debug"

executable

The path to your ELF executable. If not using an absolute path, the path will be relative to the cwd field.

"./build/zephyr.elf"

request

Whether the OpenOCD debugger should launch the ELF or attach to a running target.

"launch" or "attach"

type

The type of the configuration. Only "cortex-debug" is supported.

"cortex-debug"

servertype

The type of the configuration. Only "openocd" is supported.

"openocd"

searchDir

Array of path in which to search for the config files and scripts, which will be given to OpenOCD with the -s option. The highest version of OpenOCD should be used.

["C:/Users/<username>/AppData/Local/Texas Instruments/ti-embedded-debug/openocd/<version>/share/openocd/scripts"] on Windows, or ["~/.config/Texas Instruments/ti-embedded-debug/openocd/<version>/share/openocd/scripts"]

configFiles

Array of path to the OpenOCD GDB Server configuration files to use when debugging. These files will be given to OpenOCD with the -f options. If not using absolute paths, the paths will be relative to the configFiles field. For a Zephyr project, we give the configuration files for the XDS110 and for our board.

["./interface/xds110.cfg", "./board/ti_lp_em_cc2340r53.cfg"]

runToEntryPoint

The debugger will run until it reaches that function and then halt.

"main"

showDevDebugOutput

Used to debug the VSCode extension.

"none", "raw", "parsed", "both" or "vscode"

deviceName

Name of the SoC to debug. Any name is accepted

"ti_lp_em_cc2340r53"

For the rest of the guide, I will use the CC2340R53, so this is what my configuration file looks like :

launch.json – Example configuration for the CC2340R53 on Linux#
{
  "version": "2.0.0",
  "configurations": [
    {
      "cwd": "/home/user1/zephyrproject/zephyr/build/zephyr",
      "executable": "zephyr.elf",
      "name": "Debug with TI Embedded Debug for VS Code",
      "request": "launch",
      "type": "cortex-debug",
      "servertype": "openocd",
      "searchDir": [
        "/home/user1/.config/Texas Instruments/ti-embedded-debug/openocd/20250225/share/openocd/scripts"
      ],
      "configFiles": [
        "./interface/xds110.cfg",
        "./board/ti_lp_em_cc2340r53.cfg"
      ],
      "runToEntryPoint": "main",
      "showDevDebugOutput": "none",
      "deviceName": "ti_lp_em_cc2340r53",
    }
  ]
}

Note

It is a known issue that the deviceName property will show up as “not allowed” in launch.json, despite working fine. You can ignore this error.

Starting a debugging session#

Once your debug launch configuration file is complete, you can click Run > Start Debugging, which will start the VSCode debugger configuration that we just created. The extension will take care of flashing the device with the ELF file that we gave in the debug launch configuration, attaching to the target and stopping at the function we set in the runToEntryPoint.

../../_images/vscode_debug_interface.png

The VSCode debugging interface. In the red rectangle are the main debugging actions : Reset device, Continue, Step over, Step into, Step out, Restart and Stop#

Once you started the debugging session on the blinky project, you should be able to see the main debugging actions at the top, the local and global variables in the top left corner, the call stack and your breakpoints in the bottom left corner, and a lot of other useful data like the watched variables or the registers. You can also see the line that’s about to be executed in the next step highlighted in yellow.

Error

For SimpleLink SDK users only, Zephyr users are unaffected :

This debug method can be used for both .elf files that are generated by Zephyr or by the SimpleLink SDK. If using the TIClang compiler with the SimpleLink SDK, the resulting .elf file has a section called .TI.phattrs. On older versions of the TIClang compiler, this .TI.phattrs section contains values that are incompatible with OpenOCD. This issue results in OpenOCD failing to disassemble the ELF, and the debug session to fail.

A workaround is to execute a command to remove the problematic values in .TI.phattrs by adding a command in CCS’ post-build steps. You can find the tiarmobjcopy executable under C:\ti\ti-cgt-arm_<TICLANG_VERSION>\bin.

tiarmobjcopy --set-section-type=.TI.phattrs=1 executable_file.out

This command changes the file in place. If you want to keep the old file and create a new one, you can instead use the following command :

tiarmobjcopy --set-section-type=.TI.phattrs=1 executable_file.out new.out

The final effect is that sh_type for the section .TI.phattrs changes from SHT_TI_PHATTRS to SHT_PROGBITS.

Warning

“Wait ! My debugger did not stop in the function that I set in runToEntryPoint ! What happened ?”

The ELF file that we are debugging has been compiled by the Zephyr toolchain, and has been optimized for performance and file size. The compiler can sometimes take the liberty to remove a line, optimize out functions and variables, or to directly inline a function. If the first instruction of your function (excluding variable declarations) is a function call, it’s possible that this function was inlined, and that this function call has been replaced by copying the body of the function into the original function.

This should not have a big impact on your debugging, and you can still step out of the inlined function to return to the original function. If this behavior is too distracting for your debugging, you can disable it by adding CONFIG_NO_OPTIMIZATIONS=y to the prj.conf file of your project, which will build all files with the -O0 flag.

Once we click the Step over button, we can see the main function of our project. From here we can set breakpoints by clicking on the red dot, on the right of the line number of your instruction, use the debugging actions to move around in the program, and observe its behavior.

../../_images/vscode_breakpoint.png

The VSCode debugging interface in the main function. In this example, I have setup a breakpoint on line 33, and I am currently adding a new breakpoint on line 42.#

Hint

You can now debug any Zephyr project that runs on a SimpleLink™ board, congratulations !

Task 2 : Add logging to a Zephyr project#

In this task, we will see how to add logging in a Zephyr project using Zephyr’s logging API. This allows us to easily have configurable logging levels in our project.

Outputting text through UART#

The first step of this task will be to read text sent by the firmware through UART. Our default Blinky sample already sends text through UART thanks to the printf instruction in the main loop. This function will print the LED state every cycle of the loop. Now we are going to recieved this LED state in a terminal in order to debug the state of our firmware.

We first need to find which device to open a serial connection with. On Linux, the devices will always be referenced as an ACM device, and will always be located under /dev/. To find all ACM devices connected to your PC, you can unplug your board if it’s already plugged, plug it back, and then run the command sudo dmesg | grep ttyACM, which will return all ACM devices detected by Linux, ordered by time of detection. Since we just plugged our device, the latest device detected should be ours.

../../_images/dmesg.png

The output of the sudo dmesg | grep ttyACM command. In this screenshot, our device is either /dev/ttyACM6 or /dev/ttyACM7.#

To open a serial connection with your board, most Linux distributions come with the screen utility installed on them. This allows Linux to open a serial connection with a device. You can open a terminal and use the following command :

Example command to open a Serial connection to the /dev/ttyACM0 device on Linux#
screen /dev/ttyACM0 115200

Note

Here, 115200 is the baud rate for the UART connection. 115200 is the default baud rate for the CC234053. This value is defined in the devicetree files for the board, that you can find in zephyr/boards/ti/lp_em_cc2340r53/lp_em_cc2340r53.dts. We will talk more about Zephyr devicetrees in the next training in SimpleLink Academy, which is about board porting on Zephyr.

Warning

Make sure to replace /dev/ttyACM0 with the actual device that we found previously !

If nothing happens when running screen, you may have selected the wrong device. Try to exit screen with Ctrl + a followed by k and then a y to confirm. Then, you can open screen with another ACM device.

If everything went fine, you should be able to see the serial output in your terminal, which should look something like this :

../../_images/serial_output.png

The serial output of the Blinky sample#

Adding our own logging module and reading the output#

While using printf for small projects is acceptable, as the project grows in size, we will want to use Zephyr’s logging modules. Zephyr logging modules bring multiple advantages compared to the regular printf :

  • First, these modules allow us to split logging into multiple modules, allowing us to disable them on a per-module basis. A logging module is declared for one entire source file, and one source file can only use one logging module.

  • Second, they bring a lot more information, including the time when the log happened, and the module in which the log happened. This means that we can have the same log text in multiple files, without worrying about recognizing which one was triggered.

  • Third, they are separated into multiple levels of logging. From the most detailed to the least detailed, the levels are ranging from Debug, Info, Warning, Error. This allows us for example to only listen for errors, and to only keep the important messages.

  • Fourth, the logging instructions that are unused are optimized away automatically, saving flash size. For example, if we only listen to warnings and above, then debug and info logging instructions will not be optimized out and not be compiled.

  • Finally, by splitting logging into different modules and different levels, we can ask Zephyr to enable only the logging of one single module at one single level, allowing for more granularity. If we happen to have a bug in the Bluetooth advertisement functions, we could simply enable logging for the Bluetooth advertisement module and to only listen for errors. This also allows us to easily share our code to other users, without flooding their serial output with logs they don’t need.

Now that you are convinced that Zephyr logging modules are great, we will show how to implement them in our code. The main steps are to register a module, and then to use the logging functions.

To register a module, you need to include Zephyr’s header file for logging, and to call the LOG_MODULE_REGISTER macro.

main.c – Example of registering the blinky logging module to our Blinky project#
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(blinky, LOG_LEVEL_INF);

This code will create the logging module called blinky for the entire file, and will log all messages with a log level above or equal to Info, skipping out logging instruction with a logging level of Debug.

Warning

We can only register a logging module once ! If we wish to use the blinky logging module in another file, you must use the LOG_MODULE_DECLARE instead.

Then, we can use the logging functions to log our messages. Zephyr provides one logging function for each log level :

Finally, our Blinky sample will look like this once we use the logging modules :

main.c – Example of using the logging modules for our Blinky project#
#include <stdio.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(blinky, LOG_LEVEL_INF);

/* 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;
	bool led_state = true;

	if (!gpio_is_ready_dt(&led)) {
                LOG_ERR("LED GPIO is not ready.");
		return 0;
	}

	ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
	if (ret < 0) {
                LOG_ERR("LED GPIO configuration failed.");
		return 0;
	}

	while (1) {
		ret = gpio_pin_toggle_dt(&led);
		if (ret < 0) {
                        LOG_ERR("LED GPIO toggle failed.");
			return 0;
		}

		LOG_INF("LED Blinked!");

		led_state = !led_state;
		LOG_DBG("LED state: %s", led_state ? "ON" : "OFF");
		k_msleep(SLEEP_TIME_MS);
	}
	return 0;
}

If we build this sample and look at the serial output, we should have something similar to this :

../../_images/serial_output_logging_info.png

The serial output of the Blinky sample when using LOG_LEVEL_INF as the logging level#

You may have noticed that the text inside the LOG_DBG instruction in our loop is never displayed. This is because the logging level we have set for our blinky logging module is LOG_LEVEL_INF. This means that any logging instruction with a logging level inferior to Info, such as Debug, have been optimized out and removed by the compiler. If we want to have the Debug logging level for our logging module, we can set the logging level to LOG_LEVEL_DBG.

Making the logging module configurable#

Having to change the logging level macro for our module manually in the source code every time we want a different logging level is quite the repetitive and annoying task. To avoid this problem, we will create a Kconfig file to add more config options to our sample. Create a file called Kconfig at the root of the Blinky sample (it should be at the same level as the prj.conf file), and add this to the file :

Kconfig – Example on how to add custom log level configuration for the Blinky project#
choice "BLINKY_LOG_LEVEL_CHOICE"
	prompt "Max compiled-in log level for Blinky"
	default BLINKY_LOG_LEVEL_DEFAULT
	depends on LOG

config BLINKY_LOG_LEVEL_OFF
	bool "Off"

config BLINKY_LOG_LEVEL_ERR
	bool "Error"

config BLINKY_LOG_LEVEL_WRN
	bool "Warning"

config BLINKY_LOG_LEVEL_INF
	bool "Info"

config BLINKY_LOG_LEVEL_DBG
	bool "Debug"

config BLINKY_LOG_LEVEL_DEFAULT
	bool "Default"

endchoice

config BLINKY_LOG_LEVEL
	int
	depends on LOG
	default 0 if BLINKY_LOG_LEVEL_OFF
	default 1 if BLINKY_LOG_LEVEL_ERR
	default 2 if BLINKY_LOG_LEVEL_WRN
	default 3 if BLINKY_LOG_LEVEL_INF
	default 4 if BLINKY_LOG_LEVEL_DBG
	default LOG_DEFAULT_LEVEL if BLINKY_LOG_LEVEL_DEFAULT

source "Kconfig.zephyr"

Note

Some readers will recognize the syntax of the Kconfig files for the Linux kernel

This Kconfig file creates 7 new config options that we can select from our prj.conf file. The BLINKY_LOG_LEVEL will act as a variable that can either be set manually or be set indirectly by selecting one of the 6 other configs. For example, if we want to set BLINKY_LOG_LEVEL to debug, we can add the following lines to our prj.conf file :

prj.conf – Example on how to set the Blinky logging module logging level to Debug#
CONFIG_LOG=y
CONFIG_BLINKY_LOG_LEVEL_DBG=y

Finally, we will change logging module that we registered in the main.c file of our blinky project, to make it use our new config variable. We can change the log level in our Blinky sample code from this :

main.c – Previous logging level for the Blinky logging module#
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(blinky, LOG_LEVEL_INF);

To this :

main.c – New logging level for the Blinky logging module#
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(blinky, CONFIG_BLINKY_LOG_LEVEL);

If we build and flash the blinky sample with this new code, we should have the logging level for the blinky module set to Debug, which should look like this :

../../_images/serial_output_logging_debug.png

The serial output of the Blinky sample when using CONFIG_BLINKY_LOG_LEVEL_DBG#

Enabling Zephyr’s logging for other modules#

We have seen how to use the logging module for our own code, but now we want to change this logging module for other modules. We will use the logging module for the Bluetooth drivers in this example.

The logging system for the drivers and other parts of Zephyr all use a logging module similar to what we created for our Blinky sample. This means that we simply have to set the right config boolean in the prj.conf of our sample to enable logging with the specified module. For example, if we want to enable the logging for the Bluetooth module in Zephyr’s Bluetooth central sample, located under zephyr/samples/bluetooth/central, we can add the following lines to zephyr/samples/bluetooth/central/prj.conf :

prj.conf – Example on how to enable debug logging for the Bluetooth module#
CONFIG_LOG=y
CONFIG_BT_LOG_LEVEL_DBG=y

Finally, we can build the bluetooth central sample using west build -p always -b {BOARD_IDENTIFIER} samples/bluetooth/central, flash it using UniFlash or west flash, and connect a serial terminal like we did for the Blinky sample.

../../_images/serial_output_bluetooth.png

The serial output of the central sample with the CONFIG_BT_LOG_LEVEL_DBG option#

If you’re able to see the debug information for the Bluetooth Zephyr drivers, then congratulations, you successfully enabled logging in your project !

Warning

In the previous example, this training generously gave you the right configuration setting to enable for Bluetooth. If you want to look up the right configuration you need to enable, you can search using Zephyr’s Kconfig Search. This page allows you to search for every Kconfig configuration you might need.

To search for logging module configuration specifically, you can search for something like LOG <keyword>, which will give you all logging configurations related to logging and your keyword. For example, searching LOG Bluetooth returns 35 different logging options, ranging from Bluetooth Attribute Protocol to Bluetooth Resolvable Private Address. This page is also useful for other configurations than logging, and I recommend you to bookmark it if you plan to work with Zephyr.

Task 3 : Debugging TX/RX output with the PA/LNA pins#

In this task, we will see how to set the Power Amplification (PA) and Low Noise Amplification (LNA) pins. The RF output of our board can be mapped to pins on the LaunchPad for RF signal debugging. These pins are intended to be used when connecting an RF range extender. However, they can also help in instances where it is unclear if the device is transmitting or receiving in the right window. The Power Amplification pin will be high when the device is transmitting (TX), and the Low Noise Amplification pin will be high when the device is receiving (RX). This allows us to easily see if our radio is currently transmitting or receiving data.

Adding the PA/LNA code#

While the code to enable the PA and LNA pins is not yet implemented in our drivers, we can work around this by implementing it ourselves manually. We will use Zephyr’s peripheral sample, that can be found under zephyr/samples/bluetooth/peripheral. We want to make sure that we are sending advertisement packets using the radio, but also listening for scan requests that we can reply to.

In the main.c file of the peripheral sample, we can add the following C code :

main.c::RCL_GPIO_enable – Function to enable PA/LNA pins#
#include <ti/devices/DeviceFamily.h>
#include DeviceFamily_constructPath(inc/hw_types.h)
#include DeviceFamily_constructPath(inc/hw_memmap.h)

#include <zephyr/dt-bindings/pinctrl/cc23x0-pinctrl.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/gpio.h>
#include <driverlib/lrfd.h>


#define IOC_BASE_PIN_REG 0x00000100
#define IOC_ADDR(index)  (IOC_BASE + IOC_BASE_PIN_REG + (sizeof(uint32_t) * (index)))

void RCL_GPIO_enable()
{

    /* RFEGPO0 */
    HWREG_WRITE_LRF(LRFDDBELL_BASE + LRFDDBELL_O_GPOSEL0) = HWREG_READ_LRF(LRFDDBELL_BASE + LRFDDBELL_O_GPOSEL0) | (LRFDDBELL_GPOSEL0_SRC1_RFEGPO0);

    /* RFEGPO1 */
    HWREG_WRITE_LRF(LRFDDBELL_BASE + LRFDDBELL_O_GPOSEL0) = HWREG_READ_LRF(LRFDDBELL_BASE + LRFDDBELL_O_GPOSEL0) | (LRFDDBELL_GPOSEL0_SRC0_RFEGPO1);

    const pinctrl_soc_pin_t pins[2] = {
        {
            .pin = 11,
            .iofunc = DIO11_LRFD0,
            .iomode = 0 // Output
        },
        {
            .pin = 21,
            .iofunc = DIO21_LRFD1,
            .iomode = 0 // Output
        } 
    };

    pinctrl_configure_pins(
        pins,
        2,
        0 // Unused
    );
}

What the function does is enabling the PA and LNA output by writing to a LRF register, and sets the source for those pins to SRC1 for RFEGPO0 and SRC0 for RFEGPO1. The pins associated with LRFD0 are 3 and 11, and that the pins associated with LRFD1 are 4 and 21. Since pin 3 and pin 4 are already used for LGPT0, we chose to use pin 11 and 21.

We then set the pinmux of pin 11 and 21 to LRFD, and configure them as outputs.

The next thing we need to do is to call the RCL_GPIO_enable function in main :

main.c::main – Calling the RCL_GPIO_enable function from main#
int main(void)
{
	RCL_GPIO_enable();

	struct bt_gatt_attr *vnd_ind_attr;
	char str[BT_UUID_STR_LEN];
	int err;

  ...
}

We can finally build the project as usual by using west build -p always -b {BOARD_IDENTIFIER} samples/bluetooth/peripheral, and flash it using UniFlash or west flash.

Verifying the output of the PA/LNA pins#

With a logic analyzer, we can check the value of pins 11 and 21. We should have something similar to this :

../../_images/logic_analyzer_pa_lna.png

Output from a logic analyzer of the pins 11 in red and 21 in orange.#

In this screenshot we can see in red the pin 11, which is the PA pin, and in orange the pin 21, which is the LNA pin. What we can observe is similar to what we would expect from a scannable advertising peripheral, which alternates between listening for scan requests and sending advertisement packets on the 3 BLE advertisement channels.

Hint

You can now build and flash Zephyr project, debug through your code, read logs from the different Zephyr modules and verify if your radio is transmitting or receiving. Congratulations ! The next step is to explore the Zephyr API to see all the possibilities offered by this RTOS, and then to port Zephyr on your own custom board with the Zephyr Project Board Porting guide on SimpleLink Academy. You can also explore how to measure the energy consumption of your firmare using Energy Trace