CMSIS++: a proposal for a future CMSIS, written in C++

Overview

CMSIS++ is a portable, vendor-independent hardware abstraction layer intended for C++/C embedded applications, designed with special consideration for the industry standard ARM Cortex-M processor series. Read "CMSIS++" as "the next generation CMSIS", "CMSIS v2.0", or, more accurately, "C++ CMSIS".

Major features and benefits

Written in C++ but with C wrappers for full C support

The original ARM/Keil name stands for Cortex Microcontroller Software Interface Standard, and the CMSIS++ design inherits the good things from ARM CMSIS, but goes one step further and ventures into the world of C++; as such, CMSIS++ is not a C++ wrapper running on top of the ARM CMSIS APIs, but a set of newly designed C++ APIs, with C APIs supported as wrappers on top of the native C++ APIs.

Close adherence to standards (POSIX and ISO)

The first iteration of CMSIS++ was a direct rewrite in C++ of ARM CMSIS, but later most of the definitions were adjusted to match the POSIX IEEE Std 1003.1, 2013 Edition and the ISO/IEC 14882:2011 (E) – Programming Language C++ standards.

As such, CMSIS++ RTOS API is no longer a wrapper over Keil RTX (as ARM CMSIS unfortunately was), but a wrapper over standard threads and synchronisation objects.

Compatibility with existing ARM CMSIS

Although fully written in C++, the CMSIS++ RTOS API, initially implemented on top of the FreeRTOS scheduler, and accessible via the C wrapper, was the first non-Keil RTOS that passed the recently released CMSIS RTOS validation suite.

The CMSIS++ RTOS APIs

There are many components in the original CMSIS, but the major ones that benefit from C++ are RTOS and Drivers. Since everything revolves around the RTOS API, the C++ RTOS API was the first CMSIS++ API defined and is presented here in more detail.

Under the CMSIS++ RTOS APIs umbrella there are actually several interfaces, two in C++, two in C and one internal, in C++. The relationships between them is presented below:

cmsis-plus-rtos-overview.png

The native RTOS C++ API

This is the native RTOS interface, implemented in C++, and providing access to the entire RTOS functionality.

The classes are grouped under the os::rtos namespace, and, to access them, C++ applications need to include the <cmsis-plus/rtos/os.h> header.

Objects can be instantiated from native classes in the usual C++ way, and can be allocated statically, dynamically on the caller stack or dynamically on the heap.

Inspired by the POSIX threads usage model, all CMSIS++ native objects can be instantiated in two ways:

  • a simple, minimalistic, default way, with a default constructor, or, if not possible, a constructor with a minimum number of arguments.
  • a fully configurable, maximal way, by using a set of specific attributes, passed as the first argument to a separate constructor.

For example, to create a thread with default settings, only the pointer to the thread function and a pointer to the function arguments need to be specified, while a thread with custom settings can also have a custom priority, a static stack, and possibly other custom settings.

Here is a short example with a thread that counts 5 seconds and quits:

#include <cmsis-plus/rtos/os.h>
#include <cmsis-plus/diag/trace.h>

using namespace os;

// Define the thread function.
// Native threads can have only one pointer parameter.
void*
func(void* args)
{
  for (int i = 0; i < 5; i++).
    {
      trace::printf("%d sec\n", i);

      // Sleep for one second.
      rtos::sysclock::sleep_for(rtos::clock_systick::frequency_hz);
    }
  return nullptr;
}

// In CMSIS++, os_main() is called from main()
// after initialising and starting the scheduler.
int
os_main(int argc, char* argv[])
{
  // Create a new native thread, with pointer to function and no arguments.
  // The thread is automatically destroyed at the end of the os_main() function.
  rtos::thread th { func };

  // Wait for the thread to terminate.
  th.join();

  trace::puts("done.");
  return 0;
}

The native CMSIS++ thread is basically a POSIX thread, with some additional functionality (see the os::rtos::thread reference page for more details).

Similarly, synchronisation objects can be created with the usual C++ approach; for example a piece of code that uses a mutex to protects a counter looks like this:

#include <cmsis-plus/rtos/os.h>

// Protected resource (a counter).
typedef struct {
  int count;
} resource_t;

// Alloc the resource statically.
resource_t resource;

