0% found this document useful (0 votes)
90 views44 pages

Understanding Startup Code

__attribute__((packed)) is a compiler extension that prevents padding between structure members, reducing the size of structures in memory. This is crucial for low-level programming, network protocols, and scenarios where exact data layout is necessary. However, it may lead to performance issues and undefined behavior on some architectures due to unaligned access.

Uploaded by

Abhradeep
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
90 views44 pages

Understanding Startup Code

__attribute__((packed)) is a compiler extension that prevents padding between structure members, reducing the size of structures in memory. This is crucial for low-level programming, network protocols, and scenarios where exact data layout is necessary. However, it may lead to performance issues and undefined behavior on some architectures due to unaligned access.

Uploaded by

Abhradeep
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Packed Attribute in C

what does __atribute__((packed)) do?

__attribute__((packed)) is a GCC/Clang compiler extension in C/C++ that tells the


compiler:

⚠️ Do not add any padding bytes between the members of a structure.

🔍 Why is this important?


Normally, compilers insert padding between structure members to align data in memory for
performance. For example:

struct Normal {
char a; // 1 byte
// 3 bytes padding (to align int on 4-byte boundary)
int b; // 4 bytes
};

Total size = 8 bytes (not 5!)

✅ What does __attribute__((packed)) do?

It tells the compiler "pack everything tightly, no padding!"

struct __attribute__((packed)) Packed {


char a; // 1 byte
int b; // 4 bytes (right after 'a')
};

Total size = 5 bytes

1/44
📦 Use cases
Low-level hardware programming

Network protocols

Binary file parsing

Memory-mapped I/O

Anywhere exact data layout is critical.

⚠️ 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

Explain me this code snippet

This code snippet is a macro named HARDFAULT_HANDLING_ASM(_x) which inserts inline


assembly to handle a HardFault in ARM Cortex-M processors.

Let’s break it down line-by-line:

✅ 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

This tests bit 2 of the Link Register (LR).

Bit 2 tells which stack pointer (SP) was in use when the fault occurred:

0 → Main Stack Pointer (MSP)

1 → Process Stack Pointer (PSP)

🔹 ite eq

ARM Thumb instruction for "If-Then-Else".

This sets up conditional execution for the next two instructions based on the result of
tst .

🔹 mrseq r0, msp

If the condition was equal ( EQ ), it moves MSP into R0.

🔹 mrsne r0, psp

If the condition was not equal ( NE ), it moves PSP into R0.

So in effect:

R0 gets the correct stack pointer value at the time of fault.

🔹 b vps_fault_handler_c

Branch to the C function vps_fault_handler_c to continue fault handling.

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:

Determining the stack used when the fault happened

Passing that stack to a C-level handler for debugging or crash dump

🛡️ Why this matters in embedded firmware:


When a fault occurs, ARM automatically pushes the register context to the stack. This macro
captures that stack frame so your C function can:

Inspect the saved PC, LR, PSR, etc.

Print logs, dump memory, or reboot safely.

Let me know if you want help writing vps_fault_handler_c() or visualizing the fault stack
frame layout.

I understood the use of things inside volatile but what is


__asm volatile and give me a sample code how tis is used

Great! Since you already understood the assembly part inside volatile , let’s now break
down the usage of __asm volatile .

🔧 What is __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.

__asm = Tells the compiler you're writing inline assembly.

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

__asm__ __volatile__("assembly_code" : output_operands : input_operands :


clobbered_registers);

But in most simple use-cases (like yours), it’s just:

__asm__ volatile ("instruction1 \n"


"instruction2 \n"
"...");

🧪 Example: Basic Inline Assembly


Let’s say you want to set a variable to 10 using inline assembly:

#include <stdio.h>

int main() {
int value;

__asm__ volatile (

5/44
"mov %0, #10\n"
: "=r" (value) // output operand
: // no input
: // no clobbered registers
);

printf("Value is: %d\n", value);


return 0;
}

⚡ Real-world Embedded Example:


You want to trigger a breakpoint (used in debugging):

__asm__ volatile ("bkpt #0");

This forces the MCU to enter debug state — commonly used in fault handlers.

💡 Why use volatile with __asm ?

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

What is this code doing?

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.

🔍 Let’s break it down line-by-line:


c

