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