Understanding Startup Code
Understanding Startup Code
struct Normal {
char a; // 1 byte
// 3 bytes padding (to align int on 4-byte boundary)
int b; // 4 bytes
};
1/44
📦 Use cases
Low-level hardware programming
Network protocols
Memory-mapped I/O
⚠️ Caveats
May cause slower access on some architectures due to unaligned access.
Can lead to undefined behavior on architectures that don't support unaligned access
(like some ARM chips).
Let me know if you want a visual example or how it's used in embedded systems.
Uploaded image
✅ Code:
2/44
c
#define HARDFAULT_HANDLING_ASM(_x) \
__asm volatile( \
"tst lr, #4 \n" \
"ite eq \n" \
"mrseq r0, msp \n" \
"mrsne r0, psp \n" \
"b vps_fault_handler_c \n")
🧠 What it means:
🔹 tst lr, #4
Bit 2 tells which stack pointer (SP) was in use when the fault occurred:
🔹 ite eq
This sets up conditional execution for the next two instructions based on the result of
tst .
So in effect:
🔹 b vps_fault_handler_c
3/44
R0 (the stack frame pointer) will be passed as the first argument (according to ARM
calling convention).
📦 Use Case:
This macro is designed to help Cortex-M exception handling, especially for HardFault
analysis, by:
Let me know if you want help writing vps_fault_handler_c() or visualizing the fault stack
frame layout.
Great! Since you already understood the assembly part inside volatile , let’s now break
down the usage of __asm volatile .
4/44
In GCC-compatible compilers, __asm or asm is used for inline assembly, allowing you to
embed assembly instructions directly inside C/C++ code.
volatile = Tells the compiler "do not optimize this out", even if it seems unused.
This is useful when you're doing low-level operations where the assembly has side effects
that the compiler might not see (like hardware register access, stack manipulation, etc).
✅ Syntax:
c
#include <stdio.h>
int main() {
int value;
__asm__ volatile (
5/44
"mov %0, #10\n"
: "=r" (value) // output operand
: // no input
: // no clobbered registers
);
This forces the MCU to enter debug state — commonly used in fault handlers.
Because without it, the compiler might remove or reorder the assembly code during
optimization if it thinks it has no effect. You want it to stay exactly as written.
Let me know if you want a FreeRTOS + inline assembly integration example or something
MCU-specific.
Uploaded image
6/44
This macro HALT_IF_DEBUGGING() is a debug trap often used in embedded firmware
development to intentionally break into the debugger only when debugging is active.
#define HALT_IF_DEBUGGING() \
do { \
if ((*(volatile uint32_t *)0xE000EDF0) & (1 << 0)) { \
__asm("bkpt 1"); \
} \
} while (0)
🔧 What's happening?
🔹 (*(volatile uint32_t *)0xE000EDF0)
🔹 & (1 << 0)
1 → Debugger is connected
0 → No debugger attached
7/44
if (Debugger is attached) {
__asm("bkpt 1");
}
🔹 __asm("bkpt 1");
It:
8/44
if (some_critical_condition_failed) {
HALT_IF_DEBUGGING(); // MCU halts here only during debug sessions
}
This helps developers pause program flow exactly where something went wrong, without
affecting runtime behavior in production.
Let me know if you want to try customizing this or making a soft-fault handler around it!
Uploaded image
🔍 Breakdown:
🔹 u32 CFSR = readl(0xE000ED28);
Bus Faults
Usage Faults
9/44
🧠 What is the CFSR?
CFSR is a 32-bit status register, split into three parts:
10/44
Bus Faults (BFSR)
UFSR_NOCP – No coprocessor
✅ Purpose
This function is for firmware-level crash diagnostics.
It helps developers quickly identify the type of fault and the exact reason so they can fix
bugs like:
Division by zero
Stack overflows
DMA-related errors
Let me know if you want a custom fault handler that also prints the offending address from
MMFAR or BFAR!
11/44
Uploaded image
This function __print_reg_info() prints the CPU register values that were saved at the
time of a fault (like a HardFault, BusFault, etc.) on an ARM Cortex-M processor.
🔍 Input:
c
sContextStateFrame *frame
This is a pointer to a stack frame structure, which holds the saved context of the CPU during
exception entry.
📦 What’s in sContextStateFrame ?
🧠 Function Purpose:
c
12/44
void __print_reg_info(sContextStateFrame *frame) {
ERR("R0 : 0x%08x", frame->r0);
ERR("R1 : 0x%08x", frame->r1);
ERR("R2 : 0x%08x", frame->r2);
ERR("R3 : 0x%08x", frame->r3);
ERR("R12 : 0x%08x", frame->r12);
ERR("LR : 0x%08x", frame->lr); // Link register
ERR("PC : 0x%08x", frame->pc); // Where the fault occurred
ERR("PSR : 0x%08x", frame->xpsr); // Status flags at time of fault
}
Understand the root cause of a crash (e.g., invalid function pointer call)
✅ Real-world usage:
This is often called from a HardFault handler:
Let me know if you want help writing the sContextStateFrame struct or dumping more
debug info like fault addresses from MMFAR/BFAR!
Uploaded image
13/44
What are these two functions doing
These two functions are used for fault diagnosis and recovery in embedded systems,
especially those running an RTOS (like FreeRTOS) on ARM Cortex-M microcontrollers.
🔍 Purpose:
This function dumps a section of memory (the stack contents) starting from the pointer
frame , likely passed from a fault handler.
🔧 What it does:
c
Prints:
14/44
Value at that address ( *frame )
If the value lies in the code region ( RO_STR to RO_END ), it adds (CODE) tag.
It helps you identify if the crash involved return addresses or pointers within code memory,
which is useful to trace back call stacks.
✅ Use case:
Called in a fault handler like:
__dump_stack_frames((uint32_t *)fault_stack_ptr);
🛠️ Function 2: recover_from_task_fault(void)
🔍 Purpose:
This is a safe recovery loop used when a task crashes.
while (1) {
rtos_delay(1);
}
Infinite loop.
15/44
✅ Use case:
When a task has faulted and cannot safely return or recover, this function keeps it in a
"zombie state" without crashing the entire system.
📦 Summary:
Function Purpose
recover_from_task_fault Trap faulty tasks in a safe infinite loop to prevent system crash
Let me know if you want to hook this into a custom HardFault_Handler or add fault logging
to a UART/file!
✅ In simple terms:
It tells the compiler:
"This function or variable can be replaced by another definition with the same name."
16/44
Provide default implementations that can be overridden
void my_default_handler(void) {
// Custom behavior
}
✅ The linker will choose your new definition and override the weak one.
// in startup.c
__attribute__((weak)) void HardFault_Handler(void) {
while (1);
}
17/44
reboot_mcu();
}
Result: The linker uses your version, not the default one.
⚠️ Notes:
Only works at link time — not run time.
💡 Tip:
You can use weak symbols to:
Uploaded Uploaded
image image
18/44
This code defines the default C-level fault handler vps_fault_handler_c() used in an
embedded firmware system (like on an ARM Cortex-M MCU), and it's marked as
__attribute__((weak)) , which means it can be overridden by a stronger definition
elsewhere in the project.
It’s called when a CPU exception or fault (like HardFault, UsageFault, etc.) occurs.
This handler logs the fault, attempts recovery (if possible), or reboots the system if it's
unrecoverable.
🧠 Step-by-step Explanation:
1. Trigger a breakpoint if under debugger
c
HALT_IF_DEBUGGING();
If a debugger is connected, this triggers a bkpt instruction to halt the CPU for inspection.
19/44
3. Read fault status
c
if (faulted_from_exception || non_usage_fault_occurred)
That’s writing to AIRCR (Application Interrupt and Reset Control Register) to request a
system reset.
20/44
If this line is hit:
Clear CFSR
*cfsr |= *cfsr;
frame->pc = (uint32_t)recover_from_task_fault;
frame->lr = 0xdeadbeef; // dummy value to catch misuse
frame->xpsr = (1 << 24); // Set "Thumb" bit in xPSR (mandatory for Cortex-M)
🟢 This effectively modifies the saved stack frame so that when the exception returns,
execution resumes at recover_from_task_fault() instead of crashing again.
🔁 recover_from_task_fault() does:
21/44
while (1) {
rtos_delay(1); // Keeps the faulty task idle without blocking the system
}
✅ Summary
Part Function
asm
b vps_fault_handler_c
22/44
Let me know if you'd like help writing the sContextStateFrame struct or integrating this into
FreeRTOS!
Uploaded image
These functions are default weak implementations of exception and interrupt handlers
for an ARM Cortex-M-based embedded system. They're used in cases where specific handlers
haven’t been explicitly defined by the developer.
🧠 General Idea:
Each function:
Typically logs the error and enters an infinite loop, or calls a C-level fault handler like
vps_fault_handler_c() to try recovery.
🔍 Function-by-function Explanation:
1. NMI Handler
c
23/44
__attribute__((weak)) void nmi_handler(void) {
while (1);
}
2. HardFault Handler
Gathers the stack frame pointer during the fault (using inline assembly)
Critical handler, must preserve stack context before any function call
3. UsageFault Handler
c
24/44
Logs usage fault details
25/44
while (1);
}
Handles memory bus errors (e.g., access to invalid addresses or misaligned access)
This is a catch-all handler for interrupts that don't have a specific ISR
✅ Summary Table:
Function Handles Behavior
usagefault_handler Usage faults (e.g. div-by-zero, illegal instr) Print & halt
26/44
Function Handles Behavior
default_irq_handler Any unexpected IRQ Get IRQ number and pass to general handler
These functions form the core of a crash-resilient firmware. They help with:
Logging
Debugging
Let me know if you want to override any of these in your application or auto-capture crash
dumps to flash/log.
Uploaded image
The function RST_entry() shown in your screenshot is a low-level reset entry point in
embedded firmware. It is executed immediately after a CPU reset, before calling main() .
This code prepares the CPU and memory environment for proper operation.
Subtracting 0x8 gives a safe value below the limit (avoiding uninitialized/unpredictable
values at the top).
Then:
asm
This stores 0 at the top of the stack to avoid issues with unpredictable data.
28/44
✅ 3. Set Vector Table Address (VTOR)
c
SCB->VTOR = (uint32_t)_vectors;
Updates the VTOR (Vector Table Offset Register) to point to the vector table.
__main();
This is not your normal main() — it's usually a runtime initialization function that:
📌 Summary
Section Purpose
29/44
🔧 Optional but useful
c
// cache_enable();
Let me know if you want to adapt this for FreeRTOS boot or want to explore how __main()
works internally!
Uploaded image
This code defines the vector table for an ARM Cortex-M microcontroller.
The vector table is essential to any Cortex-M boot process — it's the first thing the
processor looks at on reset, and it tells the CPU:
30/44
Which functions to call for interrupts and exceptions
🔍 Detailed Breakdown
c
The linker script places this section at address 0x00000000 (or a remapped base
address).
31/44
Index Purpose Entry
5 BusFault bus_fault_handler
6 UsageFault usagefault_handler
11 SVCall vPortSVCHandler
13 Reserved 0
14 PendSV xPortPendSVHandler
15 SysTick xPortSysTickHandler
(vect_t)default_irq_handler,
(vect_t)default_irq_handler,
...
They can be overwritten later dynamically (e.g., using a function like request_irq() )
Registers all ISRs Ensures every exception/IRQ has a handler (even if default)
🧠 Notes:
Image$$ARM_LIB_STACK$$ZI$$Limit is from the linker file and marks the end of the
stack.
Without this vector table, your MCU will not boot properly.
Let me know if you want to see the linker script part that maps the "vectors" section to
address 0x00000000 or how dynamic IRQ registration is handled!
So when does this startup.c start? who calls it? After this
code starts what is the state of ARM processor and what is
freeRtos doing in this period?
Awesome question — you're now diving into the core of MCU bootflow and FreeRTOS
startup mechanics. Let’s break it down into 3 main parts:
🧠 The sequence after a Reset (hardware reset line goes HIGH → LOW):
1. CPU comes out of reset.
33/44
2. It fetches:
✅ Summary:
The CPU begins executing startup.c automatically after reset.
void RST_entry(void) {
// 1. Patch stack
// 2. Zero out registers
// 3. Set VTOR to point to vector table
// 4. Call __main(); → leads to C runtime init
}
34/44
Component State
Interrupts Disabled
vTaskStartScheduler();
int main(void) {
// Init clocks, peripherals
xTaskCreate(...); // Create tasks
vTaskStartScheduler(); // Start the FreeRTOS kernel
35/44
while(1); // Should never reach here
}
📌 Summary Timeline
scss
Hardware Reset
↓
CPU jumps to vector[1] → RST_entry()
↓
startup.c: stack setup, registers zeroed, VTOR set
↓
__main(): sets up .data, .bss, calls main()
↓
User main(): init HW, create tasks
↓
vTaskStartScheduler()
↓
FreeRTOS takes over
Let me know if you want a visual timeline or explanation of how FreeRTOS uses PendSV and
SysTick to manage tasks.
36/44
Perfect — this is a fundamental and elegant part of FreeRTOS internals on ARM Cortex-M
processors. Let’s dive in and understand how FreeRTOS uses PendSV and SysTick to
manage multitasking (aka context switching).
🔁 TL;DR First:
Exception Purpose in FreeRTOS
🧠 FreeRTOS setup:
When vTaskStartScheduler() is called:
SysTick_Handler() {
xTaskIncrementTick(); // Increment RTOS tick count
if (context_switch_needed) {
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk; // Pend a PendSV exception
}
}
So SysTick only triggers the need for a switch — it does not do the switch itself.
PendSV_Handler() {
// Save context of current task (registers, stack pointer)
// Select the next task to run
// Restore context of next task
}
This involves:
38/44
🏁 Starting the first task
FreeRTOS uses SVC (Supervisor Call) to kick off the first task via:
SVC_Handler() {
// Load context of first task
// Set PSP to task's stack
// Exit to thread mode
}
🧠 Summary Flow
text
+---------------------+
| vTaskStartScheduler |
+---------------------+
↓
Sets up SysTick
Triggers SVC
↓
SVC_Handler()
→ Loads first task
↓
Task runs normally
↓
SysTick fires (1ms)
↓
SysTick_Handler()
→ Decides if switch needed
→ Sets PendSV bit
↓
PendSV_Handler()
→ Saves current task
39/44
→ Picks new task
→ Restores context
→ Continues execution
PendSV runs at lowest priority, so switching doesn’t interrupt higher priority ISRs
🧪 Bonus Tip:
You can check NVIC registers to see pending exceptions:
Would you like a diagram showing how the registers and stack look during context
switching? Or a look inside the actual PendSV_Handler code (assembly)?
Awesome — understanding how the stack and registers are saved/restored during
FreeRTOS context switches on Cortex-M will give you deep control over debugging and
performance optimization.
Let’s visualize the context switch stack layout and register usage when:
40/44
A task is preempted
[SP + 0x04] R1
[SP + 0x08] R2
[SP + 0x0C] R3
✅ This is always saved on PSP (Process Stack Pointer) when in thread mode (tasks).
41/44
🔁 2. FreeRTOS PendSV Manual Save
The PendSV_Handler (in port.c ) then adds:
[SP + 0x24] R5
[SP + 0x28] R6
[SP + 0x2C] R7
[SP + 0x30] R8
[SP + 0x34] R9
📌 These are saved manually because Cortex-M does not save callee-saved regs
automatically.
42/44
| R5 |
| R4 | ← saved manually by FreeRTOS
| xPSR |
| PC |
| LR |
| R12 |
| R3 |
| R2 |
| R1 |
| R0 | ← saved automatically by hardware
+--------------------+ ← new SP after save
3. Exits from exception → hardware automatically restores R0–R3, R12, LR, PC, xPSR
43/44
asm
PendSV_Handler:
// Save R4–R11 to PSP
MRS R0, PSP
STMDB R0!, {R4-R11}
STR R0, pxCurrentTCB ; Save stack pointer
BX LR ; Return to task
Would you like me to generate a clean graphical image (PNG) of this stack diagram to use in
documentation or notes?
44/44