#define HALT_IF_DEBUGGING() \
do { \
if ((*(volatile uint32_t *)0xE000EDF0) & (1 << 0)) { \
__asm("bkpt 1"); \
} \
} while (0)

🔧 What's happening?
🔹 (*(volatile uint32_t *)0xE000EDF0)

This is reading a 32-bit register at address 0xE000EDF0 .

That address is:

🧠 DHCSR (Debug Halting Control and Status Register)


from ARM Cortex-M's Core Debug block.

🔹 & (1 << 0)

Bit 0 of DHCSR is the C_DEBUGEN bit:

1 → Debugger is connected

0 → No debugger attached

So this condition checks:

7/44
if (Debugger is attached) {
__asm("bkpt 1");
}

🔹 __asm("bkpt 1");

This is an inline assembly breakpoint instruction:

Causes the MCU to halt and break into the debugger.

The 1 is just a breakpoint ID (can be 0–255).

🔹 Why do { ... } while (0) ?

This is a common C macro trick:

Makes the macro behave like a single statement.

Ensures safe use in if-else blocks and prevents surprises.

✅ Summary: What does HALT_IF_DEBUGGING() do?

It:

1. Checks if a debugger is attached.

2. If yes, triggers a breakpoint using bkpt 1 .

3. If not, continues silently.

🧪 Real-world use case:


c

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

What is this function doing

This function __print_usagefault() is designed to diagnose faults on an ARM Cortex-M


processor by decoding the CFSR register (Configurable Fault Status Register) to print the
exact cause of a fault.

🔍 Breakdown:
🔹 u32 CFSR = readl(0xE000ED28);

Reads from CFSR (address 0xE000ED28 )

This register logs reasons for:

Memory Management Faults

Bus Faults

Usage Faults

9/44
🧠 What is the CFSR?
CFSR is a 32-bit status register, split into three parts:

Fault Type Bits Prefix

Memory Management [7:0] MMFSR

Bus Fault [15:8] BFSR

Usage Fault [31:16] UFSR

Each bit indicates a specific error condition.

🪓 What this function does:


1. Reads the CFSR value.

2. Prints the raw fault code using:

ERR("[%s] error code: 0x%08x", __func__, CFSR);

3. Checks each fault condition bit using bitwise AND ( & ).

4. If a bit is set, prints the corresponding fault message using ERR(...) .

📋 Example faults decoded:


Memory Management Faults (MMFSR)

MMFSR_IACCVIOL – Instruction access violation

MMFSR_DACCVIOL – Data access violation

MMFSR_MUNSTKERR – Fault during exception unstack

MMFSR_MMARVALID – MMFAR contains valid address

10/44
Bus Faults (BFSR)

BFSR_IBUSERR – Instruction bus error

BFSR_PRECISERR – Precise data bus error

BFSR_IMPRECISERR – Imprecise error (e.g., from DMA)

BFSR_STKERR / UNSTKERR – Stack push/pop error

BFSR_BFARVALID – BFAR has valid address

Usage Faults (UFSR)

UFSR_UNDEFINSTR – Undefined instruction

UFSR_INVSTATE – Invalid execution state

UFSR_INVPC – Invalid program counter

UFSR_NOCP – No coprocessor

UFSR_UNALIGNED – Unaligned memory access

UFSR_DIVBYZERO – Division by zero

✅ 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:

Accessing invalid memory

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

What does this function do?

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 ?

This structure typically contains:

General purpose registers: r0 , r1 , r2 , r3 , r12

Link Register: lr (return address)

Program Counter: pc (instruction address at the time of fault)

Program Status Register: xpsr (flags like zero, carry, etc.)

These are automatically pushed to the stack by ARM on exception entry.

🧠 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
}

This helps you:

Trace what the CPU was doing

Understand the root cause of a crash (e.g., invalid function pointer call)

Reproduce the bug based on register state

✅ Real-world usage:
This is often called from a HardFault handler:

void vps_fault_handler_c(uint32_t *fault_stack_ptr) {


__print_reg_info((sContextStateFrame *)fault_stack_ptr);
}

So you can see exactly what caused the crash.

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.

🧠 Function 1: __dump_stack_frames(uint32_t *frame)

🔍 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

WRN("=== Dump StackFrame ===");

Prints a header indicating the start of the stack dump.

