Federico Fuga

Engineering, Tech, Informatics & science

Threads, threads everywhere (in Zephyr, and in C++)

21 Mar 2023 14:54 CET

In this tutorial I explain how to create threads in Zephyr.

But first, let’s switch to C++, it’s easier and more clean.

To enable C++ you need to specify one variable in the configuration: CPP=y. But since you may be instantiating your objects in the main() function, and C and C++ have different declarations of it (it’s void in C and int in C++), you may want to use the C++ version. For this reason you also need to set CPP_MAIN=y.

This is the prj.conf:

CONFIG_CPP=y
CONFIG_STD_CPP14=y
CONFIG_CPP_MAIN=y

The STD_CPP* variable lets you specify what standard to use. I mostly work in C++14, so I’m defining it in my projects, but note that by default Zephyr uses C++11.

Now let’s start talking about threads. No customizations or configuration are needed, because threads are part of the Zephyr OS.

I assume, of course, that you know what threads are and how to use them.

Threads have a stack, and in Zephyr they need to be created using the K_THREAD_STACK_DEFINE macro, because some cpu has requirements about the setup and alignment of this block. You can’t simply reserve a block of memory and use it as a stack, or at least, that may work for a CPU but may fail for another.

The macro reserve a block of memory with proper alignment and size. It must be defined statically, I haven’t found a quick way to dynamically allocate a stack, and probably this is not even a usecase for a microcontroller.

A stack can have a thread local data and a Thread id. They must be of type struct k_thread and k_tid_id respectively.

A thread is launched using the k_thread_create() call, or the K_THREAD_DEFINE macro to start it automatically. Personally I don’t like it, so I’m going to use the first option.

In my project I’m goin to use a BlinkingThread class object. It is created, set up by calling setup() which sets the resources we’re going to use (in this case the LED gpio), then the start() function creates the thread using the k_thread_create() call. This passes the k_thread storage and sets the k_tid_id in the object.

Since the main thread has nothing to do, we’re going to wait for the completion of the thread using the k_thread_join() call. It pauses the main thread forever (we pass the K_FOREVER timeout constant) because the thread simply doesn’t finish.

Communication between thread con be done using the multithreading tools like mutex, conditional variables and so on, but soon we’re going to discover the Work Queues and life will be much easier.

This is the Header of the class

class BlinkingThread {
public:
	explicit BlinkingThread();

	void setup(const struct gpio_dt_spec *led);

	void start();

	void wait();

private:
	const struct gpio_dt_spec *led;

	k_tid_t threadId;
	struct k_thread threadData;

	void main();

	static void threadTrampoline(void *, void *, void *);
};

and this is the implementation:

K_THREAD_STACK_DEFINE(threadStack, STACK_SIZE);
...
void BlinkingThread::start()
{
	threadId = k_thread_create(&threadData, 
		threadStack,
		K_THREAD_STACK_SIZEOF(threadStack),
		BlinkingThread::threadTrampoline,
		this, NULL, NULL,
		THREAD_PRIORITY, 0, K_NO_WAIT);
}

void BlinkingThread::wait()
{
	k_thread_join(&threadData, K_FOREVER);
}

void BlinkingThread::threadTrampoline(void *object, void *, void *)
{
	auto threadObject = reinterpret_cast<BlinkingThread*>(object);
	threadObject->main();
}

void BlinkingThread::main()
{
	int ret;

	while (1) {
		LOG_INF("Blinking");
		ret = gpio_pin_toggle_dt(led);
		if (ret < 0) {
			return;
		}
		k_msleep(SLEEP_TIME_MS);
	}

}

As usual, you can find the repository in my github page.