FreeRTOS Scheduler Startup: Diagnose Failure Before the First Application Task Runs
When no application task reaches its entry point, locate the last completed startup dependency before changing priorities or enlarging every stack. Creating application tasks is only part of startup: the kernel must also create its idle task and, when enabled, the timer-service infrastructure before handing control to the processor port.
This procedure uses upstream FreeRTOS Kernel V11.1.0 on one core. The source-level dependency map applies to that configuration; SMP adds further idle-task requirements, and vendor ports may add initialization. A local POSIX-port failure-injection test is described below. It does not validate MCU exception vectors, tick hardware or context restoration.
Divide startup into observable checkpoints
In the pinned task implementation, vTaskStartScheduler() first calls the idle-task creation path. If it succeeds and timers are enabled, timer-task creation follows. Only after those prerequisites succeed does the function configure the running scheduler state and call the port’s scheduler-start routine.
Use debugger breakpoints, a small preallocated startup record or another board-appropriate early diagnostic path to distinguish these checkpoints. Avoid depending on a logging task that has not run yet. Record the exact build and linked port so an observed breakpoint can be matched to the correct source.

| Last proven checkpoint | Next evidence to collect | Avoid concluding |
|---|---|---|
| Application initialization entered | Each object creation result | All tasks exist because initialization returned |
| Application tasks created | Idle-task creation result and storage path | The scheduler has enough remaining storage |
| Idle task created | Timer queue and daemon creation, if enabled | No timer was started, so timers need no infrastructure |
| Port start entered | Port setup, first context and fault evidence | Every startup failure is a heap failure |
| Some task executes | Ready tasks, priorities and early blocking | The missing application marker proves scheduler startup failed |
Follow the allocation path actually compiled
When static allocation support is enabled, the V11.1.0 idle path obtains task backing storage through the idle-memory hook and creates the idle task statically. Otherwise it uses dynamic task creation. Enabling both allocation modes does not mean this path randomly chooses between them.
Inspect the effective configuration definitions and preprocessed build, not an unused configuration file. The release also provides a kernel-supplied static-memory option, with restrictions for MPU ports. If the application supplies the hooks, verify the linked implementation, returned stack/TCB storage and declared stack depth.
Do not make a broad “all memory is static” claim just because application tasks use static creation. Check the kernel objects and each enabled service separately. For this investigation, the required artifact is a startup object ledger showing the object, construction path, storage provider and success checkpoint. Runtime fragmentation and long-term task-stack tuning belong to later investigations.
Include timer infrastructure even before a callback runs
The V11.1.0 timer implementation checks and initializes its command queue before creating the timer service task. Some initialization may already have happened if timers were created earlier. Do not assume an allocation’s ordinal is stable across changes to pre-scheduler initialization.
Record configUSE_TIMERS, the command-queue length, daemon priority and stack-depth configuration. A timer-service task needs resources even if the application’s first callback is scheduled much later. Turning timers off can be a controlled diagnostic comparison, but it is not an acceptable repair when the product depends on them.
Save the actual assertion location or failed return path. Different startup failures can stop at different checks. A generic “scheduler returned” message loses the distinction between idle storage, timer infrastructure and a port-specific failure. Keep an error path after the scheduler-start call, but do not depend on reaching it when assertions stop execution first.
Inject one failure at a time
- Build a minimal baseline with one application task whose first action records entry through a pre-established diagnostic path.
- Record successful application creation, then arm a test-only allocation failure at a specific subsequent startup allocation.
- Run each case in a fresh process or reset test target so earlier partially constructed objects cannot contaminate the next case.
- Require the intended failure to be observed and the application-entry marker to remain absent. Retain the failure site and allocation class.
- Remove injection and require the positive baseline to reach application entry again.
In a local test, unmodified V11.1.0 kernel sources and the official POSIX port were compiled with GCC 14.2.0, dynamic allocation and timers enabled. A link-time allocator wrapper injected a null result into each of five startup allocations after application-task creation. The baseline reached the first application task; every injected case stopped at an assertion without reaching it.
Those five allocations corresponded to idle stack/TCB, timer queue and timer-task stack/TCB in that exact build. Their count, order and sizes are not universal FreeRTOS constants. The test demonstrates the dependency and diagnostic method, not a target memory requirement. Static-hook fault injection and MCU port-start failures were not executed.
Hypothetical example: enough memory for the application, not startup
Imagine a hypothetical device whose application creates several tasks successfully and then never lights its task-entry indicator. The developer raises that task’s priority, but the idle task’s later storage request still fails. The indicator cannot reveal the problem because no scheduler handoff occurred.
The checkpoint trace identifies idle creation as the first failure. The fix is a reviewed startup memory plan or correct static-storage integration, followed by the same fault campaign. If port entry is already proven instead, investigate that port’s documented startup path and fault evidence rather than continuing to modify the heap without support.
Close the startup investigation
Deliver the effective configuration, linked port, startup object ledger, first failing checkpoint, assertion/fault evidence and baseline result. Retain a negative test proving that the diagnostic still catches an intentionally missing dependency. Acceptance requires both successful first-task entry and a defined response when a mandatory startup resource is unavailable; a larger heap alone is not that evidence.