Skip to content

Công nghệ 9 giây

“Website hướng dẫn, thử nghiệm và giải thích công nghệ theo cách dễ hiểu cho mọi người.”

Menu
  • Home
  • Công nghệ
  • STM32
  • ESP32
  • Altium Desinger
  • Kiến thức điện máy
Menu

📘 BÀI 15 – static, const, volatile: 3 TỪ KHÓA CỰC KỲ QUAN TRỌNG TRONG STM32

Posted on August 20, 2026August 23, 2026 by congnghe9s

📘 BÀI 15 – static, const, volatile: 3 TỪ KHÓA CỰC KỲ QUAN TRỌNG TRONG STM32

Đây là một bài rất quan trọng.

Nếu struct, enum, typedef, pointer giúp bạn tổ chức dữ liệu, thì:

static
const
volatile

giúp bạn kiểm soát cách biến tồn tại, có được phép thay đổi hay không, và cách compiler phải xử lý nó.

Đặc biệt:

volatile là kiến thức bắt buộc khi bạn bắt đầu làm STM32 thực tế.


1. const – biến không được phép thay đổi

Ví dụ:

const int MAX_SPEED = 1000;

Sau đó:

MAX_SPEED = 2000;

❌ Không hợp lệ.

Bởi vì MAX_SPEED đã được khai báo là const.


Ví dụ với STM32

Giả sử động cơ của bạn tối đa 1000 RPM:

const uint16_t MOTOR_MAX_SPEED = 1000;

Trong chương trình:

if (speed > MOTOR_MAX_SPEED)
{
    speed = MOTOR_MAX_SPEED;
}

Điều này rất tốt.

Bạn đang nói với compiler:

“Giá trị này không được chương trình thay đổi.”


2. const không có nghĩa là “không nằm trong RAM”

Đây là điểm bạn cần nhớ.

Khi học C trên STM32, đừng hiểu đơn giản:

const = chắc chắn nằm trong Flash

Không phải lúc nào cũng vậy.

const chủ yếu mang ý nghĩa:

Code của bạn không được phép sửa giá trị thông qua đối tượng đó.

Compiler/linker có thể đặt dữ liệu const ở vùng nhớ phù hợp, thường có thể nằm trong Flash đối với dữ liệu chỉ đọc.


3. const với pointer

Đây là phần quan trọng hơn.

Có:

int value = 10;
int *ptr = &value;

Ta có thể:

*ptr = 20;

Nhưng:

const int *ptr = &value;

thì:

*ptr = 20;

❌ Không được.

Ta có thể đọc:

printf("%d", *ptr);

nhưng không được sửa thông qua ptr.


4. static – biến sống lâu hơn phạm vi của nó

Ví dụ:

void Counter(void)
{
    int count = 0;

    count++;

    printf("%d", count);
}

Mỗi lần gọi:

Counter();
Counter();
Counter();

count lại được tạo lại.

Kết quả:

1
1
1

Nhưng nếu:

void Counter(void)
{
    static int count = 0;

    count++;

    printf("%d", count);
}

thì:

Counter();
→ 1

Counter();
→ 2

Counter();
→ 3

Tại sao?

Vì:

static int count;

không bị mất sau khi hàm kết thúc.


5. Hãy hình dung RAM

Biến bình thường:

void Function(void)
{
    int x = 10;
}

Có thể hình dung:

Function()
    ↓
Stack
┌─────────┐
│ x = 10  │
└─────────┘
    ↓
Function kết thúc
    ↓
x không còn tồn tại

Còn:

static int x = 10;

thì:

RAM
┌─────────────┐
│ static x=10 │
└─────────────┘
      ↑
      │
Function()

Hàm kết thúc nhưng x vẫn tồn tại.


6. static rất hữu ích trong firmware

Ví dụ muốn tạo bộ đếm:

void Task_1ms(void)
{
    static uint32_t counter = 0;

    counter++;

    if (counter >= 1000)
    {
        counter = 0;

        // làm một việc
    }
}

Nếu Task_1ms() được gọi mỗi 1 ms:

1 ms   → counter = 1
2 ms   → counter = 2
3 ms   → counter = 3
...
1000ms → counter = 1000

Sau đó:

counter = 0;

và bắt đầu lại.

Đây là kỹ thuật bạn sẽ gặp rất nhiều trong STM32.


7. static còn có một ý nghĩa khác

Ví dụ:

static int motorSpeed;

ở phạm vi file .c.

Nó có nghĩa rằng biến này chỉ được sử dụng trong file đó.

Ví dụ:

Motor.c
    static int motorSpeed;

thì các file khác không thể truy cập trực tiếp motorSpeed.

Điều này giúp đóng gói module.


8. Ví dụ module STM32

Giả sử:

Motor.c
Motor.h

Trong Motor.c:

static uint16_t motorSpeed = 0;

Bạn có:

void Motor_SetSpeed(uint16_t speed)
{
    motorSpeed = speed;
}

và:

uint16_t Motor_GetSpeed(void)
{
    return motorSpeed;
}

Bên ngoài chỉ cần:

Motor_SetSpeed(500);

không cần biết biến motorSpeed nằm ở đâu.

Đây là tư duy encapsulation rất quan trọng.


9. volatile – phần quan trọng nhất

Bây giờ đến từ khóa quan trọng nhất:

volatile

volatile nói với compiler:

“Giá trị của biến này có thể thay đổi từ bên ngoài luồng code bình thường. Đừng tự ý tối ưu hóa việc đọc nó.”

Đây chính là tình huống xảy ra rất nhiều trong STM32.


10. Ví dụ đơn giản

volatile uint8_t flag = 0;

Sau đó chương trình:

while (flag == 0)
{
}

Compiler phải hiểu rằng:

flag
↓
có thể thay đổi bất kỳ lúc nào
↓
phải đọc lại từ bộ nhớ

11. Tại sao STM32 cần volatile?

Vì giá trị có thể thay đổi bởi:

Interrupt
Timer
DMA
Peripheral
Hardware register

chứ không nhất thiết do câu lệnh thông thường trong main() thay đổi.


12. Ví dụ Interrupt

Giả sử:

volatile uint8_t buttonPressed = 0;

Trong main():

while (1)
{
    if (buttonPressed)
    {
        buttonPressed = 0;

        // xử lý nút
    }
}

Trong interrupt:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    buttonPressed = 1;
}

Ta có:

             INTERRUPT
                 │
                 ▼
        buttonPressed = 1
                 │
                 ▼
                RAM
                 │
                 ▼
              main()
                 │
                 ▼
       if(buttonPressed)

buttonPressed có thể thay đổi ngoài luồng thực thi thông thường của main().

Vì vậy:

volatile uint8_t buttonPressed;

là rất phù hợp.


13. Nếu không có volatile thì sao?

Compiler có thể tối ưu hóa code dựa trên giả định rằng một biến bình thường không tự nhiên thay đổi giữa các lần đọc nếu không có code tương ứng ghi vào nó.

Ví dụ:

uint8_t flag = 0;

while (flag == 0)
{
}

Nếu compiler thấy không có đoạn code thông thường nào thay đổi flag, nó có thể tối ưu mạnh đoạn code này.

Nhưng với:

volatile uint8_t flag = 0;

compiler phải tôn trọng khả năng flag bị thay đổi bởi cơ chế bên ngoài như interrupt hoặc phần cứng.


14. Hardware Register và volatile

Đây là nơi bạn sẽ thấy volatile rất nhiều.

STM32 có các thanh ghi phần cứng.

Ví dụ khái niệm:

volatile uint32_t *REG = ...;

CPU đọc:

value = *REG;

Phần cứng có thể làm thay đổi giá trị thanh ghi.