for (int i = 0; i < 100; i++, frame++) {


if (*frame >= RO_STR && *frame <= RO_END) {
WRN("[%08x] 0x%08x (CODE)", (u32)frame, *frame);
} else {
WRN("[%08x] 0x%08x", (u32)frame, *frame);
}
}

Iterates over 100 words (400 bytes if uint32_t )

Prints:

Stack address ( frame )

14/44
Value at that address ( *frame )

If the value lies in the code region ( RO_STR to RO_END ), it adds (CODE) tag.

🧠 Why tag (CODE) ?

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);

To inspect the state of the stack after a crash.

🛠️ 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.

Uses rtos_delay(1) to yield CPU time, instead of locking up the system.

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

__dump_stack_frames Print memory contents of the stack for post-mortem debug

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!

What does __attribute__((weak)) do?

__attribute__((weak)) is a GCC and Clang extension used in C/C++ to declare a weak


symbol.

✅ In simple terms:
It tells the compiler:
"This function or variable can be replaced by another definition with the same name."

🧠 Why use it?


It's commonly used to:

16/44
Provide default implementations that can be overridden

Allow optional overrides in firmware or libraries

Enable flexible linking without linker errors

📦 Common use case:


c

__attribute__((weak)) void my_default_handler(void) {


// Default behavior
}

If you define this again in another file:

void my_default_handler(void) {
// Custom behavior
}

✅ The linker will choose your new definition and override the weak one.

🔧 Real-world Embedded Example:


c

// in startup.c
__attribute__((weak)) void HardFault_Handler(void) {
while (1);
}

// in your custom fault.c


void HardFault_Handler(void) {
log_fault();

17/44
reboot_mcu();
}

Result: The linker uses your version, not the default one.

⚠️ Notes:
Only works at link time — not run time.

If no strong version exists, the weak version is used.

You can make variables weak too.

__attribute__((weak)) int my_flag = 1;

💡 Tip:
You can use weak symbols to:

Stub out APIs in early development

Create flexible HALs (Hardware Abstraction Layers)

Override interrupt handlers or hooks cleanly

Let me know if you want a comparison with __attribute__((alias)) or a use case in


FreeRTOS/firmware!

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.

🔍 What is the purpose of vps_fault_handler_c() ?

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.

2. Log system state


c

__print_fw_version(); // Log firmware version


__print_fault_regs(); // Print fault status registers (CFSR, HFSR, etc.)
__print_reg_info(frame); // Print register context saved at time of fault
__dump_stack_frames((uint32_t *)frame); // Print raw stack contents

19/44
3. Read fault status
c

volatile u32 *cfsr = (volatile u32_t *)0xE000ED28;


const uint32_t usage_fault_mask = 0xffff0000;
bool non_usage_fault_occurred = (*cfsr & usage_fault_mask) != 0;
bool faulted_from_exception = ((frame->xpsr & 0xFF) != 0);

CFSR : Holds Usage/Bus/Memory Fault flags

xPSR & 0xFF : If non-zero, CPU was in an exception context

4. If fault occurred in ISR or serious non-usage fault → Reboot


c

if (faulted_from_exception || non_usage_fault_occurred)

Inside this block:

Logs HFSR (Hard Fault Status Register)

Detects specific hard fault types:

HARDFAULT_VECTBL : Fault on vector table fetch

HARDFAULT_FORCED : Escalated fault (e.g., from usage/bus fault)

HARDFAULT_DEBUGEVT : Debug-related fault

If unrecoverable, forces a system reset:

*aircr = (0x5FA << 16) | 0x1 << 2;

That’s writing to AIRCR (Application Interrupt and Reset Control Register) to request a
system reset.

20/44
If this line is hit:

while(1); // This shouldn't be reached; CPU should reset

5. Else: Attempt Recovery


If the system did not fault in ISR and it's a recoverable usage fault:

Clear CFSR

*cfsr |= *cfsr;

Redirect PC and LR to safe code

frame->pc = (uint32_t)recover_from_task_fault;
frame->lr = 0xdeadbeef; // dummy value to catch misuse

Fix PSR to ensure proper state

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

__attribute__((weak)) Allows user to override the fault handler

HALT_IF_DEBUGGING() Stops execution for debugger inspection

Logging functions Dumps FW version, registers, and stack

Fault analysis Reads CFSR, HFSR, xPSR to decide fault source

Reboot path Triggers MCU reset if irrecoverable

Recovery path Clears faults and jumps to safe infinite loop

