FreeRTOS Under the Hood
The FreeRTOS scheduler beneath a Neuro N6 sketch, the core's tasks and priorities, and guidance for sketch-created tasks.
The Neuro N6 core boots a FreeRTOS scheduler before setup() runs. loop() executes as one task, and the camera, USB and optional network paths execute as separate tasks. Standard Arduino calls behave as expected; the scheduler is visible only to sketches that create tasks of their own or that block loop() for long periods.
Core tasks
| Task | Priority | Stack | Function |
|---|---|---|---|
ArduinoLoop | idle + 2 | 16 KiB | setup() and loop(). Vision inference runs here |
n6_capture | idle + 3 | 4 KiB | Camera frame to JPEG encode, metadata composition across slots, posting to the transport queue |
n6_usb | idle + 3 | 8 KiB | TinyUSB servicing, the vendor endpoint transmit pump, the CDC console and the bootloader command, and draining the USB frame queue |
With a WiFi transport and the MayaW476 library linked, two more are created inside Vision.begin():
| Task | Priority | Stack | Function |
|---|---|---|---|
maya_w4_conn | idle + 2 | 4 KiB | One shot: wait for the module, associate or host the access point, then idle |
maya_w4_vid | idle + 3 | 6 KiB | Drains the WiFi frame queue into a TCP socket |
The capture task posts to whichever queue matches cfg.transport. The USB task runs regardless, so the console and the bootloader command remain available while video flows over WiFi. Both frame queues exist unconditionally; the WiFi consumer attaches to the second when the library is present.
Behaviour of inference
Vision.run(), runWith() and runRoi() are synchronous. They block the calling task while the NPU executes, but block on a FreeRTOS semaphore that the NPU interrupt signals, so the capture and USB tasks continue during inference. Video throughput and inference throughput are therefore independent.
Frames are dropped rather than queued when the consumer is busy. Heavy work in loop() between inference calls lowers the visible frame rate but does not block it.
Sketch-created tasks
extern "C" {
#include "FreeRTOS.h"
#include "task.h"
}
static void blink_task(void *) {
for (;;) {
digitalWrite(LED_BUILTIN, HIGH);
vTaskDelay(pdMS_TO_TICKS(500));
digitalWrite(LED_BUILTIN, LOW);
vTaskDelay(pdMS_TO_TICKS(500));
}
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
xTaskCreate(blink_task, "Blink", 1024 / sizeof(StackType_t), NULL,
tskIDLE_PRIORITY + 1, NULL);
Vision.begin(&NN_Instance_yolov8_iseg, PostProcess_YOLOv8_ISEG(0.4f, 0.5f));
}Tasks are created in setup(). A task without a real time deadline is given a priority below loop(). Long waits use vTaskDelay(). A task at or above the capture task's priority that does not yield stalls the camera pipeline.
Configuration
| Parameter | Value |
|---|---|
| Kernel | FreeRTOS V10.6.2 |
| Port | Cortex-M55, ARM_CM55_NTZ/non_secure |
| Tick | 1 kHz |
| Heap | 96 KiB, with both static and dynamic allocation enabled |
| Priorities | 56 levels |
The port owns SVC_Handler, PendSV_Handler and SysTick_Handler. The HAL time base runs on TIM16, and HAL_GetTick() returns the FreeRTOS tick count once the scheduler is running. The NPU runtime is compiled with its FreeRTOS abstraction, so its wait-for-event call blocks on a semaphore rather than a WFE instruction.
Idle hook
configUSE_IDLE_HOOK is enabled and the core provides a weak, empty vApplicationIdleHook. The power save library supplies a strong override that executes __WFI(). Without it the idle task spins at the full core clock, measured at about 100 mA during a blocking wait. Sketches that do not link the library are unaffected; the two cases were verified to produce different symbol sizes in the linked ELF.
Source locations
The kernel is in cores/NeuroN6/FreeRTOS/Source. Configuration is in cores/NeuroN6/FreeRTOSConfig.h. The capture and USB tasks are in cores/NeuroN6/NeuroN6.c, with the frame queue interface in NeuroN6_FrameQueue.h.