Part 10: System Integration – The Complete Picture

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:

  1. Power Applied (0μs)
    • Voltage begins rising on all power pins
    • Decoupling capacitors start charging
    • All circuits are in undefined state
  2. Voltage Stabilizes (1μs)
    • Power-on Reset (POR) circuit monitors voltage
    • Waits until voltage exceeds threshold (typically 2.0V)
    • Ensures clean startup conditions
  3. Reset Released (10μs)
    • POR circuit releases the reset line
    • CPU begins its startup sequence
    • All registers set to default values
  4. 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
  5. 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
  6. 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:

  1. Stack Pointer Setup
    • Points to top of RAM (0x20030000)
    • Stack grows downward from here
    • Critical for function calls and local variables
  2. Data Section Copy
    • Initialized global variables stored in Flash
    • Must be copied to RAM for modification
    • Example: int counter = 10; needs RAM copy
  3. BSS Section Clear
    • Uninitialized globals must start at zero
    • Example: int buffer[100]; cleared to zeros
    • Ensures predictable behavior
  4. 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:

  1. 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
  2. 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
  3. 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
  4. Power Management Setup (5-7ms)
    • Configures voltage scaling
    • Sets up wake-up sources
    • Prepares low-power modes
  5. 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:

  1. Task Scheduler Check (0.1ms)
    • Examines all 8 task slots
    • Calculates which tasks are due
    • Executes highest priority ready task
    • Updates timing statistics
  2. Command Processing (0-50ms)
    • Checks UART receive flag
    • If character available, processes it
    • Simple commands: <1ms
    • Status display: ~50ms
  3. 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:

  1. Task 0 (HIGH priority) runs first
  2. Task 1 (NORMAL priority) runs second
  3. Scheduler returns to main loop
  4. 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
  1. COMMAND DISPATCH switch(cmd) { case 'd': { TaskSchedulerStatus_t status; // 1KB stack allocation TaskScheduler_GetStatus(&status); UART_SendString(status.statusMessage); break; }
  2. 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]); } }
  3. UART TRANSMISSION (43ms)
    • ~500 characters to send
    • Each character: 87μs
    • Total time: 500 × 87μs = 43.5ms
    • System blocked during transmission
  4. 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:

  1. TIME KEEPING
    • SysTick interrupt fires (every 1ms)
    • ISR increments: systick_counter = 500
    • Returns to main loop
  2. SCHEDULER ACTIVATION void TaskScheduler_RunDispatcher(void) { uint32_t currentTime = systick_counter; // Capture time uint8_t taskToRun = MAX_TASKS; uint8_t highestPriority = 0xFF;
  3. 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 } } } }
  4. 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;
  5. 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)
  6. 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 RangeContentSizePurpose
0x08000000-0x080001FFInterrupt Vector Table512BISR addresses
0x08000200-0x080003FFStartup Code512BReset handler
0x08000400-0x0800FFFFApplication Code (.text)~63KBYour functions
0x08010000-0x0801FFFFConstants (.rodata)~64KBString literals
0x08020000-0x0802FFFFInitial Values (.data init)~64KBFor RAM copy
0x08030000-0x081FFFFF(Unused)~1.8MBAvailable 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 RangeContentSizeUsage Pattern
0x20000000-0x20000FFF.data section4KBInitialized globals
0x20001000-0x20007FFF.bss section28KBZero-init globals
0x20008000-0x20027FFFHeap (unused)128KBWould be for malloc
0x20028000-0x2002FFFFStack32KBLocal variables
0x20030000Initial SPStack 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";
  1. .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
  2. 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:

  1. Main loop overhead (~1.1ms per iteration)
  2. Task execution time (varies by task)
  3. UART operations (block during transmission)
  4. 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

  1. Startup Diagnostics // Interrupt vector verification if (rtc_handler == 0 || rtc_handler < 0x08000000) { UART_SendString("[ERROR] Invalid interrupt vector!\r\n"); }
  2. Runtime Status Commands
    • Press ‘d’: Display complete scheduler status
    • Press ‘s’: Show system statistics
    • Press ‘p’: Power management information
    • Press ‘5’: Current power consumption
  3. 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

  1. Layered Design
    • Clear separation between hardware and application
    • Each layer has defined responsibilities
    • Easy to modify or extend individual components
  2. Efficient Resource Usage
    • Minimal RAM and Flash consumption
    • Low CPU utilization (3.6%)
    • Extensive power management
  3. Robust Operation
    • Deterministic timing behavior
    • Error detection and recovery
    • Built-in diagnostics
  4. Professional Implementation
    • Industry-standard patterns
    • Comprehensive documentation
    • Production-ready features

Key Integration Points

  1. Timing Foundation
    • SysTick provides common timebase
    • All components synchronized to 1ms tick
    • Predictable scheduling behavior
  2. Communication Hub
    • UART enables user interaction
    • Debug messages from all components
    • Non-blocking command interface
  3. Power Awareness
    • Components coordinate for low power
    • Wake sources properly configured
    • Graceful sleep/wake transitions
  4. 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!

Leave a Comment

Your email address will not be published. Required fields are marked *