Rohan Dey https://rodeytech.com Electronics Engineer Fri, 29 Mar 2024 03:54:07 +0000 en-US hourly 1 https://wordpress.org/?v=6.6.1 230871215 Zephyr RTOS project step debugging with OpenOCD on Eclipse IDE https://rodeytech.com/2024/03/28/zephyr-rtos-project-step-debugging-with-openocd-on-eclipse-ide/ https://rodeytech.com/2024/03/28/zephyr-rtos-project-step-debugging-with-openocd-on-eclipse-ide/#respond Thu, 28 Mar 2024 13:39:24 +0000 https://rodeytech.com/?p=130 The Zephyr RTOS project from Linux Foundation has gained a lot of popularity recently. It is one of the best-in-class small, scalable, real-time operating system (RTOS) optimized for resource-constrained devices, across multiple architectures.

In firmware development debugging the code is essential to make sure everything works. The Zephyr project provides all the necessary tools to build, flash and debug our applications, but configuring everything to work from an IDE is not straight forward. I spent days figuring it out. Below I will show the steps required for debugging a Zephyr project on Eclipse IDE using OpenOCD.

Example

To demonstrate we will use the hello_world sample from Zephyr available at zephyr/tree/main/samples/hello_world and step debug it on STM32F3DISCOVERY board. While the Zephyr documentation for this board provides information on how to flash and debug, it does not tells us how to configure the Eclipse IDE and step debug applications.

Required tools

Before proceeding, please make sure you have the following tools installed:

1. Eclipse CDT IDE

2. Zephyr RTOS and its dependencies

Prepare the project

First, copy the hello_world project to your preferred directory and create a directory called source and move the contents of hello_world into source. Then move the source directory into hello_world. The source directory is needed because Zephyr’s build tool west uses CMake and the CMake generator expects the files along with the main CMakeLists.txt file inside of a folder (source in our case) when generating for Eclipse IDE.

