Skip to content

💾 Memory Constraints — Coding cho Low RAM/ROM

Trong embedded, bạn có thể chỉ có 2KB RAM. Mỗi byte đều quý giá!

Analogy: Căn hộ nhỏ

┌─────────────────────────────────────────────────────────────────┐
│                    SMALL APARTMENT ANALOGY                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   🏠 DESKTOP = Biệt thự 500m²                                   │
│   ─────────────────────────                                     │
│   - Mua đồ thoải mái                                            │
│   - Nhiều phòng chứa đồ                                         │
│   - Không lo hết chỗ                                            │
│                                                                 │
│   🏢 EMBEDDED = Studio 20m²                                     │
│   ─────────────────────────                                     │
│   - Chỉ mua đồ thật cần thiết                                   │
│   - Mỗi món đồ có vị trí cố định                                │
│   - Tối ưu từng cm² không gian                                  │
│                                                                 │
│   💡 Embedded programming = Marie Kondo cho code!               │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Memory Layout trong Embedded

┌─────────────────────────────────────────────────────────────────┐
│                    EMBEDDED MEMORY MAP                           │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   FLASH (ROM) - 64KB          Code + const data                 │
│   ┌───────────────────┐       • Chương trình                    │
│   │      .text        │       • const variables                 │
│   │    (code)         │       • Lookup tables                   │
│   ├───────────────────┤                                         │
│   │     .rodata       │                                         │
│   │  (const data)     │                                         │
│   └───────────────────┘                                         │
│                                                                 │
│   RAM - 8KB                   Variables + Stack                 │
│   ┌───────────────────┐                                         │
│   │      .data        │  ← Initialized globals                  │
│   ├───────────────────┤                                         │
│   │      .bss         │  ← Uninitialized globals (zeroed)       │
│   ├───────────────────┤                                         │
│   │      HEAP         │  ← Dynamic allocation (AVOID!)          │
│   │        ↓          │                                         │
│   │        ↑          │                                         │
│   │      STACK        │  ← Local variables, function calls      │
│   └───────────────────┘                                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Rule #1: Avoid Dynamic Allocation

Desktop Style (Bad for Embedded)

cpp
// ❌ NEVER in embedded!
void processData() {
    int* buffer = new int[1000];  // Heap allocation
    
    // ... processing ...
    
    delete[] buffer;  // Memory fragmentation risk!
}

// ❌ STL containers use heap
std::vector<int> data;  // malloc() internally
std::string name;       // malloc() for long strings

Embedded Style

cpp
// ✅ Static allocation - size known at compile time
int buffer[1000];  // On stack or .bss

// ✅ Use std::array instead of std::vector
#include <array>
std::array<int, 100> fixedArray;  // Stack allocated, size fixed

// ✅ Fixed-size string buffer
char name[32];  // No heap!

Static vs Dynamic Allocation

AspectStaticDynamic (Heap)
When allocatedCompile timeRuntime
SizeFixedVariable
FragmentationNoneHigh risk
SpeedO(1)Varies
FailureCompile errorRuntime crash
Embedded✅ Preferred❌ Avoid

Memory-Efficient Patterns

1. Use const for Read-Only Data

cpp
// ❌ Wastes RAM - copied to RAM at startup
int lookup[] = {1, 2, 4, 8, 16, 32, 64, 128};

// ✅ Stays in FLASH (ROM) - saves RAM
const int lookup[] = {1, 2, 4, 8, 16, 32, 64, 128};

// ✅ Even better with constexpr (C++11)
constexpr int lookup[] = {1, 2, 4, 8, 16, 32, 64, 128};

2. Choose Smallest Data Type

cpp
// ❌ Wastes memory
int counter = 0;           // 4 bytes
int flag = 1;              // 4 bytes for 0/1?

// ✅ Use appropriate sizes
uint8_t counter = 0;       // 1 byte (0-255)
bool flag = true;          // 1 byte
int16_t temperature = 0;   // 2 bytes (-32768 to 32767)

3. Bit Fields for Flags

cpp
// ❌ 4 bytes for 4 flags
struct DeviceStatus {
    bool isOn;       // 1 byte (padded to 4)
    bool isError;
    bool isReady;
    bool isBusy;
};  // Total: 4+ bytes

// ✅ 1 byte for 8 flags
struct DeviceStatus {
    uint8_t isOn    : 1;  // 1 bit
    uint8_t isError : 1;
    uint8_t isReady : 1;
    uint8_t isBusy  : 1;
    uint8_t reserved: 4;
};  // Total: 1 byte!

Stack Size Management

cpp
// ❌ Stack overflow risk!
void badFunction() {
    char hugeBuffer[4096];  // 4KB on stack - may overflow!
    // ...
}

// ✅ Use static for large buffers
void goodFunction() {
    static char buffer[4096];  // In .bss, not stack
    // Note: Not reentrant!
}

// ✅ Or global with limited scope
namespace {
    char gBuffer[4096];  // Anonymous namespace
}

Object Pools

Thay vì new/delete, dùng pre-allocated pools:

cpp
template<typename T, size_t N>
class ObjectPool {
    std::array<T, N> pool_;
    std::array<bool, N> used_;
    
public:
    ObjectPool() { used_.fill(false); }
    
    T* allocate() {
        for (size_t i = 0; i < N; ++i) {
            if (!used_[i]) {
                used_[i] = true;
                return &pool_[i];
            }
        }
        return nullptr;  // Pool exhausted
    }
    
    void deallocate(T* ptr) {
        size_t index = ptr - &pool_[0];
        if (index < N) {
            used_[index] = false;
        }
    }
};

// Usage
ObjectPool<Sensor, 10> sensorPool;  // Max 10 sensors
Sensor* s = sensorPool.allocate();
// ...
sensorPool.deallocate(s);

Checking Memory Usage

cpp
// GCC/ARM: Get stack usage
extern "C" char _estack;  // End of stack (linker symbol)
extern "C" char _Min_Stack_Size;

size_t getStackUsage() {
    char stackVar;
    return &_estack - &stackVar;
}

// Compile-time size check
static_assert(sizeof(MyStruct) <= 64, "MyStruct too large!");

📚 Tổng kết

RuleDescription
Avoid heapNo new, delete, malloc
Use constKeep read-only data in FLASH
Small typesuint8_t, int16_t not int
Bit fieldsPack flags into single byte
Static buffersPre-allocate, don't dynamically allocate
Object poolsReuse fixed-size object arrays

➡️ Tiếp theo

Khi làm việc với hardware registers, bạn cần kiểm soát từng bit...

Bit Manipulation → — Control registers với bitwise operators.