// Define a native mutex to protect the resource.
rtos::mutex mx;

void
count(void)
{
  mx.lock();
  // Not much here, real applications are more complicated.
  resource.count++;
  mx.unlock();
}

The ISO C++ Threads API

The CMSIS++ ISO C++ Threads API is an accurate implementation of the ISO C++ 11 standard threads specifications.

With the ISO standard threads defined as wrappers over POSIX threads, and with the CMSIS++ native threads functionally compatible with POSIX threads, the implementation of the CMSIS++ ISO threads was quite straightforward.

The classes are grouped under the os::estd namespace, and, to access them, C++ applications have to include headers from the cmsis-plus/estd folder, like <cmsis-plus/estd/thread>. The namespace std:: and the standard header names (like <thread>) could not be used, to avoid clashes with system definitions when building CMSIS++ applications on POSIX host systems. The e in estd stands for embedded, so the namespace is dedicated to embedded standard definitions.

A similar example using the standard C++ threads:

#include <cmsis-plus/estd/thread>
#include <cmsis-plus/estd/chrono>
#include <cmsis-plus/diag/trace.h>

using namespace os;
using namespace os::estd; // Use the embedded version of 'std::'.

// Define the thread function.
// Thanks to the magic of C++ tuples, standard threads
// can have any number of arguments, of any type.
void*
func(int max_count, const char* msg)
{
  for (int i = 0; i < max_count; i++).
    {
      trace::printf("%d sec, %s\n", i, msg);

      // Sleep for one second. <chrono> is very convenient,
      // notice the duration syntax.
      this_thread::sleep_for (1s);
    }
  return nullptr;
}

// In CMSIS++, os_main() is called from main()
// after initialising and starting the scheduler.
int
os_main(int argc, char* argv[])
{
  // Create a new standard thread, and pass two arguments.
  // The thread is automatically destroyed at the end of the os_main() function.
  thread th { func, 5, "bing" };

  // Wait for the thread to terminate.
  th.join();

  trace::puts("done.");
  return 0;
}

Most of the goodies of the C++ 11 standard can be used, for example RAII mutex locks, condition variables, lambdas:

#include <cmsis-plus/estd/mutex>
#include <cmsis-plus/estd/condition_variable>

using namespace os;
using namespace os::estd;

// Protected resource (a counter and a limit).
typedef struct {
  int count;
  int limit;
} resource_t;

// Alloc the resource statically.
resource_t resource { 0, 10 };

// Define a standard mutex to protect the resource.
mutex mx;
// Define a condition variable to notify listeners and detect limits.
condition_variable cv;

// Increment count and notify possible listeners.
void
count(void)
{
  unique_lock<mutex> lck(mx); // Enter the locked region.

  resource.count++;
  cv.notify_one();

  // No need to explicitly unlock, done automatically.
}

// Return only when count reaches the limit.
void
wait_for_limit()
{
  unique_lock<mutex> lck(mx); // Enter the locked region.

  cv.wait(lck,
          []{ return (res.count >= res.limit); }
  );
}

The new CMSIS++ RTOS C API

Although fully written in C++, CMSIS++ also provides a C API, to be used by C applications. Yes, that's correct, plain C applications can use CMSIS++ without any problems. Only that function names are a bit longer and some of the C++ magic (like running the constructors and the destructors) needs to be done by hand, but otherwise the entire functionality is available.

The C API is defined in the <cmsis-plus/rtos/os-c-api.h> header.

The same simple example that counts 5 seconds and quits, in C would look like:

#include <cmsis-plus/rtos/os-c-api.h>
#include <cmsis-plus/diag/trace.h>

// Define the thread function.
// Native threads can have only one pointer parameter.
void*
func(void* args)
{
  for (int i = 0; i < 5; i++).
    {
      trace_printf("%d sec\n", i);

      // Sleep for one second.
      os_sysclock_sleep_for(OS_INTEGER_SYSTICK_FREQUENCY_HZ);
    }
  return NULL;
}

