Create individual task stacks and implement manual context switching using debugger tools. This lab introduces the fundamental concepts of task context storage and stack-based task switching that form the foundation of real-time operating systems.
- Understand ARM Cortex-M exception frame layout
- Create and initialize task stacks manually
- Learn stack pointer management and manipulation
- Practice manual context switching using debugger
- Understand how RTOS kernel switching works internally
- TM4C123GH6PM LaunchPad Evaluation Kit
- USB cable for programming and debugging
- PF1 (Red LED): Output for Task1 (RedBlinky)
- PF2 (Blue LED): Output for Task2 (BlueBlinky)
No external connections required - uses on-board LEDs.
In Lab 1, we manually changed the PC register value, which is not a legal or practical approach for real applications. Real-time operating systems need automatic task switching without manual intervention. This requires understanding:
- Exception Frame Layout: How ARM Cortex-M stores context during interrupts
- Stack Management: Each task needs its own stack space
- Context Switching: Saving and restoring complete task state
When an interrupt occurs, the ARM Cortex-M automatically pushes registers onto the stack:
Higher Memory Address
+------------------+
| xPSR | <- Status register with THUMB bit
+------------------+
| PC | <- Program Counter (return address)
+------------------+
| LR | <- Link Register
+------------------+
| R12 | <- General purpose register
+------------------+
| R3 | <- General purpose register
+------------------+
| R2 | <- General purpose register
+------------------+
| R1 | <- General purpose register
+------------------+
| R0 | <- General purpose register
+------------------+ <- Stack Pointer (SP) points here
Lower Memory Address
Important: ARM stack grows downward (from high to low memory addresses).
Start with your Lab 1 implementation ensuring you have:
- SysTick timer configured for 100ms interrupts
- Two task functions: one for RED LED, one for BLUE LED
- Basic GPIO initialization for Port F
Create dedicated memory space for each task's stack:
// Task 1 Stack (Red LED)
uint32_t stack_RedBlinky[40];
uint32_t *sp_RedBlinky = &stack_RedBlinky[40]; // Points past end
// Task 2 Stack (Blue LED)
uint32_t stack_BlueBlinky[40];
uint32_t *sp_BlueBlinky = &stack_BlueBlinky[40]; // Points past endKey Points:
- Each stack is 40 words (160 bytes)
- Stack pointer initialized to point one word past the array end
- Stack grows downward, so we start from the high address
In main function, prepare both stacks as if preempted by interrupt:
void Prepare_Task_Stacks(void){
// ---- Red LED Task Stack ----
sp_RedBlinky--; // Pre-decrement pointer
*sp_RedBlinky = 0x01000000; // xPSR with THUMB bit set
sp_RedBlinky--; // Pre-decrement for PC
*sp_RedBlinky = (uint32_t)Task1; // PC points to task function
sp_RedBlinky--; *sp_RedBlinky = 0x14141414; // LR (test pattern)
sp_RedBlinky--; *sp_RedBlinky = 0x12121212; // R12 (test pattern)
sp_RedBlinky--; *sp_RedBlinky = 0x03030303; // R3 (test pattern)
sp_RedBlinky--; *sp_RedBlinky = 0x02020202; // R2 (test pattern)
sp_RedBlinky--; *sp_RedBlinky = 0x01010101; // R1 (test pattern)
sp_RedBlinky--; *sp_RedBlinky = 0x00000000; // R0 (test pattern)
// ---- Blue LED Task Stack ----
sp_BlueBlinky--;
*sp_BlueBlinky = 0x01000000; // xPSR with THUMB bit
sp_BlueBlinky--;
*sp_BlueBlinky = (uint32_t)Task2; // PC points to task function
sp_BlueBlinky--; *sp_BlueBlinky = 0x25252525; // LR (different pattern)
sp_BlueBlinky--; *sp_BlueBlinky = 0x12121212; // R12
sp_BlueBlinky--; *sp_BlueBlinky = 0x03030303; // R3
sp_BlueBlinky--; *sp_BlueBlinky = 0x02020202; // R2
sp_BlueBlinky--; *sp_BlueBlinky = 0x01010101; // R1
sp_BlueBlinky--; *sp_BlueBlinky = 0x00000000; // R0
}- Open Memory Window in debugger
- Navigate to stack addresses to verify correct values
- Check that xPSR = 0x01000000 and PC points to task functions
- Add
sp_RedBlinkyto watch window - Add
sp_BlueBlinkyto watch window - Monitor stack pointer values during debugging
- Set breakpoint in
SysTick_Handler() - When breakpoint hits, open Registers window
- Manually set SP register to
sp_RedBlinkyvalue - Remove breakpoint and continue execution
- Result: Should jump to Task1 (Red LED function)
- Set breakpoint in
SysTick_Handler()again - Save current context: Copy current SP value to
sp_RedBlinky - Load new context: Set SP register to
sp_BlueBlinkyvalue - Continue execution
- Result: Should switch to Task2 (Blue LED function)
- Task1: Red LED blinks every 500ms when active
- Task2: Blue LED blinks every 1000ms when active
- Switching: Manual via debugger SP register modification
- Timing: Maintained by SysTick 100ms interrupts
- Screenshot_1: Memory view showing stack contents and watch window with stack pointers
- Screenshot_2: Debugger after manually setting SP to Task1, showing execution in RedBlinky function
- Screenshot_3: Debugger showing successful switch to Task2 (BlueBlinky function)
int main(void){
PortF_Init(); // Initialize GPIO
SysTick_Init(); // Configure 100ms timer
Prepare_Task_Stacks(); // Setup both task stacks
while(1){
// Infinite loop - switching done manually via debugger
}
}void Task1(void){ // Red LED Task
uint32_t lastToggleTime = 0;
GPIO_PORTF_DATA_R &= ~RED_LED; // Initialize OFF
for(;;){ // Task infinite loop
if((counter - lastToggleTime) >= 5){ // 500ms timing
lastToggleTime = counter;
GPIO_PORTF_DATA_R &= ~BLUE_LED; // Ensure other LED OFF
GPIO_PORTF_DATA_R ^= RED_LED; // Toggle Red LED
}
}
}- Open project file
- Build project (F7)
- Start debugging (Ctrl+F5)
- Follow Task 4 debugging procedure
- Take required screenshots
- Verify task switching works correctly
- FPU: Must be disabled (matches exception frame layout)
- Optimization: Disable for easier debugging
- Debug Info: Enable full symbols
-
System Hangs After SP Change
- Verify THUMB bit is set in xPSR (0x01000000)
- Check PC points to valid task function address
- Ensure stack pointer is properly aligned
-
Tasks Don't Execute Properly
- Verify task functions have infinite loops (
for(;;)) - Check SysTick interrupt is running (
counterincrementing) - Ensure GPIO initialization completed successfully
- Verify task functions have infinite loops (
-
Stack Preparation Errors
- Verify pre-decrement operations (
--pointerbefore assignment) - Check stack grows downward (high to low addresses)
- Ensure sufficient stack space (40 words minimum)
- Verify pre-decrement operations (
- Use memory window to inspect stack contents visually
- Monitor register values during context switch
- Verify exception frame matches ARM documentation
- Check stack alignment (8-byte boundary)
This lab demonstrates:
- Foundation Concepts: How RTOS task switching works internally
- Hardware Understanding: ARM Cortex-M exception handling mechanism
- Debug Skills: Advanced debugger usage for system analysis
- Memory Management: Stack allocation and pointer manipulation
Future labs will build on these concepts to implement:
- Automatic context switching
- Task scheduling algorithms
- Complete RTOS kernel functionality
- Hardware-based task switching
main.c: Complete implementation with manual stack-based switchingtm4c.h: TM4C123 register definitions- Project files: Keil µVision configuration
Note: This lab provides hands-on experience with the low-level mechanisms that real-time operating systems use for task management. Understanding these fundamentals is essential for embedded systems development.