⚠️ When would this run?


This handler is likely called from assembly like:

asm

b vps_fault_handler_c

from a HardFault_Handler , passing in the stack pointer .

22/44
Let me know if you'd like help writing the sContextStateFrame struct or integrating this into
FreeRTOS!

Uploaded image

What are these functions doing

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:

Handles a specific fault/interrupt type (e.g., HardFault, UsageFault, BusFault)

Is marked with __attribute__((weak)) → can be overridden by a user-defined version

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);
}

Handles Non-Maskable Interrupts

Default behavior is to halt the system

2. HardFault Handler

__attribute__((weak)) void hardfault_handler(void) {


...
__asm volatile(...); // Determines correct stack pointer (MSP or PSP)
...
vps_fault_handler_c((sContextStateFrame*)stack_ptr);
}

Gathers the stack frame pointer during the fault (using inline assembly)

Passes it to vps_fault_handler_c() for detailed analysis and potential recovery

Critical handler, must preserve stack context before any function call

3. UsageFault Handler
c

__attribute__((weak)) void usagefault_handler(void) {


__print_exception_reference();
__print_usagefault();
while (1);
}

24/44
Logs usage fault details

Enters an infinite loop (system halt unless overridden)

4. Debug Monitor Handler

__attribute__((weak)) void debugmon_handler(void) {


while (1);
}

Handles debug monitor exception

Rarely used unless debug features are active

5. Memory Management Fault Handler


c

__attribute__((weak)) void mm_fault_handler(void) {


ERR("MemoryManagement fault!!");
while (1);
}

Handles faults like illegal memory access, page faults, etc.

6. Bus Fault Handler


c

__attribute__((weak)) void bus_fault_handler(void) {


ERR("BusFault fault!!");

25/44
while (1);
}

Handles memory bus errors (e.g., access to invalid addresses or misaligned access)

7. Default IRQ Handler


c

__attribute__((weak)) void default_irq_handler(void) {


u32 irq_num = get_mode() - NVIC_USER_IRQ_OFFSET;
intr_handle_service(irq_num);
}

This is a catch-all handler for interrupts that don't have a specific ISR

Determines the IRQ number

Delegates to intr_handle_service() to handle it generically

✅ Summary Table:
Function Handles Behavior

nmi_handler NMI Infinite loop

hardfault_handler HardFault Stack trace & call recovery handler

usagefault_handler Usage faults (e.g. div-by-zero, illegal instr) Print & halt

debugmon_handler Debug monitor exception Halt

mm_fault_handler Memory faults Print & halt

bus_fault_handler Bus faults 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:

Safe fault capture

Logging

Debugging

System recovery or reboot

Let me know if you want to override any of these in your application or auto-capture crash
dumps to flash/log.

Uploaded image

What does this RST_entry() do?

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.

🔍 Let's go through it section-by-section:


✅ 1. Fix the Top of Stack
27/44
c

extern unsigned int Image$$ARM_LIB_STACK$$ZI$$Limit;


uint32_t reg0 = (uint32_t)&Image$$ARM_LIB_STACK$$ZI$$Limit - 0x8;

Image$$ARM_LIB_STACK$$ZI$$Limit is a scatter-loading symbol from the linker script,

marking the end of the stack region.

Subtracting 0x8 gives a safe value below the limit (avoiding uninitialized/unpredictable
values at the top).

Then:

asm

MOV r0, %[reg0]


MOVS r2, #0x0
STR r2, [r0]

This stores 0 at the top of the stack to avoid issues with unpredictable data.

✅ 2. Zero out CPU registers (R1–R12)


asm

MOVS r0, #0x0 ; r0 = 0


MOV r1, r0 ; r1 = 0
...
MOV r12, r0 ; r12 = 0

This is pure defensive initialization.

Prevents weird behavior from residual register values.

Useful in MCUs that don’t guarantee power-on reset values.

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.

_vectors is usually defined in the linker script or startup code.

Essential if vector table is relocated (e.g., in bootloaders or dual-image systems).

✅ 4. Start the main application


c

__main();

This is not your normal main() — it's usually a runtime initialization function that:

Sets up C runtime (data segment, BSS, etc.)

Then calls the actual main() function

📌 Summary
Section Purpose

Stack fix Clear top of stack to avoid invalid values

Register zeroing Avoid random values in CPU registers