// In CMSIS++, os_main() is called from main()
// after initialising and starting the scheduler.
int
os_main(int argc, char* argv[])
{
  // Manually allocate space for the thread.
  os_thread_t th;

  // Initialise a new native thread, with function and no arguments.
  os_thread_create(&th, NULL, func, NULL, NULL);

  // Wait for the thread to terminate.
  os_thread_join(&th, NULL);

  // Manually destroy the thread.
  os_thread_destroy(&th);

  trace_puts("done.");
  return 0;
}

The ARM CMSIS RTOS C API (compatibility layer)

Even more, the CMSIS++ C wrapper also implements the original ARM CMSIS API. This is a full and accurate implementation, since this API already passed the ARM CMSIS RTOS validation test.

To access this API, include the <cmsis_os.h> header provided in the CMSIS++ package.

#include <cmsis_os.h>
#include <cmsis-plus/diag/trace.h>

// Define the thread function.
// ARM CMSIS threads can have only one pointer parameter.
void
func(void* args)
{
  for (int i = 0; i < 5; i++).
    {
      trace_printf("%d sec\n", i);

      // Sleep for one second.
      osDelay(osKernelSysTickFrequency);
    }
  // ARM CMSIS threads can return, but there is
  // no way to know when this happens.
}

// The unusual way of defining a thread, specific to CMSIS RTOS API.
// It looks like a function, but it is not, it is a macro that defines
// some internal structures.
osThreadDef(func, 0, 1, 0);

// In CMSIS++, os_main() is called from main()
// after initialising and starting the scheduler.
int
os_main(int argc, char* argv[])
{
  // Initialise a new ARM CMSIS thread, with function and no arguments.
  osThreadCreate(osThread(func), NULL);

  // Since ARM CMSIS has no mechanism to wait for a thread to terminate,
  // a more complicated synchronisation scheme must be used.
  // In this test just sleep for a little longer.
  osDelay(6 * osKernelSysTickFrequency);

  trace_puts("done.");
  return 0;
}

The CMSIS++ RTOS Reference

The entire CMSIS++ RTOS interface is fully documented in the separate site, available in the project web :

cmsis-plus-rtos-reference.png

More CMSIS++ components

In addition to the RTOS APIs, CMSIS++ also includes:

  • CMSIS++ Drivers - a C++ rewrite of CMSIS Drivers, with extensions;
  • CMSIS++ POSIX I/O - a layer bringing together access to terminal devices, files and sockets, via a unified and standard API, using open(), close(), read(), write() as main functions;
  • CMSIS++ Startup - a portable startup code, replacing non-portable vendor assembly code;
  • CMSIS++ Core - C++ API for the ARM Cortex-M processors core and peripherals;
  • CMSIS++ Diagnostics - a C++/C API providing support for diagnostics and instrumentation.

Conclusions

CMSIS++ is still a young project, and many things need to be addressed, but the core component, the RTOS API, is pretty well defined and awaiting for comments.

For now it may not be perfect (as it tries to be), but it definitely provides a more standard set of primitives, closer to POSIX, and a wider set of APIs, covering both C++ and C applications; at the same time it does its best to preserve compatibility with the original ARM CMSIS APIs.

Any contributions to improve CMSIS++ will be highly appreciated.

More info

CMSIS++ is an open source project, maintained by Liviu Ionescu.

The main source of information for CMSIS++ is the project web.

The Git repositories and all public releases are available from GitHub.

For questions and discussions, please use the CMSIS++ section of the GNU ARM Eclipse forum.

For bugs and feature requests, please use the GitHub issues.