Do đó compiler không được giả định rằng giá trị luôn giữ nguyên.

Đây là lý do các thanh ghi peripheral thường được khai báo theo cơ chế tương đương volatile.


15. volatile KHÔNG làm code “an toàn tuyệt đối”

Đây là lỗi hiểu rất phổ biến.

Nhiều người nghĩ:

volatile

có nghĩa:

“Biến này an toàn khi dùng trong interrupt.”

❌ Không hoàn toàn đúng.

Ví dụ:

volatile uint32_t counter;

volatile đảm bảo compiler không tối ưu hóa các lần truy cập theo cách làm mất ý nghĩa volatile.

Nhưng nó không tự biến phép đọc/ghi thành một thao tác nguyên tử trong mọi trường hợp, và không giải quyết mọi vấn đề đồng bộ giữa main, ISR, DMA hoặc các ngữ cảnh khác.

Đây là kiến thức chúng ta sẽ học sâu hơn sau.


16. So sánh 3 từ khóa

Từ khóa Ý nghĩa chính
const Code không được phép sửa giá trị qua đối tượng đó
static Điều khiển thời gian sống/phạm vi liên kết của biến
volatile Báo compiler rằng giá trị có thể thay đổi ngoài luồng code thông thường

Hãy nhớ:

const
↓
KHÔNG CHO PHÉP CODE SỬA

static
↓
GIỮ TRẠNG THÁI / GIỚI HẠN PHẠM VI

volatile
↓
GIÁ TRỊ CÓ THỂ THAY ĐỔI BẤT NGỜ

17. Kết hợp cả 3

Bạn hoàn toàn có thể gặp:

static volatile uint8_t flag;

Nghĩa là:

static
↓
chỉ dùng trong module này
và tồn tại lâu dài

volatile
↓
giá trị có thể bị thay đổi
bởi interrupt/hardware

Ví dụ:

static volatile uint8_t uartRxComplete = 0;

Đây là kiểu khai báo rất thực tế trong firmware.


18. Một ví dụ STM32 hoàn chỉnh

Giả sử UART nhận dữ liệu.

static volatile uint8_t rxComplete = 0;

ISR/callback:

void UART_RxComplete(void)
{
    rxComplete = 1;
}

Trong main():

while (1)
{
    if (rxComplete)
    {
        rxComplete = 0;

        // xử lý dữ liệu
    }
}

Ở đây:

static

→ biến chỉ thuộc module.

volatile

→ ISR có thể thay đổi nó.


19. Một ví dụ const rất thực tế

Giả sử STM32 điều khiển động cơ:

#define MOTOR_MAX_SPEED 1000

hoặc:

const uint16_t MOTOR_MAX_SPEED = 1000;

Sau đó:

if (speed > MOTOR_MAX_SPEED)
{
    speed = MOTOR_MAX_SPEED;
}

Nếu đây là một tham số không thay đổi trong chương trình, const giúp thể hiện rõ ý định.


20. Kết hợp với STRUCT của Bài 14

Bây giờ chúng ta nâng cấp kiến thức.

typedef enum
{
    MOTOR_STOP,
    MOTOR_RUN,
    MOTOR_ERROR
} MotorState_t;
typedef struct
{
    MotorState_t state;
    uint16_t speed;
    uint8_t errorCode;
} Motor_t;

Có thể có:

static Motor_t motor1;

Nghĩa là motor1 tồn tại suốt thời gian chương trình chạy và nếu đặt ở phạm vi file thì chỉ file đó sử dụng trực tiếp nó.


21. Một bài tập rất quan trọng

Hãy nhìn đoạn code:

static volatile uint8_t buttonFlag = 0;

void EXTI_Callback(void)
{
    buttonFlag = 1;
}

void Main_Task(void)
{
    if (buttonFlag == 1)
    {
        buttonFlag = 0;

        // xử lý nút nhấn
    }
}

