Giao diện
🔒 volatile — Hardware Mapping
volatile nói với compiler: "Đừng optimize biến này — giá trị có thể thay đổi bất kỳ lúc nào!"Analogy: Kiểm tra hộp thư
┌─────────────────────────────────────────────────────────────────┐
│ MAILBOX CHECKING ANALOGY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ KHÔNG có volatile = Nhớ trong đầu │
│ ───────────────────────────────── │
│ Bạn đã kiểm tra hộp thư sáng nay → "Không có thư" │
│ Buổi chiều: "Chắc vẫn không có thư" (không kiểm tra lại) │
│ → Bỏ lỡ thư quan trọng! │
│ │
│ CÓ volatile = Kiểm tra thật mỗi lần │
│ ───────────────────────────────── │
│ Mỗi lần cần biết → ĐI RA kiểm tra hộp thư thật │
│ → Không bao giờ bỏ lỡ! │
│ │
│ Hardware register = Hộp thư │
│ volatile = Luôn kiểm tra từ memory, không dùng cache │
│ │
└─────────────────────────────────────────────────────────────────┘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
Tại sao cần volatile?
Compiler Optimization Problem
cpp
// Hardware register at address 0x40000000
uint8_t* status_reg = (uint8_t*)0x40000000;
// Wait for hardware to set bit 0
while ((*status_reg & 0x01) == 0) {
// Wait...
}1
2
3
4
5
6
7
2
3
4
5
6
7
Compiler nghĩ: "Biến này không thay đổi trong loop → chỉ cần đọc 1 lần!"
cpp
// Compiler tối ưu thành:
uint8_t cached = *status_reg; // Đọc 1 lần
while ((cached & 0x01) == 0) {
// Infinite loop! Never re-reads!
}1
2
3
4
5
2
3
4
5
Giải pháp: volatile
cpp
// ✅ CORRECT: volatile forces re-read every time
volatile uint8_t* status_reg = (volatile uint8_t*)0x40000000;
while ((*status_reg & 0x01) == 0) {
// Compiler MUST read from memory each iteration
}1
2
3
4
5
6
2
3
4
5
6
When to Use volatile?
┌─────────────────────────────────────────────────────────────────┐
│ WHEN TO USE volatile │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ✅ USE volatile: │
│ ──────────────── │
│ • Memory-mapped hardware registers │
│ • Variables shared with ISR (Interrupt Service Routine) │
│ • Variables shared between threads (though use atomic better) │
│ • Memory shared with DMA controller │
│ │
│ ❌ DON'T USE volatile: │
│ ────────────────────── │
│ • Regular variables (performance hit) │
│ • Thread synchronization (use std::atomic instead) │
│ │
└─────────────────────────────────────────────────────────────────┘1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Memory-Mapped I/O Example
cpp
// GPIO register definitions
struct GPIO_Registers {
volatile uint32_t DATA; // 0x00: Data register
volatile uint32_t DIR; // 0x04: Direction register
volatile uint32_t STATUS; // 0x08: Status register
};
// Map to hardware address
GPIO_Registers* const GPIOA = (GPIO_Registers*)0x40020000;
void blinkLED() {
// Set pin 5 as output
GPIOA->DIR |= (1 << 5);
while (true) {
GPIOA->DATA |= (1 << 5); // LED ON
delay(500);
GPIOA->DATA &= ~(1 << 5); // LED OFF
delay(500);
}
}
void waitForButton() {
// Wait until button pressed (bit 3 goes HIGH)
while ((GPIOA->STATUS & (1 << 3)) == 0) {
// volatile ensures we re-read STATUS each time
}
// Button was pressed!
}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
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
volatile with ISR
cpp
// ✅ MUST be volatile - modified by ISR
volatile bool buttonPressed = false;
volatile uint32_t tickCount = 0;
// Interrupt Service Routine
extern "C" void EXTI3_IRQHandler() {
buttonPressed = true;
// Clear interrupt flag...
}
extern "C" void SysTick_Handler() {
tickCount++; // Updated every 1ms
}
int main() {
while (true) {
// Without volatile, compiler might cache these values!
if (buttonPressed) {
buttonPressed = false;
handleButton();
}
// Use tickCount for timing
if (tickCount >= 1000) {
tickCount = 0;
doPeriodicTask();
}
}
}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
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
volatile vs const
Có thể kết hợp cả hai!
cpp
// Read-only hardware register
// Value can change (by hardware) but code can't write to it
volatile const uint32_t* const CHIP_ID =
(volatile const uint32_t*)0x1FFF7A10;
void printChipID() {
uint32_t id = *CHIP_ID; // Read hardware ID
// *CHIP_ID = 0; // ❌ Compile error - const!
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
| Keyword | Value can change | Code can modify |
|---|---|---|
volatile | Yes (externally) | Yes |
const | No | No |
volatile const | Yes (externally) | No |
volatile Does NOT Mean Atomic!
⚠️ CRITICAL
volatile không đảm bảo thread-safety hay atomicity!
cpp
volatile int counter = 0;
// Thread 1
void increment() {
counter++; // NOT ATOMIC! Still a race condition!
}
// Thread 2
void decrement() {
counter--; // NOT ATOMIC!
}
// ✅ For thread safety, use std::atomic
#include <atomic>
std::atomic<int> safeCounter{0};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Compiler Optimization Prevention
volatile prevents these optimizations:
| Optimization | Without volatile | With volatile |
|---|---|---|
| Caching | Read once, reuse | Re-read every time |
| Reordering | May reorder | Maintains order |
| Dead store elimination | May skip writes | All writes happen |
cpp
volatile uint8_t* reg = (volatile uint8_t*)0x40000000;
// Without volatile, compiler might:
// - Combine writes
// - Skip "useless" writes
// - Reorder operations
*reg = 0x01; // Write 1
*reg = 0x02; // Write 2 (compiler might skip 1!)
*reg = 0x03; // Write 3
// With volatile: ALL 3 writes happen in order!1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
📚 Tổng kết
| Aspect | Description |
|---|---|
| Purpose | Prevent compiler caching/optimization |
| Use for | Hardware registers, ISR-shared vars |
| NOT for | Thread synchronization (use atomic) |
| Effect | Forces memory read/write every time |
➡️ Tiếp theo
Trong embedded, khi nào dùng smart pointers và khi nào dùng raw pointers?
Smart Pointers in Embedded → — unique_ptr vs raw pointers.