VTOR setup Point to the correct interrupt vector table

Call __main() Start C runtime and eventually main()

29/44
🔧 Optional but useful
c

// cache_enable();

Might enable instruction/data cache if uncommented.

✅ This function ensures:


1. The stack and registers are clean.

2. The CPU is pointing to the right interrupt vectors.

3. Execution transitions cleanly to the C runtime ( __main() ).

Let me know if you want to adapt this for FreeRTOS boot or want to explore how __main()
works internally!

Uploaded image

What does this section do?

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:

Where the stack pointer starts

30/44
Which functions to call for interrupts and exceptions

🔍 Detailed Breakdown
c

vect_t __vectors[] __attribute__((section("vectors"))) = {

__vectors[] is placed in a special memory section called "vectors" .

The linker script places this section at address 0x00000000 (or a remapped base
address).

🧠 What’s in the Vector Table?


Each entry is a pointer to a function (of type vect_t , likely a typedef like typedef
void(*vect_t)(void) ).

✅ The First Few Entries:


Index Purpose Entry

0 Initial Stack Pointer &Image$$ARM_LIB_STACK$$ZI$$Limit

1 Reset Handler RST_entry

2 NMI Handler nmi_handler

3 HardFault Handler hardfault_handler

4 MemManage Fault mm_fault_handler

31/44
Index Purpose Entry

5 BusFault bus_fault_handler

6 UsageFault usagefault_handler

7–10 Reserved 0 or unused

11 SVCall vPortSVCHandler

12 Debug Monitor debugmon_handler

13 Reserved 0

14 PendSV xPortPendSVHandler

15 SysTick xPortSysTickHandler

These are core exceptions in ARM Cortex-M.

🧩 External Interrupts (IRQ 0–64):


c

(vect_t)default_irq_handler,
(vect_t)default_irq_handler,
...

These are placeholder ISRs for all external interrupts

By default, they point to default_irq_handler

They can be overwritten later dynamically (e.g., using a function like request_irq() )

🔧 What this achieves:


32/44
✅ Purpose 💡 Outcome
Sets up stack CPU loads SP from first entry

Sets reset vector CPU jumps to RST_entry() on boot

Registers all ISRs Ensures every exception/IRQ has a handler (even if default)

Ensures recoverability Avoids crashing into invalid memory on unexpected IRQs

🧠 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:

🧩 1. When does startup.c start? Who calls it?

🧠 The sequence after a Reset (hardware reset line goes HIGH → LOW):
1. CPU comes out of reset.

33/44
2. It fetches:

SP (Stack Pointer) from vector table entry 0.

PC (Program Counter) from vector table entry 1 → typically points to RST_entry() .

3. So your MCU itself calls RST_entry() automatically — no one else does.

✅ Summary:
The CPU begins executing startup.c automatically after reset.

The Reset Handler (entry 1 in vector table) is usually RST_entry() or Reset_Handler .

🧠 What is happening in RST_entry() ?

From your code:

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
}

The call to __main() :

Initializes BSS, DATA, etc.

Then calls the actual user-defined main() function.

🧩 2. What is the state of the ARM processor at this


point?
The ARM Cortex-M after reset is in:

34/44
Component State

PC Set to reset handler ( RST_entry() )

SP Set from vector[0] (usually SRAM top)

Interrupts Disabled

System Mode Thread mode, privileged, handler running

Stack Pointer Using MSP (Main Stack Pointer)

Registers Unknown unless initialized

Memory FLASH is executable; RAM is empty

🧩 3. What is FreeRTOS doing during this period?


Nothing yet.

❗At this stage, FreeRTOS hasn’t started.


You're still in the bare-metal boot phase.

FreeRTOS only starts when you explicitly call:

vTaskStartScheduler();

This is usually done inside your main() , which looks like:

int main(void) {
// Init clocks, peripherals
xTaskCreate(...); // Create tasks
vTaskStartScheduler(); // Start the FreeRTOS kernel

35/44
while(1); // Should never reach here
}

Once vTaskStartScheduler() is called:


ARM switches to using PSP (Process Stack Pointer) for tasks.

SysTick timer is started to generate context switches.

The first task is launched using PendSV and SVC exceptions.

You’re now officially “in FreeRTOS world.”

📌 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.

Yes I want to know 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

SysTick Timer interrupt used to generate RTOS ticks (time slices)

