Giao diện
💾 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! │
│ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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 │
│ └───────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 strings1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
✅ 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!1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Static vs Dynamic Allocation
| Aspect | Static | Dynamic (Heap) |
|---|---|---|
| When allocated | Compile time | Runtime |
| Size | Fixed | Variable |
| Fragmentation | None | High risk |
| Speed | O(1) | Varies |
| Failure | Compile error | Runtime 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};1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
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)1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
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!1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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!");1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
📚 Tổng kết
| Rule | Description |
|---|---|
| Avoid heap | No new, delete, malloc |
| Use const | Keep read-only data in FLASH |
| Small types | uint8_t, int16_t not int |
| Bit fields | Pack flags into single byte |
| Static buffers | Pre-allocate, don't dynamically allocate |
| Object pools | Reuse 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.