$ cp -r ~/zephyrproject/zephyr/samples/hello_world/ ~/my_workspace/
$ cd ~/my_workspace/
$ mkdir -p source
$ mv hello_world/* source/
$ mv source/ hello_world/
$ cd hello_world/

At this point the contents of the hello_world directory should look like this:

$ ls -lR
.:
total 4
drwxrwxr-x 3 user user 4096 Mar 28 12:29 source

./source:
total 20
-rw-rw-r-- 1 user user  194 Mar 28 12:21 CMakeLists.txt
-rw-rw-r-- 1 user user   15 Mar 28 12:21 prj.conf
-rw-rw-r-- 1 user user  610 Mar 28 12:21 README.rst
-rw-rw-r-- 1 user user  324 Mar 28 12:21 sample.yaml
drwxrwxr-x 2 user user 4096 Mar 28 12:21 src

./source/src:
total 4
-rw-rw-r-- 1 user user 196 Mar 28 12:21 main.c

Generate the Eclipse Project

Now, we must build the Zephyr project and use CMake’s generator to generate an Eclipse project for us. So, source the zephyr environment and build the project for STM32F3DISCOVERY board with the following commands:

$ source ~/zephyrproject/.venv/bin/activate
$ source ~/zephyrproject/zephyr/zephyr-env.sh
$ west build -p always -d build/ source/ -b stm32f3_disco -G"Eclipse CDT4 - Ninja"

Now, import the project into Eclipse from File -> Import -> General -> Existing Projects into Workspace -> Next -> Browse for the hello_world project -> Finish

Set the Debug Configurations

Select the project on the Project Explorer and go to Run -> Debug Configurations.

Then right click on GDB Open OCD Debugging and create a new configuration.

Now, set the following options are per below points and screenshots

1. Open the “Main” tab of Debug Configurations

2. Type the C/C++ Application name as zephyr/zephyr.elf

3. Open the “Debugger tab”

4. Browse for the OpenOCD executable file inside the zephyr SDK installation directory.

5. Verify the path of the selected OpenOCD executable.

6. Enter TCL port 6333

7. Enter the following OpenOCD config options. Please verify the paths according to your system.

-s /home/<username>/zephyrproject/zephyr/boards/arm/stm32f3_disco/support

-s /home/<username>/zephyr-sdk-0.16.3/sysroots/x86_64-pokysdk-linux/usr/share/openocd/scripts

-f /home/<username>/zephyrproject/zephyr/boards/arm/stm32f3_disco/support/openocd.cfg

8. Browse for the GDB executable file inside the zephyr SDK installation directory.

9. Click Debug to start debugging.

Debug the project

All settings are done, now you can step debug the project.

Thanks for reading!

]]>
https://rodeytech.com/2024/03/28/zephyr-rtos-project-step-debugging-with-openocd-on-eclipse-ide/feed/ 0 130
Create custom device drivers for Zephyr RTOS project https://rodeytech.com/2024/03/27/create-custom-device-drivers-for-zephyr-rtos-project/ https://rodeytech.com/2024/03/27/create-custom-device-drivers-for-zephyr-rtos-project/#respond Wed, 27 Mar 2024 08:44:20 +0000 https://rodeytech.com/?p=118 This article assumes that you have some experience working with the Zephyr RTOS. If not, it would be good to go through the documentation and try out a few samples or you may choose to continue reading to get an overview.

The Zephyr RTOS supports a wide range of boards and devices and the list is growing every day. But, like myself, you might face a situation where your application requires a peripheral or a sensor which is not supported by Zephyr yet. One way to tackle the situation is to write an application-level driver and call its functions directly, but the proper way would be if the driver can be put into Zephyr’s device driver model and use Zephyr’s Sensing Subsystem APIs to communicate with the driver. Luckily, Zephyr supports out-of-tree builds where you can add a device driver for a new peripheral to your project without modifying Zephyr’s source code. This device driver will follow Zephyr’s device driver model although not being part of the Zephyr RTOS’s source code.

Some reading

Before we dig deeper there are a few topics to get familiar with. If you are already familiar with these topics or have experience with developing Zephyr applications, you may ignore this and move on to the next section.

Zephyr RTOS getting started

Zephyr application development

Device tree

Device tree bindings

Sensing subsystem

Example

Let’s start with an example. The source code for this example is available at:

https://github.com/deyro/zephyr_custom_driver

This example is a Zephyr project which includes a custom device driver that uses the sensor driver interface so that an application can call the sensing subsystem APIs to interact with the device driver. The driver produces simulated data. It can be enhanced to become an actual driver for a temperature sensor, accelerometer or any other device/sensor.

Example project walkthrough

The project is structured as shown below.

zephyr_custom_driver
 |- source
     |- boards
	 |    |- native_posix.conf
	 |    |- native_posix.overlay
	 |
	 |- dts
	 |    |- bindings
	 |        |- sensor
	 |            |- rdt,sensor_sim.yaml
	 |
	 |- modules
	 |    |- sensor_sim
	 |    |   |- CMakeLists.txt
	 |    |   |- Kconfig
	 |    |   |- sensor_sim.c
	 |    |   |- sensor_sim.h
	 |    |
	 |    |- zephyr
	 |    |    |- module.yml
	 |    |
	 |    |- CMakeLists.txt
	 |    |- Kconfig
	 |
	 |- src
	 |    |- CMakeLists.txt
	 |    |- Kconfig
	 |    |- main.c
	 |
	 |- CMakeLists.txt
	 |- prj.conf
	 |- sample.yaml

The below points explain each directory and the files. The sequence is based on importance of the directories for this article.

modules directory

Contains our custom device driver; it can contain other device drivers or modules also. This directory will be provided to the CMake generator to include it in the build.

  • sensor_sim directory – contains the source files for our custom device driver. Explanation of the driver source code is provided below.
  • zephyr directory – contains a module.yml file which tells the build system to include the modules directory in the build. The zephyr/module.yml is a mandatory file
  • CMakeLists.txt – adds the list of sub directories to the upper level CMakeLists.txt
  • Kconfig – includes the Kconfig file available in the sub directories.

dts directory

Contains the device tree binding for our custom driver. Each device requires a device tree binding. The device tree bindings are described in a yaml file. The yaml file is basically a declaration of the device tree. It provides information on the contents of the nodes and provides specific semantic information. Please read Devicetree bindings for more information.

description: |
  Sensor simulation driver with dummy data

  Example usage:
  sensor_sim {
    compatible = "rdt,sensor_sim";
    sim-attr = <5>;
  };

compatible: "rdt,sensor_sim"

properties:
  sim-attr:
    type: int
    description: |
      A random integer.

boards directory

Our example uses the native_posix board, but can be built and run on other boards also.

  • native_posix.conf – board specific configurations are set here
  • native_posix.overlay – contains the device tree for our custom sensor driver. The dts includes two instances of the sensor_sim driver
/ {
	sensor_sim1: sensor_sim1 {
		status = "okay";
		compatible = "rdt,sensor_sim";
		sim-attr = <5>;
	};

	sensor_sim2: sensor_sim2 {
		status = "okay";
		compatible = "rdt,sensor_sim";
		sim-attr = <3>;
	};
};

src directory

Contains the application source code and configurations.

  • main.c – application source code
  • Kconfig – application specific configurations if needed
  • CMakeLists.txt – includes the application sources for the generator

CMakeLists.txt

This is the main CMakeLists file. It contains the relevant information to add our modules, dts and boards directory to the Zephyr’s build system. It also adds the application sources for the build.

cmake_minimum_required(VERSION 3.20.0)

# Add our modules directory to Zephyr Modules
list(APPEND ZEPHYR_EXTRA_MODULES ${CMAKE_CURRENT_SOURCE_DIR}/modules)

# Set project, dts and board directories
set(PROJ_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/)
set(DTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/dts)
set(BOARD_ROOT ${CMAKE_CURRENT_LIST_DIR})


find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(zephyr_custom_driver)

add_subdirectory(src)

prj.conf

application specific configurations are set here

sample.yaml

This is an information file related to the sample and what is being demonstrated. It can also contain test cases for Twister, Zephyr’s Test Runner. This file is not mandatory.

Code Walkthrough

Below are the important sections of the custom driver sensor_sim.c

1. Define the driver compatible

The driver’s compatible must be defined in the source file. This must match the compatible declared in the device tree binding, i.e. compatible: rdt,sensor_sim. The commas should be changed to underscores.

#define DT_DRV_COMPAT rdt_sensor_sim
2. Declare the config and data structures

Two structures should be declared (a) config, (b) data. These contain the variables needed for the device driver to work

struct sensor_sim_config {

};

struct sensor_sim_data {
	int sensor_attr;
	uint32_t sensor_val;
};
3. Define the init function

Each zephyr driver must have an init function. The init function is automatically called by the kernel during system boot. This function should ideally check the status of the hardware device and return a success or failure code accordingly. If this function returns a failure code, then the driver will not be registered with the Kernel and the application will not be able to call its APIs. Here we are returning 0 as a success code.

static int sensor_sim_init(const struct device *dev)
{
	ARG_UNUSED(dev);
	int ret = 0;

	LOG_INF("sensor_sim_init");

	return ret;
}
4. Define the driver API and its functions

Here we are using the sensor driver API interface, hence we are defining the sample fetch and sample get function pointers. When an application calls the sample fetch and sample get functions through the sensing API, these functions sensor_sim_sample_fetch and sensor_sim_sample_get will get called

static const struct sensor_driver_api sensor_sim_driver_api = {
	.sample_fetch = sensor_sim_sample_fetch,
	.channel_get = sensor_sim_channel_get,
};

If you don’t want to use the sensor driver API interface, you can also define a custom set of APIs to interact with the driver. This is beyond the scope of this article, so let’s proceed with the senor API.

5. Finally add the driver to the Zephyr kernel

Now we can add the above variables to the kernel though the DEVICE_DT_INST_DEFINE macro. This MACRO creates a device object from a devicetree node identifier and sets it up for boot time initialization. Here we need to create 2 variables for the config and data structures. Since there can be multiple instances for the same driver as in our case (see the native_posix.overlay file) we use the MACRO DT_INST_FOREACH_STATUS_OKAY which iterates for each of the nodes and calls DEVICE_DT_DEFINE through DEVICE_DT_INST_DEFINE to create the device object. See the documentation for DEVICE_DT_DEFINE for description on the parameters.

#define DEVICE_INSTANCE(inst) \
\
const static struct sensor_sim_config sensor_sim_##inst##_cfg;\
\
static struct sensor_sim_data sensor_sim_##inst##_drvdata = { \
		.sensor_attr = DT_PROP(DT_DRV_INST(inst),sim_attr), \
}; \
\
DEVICE_DT_INST_DEFINE(inst,						\
		sensor_sim_init,								\
		device_pm_control_nop,							\
		&sensor_sim_##inst##_drvdata,					\
		&sensor_sim_##inst##_cfg,						\
		POST_KERNEL, CONFIG_SENSOR_SIM_INIT_PRIORITY,	\
		&sensor_sim_driver_api);

DT_INST_FOREACH_STATUS_OKAY(DEVICE_INSTANCE);

That’s it. Now our application can call the custom driver.

6. Call the driver functions though Zephyr’s sensing APIs

 The below code is self-explanatory.

const struct device* sim_dev1 = DEVICE_DT_GET(DT_NODELABEL(sensor_sim1));
	if (sim_dev1 == NULL) {
		LOG_ERR("device sensor_sim1 not found");
		return -1;
	}

	const struct device* sim_dev2 = DEVICE_DT_GET(DT_NODELABEL(sensor_sim2));
	if (sim_dev2 == NULL) {
		LOG_ERR("device sensor_sim2 not found");
		return -1;
	}

	struct sensor_value val;

	while (1) {
		sensor_sample_fetch_chan(sim_dev1, SENSOR_CHAN_AMBIENT_TEMP);
		sensor_channel_get(sim_dev1, SENSOR_CHAN_AMBIENT_TEMP, &val);
		LOG_INF("sim1 val = %d\n", val.val1);

		sensor_sample_fetch_chan(sim_dev2, SENSOR_CHAN_AMBIENT_TEMP);
		sensor_channel_get(sim_dev2, SENSOR_CHAN_AMBIENT_TEMP, &val);
		LOG_INF("sim2 val = %d\n", val.val1);

		k_sleep(K_MSEC(1000));
	}

Thanks for reading! Please send your comments or questions.

]]>
https://rodeytech.com/2024/03/27/create-custom-device-drivers-for-zephyr-rtos-project/feed/ 0 118
Setup a simple CMake project for a C/C++ application https://rodeytech.com/2024/03/20/setup-a-simple-cmake-project-for-a-c-c-application/ https://rodeytech.com/2024/03/20/setup-a-simple-cmake-project-for-a-c-c-application/#respond Wed, 20 Mar 2024 12:35:29 +0000 https://rodeytech.com/?p=89 CMake is a cross-platform free and open-source software for build automation, testing, packaging and installation of software by using a compiler-independent method. CMake is not a build system itself; it generates another system’s build files. It supports directory hierarchies and applications that depend on multiple libraries. It can invoke native build environments such as Make, Qt Creator, Ninja, Android Studio, Apple’s Xcode, and Microsoft Visual Studio. It has minimal dependencies, requiring only a C++ compiler on its own build system. Source Wikipedia.

In this article I will show how to use CMake in a simple C++ project to generate the required makefiles and build the project on Linux and Windows host machines. Let’s get started.

CMake Installation

By default, the newer versions of Ubuntu comes with the CMake package installed. To verify whether CMake is installed or not, open a terminal window and run:

$ cmake --version

If CMake is not installed download and install from https://cmake.org/download/ or run the following command:

$ sudo apt install cmake

Please read the “Windows” section under “Other tools to install” to install cmake along with other utilities.

Other tools to install

To build a C/C++ project you must install a compiler and a make utility. Here I will show how to install and use the gcc, g++ and make utilities.

By default, gcc, g++ and make utilities are installed. To verify whether they are installed or not, open a terminal and type:

$ gcc --version
$ g++ --version
$ make --version

If these utilities are not installed, then run the following command:

$ sudo apt install make gcc gcc-multilib g++-multilib

Download and install MSYS2 from: https://www.msys2.org/

MSYS2 is a collection of tools and libraries providing you with an easy-to-use environment for building, installing and running native Windows software.

After installation start the MSYS2 terminal app from the start menu.

Install the gcc, g++ compilers, cmake and make using the following commands:

$ pacman -S mingw-w64-ucrt-x86_64-gcc
$ pacman -S cmake
$ pacman -S make

Verify each installation by running the below commands:

$ gcc --version
$ g++ --version
$ cmake --version
$ make --version

If all is good you can generate and build your program using CMake

Configure a C/C++ project with CMake

Consider the below C++ project having multiple sub-directories. Each of the directories contains a “CMakeLists.txt” file. These CMakeLists.txt files contains the necessary cmake configurations to generate the relevant makefiles and compile the program:

cmake_cpp
    |- source
            |- CMakeLists.txt
            |- src
                 |- CMakeLists.txt
                 |- main.cpp
                 |
                 |- module1
                 |      |- CMakeLists.txt
                 |      |- module1.cpp
                 |      |- module1_priv.h
                 |
                 |- module2
                 |      |- CMakeLists.txt
                 |      |- module2.cpp
                 |      |- module2_priv.h
                 |
                 |- include
                         |- main.h
                         |- module1.h
                         |- module2.h

Below are the content and explanation of each CMakeLists.txt file.

cmake_cpp/source/CMakeLists.txt`
# set the minimum cmake version requirement
cmake_minimum_required(VERSION 3.20)

# set the project name and version number
project (cmake_cpp
	VERSION 0.1
	DESCRIPTION "CMake demo")

# This project will output an executable file called app
add_executable(app "")

# add the next lower level directory
add_subdirectory(src)
cmake_cpp/source/src/CMakeLists.txt
# add the next lower level directories
add_subdirectory(module1)
add_subdirectory(module2)

# make a list of source files to be compiled
list(APPEND app_sources 
			main.cpp
# 			add more source files here
	)

# this function adds the list of source files to the executable app
target_sources(app PUBLIC ${app_sources})

# add paths to include directories
target_include_directories(app PUBLIC . include)
cmake_cpp/source/src/module1/ CMakeLists.txt (same for cmake_cpp/source/src/module2/ CMakeLists.txt)
# make a list of source files to be compiled
list(APPEND app_sources 
			module1.cpp
# 			add more source files here
	)

# this function adds the list of source files to the executable app
target_sources(app PUBLIC ${app_sources})

# if there is a need to add module specific include directories, uncomment the following line
#target_include_directories(app PUBLIC .)

Generate and Build

The next step is to generate the relevant makefiles and then call the make utility to compile and link and build the executable. Below are the set of commands to do so. Please note these commands will run on Ubuntu and Windows (tested in MSYS2)

Change to the `cmake_cpp` directory (the below command is considering the directory is at /home/user)

$ cd /home/user/cmake_cpp

The next command is to tell the cmake program to use the files under the source directory and generate the necessary makefiles inside a build directory. Note that the build directory may or may not be present. If not present the cmake program will create the build directory.

$ cmake -Ssource -Bbuild

Next is to compile the code. This command tells the cmake program to compile the files present inside `build` directory.

$ cmake --build build

Use the below commands if the above build fails. If cmake cannot find the compiler, the path can be set from the command line. A known issue with MSYS2 is, it may use the gcc compiler in place of g++ compiler. If you get linker errors of undefined references to C++ standard library objects, then set the g++ compiler as shown below.

$ cmake -DCMAKE_CXX_COMPILER=/ucrt64/bin/g++.exe -Ssource -Bbuild
$ cmake --build build

Use the command whereis g++ to get the path to the g++ executable

CMake can also generate configuration files for IDEs. Below is a list of Generators available. Use cmake –help to see the list.

Generators

The following generators are available on this platform (* marks default):
  Green Hills MULTI            = Generates Green Hills MULTI files
                                 (experimental, work-in-progress).
* Unix Makefiles               = Generates standard UNIX makefiles.
  Ninja                        = Generates build.ninja files.
  Ninja Multi-Config           = Generates build-<Config>.ninja files.
  Watcom WMake                 = Generates Watcom WMake makefiles.
  CodeBlocks - Ninja           = Generates CodeBlocks project files.
  CodeBlocks - Unix Makefiles  = Generates CodeBlocks project files.
  CodeLite - Ninja             = Generates CodeLite project files.
  CodeLite - Unix Makefiles    = Generates CodeLite project files.
  Eclipse CDT4 - Ninja         = Generates Eclipse CDT 4.0 project files.
  Eclipse CDT4 - Unix Makefiles= Generates Eclipse CDT 4.0 project files.
  Kate - Ninja                 = Generates Kate project files.
  Kate - Unix Makefiles        = Generates Kate project files.
  Sublime Text 2 - Ninja       = Generates Sublime Text 2 project files.
  Sublime Text 2 - Unix Makefiles
                               = Generates Sublime Text 2 project files.

For instance, if you want to generate the cmake_cpp program for Eclipse IDE use the below command.

$ cmake -Ssource -Bbuild -G"Eclipse CDT4 - Unix Makefiles"
$ cmake --build build

To enable debugging in Eclipse IDE use.

$ cmake -Ssource -Bbuild -DCMAKE_BUILD_TYPE=Debug -G"Eclipse CDT4 - Unix Makefiles"
$ cmake --build build

The Eclipse project is generated inside the build directory. To open the project in eclipse go to File -> Import -> Existing Projects into Workspace and browse for the cmake_cpp/build directory

Source files

Complete source can be found in the following github link:

https://github.com/deyro/cmake_cpp

I hope the information provided here is useful and will help others to get started with using CMake in C/C++ projects.

For any questions please comment.

]]>
https://rodeytech.com/2024/03/20/setup-a-simple-cmake-project-for-a-c-c-application/feed/ 0 89
First Post https://rodeytech.com/2024/03/03/test-post/ https://rodeytech.com/2024/03/03/test-post/#respond Sun, 03 Mar 2024 16:02:31 +0000 https://rodeytech.com/?p=7 Hello! This is my first post. Thank you for visiting my blog

Do check out my other posts https://rodeytech.com/blog/

]]>
https://rodeytech.com/2024/03/03/test-post/feed/ 0 7