Bạn hãy tự phân tích:

static làm gì?

→ buttonFlag có thời gian sống suốt chương trình; nếu ở phạm vi file thì nó còn được giới hạn trong file đó.

volatile làm gì?

→ Báo compiler rằng buttonFlag có thể thay đổi ngoài luồng code bình thường, ví dụ bởi interrupt.

uint8_t làm gì?

→ Chọn kiểu dữ liệu số nguyên không dấu 8 bit.


🧠 Bài tập Bài 15

Bài 1

Cho:

const uint16_t MAX_SPEED = 1000;

Câu hỏi:

MAX_SPEED = 2000;

có hợp lệ không?


Bài 2

Đoán kết quả:

void Counter(void)
{
    static int count = 0;

    count++;

    printf("%d\n", count);
}

Nếu gọi:

Counter();
Counter();
Counter();
Counter();

Kết quả là gì?


Bài 3

Tại sao biến được thay đổi trong interrupt thường nên được xem xét khai báo:

volatile

?


Bài 4 – quan trọng nhất

Giải thích từng từ khóa trong:

static volatile uint8_t adcReady = 0;

Bài 5 – STM32 thực tế

Giả sử:

volatile uint8_t buttonFlag = 0;

Trong interrupt:

buttonFlag = 1;

Trong while(1):

if (buttonFlag == 1)
{
    buttonFlag = 0;

    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}

Hãy giải thích luồng hoạt động:

Nút nhấn
   ↓
GPIO Interrupt
   ↓
buttonFlag = 1
   ↓
main()
   ↓
if(buttonFlag)
   ↓
Toggle PC13

🎯 Sau Bài 15 bạn cần thuộc lòng 3 câu

const → Không cho code sửa giá trị qua đối tượng đó.

static → Giữ trạng thái/lifetime và có thể giới hạn phạm vi của biến hoặc hàm.

volatile → Giá trị có thể thay đổi ngoài luồng code bình thường; compiler phải thực hiện các truy cập theo yêu cầu của volatile.

Và từ Bài 16, chúng ta có thể bước sang một phần cực kỳ quan trọng để lập trình STM32:

BÀI 16 – BIT, BYTE, MASK, &, |, ^, ~, <<, >>: CÁCH STM32 THỰC SỰ ĐIỀU KHIỂN THANH GHI.

Đây sẽ là bài nối trực tiếp kiến thức C của bạn với GPIO, RCC, Timer, UART, ADC và các thanh ghi của STM32F103C8T6.

Leave a Reply Cancel reply

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

Recent Posts

  • Bạn có thể hướng dẫn tôi quy trình sửa chữa một mainboard laptop cũng như mainboard của máy tính để bàn không?
  • Có những mức nguồn DC nào trên một bo mạch mainboard laptop?Kỹ thuật đốt dòng để kiểm tra nguồn laptop được thực hiện như thế nào?
  • bài tiếp theo hợp lý là: BÀI 63 – SENSOR FUSION THỰC CHIẾN: ENCODER + IMU MPU6050 + EKF + ODOMETRY X,Y,θ TRÊN STM32F103
  • BÀI 62 – EKF (EXTENDED KALMAN FILTER) CHO ROBOT 2 BÁNH: FUSION ENCODER + IMU + ƯỚC LƯỢNG X, Y, θ, V, ω
  • BÀI 61 – KALMAN FILTER CHO ENCODER + IMU: ƯỚC LƯỢNG TỐC ĐỘ, GÓC θ VÀ SENSOR FUSION

Recent Comments

No comments to show.

Archives

  • September 2026
  • August 2026
  • July 2026
  • June 2026
  • May 2026
  • February 2026

Categories

  • Altium Desinger
  • Công nghệ
  • ESP32
  • Khác
  • Kiến thức điện máy
  • STM32
Live 3D Globes Visitor
©2026 Công nghệ 9 giây | Design: Newspaperly WordPress Theme