Begin after FreeRTOS tasks and queues and
CMSIS-Core. You will express familiar scheduling and
communication operations through cmsis_os2.h, while retaining an explicit
contract for the backend that implements them.
The header describes; the kernel schedules
This runner uses CMSIS 6.4.0, the CMSIS-FreeRTOS 11.3.0 adapter, and the
existing FreeRTOS-Kernel 11.3.2 on Cortex-M4F. Including cmsis_os2.h
automatically selects that support. It does not replace the kernel with RTX5.
The adapter owns SysTick dispatch; startup still provides the vector table and
FreeRTOS still owns its SVC/PendSV context switching. Do not add competing
handlers or reconfigure SysTick while the scheduler is active.
A standalone program calls osKernelInitialize(), creates its objects and
threads, then calls osKernelStart(). Creation can also happen later from a
running thread. Check every handle and status. Threads use a void(void*)
entry point and finish with osThreadExit() when their bounded work is done.
The graded harness already initializes and starts the kernel, and runs
TEST bodies at osPriorityNormal. A helper must not start it again.
Units and priority directions are part of the API
osThreadAttr_t attr{} initializes defaults safely. Its stack_size is in
bytes; native xTaskCreate on this Cortex-M4 takes four-byte stack words.
A native depth of 512 corresponds to a CMSIS byte count of 2048. A name
pointer is borrowed: keep its storage alive for the thread's lifetime. Default
dynamic allocation is useful for these small exercises, not a bound on
production allocation latency.
CMSIS thread priorities such as osPriorityNormal and osPriorityAboveNormal
use larger values for higher priority. NVIC IRQ priorities use smaller
values for greater urgency. Do not interchange those numbers. This adapter
requires a 56-priority FreeRTOS configuration and maps CMSIS thread priority
p to native priority p-1. Native-only FreeRTOS exercises retain their
separate eight-priority configuration; the test runner uses native 23 to
represent CMSIS Normal (24).
Messages copy bytes; statuses describe the attempt
osMessageQueueNew(count,sizeof(sample),nullptr) fixes both capacity and
copied-item size. osMessageQueuePut(q,&sample,0,timeout) copies that many bytes;
changing the sender's scalar values later cannot change an already queued
sample. A pointer field would copy only the pointer, leaving its pointee's
ownership and lifetime unresolved. The receiving helper validates message size
and uses a temporary so it changes the caller's output only on success.
Timeouts and osDelay use kernel ticks, not an API-wide assumption of
milliseconds. This runner is 1 kHz, but portable conversion queries or accepts
the frequency and rounds positive durations up. Compute in 64 bits, then clamp
finite waits below the osWaitForever sentinel. Every wait in a test must be
bounded so a broken solution produces a failure instead of hanging.
#include "cmsis_os2.h"
#include <cassert>
#include <cstdio>
#include <cstdlib>
static void demo(void*) {
auto q=osMessageQueueNew(1,sizeof(int),nullptr); assert(q);
int sent=7, received=0;
assert(osMessageQueuePut(q,&sent,0,0)==osOK); sent=99;
assert(osMessageQueueGet(q,&received,nullptr,0)==osOK);
std::printf("copied value = %d; tick Hz = %lu\n",received,(unsigned long)osKernelGetTickFreq());
assert(osMessageQueueDelete(q)==osOK);
std::exit(0);
}
int main() {
assert(osKernelInitialize()==osOK);
osThreadAttr_t attr{}; attr.stack_size=4096; attr.priority=osPriorityNormal;
assert(osThreadNew(demo,nullptr,&attr));
osKernelStart();
}The backend ignores message priority; queues here are FIFO. An exposed parameter is not evidence that every backend implements its generic behavior. Read the pinned adapter and its documented limitations before porting software.
Flags, interrupts and object retirement
Thread flags belong to a particular thread. Event flags are a separate shared
object. Repeated sets coalesce; use a queue or counter if every occurrence
matters. Flag APIs return uint32_t with failures encoded through the
osFlagsError bit: test that bit, rather than treating nonzero as success.
This backend offers 31 thread-flag bits and 24 event-flag bits, and uses native
notification slot 0 for thread flags. Do not share that slot with unrelated
native notifications or stream/message-buffer waits in the same thread.
CMSIS internally selects ISR operations only for the documented ISR-safe calls. Queue put/get from an ISR require timeout zero and a priority legal for the FreeRTOS port (logical IRQ priorities 5–7 here). Thread creation, blocking waits, mutexes and object deletion are not arbitrary ISR operations. The build tests pend a real priority-6 IRQ to check the queue's polling path and its rejection of a positive timeout.
The adapter also supplies mutexes, semaphores, memory pools and software timers; runtime tests exercise those objects. CMSIS does not remove their ownership rules. Timer callbacks run in the shared daemon, must not block, and must retain their argument storage until callbacks/queued deletion have retired. See timers and event coordination. An accepted delete command is not a general completion handshake.
Static allocation needs backend-specific control-block sizes and alignment, plus correctly sized backing memory; providing arbitrary byte arrays does not make it portable or correct. This adapter rejects joinable threads and robust mutex behavior. It does not implement the full newer RTOS2 process-isolation, TrustZone or SMP interfaces. These exercises claim this tested single-core FreeRTOS backend, not universal CMSIS conformance. There is no physical-board latency or hardware-in-the-loop evidence in an emulated run.