PendSV Exception used to perform context switching

SVC (optional) Used to start the first task

📦 Let's walk through step-by-step:


1️⃣ SysTick: The heartbeat of the OS
⏱ What is SysTick?
A timer peripheral built into Cortex-M CPUs

Can generate periodic interrupts (e.g., every 1ms)

🧠 FreeRTOS setup:
When vTaskStartScheduler() is called:

FreeRTOS configures SysTick to fire at configTICK_RATE_HZ

Example: 1000 Hz → 1ms per tick

💥 On every SysTick interrupt:


37/44
c

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.

2️⃣ PendSV: The context switcher


🧩 What is PendSV?
A low-priority, software-triggered interrupt

Meant for deferred handling, perfect for context switching

🔁 What FreeRTOS does in PendSV_Handler:


c

PendSV_Handler() {
// Save context of current task (registers, stack pointer)
// Select the next task to run
// Restore context of next task
}

This involves:

Saving R4–R11 (callee-saved) manually

Updating PSP (Process Stack Pointer)

Returning to the new task with BX LR

💡 The actual low-level implementation is in assembly in port.c .

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
}

This happens once, at the end of vTaskStartScheduler() .

🧠 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

🚦 Why use PendSV and not just SysTick?


Because:

SysTick should stay fast/light — just set a flag

PendSV runs at lowest priority, so switching doesn’t interrupt higher priority ISRs

Context switching takes time — PendSV allows deferred, predictable switching

🧪 Bonus Tip:
You can check NVIC registers to see pending exceptions:

SCB->ICSR & SCB_ICSR_PENDSVSET_Msk // Is PendSV pending?

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)?

I would like to have a diagram showing how the registers


and stack look during context switching

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

A new task is restored

🧠 Context Switch in Cortex-M


(FreeRTOS) — Illustrated
When a task switch occurs, FreeRTOS performs:

1. Automatic Context Save by hardware (on exception entry)

2. Manual Context Save/Restore by PendSV_Handler (FreeRTOS code)

💥 1. Hardware Save (on entering interrupt)


When any exception occurs (like SysTick), Cortex-M automatically pushes:

Stack (descending) Register Description

[SP + 0x00] R0 Argument reg

[SP + 0x04] R1

[SP + 0x08] R2

[SP + 0x0C] R3

[SP + 0x10] R12

[SP + 0x14] LR Link register (return)

[SP + 0x18] PC Program counter

[SP + 0x1C] xPSR Program status register

✅ 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:

Stack (descending) Register Description

[SP + 0x20] R4 Callee-saved

[SP + 0x24] R5

[SP + 0x28] R6

[SP + 0x2C] R7

[SP + 0x30] R8

[SP + 0x34] R9

[SP + 0x38] R10

[SP + 0x3C] R11

📌 These are saved manually because Cortex-M does not save callee-saved regs
automatically.

📤 Diagram: Stack Layout at Context Switch (Saved


Context)
plaintext

+--------------------+ ← SP before exception (PSP)


| R11 |
| R10 |
| R9 |
| R8 |
| R7 |
| R6 |

42/44
| R5 |
| R4 | ← saved manually by FreeRTOS
| xPSR |
| PC |
| LR |
| R12 |
| R3 |
| R2 |
| R1 |
| R0 | ← saved automatically by hardware
+--------------------+ ← new SP after save

Total = (8 + 8) x 4 bytes = 64 bytes per context switch

🧠 On Task Restore (switching in):


FreeRTOS:

1. Sets PSP to saved task stack pointer

2. Pops R4–R11 manually

3. Exits from exception → hardware automatically restores R0–R3, R12, LR, PC, xPSR

✅ Task resumes exactly where it was before being switched out.

🧩 Why this works so well:


Cortex-M provides automatic lightweight interrupt entry/exit

FreeRTOS only needs to manage R4–R11 and stack pointers

Fast and deterministic: perfect for real-time systems

Bonus: FreeRTOS Context Switch Assembly (simplified)

43/44
asm

PendSV_Handler:
// Save R4–R11 to PSP
MRS R0, PSP
STMDB R0!, {R4-R11}
STR R0, pxCurrentTCB ; Save stack pointer

LDR R0, pxNextTCB


LDR R0, [R0]
LDMIA R0!, {R4-R11}
MSR PSP, R0 ; Restore 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

You might also like