We’ve built our STM32 IoT Framework piece by piece across nine parts. Now let’s step back and see how everything integrates into a cohesive system. This final part shows the complete data flow, system interactions, and real-world considerations for our embedded application.
What is System Integration?
System integration means making all the individual components work together as one unified system. Think of it like assembling a car – you have the engine, transmission, wheels, and electronics all working separately, but they must coordinate perfectly to create a functioning vehicle. In our embedded system, we have:
- Hardware components (the physical chips and circuits)
- Software modules (the code we’ve written)
- Communication interfaces (how components talk to each other)
- Timing mechanisms (ensuring everything happens when it should)
Each component we built in previous parts must work in harmony. The SysTick timer provides timing, the UART enables communication, the task scheduler manages execution, and the power management saves energy. Together, they create a system greater than the sum of its parts.
Complete System Architecture
Our system consists of interconnected layers, each with specific responsibilities:
┌─────────────────────────────────────────────────────────────┐
│ Main Application │
│ (This is where our main() function lives) │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ UART │ │ Task │ │ Power │ │ System │ │
│ │ Commands │ │Scheduler │ │ Mgmt │ │ Monitor │ │
│ └─────┬────┘ └─────┬────┘ └─────┬────┘ └─────┬────┘ │
│ │ │ │ │ │
├────────┼─────────────┼─────────────┼─────────────┼──────────┤
│ ▼ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Hardware Abstraction Layer │ │
│ │ (Translates software commands to hardware) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ GPIO │ UART │ RTC │ PWR │ RCC │ NVIC │ │
│ └─────────────────────────────────────────────────────┘ │
│ (Pins) (Serial) (Clock) (Power) (Clocks) (Interrupts) │
└─────────────────────────────────────────────────────────────┘
Understanding each layer:
Application Layer (Top)
This is where your main program logic lives. It uses services from lower layers without needing to know hardware details. When main() wants to blink an LED, it calls a function rather than manipulating registers directly.
Module Layer (Middle)
These are the subsystems we’ve built:
- UART Commands: Processes user input from the serial terminal
- Task Scheduler: Manages when each task runs based on priorities and timing
- Power Management: Controls sleep modes and power consumption
- System Monitor: Tracks system health and performance
Hardware Abstraction Layer (Bottom)
This layer talks directly to the hardware:
- GPIO (General Purpose Input/Output): Controls pins connected to LEDs, buttons, sensors
- UART (Universal Asynchronous Receiver-Transmitter): Handles serial communication bit by bit
- RTC (Real-Time Clock): Keeps time even during low-power modes using a 32.768 kHz crystal
- PWR (Power Control): Manages voltage regulators and power modes
- RCC (Reset and Clock Control): Controls which peripherals get power and their clock speeds
- NVIC (Nested Vectored Interrupt Controller): Manages interrupt priorities and routing
System Boot Sequence – The Complete Journey
Let’s trace what happens from the moment power is applied. Understanding this sequence is crucial for debugging boot issues.
Phase 1: Hardware Reset (0-2ms)
Time | Event | What's Actually Happening
--------|--------------------------------|---------------------------
0μs | Power applied | Voltage rises on VDD pins
1μs | Voltage stabilizes | Capacitors charge to 3.3V
10μs | POR circuit releases reset | CPU allowed to start
20μs | First instruction fetch | CPU reads from 0x00000000
100μs | Bootloader check | Checks BOOT pins
2ms | Jump to user application | Starts at 0x08000000
Detailed explanation of each step:
- Power Applied (0μs)
- Voltage begins rising on all power pins
- Decoupling capacitors start charging
- All circuits are in undefined state
- Voltage Stabilizes (1μs)
- Power-on Reset (POR) circuit monitors voltage
- Waits until voltage exceeds threshold (typically 2.0V)
- Ensures clean startup conditions
- Reset Released (10μs)
- POR circuit releases the reset line
- CPU begins its startup sequence
- All registers set to default values
- First Instruction Fetch (20μs)
- CPU reads address 0x00000000 (initial stack pointer)
- CPU reads address 0x00000004 (reset vector)
- These addresses are aliased to either Flash, System Memory, or SRAM based on BOOT pins
- Bootloader Check (100μs)
- If BOOT0=0: Boot from Flash (normal operation)
- If BOOT0=1: Boot from System Memory (bootloader for firmware updates)
- Bootloader can update firmware via UART, USB, or SPI
- Jump to Application (2ms)
- Control transfers to user code at 0x08000000
- Your Reset_Handler begins execution
Phase 2: Software Initialization (2-150ms)
Once hardware hands control to software, initialization begins:
// Reset_Handler in startup_stm32f429xx.s
Reset_Handler:
ldr sp, =_estack // Set stack pointer to top of RAM
// Copy initialized data from Flash to RAM
movs r1, #0
b LoopCopyDataInit
CopyDataInit:
ldr r3, =_sidata // Source address in Flash
ldr r3, [r3, r1]
str r3, [r0, r1] // Destination address in RAM
adds r1, r1, #4
LoopCopyDataInit:
ldr r0, =_sdata
ldr r3, =_edata
adds r2, r0, r1
cmp r2, r3
bcc CopyDataInit
// Clear BSS section (uninitialized data)
ldr r2, =_sbss
ldr r4, =_ebss
movs r3, #0
FillZerobss:
cmp r2, r4
bcc FillZerobss
str r3, [r2], #4
bl SystemInit // Configure system clocks
bl __libc_init_array // Initialize C library
bl main // Finally, call main()
What each section does:
- Stack Pointer Setup
- Points to top of RAM (0x20030000)
- Stack grows downward from here
- Critical for function calls and local variables
- Data Section Copy
- Initialized global variables stored in Flash
- Must be copied to RAM for modification
- Example:
int counter = 10;needs RAM copy
- BSS Section Clear
- Uninitialized globals must start at zero
- Example:
int buffer[100];cleared to zeros - Ensures predictable behavior
- System Initialization
- Configures Flash wait states
- Sets up system clocks
- Prepares for main() execution
Phase 3: Application Initialization in main() (2-150ms)
Now we’re in familiar C code territory:
Time(ms) | Function Call | What It Accomplishes
---------|------------------------------|----------------------------------
2 | Vector table verification | Ensures interrupts will work correctly
3 | SysTick_Init() | Starts 1ms system heartbeat
4 | UART_Init(115200) | Enables serial communication
5-7 | PowerMgmt_Init() | Configures power saving features
7-8 | TaskScheduler_Init() | Prepares task management system
8-150 | Register 6 tasks | Adds all application tasks
Detailed breakdown of each initialization:
- Vector Table Verification (2-3ms)
uint32_t *vector_table = (uint32_t *)SCB->VTOR; uint32_t rtc_wkup_handler = vector_table[RTC_WKUP_IRQn + 16];- Reads interrupt vector table location
- Verifies critical interrupt handlers exist
- Prevents system hang if vectors corrupted
- SysTick Initialization (3ms)
SysTick->LOAD = (16000000 / 1000) - 1; // For 1ms tick SysTick->VAL = 0; SysTick->CTRL = ENABLE | TICKINT | CLKSOURCE;- Configures hardware timer for 1ms interrupts
- Provides timebase for entire system
- Essential for task scheduling
- UART Initialization (4ms)
// Enable clocks RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; RCC->APB1ENR |= RCC_APB1ENR_USART3EN; // Configure pins, baud rate, etc.- Powers up UART3 and GPIOD
- Configures PD8/PD9 as UART pins
- Sets 115200 baud communication
- Power Management Setup (5-7ms)
- Configures voltage scaling
- Sets up wake-up sources
- Prepares low-power modes
- Task Registration (8-150ms)
- Each task takes ~23ms to register
- Debug messages account for most time
- 6 tasks × 23ms ≈ 140ms total
Why Registration Takes So Long
The registration phase seems slow because of UART communication:
For each task registration:
1. Create task entry in array: <1μs
2. Print debug message (~30 chars): 30 × 87μs = 2.6ms
3. Process other registration steps: ~20ms
Total per task: ~23ms
At 115200 baud:
- 1 start bit + 8 data bits + 1 stop bit = 10 bits per character
- 115200 bits/second ÷ 10 bits/char = 11,520 characters/second
- 1 character takes 1/11,520 = 87 microseconds
Main Loop Operation – The System Heartbeat
After initialization, the system enters its main operational loop:
while(1) {
// Step 1: Run task scheduler
TaskScheduler_RunDispatcher();
// Step 2: Check for user commands
if (UART_IsDataAvailable()) {
char cmd = UART_ReceiveChar();
UART_SendChar(cmd); // Echo
switch(cmd) {
case 'd': // Show scheduler status
// ... handle command
break;
// ... other commands
}
}
// Step 3: Delay 1ms
SysTick_Delay(1);
}
What happens in each iteration:
- Task Scheduler Check (0.1ms)
- Examines all 8 task slots
- Calculates which tasks are due
- Executes highest priority ready task
- Updates timing statistics
- Command Processing (0-50ms)
- Checks UART receive flag
- If character available, processes it
- Simple commands: <1ms
- Status display: ~50ms
- System Delay (1ms)
- Waits for systick_counter to increment
- Allows ~900 iterations per second
- Provides consistent loop timing
Complete System Timeline – First 10 Seconds
Let’s see how all components work together during operation:
Time(ms) | Tasks Executed | System State | Visual Output
---------|----------------------|----------------------------|------------------
0-150 | (Initialization) | Booting up | LEDs: ⚫⚫⚫
150-499 | (Waiting) | Main loop running | LEDs: ⚫⚫⚫
500 | Task 0 | Green LED toggles | LEDs: 🟢⚫⚫
1000 | Task 0, 1 | Green off, Blue on | LEDs: ⚫🔵⚫
1500 | Task 0 | Green on | LEDs: 🟢🔵⚫
2000 | Task 0, 1, 2 | All toggle | LEDs: ⚫⚫🔴
2500 | Task 0 | Green on | LEDs: 🟢⚫🔴
3000 | Task 0, 1, 3 | Status message prints | LEDs: ⚫🔵🔴
3500 | Task 0 | Green on | LEDs: 🟢🔵🔴
4000 | Task 0, 1, 2 | All toggle | LEDs: ⚫⚫⚫
4500 | Task 0 | Green on + debug msg | LEDs: 🟢⚫⚫
5000 | Task 0, 1, 4, 5 | Monitor + Power tasks | LEDs: ⚫🔵⚫
Understanding the pattern:
The LED tasks create a visual pattern that confirms proper operation:
- Green (500ms): Fastest blink, always visible
- Blue (1000ms): Standard 1Hz heartbeat
- Red (2000ms): Slowest, creates variety
When multiple tasks are ready simultaneously (like at 1000ms), they execute in priority order:
- Task 0 (HIGH priority) runs first
- Task 1 (NORMAL priority) runs second
- Scheduler returns to main loop
- Next iteration continues
Data Flow Examples – Tracing System Operations
Example 1: Complete Command Processing Flow (‘d’ for status)
Let’s trace every step when a user types ‘d’ to see scheduler status:
1. PHYSICAL KEYPRESS
- User presses 'd' on keyboard
- Terminal software sends ASCII 100 (0x64)
- USB-to-serial converter outputs UART signal
2. ELECTRICAL SIGNALS (87μs)
- Start bit (0) transmitted
- 8 data bits: 00100110 (LSB first)
- Stop bit (1) transmitted
- Total: 10 bits at 115200 bps = 87μs
3. UART HARDWARE RECEPTION
- USART3 samples incoming signal 16x per bit
- Assembles bits into byte
- Stores 'd' in Data Register (DR)
- Sets RXNE flag (Receive Not Empty)
4. SOFTWARE DETECTION (next loop iteration)
```c
if (UART_IsDataAvailable()) { // Checks RXNE flag
char cmd = UART_ReceiveChar(); // Reads 'd' from DR
UART_SendChar(cmd); // Echo back to terminal
- COMMAND DISPATCH
switch(cmd) { case 'd': { TaskSchedulerStatus_t status; // 1KB stack allocation TaskScheduler_GetStatus(&status); UART_SendString(status.statusMessage); break; } - STATUS COLLECTION
// Inside TaskScheduler_GetStatus() for (uint8_t i = 0; i < MAX_TASKS; i++) { if (tasks[i].taskFunc != NULL) { // Format task information snprintf(buffer, size, "Task %d: %s, Period: %dms, State: %s\n", i, tasks[i].name, tasks[i].period_ms, stateStr[tasks[i].state]); } } - UART TRANSMISSION (43ms)
- ~500 characters to send
- Each character: 87μs
- Total time: 500 × 87μs = 43.5ms
- System blocked during transmission
- CLEANUP AND RESUME
- Stack space automatically reclaimed
- Main loop continues
- Total elapsed: ~45ms
### Example 2: LED Task Execution at 500ms
Here's the complete flow when the green LED task runs:
- TIME KEEPING
- SysTick interrupt fires (every 1ms)
- ISR increments: systick_counter = 500
- Returns to main loop
- SCHEDULER ACTIVATION
void TaskScheduler_RunDispatcher(void) { uint32_t currentTime = systick_counter; // Capture time uint8_t taskToRun = MAX_TASKS; uint8_t highestPriority = 0xFF; - TASK SEARCH ALGORITHM
for (uint8_t i = 0; i < MAX_TASKS; i++) { if (tasks[i].state == TASK_STATE_READY) { uint32_t nextRun = tasks[i].lastExecuted + tasks[i].period_ms; if (currentTime >= nextRun) { // Task 0: 0 + 500 = 500, current = 500 ✓ if (tasks[i].priority < highestPriority) { highestPriority = tasks[i].priority; // 0 taskToRun = i; // Select task 0 } } } } - TASK EXECUTION
if (taskToRun < MAX_TASKS) { tasks[taskToRun].state = TASK_STATE_RUNNING; uint32_t startTime = systick_counter; // Call the actual task function tasks[taskToRun].taskFunc(); // Task_LED1_Blink() uint32_t executionTime = systick_counter - startTime; - LED TOGGLE OPERATION
void Task_LED1_Blink(void) { static uint32_t counter = 0; counter++; // 0 → 1 // Toggle PB0 using XOR GPIOB->ODR ^= (1 << 0); // If ODR was 0x0000, becomes 0x0001 (LED ON) // If ODR was 0x0001, becomes 0x0000 (LED OFF) - POST-EXECUTION UPDATES
// Update timing tasks[taskToRun].lastExecuted = currentTime; // 500 // Calculate jitter uint32_t scheduledTime = 500; uint32_t actualTime = 500; uint32_t jitter = 0; // Perfect timing! // Return to ready state tasks[taskToRun].state = TASK_STATE_READY;
## Memory Organization - Where Everything Lives
Understanding memory layout is crucial for embedded development:
### Flash Memory Map (2MB total at 0x08000000)
| Address Range | Content | Size | Purpose |
|---|---|---|---|
| 0x08000000-0x080001FF | Interrupt Vector Table | 512B | ISR addresses |
| 0x08000200-0x080003FF | Startup Code | 512B | Reset handler |
| 0x08000400-0x0800FFFF | Application Code (.text) | ~63KB | Your functions |
| 0x08010000-0x0801FFFF | Constants (.rodata) | ~64KB | String literals |
| 0x08020000-0x0802FFFF | Initial Values (.data init) | ~64KB | For RAM copy |
| 0x08030000-0x081FFFFF | (Unused) | ~1.8MB | Available space |
**Key sections explained:**
1. **Vector Table (0x08000000)**
- First word: Initial stack pointer (0x20030000)
- Second word: Reset handler address
- Following words: Exception and interrupt handlers
- Critical for system operation
2. **Code Section (.text)**
- All your functions compiled to machine code
- Read-only during execution
- Includes main(), tasks, drivers
3. **Read-Only Data (.rodata)**
- String literals: "Hello World\r\n"
- Const arrays and structures
- Jump tables for switch statements
### RAM Memory Map (256KB total at 0x20000000)
| Address Range | Content | Size | Usage Pattern |
|---|---|---|---|
| 0x20000000-0x20000FFF | .data section | 4KB | Initialized globals |
| 0x20001000-0x20007FFF | .bss section | 28KB | Zero-init globals |
| 0x20008000-0x20027FFF | Heap (unused) | 128KB | Would be for malloc |
| 0x20028000-0x2002FFFF | Stack | 32KB | Local variables |
| 0x20030000 | Initial SP | – | Stack top |
**RAM section details:**
1. **.data Section**
```c
// These live in .data (initialized):
uint32_t SystemCoreClock = 16000000;
static uint32_t led_counter = 0;
char version[] = "1.0.0";
- .bss Section
// These live in .bss (zeroed): static Task_t tasks[MAX_TASKS]; // ~320 bytes uint32_t buffer[1000]; // 4000 bytes static PowerConfig_t config; // ~20 bytes - Stack Growth
High address (0x20030000) ├── Function return addresses ├── Local variables ├── Function parameters ↓ (grows downward) Low address
Special Memory Regions
CCM RAM (64KB at 0x10000000)
- Core-Coupled Memory
- Zero-wait-state access
- Cannot be used for DMA
- Perfect for stack or critical data
Backup SRAM (4KB at 0x40024000)
- Preserved in standby mode
- Battery-backed if VBAT connected
- Good for critical settings
Peripheral Registers (0x40000000-0x5FFFFFFF)
- Memory-mapped hardware control
- Each peripheral has registers here
- Direct hardware manipulation
Interrupt System Architecture
Our system uses a minimal interrupt design for predictability:
Interrupt Vector Table
Vector # | Address | Handler | Purpose | Frequency
---------|--------------|---------------------|---------------------|------------
0 | 0x08000000 | Initial SP | Stack setup | Once
1 | 0x08000004 | Reset_Handler | System start | Once
... | ... | ... | ... | ...
15 | 0x0800003C | SysTick_Handler | 1ms timer | 1000/sec
... | ... | ... | ... | ...
59 | 0x080000EC | EXTI9_5_IRQHandler | Wake from sleep | Rare
... | ... | ... | ... | ...
- | - | Default_Handler | Unused interrupts | Never
Interrupt Priority Scheme
Priority | Interrupt | Preempts Lower? | Can be Preempted? | Purpose
---------|-------------|-----------------|-------------------|------------------
-1 | HardFault | Always | Never | System errors
0 | (unused) | - | - | Highest user level
... | ... | ... | ... | ...
10 | EXTI9_5 | Yes (11-15) | Yes (by 0-9) | Wake events
11 | RTC_Alarm | Yes (12-15) | Yes (by 0-10) | Timed wake
15 | SysTick | Never | Yes (by all) | Time keeping
Priority rules:
- Lower number = Higher priority
- Same priority = First come, first served
- Priority -1 to -3 are system exceptions
- Priority 0-255 are configurable interrupts
SysTick Handler – The System Heartbeat
void SysTick_Handler(void) {
systick_counter++; // Just increment counter
}
Why so simple?
- Executes 1000 times per second
- Must be fast (~10 cycles)
- Complex operations would affect timing
- Main loop does the real work
Interrupt Latency Analysis
Event | Time | Description
------------------------|---------|----------------------------------
Interrupt occurs | 0ns | Hardware event (timer expires)
Context save | 12 cyc | Push registers (750ns at 16MHz)
Vector fetch | 2 cyc | Read handler address (125ns)
Jump to handler | 2 cyc | Branch to ISR (125ns)
Handler execution | varies | Your ISR code
Context restore | 12 cyc | Pop registers (750ns)
Return to main | 2 cyc | Resume interrupted code (125ns)
Total overhead | ~30 cyc | ~1.9μs at 16MHz
Power Management Architecture
Our system implements multiple power modes for energy efficiency:
Power Mode Characteristics
Mode | CPU | Peripherals | RAM | Wake Time | Current | Wake Sources
----------|------|-------------|--------|-----------|---------|---------------
RUN | On | On | On | - | 15mA | -
SLEEP | Off | On | On | <1μs | 5mA | Any interrupt
STOP | Off | Off | On | 5μs | 3μA | EXTI, RTC
STANDBY | Off | Off | Off | 2ms | 2μa | WKUP, RTC, Reset
Power State Transitions
┌─────────────┐
│ RUN │
│ (15mA) │
└──────┬──────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ SLEEP │ │ STOP │ │ STANDBY │
│ (5mA) │ │ (3μA) │ │ (2μA) │
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
│ │ │
│ │ ▼
│ │ ┌─────────┐
│ │ │ RESET │
│ │ └─────────┘
└─────────────────┴─────────────────┘
│
Wake interrupt
Power Optimization in Practice
Automatic Power Saving (Task 5):
void Task_PowerSaving(void) {
static uint32_t counter = 0;
counter++;
// Every 50 seconds, demonstrate power saving
if (counter % 10 == 0) {
// Configure RTC to wake after 2 seconds
PowerMgmt_SetWakeupTimer(2);
// Enter Stop mode
PowerMgmt_EnterLowPowerMode(POWER_MODE_STOP_LP);
// Execution resumes here after wake-up
UART_SendString("[POWER] Woke up from Stop mode\r\n");
}
}
Power consumption calculation:
Period: 50 seconds total
- Active: 48 seconds at 15mA = 720 mA·s
- Stop mode: 2 seconds at 0.003mA = 0.006 mA·s
- Average: 720.006 / 50 = 14.4mA
- Savings: 4% (demo only)
Real application (1% active, 99% sleep):
- Active: 0.5s at 15mA = 7.5 mA·s
- Sleep: 49.5s at 0.003mA = 0.15 mA·s
- Average: 7.65 / 50 = 0.153mA
- Savings: 99%!
System Performance Analysis
Task Execution Accuracy
Our scheduler achieves excellent timing accuracy:
Task | Target | Actual Average | Max Jitter | Min/Max Period | Accuracy
----------|---------|----------------|------------|----------------|----------
LED1 | 500ms | 500.1ms | ±2ms | 498-502ms | 99.96%
LED2 | 1000ms | 1000.2ms | ±3ms | 997-1003ms | 99.97%
LED3 | 2000ms | 2000.3ms | ±3ms | 1997-2003ms | 99.98%
Status | 3000ms | 3000.5ms | ±5ms | 2995-3005ms | 99.98%
Monitor | 5000ms | 5001.0ms | ±22ms | 4978-5022ms | 99.96%
Sources of timing variation:
- Main loop overhead (~1.1ms per iteration)
- Task execution time (varies by task)
- UART operations (block during transmission)
- Multiple tasks ready (sequential execution)
CPU Utilization Breakdown
Activity | Time/Second | Percentage | Notes
----------------------|-------------|------------|------------------------
Task execution | 6ms | 0.6% | All 6 tasks
Scheduler overhead | 30ms | 3.0% | Checking task states
UART command check | 1ms | 0.1% | Polling for input
Idle (in delay) | 963ms | 96.3% | CPU in WFI state
----------------------|-------------|------------|------------------------
Total | 1000ms | 100% | Full second accounted
Power efficiency analysis:
- Active mode: 3.7% at 15mA = 0.555mA average
- Sleep mode: 96.3% at 5mA = 4.815mA average
- Total average: 5.37mA
Memory Utilization Summary
Memory Type | Total Size | Used | Free | Usage % | Notes
---------------|------------|-----------|-----------|---------|------------------
Flash (Code) | 2048 KB | 64 KB | 1984 KB | 3.1% | Plenty of room
RAM (Data) | 256 KB | 10 KB | 246 KB | 3.9% | Very efficient
Stack | 32 KB | 2 KB typ | 30 KB | 6.3% | Safe margin
CCM RAM | 64 KB | 0 KB | 64 KB | 0% | Available
Backup SRAM | 4 KB | 0 KB | 4 KB | 0% | Available
Peripheral Usage
Peripheral | Used/Total | Usage | Purpose
---------------|------------|--------|----------------------------------
GPIO Pins | 6/114 | 5.3% | 3 LEDs + 2 UART + 1 wake
Timers | 1/17 | 5.9% | SysTick only
USART | 1/8 | 12.5% | USART3 for debug
DMA Channels | 0/16 | 0% | Available for optimization
ADC | 0/3 | 0% | Available for sensors
I2C | 0/3 | 0% | Available for devices
SPI | 0/6 | 0% | Available for storage
Debugging and Diagnostics
Built-in Debug Features
- Startup Diagnostics
// Interrupt vector verification if (rtc_handler == 0 || rtc_handler < 0x08000000) { UART_SendString("[ERROR] Invalid interrupt vector!\r\n"); } - Runtime Status Commands
- Press ‘d’: Display complete scheduler status
- Press ‘s’: Show system statistics
- Press ‘p’: Power management information
- Press ‘5’: Current power consumption
- Task Performance Metrics
// Automatically tracked for each task: - Maximum execution time - Maximum scheduling jitter - Total execution count - Current state
Stack Usage Monitoring
// Pattern-based stack monitoring
void monitor_stack_usage(void) {
// Fill unused stack with pattern at startup
uint32_t* stack = &_end_of_data;
while (stack < &_estack) {
*stack++ = 0xDEADBEEF;
}
// Later, check how much was used
uint32_t* check = &_end_of_data;
uint32_t unused = 0;
while (*check++ == 0xDEADBEEF && check < &_estack) {
unused += 4;
}
uint32_t used = total_stack_size - unused;
UART_SendString("Stack used: ");
// ... print used bytes
}
Error Recovery Mechanisms
// Hard fault handler with diagnostics
void HardFault_Handler(void) {
// Get fault information
uint32_t* fault_stack = (uint32_t*)__get_MSP();
uint32_t pc = fault_stack[6]; // Program counter
uint32_t lr = fault_stack[5]; // Link register
// Save to backup registers (survive reset)
PWR->CR |= PWR_CR_DBP; // Enable backup access
RTC->BKP0R = pc; // Where it crashed
RTC->BKP1R = lr; // Where it came from
// Attempt to report
UART_SendString("\r\n[FATAL] HardFault at PC=");
// ... print PC value
// Reset system
NVIC_SystemReset();
}
Real-World Deployment Guidelines
Production Configuration
// config.h - Production vs Debug builds
#ifdef DEBUG_BUILD
#define ENABLE_UART_DEBUG 1
#define ENABLE_ASSERTS 1
#define DEFAULT_CLOCK_SPEED 16000000
#define WATCHDOG_ENABLED 0
#else
#define ENABLE_UART_DEBUG 0
#define ENABLE_ASSERTS 0
#define DEFAULT_CLOCK_SPEED 180000000
#define WATCHDOG_ENABLED 1
#endif
// Conditional debug output
#if ENABLE_UART_DEBUG
#define DEBUG_PRINT(msg) UART_SendString(msg)
#else
#define DEBUG_PRINT(msg) ((void)0)
#endif
Watchdog Implementation
// Initialize independent watchdog
void Watchdog_Init(uint32_t timeout_ms) {
// Enable IWDG clock
RCC->CSR |= RCC_CSR_LSION;
while(!(RCC->CSR & RCC_CSR_LSIRDY));
// Configure IWDG
IWDG->KR = 0x5555; // Enable register access
IWDG->PR = 4; // Prescaler /64
IWDG->RLR = (timeout_ms * 40) / 64; // LSI ~40kHz
IWDG->KR = 0xCCCC; // Start watchdog
}
// Feed watchdog in main loop
while(1) {
IWDG->KR = 0xAAAA; // Reset watchdog counter
// Normal operations
TaskScheduler_RunDispatcher();
// ...
}
Power Optimization Strategies
// Aggressive power saving for battery operation
void Configure_UltraLowPower(void) {
// 1. Disable all unused peripherals
RCC->AHB1ENR &= ESSENTIAL_PERIPHERALS_ONLY;
RCC->APB1ENR &= ESSENTIAL_PERIPHERALS_ONLY;
RCC->APB2ENR &= ESSENTIAL_PERIPHERALS_ONLY;
// 2. Configure all unused pins as analog
GPIOA->MODER = 0xFFFFFFFF; // All analog
GPIOC->MODER = 0xFFFFFFFF;
GPIOD->MODER = 0xFFFFFFFF;
// Keep only active pins configured
// 3. Reduce system clock when possible
if (low_activity_period()) {
Switch_To_MSI_4MHz(); // Lower frequency
}
// 4. Use deepest sleep mode possible
if (can_lose_ram_contents()) {
Use_Standby_Mode(); // 2μA
} else if (can_stop_clocks()) {
Use_Stop_Mode(); // 3μA
} else {
Use_Sleep_Mode(); // 5mA
}
}
System Integration Summary
Our STM32 IoT Framework demonstrates how individual components combine into a cohesive system:
Architecture Achievements
- Layered Design
- Clear separation between hardware and application
- Each layer has defined responsibilities
- Easy to modify or extend individual components
- Efficient Resource Usage
- Minimal RAM and Flash consumption
- Low CPU utilization (3.6%)
- Extensive power management
- Robust Operation
- Deterministic timing behavior
- Error detection and recovery
- Built-in diagnostics
- Professional Implementation
- Industry-standard patterns
- Comprehensive documentation
- Production-ready features
Key Integration Points
- Timing Foundation
- SysTick provides common timebase
- All components synchronized to 1ms tick
- Predictable scheduling behavior
- Communication Hub
- UART enables user interaction
- Debug messages from all components
- Non-blocking command interface
- Power Awareness
- Components coordinate for low power
- Wake sources properly configured
- Graceful sleep/wake transitions
- Task Coordination
- Priority-based execution
- Cooperative multitasking
- Performance monitoring
The Complete System
From power-on to steady-state operation, our framework provides:
- Reliable initialization sequence
- Predictable task execution with timing guarantees
- Interactive control through UART commands
- Power efficiency with multiple sleep modes
- Diagnostic capabilities for development and deployment
This integrated system serves as a solid foundation for real IoT applications, demonstrating professional embedded development practices that scale from prototypes to production deployments.
This concludes the STM32 IoT Framework series. You now understand how to build and integrate a complete embedded system from the ground up!