Five EmbedDev logo Five EmbedDev

An Embedded RISC-V Blog

Exploring C++20 coroutines for embedded and bare-metal development on RISC-V platforms

Can C++20 coroutines build efficient, real-time embedded applications on bare-metal RISC-V platforms — no RTOS required?

November 24, 2024 (C++,baremetal,coroutines)

Introduction

This post is about using C++ coroutines to suspend and resume functions in real time. The objective is a simple way of building real time tasks using only C++, without the need for an RTOS or operating system kernel.

Coroutines are functions that can be suspended and resumed, using the keywords co_await, co_yield and co_return. The C++20 standard introduced coroutines to the language.

C++ standardized the keywords and type concepts for coroutines, but it did not standardize a runtime1. The lack of a standard runtime has made them hard to use them “out of the box”, but the implementation of coroutines is very adaptable to different use cases.

Project X

Here I use a simple runtime implementing C++20 coroutines on bare metal (no operating system) for RISC-V, using the co_await keyword. This is done by passing the real time scheduler and resume time condition as the argument to the asynchronous wait operator.

The runtime is described in detail in this post.

This story is also published on Medium.

  1. The C++23 standard library provides a limited runtime for coroutines generators. 

(more...)


Building a header-only C++20 coroutine runtime for bare-metal RISC-V

Designing a lightweight coroutine runtime for real-time tasks without an OS, featuring awaitable timers and static memory allocation

November 24, 2024 (C++,baremetal,coroutines)

Creating the coroutines runtime infrastructure

A simple coroutine example was presented in “C++20 coroutines, header only, without an OS”. This post describes the runtime used for that example in detail.

Project X

This story is also published on Medium.

Summary of the runtime files

The runtime for this example is a set of include files in include/coro. These files are used:

  • nop_task.hpp : Task structure including promise_type to conform the C++ coroutines task concept.
  • scheduler.hpp : Generic scheduler class that can manage a set of std::coroutine_handle to determine when they should resume and implement the resumption.
  • awaitable_timer.hpp : An “awaitable” class that can be used with co_await to schedule a coroutines to wake up after a given std::chono delay.
  • static_list.hpp: An alternative to std::list that uses custom memory allocation from a static region to avoid heap usage.
  • awaitable_priority.hpp: An alternative “awaitable” class for tasks to be scheduled to wake according to priority.

NOTE: All classes here are designed to not use the heap for allocation. They will allocate all memory from statically declared buffers.

(more...)


Direct Hardware Access in C

A RISC-V Example

March 20, 2023 (baremetal,C,articles,interrupts,timer)

This article was also posed to Medium.

The C programming language provides a thin hardware abstraction that allows us to create low-level systems programs. However, there are still many hardware features that aren’t exposed by the programming language. How do we access hardware while programming in C?

This article covers some tricks used to write low-level code in C and build a simple bare-metal run-time environment. The target hardware is a RISC-V RV32I in machine mode. The RISC-V is a simple architecture well suited to understanding low-level programming, and a small 32-bit core is ideal for applications that benefit from bare-metal programming.

System

(more...)


A Baremetal Introduction using C++. Interrupt Handling.

May 06, 2021 (baremetal,C++,interrupts)

What are the basics of interrupt handing in RISC-V? Can we utilize modern C++ to simplify the interrupt handling?


A Baremetal Introduction using C++. Machine Mode Timer.

May 05, 2021 (baremetal,C++,timer)

This is the sixth post in a series, about the RISC-V machine mode timer and timing keeping using the C++ std::chrono library.


A Baremetal Introduction using C++. System Registers.

May 03, 2021 (baremetal,C++,csr)

What are system registers in RISC-V? How can we access them with modern C++?

System registers require special instructions to access, so unlike memory mapped registers (MMIO) we can’t just cast a pointer to memory to get access them in C++.


A Baremetal Introduction using C++. Startup.

May 03, 2021 (baremetal,C++,startup)

In the last post, we set up the development environment. This post is about how the RISC-V core executes our program.

How do we go from reset to entering the main() function in C++ in RISC-V? Startup code is generally not something you need to worry about, however, it is of interest when bringing up a device from scratch.


A Baremetal Introduction using C++. Development Environment

April 30, 2021 (baremetal,C++,toolchain)

For this series of posts, my platform is a SiFive HiFive1 Rev B development board. It’s equipped with a 320MHz RV32IMAC (FE310 core). For software build and debug the choice is Platform IO, an IDE integrated into VS Code.


A Baremetal Introduction using C++. Overview.

April 30, 2021 (baremetal,C++)

As described in Part1, a simple C++ application to blink an LED, what does this look like with no operating system?


A Baremetal Introduction using C++. Introduction.

April 30, 2021 (baremetal,C++)

This is a series of posts where I’d like to combine those topics for embedded systems programming. RISC-V and C++ have been evolving rapidly, and you will see modern C++ is a great way to explore RISC-V and develop embedded systems.