Memory is the most constrained resource in embedded systems. An 8-bit microcontroller may have 4 KB of flash and 512 bytes of RAM. A 32-bit Cortex-M0 may have 32 KB flash and 8 KB RAM. Even a relatively capable Cortex-M4 with 256 KB flash and 64 KB RAM can become constrained when a full protocol stack, RTOS, and application logic must share that space. Every byte of ROM occupied by unused code is flash that cannot be used for application features. Every byte of RAM occupied by oversized buffers is memory unavailable for runtime state. Memory optimisation is not a cleanup activity - it is a design discipline that must be applied continuously throughout embedded firmware development.
ROM Optimisation: Reducing Flash Footprint
ROM footprint is determined by the code and constant data that the compiler and linker place in flash memory. Protocol stack configuration is the first lever for ROM reduction - most embedded protocol stacks provide compile-time configuration options that exclude unused features from the binary. A UDS stack configured without the WriteDataByIdentifier service eliminates the code that implements it. A Modbus stack configured for slave-only mode eliminates the master request generation code. Unused feature elimination through compile-time configuration is the most impactful single ROM reduction technique and should be applied before any other optimisation.
Compiler optimisation flags significantly affect code size. GCC's -Os flag optimises for code size rather than speed, typically reducing binary size by 15–30% compared to -O2 without meaningful performance impact for most embedded applications. Link-time optimisation (LTO) allows the linker to eliminate unreachable functions across compilation unit boundaries - functions that are present in object files but never called from any code path are excluded from the final binary. For embedded targets, function-level linking through the -ffunction-sections and -fdata-sections flags combined with --gc-sections linker flag eliminates any function or data object not referenced from the application, regardless of which object file it came from.
| Optimisation Technique | Typical ROM Saving | Complexity | Risk |
|---|---|---|---|
| Feature exclusion via config | 20-40% | Low | None - unused code removed |
| Compiler -Os flag | 15-30% | Low | Verify timing-critical paths |
| Link-time optimisation (LTO) | 5-15% | Low | Verify debug symbols |
| Function-level linking | 5-20% | Low | Verify ISR table completeness |
| Algorithmic replacement | 5-50% | High | Requires re-verification |
| Assembly optimisation | 1-10% | Very high | Requires assembly expertise |
RAM Optimisation: Reducing Runtime Memory Footprint
RAM footprint has two components: static RAM - BSS and data sections allocated at compile time - and dynamic RAM - stack and heap space consumed at runtime. Static RAM is determined by global and static variable sizes. Every global buffer should be sized to the maximum expected payload rather than a generous overestimate - a 512-byte receive buffer where 128 bytes is the maximum possible message size wastes 384 bytes of static RAM. Shared buffer architectures, where multiple protocol stack modules share a common buffer pool rather than each allocating their own private buffers, can significantly reduce total static RAM across a multi-stack integration.
Stack Size Tuning
Stack overflow is one of the most dangerous and difficult-to-diagnose bugs in embedded systems. The temptation to allocate generously-sized stacks to avoid overflows wastes RAM unnecessarily. Correct stack sizing requires measuring actual maximum stack depth using stack colouring - filling the stack region with a known pattern at startup and measuring how far into the pattern execution writes during worst-case scenarios - or using a static analysis tool that calculates worst-case stack depth from the call graph. For RTOS-based systems, each task stack must be sized independently based on that task's call depth, local variable usage, and interrupt nesting depth.
Static vs Dynamic Memory Allocation
MISRA-C and most embedded safety coding standards prohibit dynamic memory allocation using malloc and free. The reasons are well-founded: dynamic allocation is non-deterministic in timing, can fail at runtime without predictable recovery paths, and is susceptible to fragmentation over long operation periods. Static allocation - allocating all buffers at compile time with fixed sizes - eliminates these risks at the cost of some flexibility. For embedded protocol stacks, static allocation of message buffers, connection tables, and session state structures is standard practice. Where variable-size data must be handled, a static pool allocator with fixed-size blocks provides controlled dynamic-like allocation with deterministic performance and no fragmentation risk.
Memory Footprint of RAPIDSEA Protocol Stacks
RAPIDSEA's protocol stacks are designed for production embedded deployments with significant memory constraints. The UDS server stack occupies approximately 100 KB ROM and under 14 KB RAM for a full-featured configuration - competitive with any commercial embedded UDS implementation. Smaller configurations excluding unused diagnostic services reduce these figures further. All stacks use static allocation exclusively - MISRA-C compliance prohibits dynamic allocation throughout the codebase. Buffer sizes are compile-time configurable through header file constants, allowing each deployment to allocate exactly the memory required for its maximum expected message size without overprovisioning.
Conclusion
Memory optimisation in resource-constrained embedded systems requires systematic application of compile-time configuration, compiler and linker settings, static allocation discipline, and stack measurement - applied throughout development rather than as a late-stage correction. RAPIDSEA's compile-time configurable, statically-allocating protocol stacks provide a memory-efficient starting point for embedded firmware integration, with footprint characteristics that fit production MCU memory budgets without requiring custom stack modifications.
Ready to discuss memory footprint optimisation for your embedded programme? Contact our team to request an evaluation build or book a technical demo.