Follow-ups

  • I welcome this idea. I may come with comments on architecture and/or design, once I look through (and possibly try) the specification and code.

    For now I have remarks on licence. I think LGPL is not suitable for embedded systems. Most of Cortex-M software is delivered as a firmware of a product, and application dedicated to a specific hardware. Therefore providing object code brings no much benefit.

    From my viewpoint the best would be to use the same licence of original CMSIS. However, if you feel that C++ CMSIS source itself should be copylefted, I would suggest GPL with linkage exception. Example of such licence: http://ecos.sourceware.org/license-overview.html.

    Regards

    Ilija

  • > I may come with comments on architecture and/or design

    Please do, comments and suggestions are welcomed. For discussions, preferably use the forum; for source/documentation specific suggestions, pull requests or GitHub issues are preferred.

    If you want to stay current with the progress, perhaps it would be useful to "watch" the GitHub projects (the source code and the documentation).

    > LGPL is not suitable for embedded systems

    I'm not a lawyer, so I might be wrong, but the term "Library" in LGPL does not explicitly mean binary library, so in the project license notice (The CMSIS++ / µOS++ IIIe Project) I mentioned that the term includes the source files.

    If necessary, I can make such a notice (or a better one, as eCos does) in each source file. But L in LGPL means "lesser", no "library", and to me it seems to do exactly what we need. Otherwise I would stay away from plain GPL, it is too extreme for this purpose.

  • Liviu

    I am not a lawyer either, but it's not the legal issue that I worry about.

    The trouble with LGPL is the requirement to provide the object code  [Section 4. Combined Works - see excerpt below]. I am afraid that this is unnecessary burden for embedded system producers/developers/designers and may repel many of them.  On the other hand, Cortex-M applications, almost exclusively run on custom hardware, so publishing the object code brings very little, (if any at all) benefit to anybody.

    GPL with linking exception licences have no such or any other requirement, that would concern independent/original user (application) code.

    FYI, besides eCos, there are other projects with similar licence terms: RTEMS, FreeRTOS, parts of NewLib, etc.

    Ilija

    ==================================================================

    Excerpt from LGPL

    ...

    4. Combined Works.

    [snip]

       d) Do one of the following:

           0) Convey the Minimal Corresponding Source under the terms of this

           License, and the Corresponding Application Code in a form

           suitable for, and under terms that permit, the user to

           recombine or relink the Application with a modified version of

           the Linked Version to produce a modified Combined Work, in the

           manner specified by section 6 of the GNU GPL for conveying

           Corresponding Source.

           1) Use a suitable shared library mechanism for linking with the

           Library.  A suitable mechanism is one that (a) uses at run time

           a copy of the Library already present on the user's computer

           system, and (b) will operate properly with a modified version

           of the Library that is interface-compatible with the Linked

           Version.

    ...

  • > the requirement to provide the object code

    My reading of LGPL does not concur to this. LGPL 2. requires to convey the modified versions of the source files, and here everything seems clear, there are no mentions of object code.  LGPL 4. d) 0) that you refer to is a bit more obscure, but it requires to convey the “Minimal Corresponding Source” (which I read as minimum additional source files required to compile the modified library source files) and “Corresponding Application Code” (which I read as any other code or utilities required to allow the integration of the modified source files into an application).

    In other words, if I issue a version of CMSIS++ that works with my own applications, and you improve it and integrate it into your applications, you need to convey not only the modified sources, but also all necessary details on how to do it (new headers, possible binary libraries you call from the modified sources, tools you use for the build, scripts, utilities, data files, procedures, etc). In other words, if I want my application to benefit from your modifications, you must allow me to.

    If my understanding is correct, there should be no significant burden in doing so, to me it seems more like common sense from a code sharing perspective.

    If I'm wrong, can you be more specific which object code need to be provided?

  • Liviu Ionescu wrote:

    > the requirement to provide the object code

    My reading of LGPL does not concur to this. LGPL 2. requires to convey the modified versions of the source files, and here everything seems clear, there are no mentions of object code.  LGPL 4. d) 0) that you refer to is a bit more obscure, but it requires to convey the “Minimal Corresponding Source” (which I read as minimum additional source files required to compile the modified library source files) and “Corresponding Application Code” (which I read as any other code or utilities required to allow the integration of the modified source files into an application).

    In other words, if I issue a version of CMSIS++ that works with my own applications, and you improve it and integrate it into your applications, you need to convey not only the modified sources, but also all necessary details on how to do it (new headers, possible binary libraries you call from the modified sources, tools you use for the build, scripts, utilities, data files, procedures, etc). In other words, if I want my application to benefit from your modifications, you must allow me to.

    If my understanding is correct, there should be no significant burden in doing so, to me it seems more like common sense from a code sharing perspective.

    If I'm wrong, can you be more specific which object code need to be provided?

    “Corresponding Application Code” is defined in Section 0 Additional definitions:

    • The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work.

    To my understanding it includes Application object code.

  • Engineering in C++/OOP style on nowadays MCU, which are machines for pipeline execution and following same programming model, makes no sense to me.

    Only, and I say this positively, only sensible deployment of pure C++ framework on actual Embedded Devices would be for Developers who don't need so much HW awareness, because they write the APPLICATION portion of one Firmware. And not the low level system one. That makes some sense regarding MCU. Just for example, in Linux we have kernel developers and we have GUI developers[*OT1]. So yeah, it's actually an approach that needs to be transferred, and will be - sooner or later. But not in such a way, that it actually promotes people, who already have 0 to none Machine Sympathy to code on devices, which require it even more[*OT2].

    Almost can't think of a benefit that C++ style can introduce to my code in Embedded C source. Most notably:

    * Better maintainability - No, not related to the C++ style, but to programmer's individual style. C++ improvements are to meet the OOP model. Even in the Linux kernel, which runs on embedded application cores, only some improvements from C99 are accepted. There are still some twisted definitions, but it's C + ... , not C++

    * Better readability - No, same reason as above.

    * Better performance - No way, it's only a higher abstraction layer for the same HW, that's below. I even doubt it will generate more compact code size in an actual completed project. No features will suddenly improve the underlying RISC MCU operation.

    Isn't it obvious: C++ is to a high degree an extension of C. And the big distinctions that we can find in C++ can't be something to benefit on nowadays MCU architectures and programming model (including Cortex-M from ARM). Further more I remember that even Microchip PIC18, Atmel ATXMEGA were optimized for C. It was on the brochures back when they were released - For use of pointers .... and so on. So how C++ can provide better pointer arithmetic than C? Well it can't. And on algorithmic level ... again if it's an application code.

    What CMSIS++ can achieve is to separate the Engineer (or Developer) from the HW even more - control and so on. I simply can't find any feature for C++ style code, that will make it execute better or even the same as the native C code, on MCU. Nor I can see it saving me time to write the code, init clocks, peripherals, config the MCU, handle RTOS tasks or StateMachines or whatever. Not in a way that is going to truly be beneficial for me, my team or a project running on Cortex-M. For the actual tasks of one RTOS based Firmware, if they don't have too much interaction with the outside / real world or peripherals internally - uncertain yes.

    I'm sorry for the long and maybe a bit ranting post, but from my perspective this is so much off focus, that it's frightening to see so much efforts put in no practical foreseeable outcome. And again, and of course, that's from my point of view. Only sensible move - Use this experience to create C++ Framework for co-integration with CMSIS, thus allowing less HW aware engineers/developers to code application code, i.e not system code, for MCUs. Probably we can use the analogy of Apple HealthKit -> I create an embedded system that has all the peripherals up and running, keeping sensible configuration, MCU running, power saving if needed. But what application it will actually perform for the end user, well someone might put Speed tracking application code, other Acceleration tracking application code. But the underlying system code is the same. That's the only scenario that I can think of.

    Good Luck.

    *OT1(Offtopic): Just for example, in Linux we have kernel developers and we have GUI developers. Although if you ask the Outsourcing "Automotive" industry, they will most definitely tell you, that they seek "Embedded C++ Kernel Developers" when they actually need QT Embedded Developers, who can patch the timer sub-system of the kernel on occasion Sorry, but it's ridiculous. Yet I thought of it, when stumbled upon CMSIS++ . C++ w/ Embedded w/ Low level system code on MCU/SoC/etc. Only place where I can see OOP efficient regarding MCU is on application level, and there it truly makes sense.

    *OT2(Offtopic):  For sure it will be better than the Duino non-sense promoting even more ignorance, but that doesn't make it good for the main case, where someone has to write the instructions in High-level language for a microcontroller. C has proven as that, and my observation is that very few people will learn useful C++, when they can't coupe with C. People who begin with C++ usually end up with even higher abstraction such as C#, Java, etc. And there it has its purpose, but it's not on system level. I even remember one IT course, that started last year (2015) in Europe, if not mistaken actually it was from edu institution or organization in the UK, where teaching Embedded Drivers is done through development in C#/.NET, because students find C too difficult Dimitar Tomov on Twitter: "It's one to eliminate #complexity, thus narrowing down the #set of #knowledge required to emp…

  • > it includes Application object code.

    I don't think so. My understanding of "object code and/or source code ... data and utility programs needed for reproducing the Combined Work" is "all I need to replicate your changes into my application". Having the object code of your application is of little use for this.

  • Hi @trm, thank you for your feedback. I respect your opinion, but I do not concur to it. And like me, many others (for example see the mbed project). Time will tell if these efforts will have "no practical foreseeable outcome".

  • Same about respect ilg + It's better to have different stands during development phase :-) By the way I have expressed similar opinion for mbed , especially regarding the low-power context that it was promoted last year. Very extensively if I may say. It was so amazing to see that 1 higher abstraction library, that provides nothing more related to how the MCU is managed, is achieving better "LP performance" But that's another topic. Just wanted to mentioned that mbed is yet another good example for what I'm stating above ... It's irony and not at the same time. Also I agree, that time will show. But another aspect is that if it's going to be yet another "yocto" type of product ... . Which is going to be used simply because N companies are already using it, then NN companies, then NNN and so on. This is not technical reasoning, it's poor management from my perspective - Lets do like the others, cause you see they promote it so strong it's actually being used. Let me stop there. But the jokes about yocto come from somewhere "Why there is Yocto? So LF can provide trainings for it." I hope we don't see in 4 years "Why there is CMSIS++/mbed? So they can sell more trainings for Embedded C++ programming" ;-)

    ps: I tried to stay even more positive in my reply this time

  • Liviu Ionescu wrote:

    > it includes Application object code.

    I don't think so. My understanding of "object code and/or source code ... data and utility programs needed for reproducing the Combined Work" is "all I need to replicate your changes into my application". Having the object code of your application is of little use for this.

    On the contrary, unless you have the source, having object code is essential. How shall you reproduce the combined work, if you do not have object (or source)?


  • > How shall you reproduce the combined work?

    Sorry to say, but why should I reproduce your application? My interest would be to reintegrate the changes you made to the LGPL code back into my applications.

    I guess the reason why the license text introduced the  "Corresponding Application Code" definition, is to differentiate from "Application code", which the license allows to remain outside LGPL.


  • > "Why there is CMSIS++/mbed? So they can sell more trainings for Embedded C++ programming" ;-)

    that's a good one! ;-)

    trm, if you'll ever have to deal with CMSIS++, I promise you that you'll never need any specialised training; I plan to keep the C APIs always available, so any C programmer will have no problem to use CMSIS++, and if you also have standard C++11 knowledge you'll be able to use the native C++ API without any problems.

    if I may abuse your time, please take a look at the article examples of using the two C APIs and let me know if you foresee any problems; the last API presented is exactly the current ARM CMSIS RTOS API (which, due to the abuse of macros, I find it very confusing), and the previous one is a proposal for a more straightforward C API, which maps 1:1 to the C++ API, and does not use any confusing macros at all.

    > I tried to stay even more positive in my reply this time

    I appreciate it :-)

    looking forward for more constructive feedback.

  • I figured out a better summary / reply in 1 line - You are asking people that have little or no previous experience writing low level system code, to write on even higher level of abstraction programming language. That can't be good. Not for the system code.

    Of course for programmers, that are proficient in C it's highly unlikely to have difficulties with C++, but I can't see the benefits of using it over C for low level / system implementations ;-)

    But I was interested yesterday in exactly this comparison you mention as well. Therefore most certainly my time won't be abused I will take a look at first possible time.

    Talk to you soon,

    Dimitar

    ps: under your post there is a button (or for you under my post)  "Action" - And the only possible action for me is "Report abuse" , but no "Report abused time" LOL

  • > I can't see the benefits of using it over C for low level / system implementations

    trm, to better understand your position, could you estimate your level of proficiency in C++11? (or at least in C++, although with C++11, and with C++14 even more, we are already talking about a different thing, features like constexpr used together with inline templates greatly improve code quality).

    > I will take a look at first possible time.

    thank you. could you post your technical feedback on the CMSIS++ section of the project forum?

  • ilg it's not my main strength so I will put it at max 7 out of 10.

    For your example with constexpr together with inline templates, that has limited applicability, doesn't it? + I'm starting to think that we look at code quality differently. Because this optimizes the code as execution, which is performance, not quality from my PoV. And to state again that this will have limited applicability on code of bare-metal Firmware.

    Noted about the project forum.