STM32

Bắt đầu học Bài 1 – “10 kiểu biến và cách STM32 thực sự lưu chúng trong RAM”

BÀI 1 — 10 KIỂU BIẾN VÀ CÁCH STM32 THỰC SỰ LƯU CHÚNG TRONG RAM

Hôm nay chúng ta chưa vội làm GPIO, ADC hay motor. Mục tiêu là hiểu một chuyện rất quan trọng:

Khi bạn viết uint16_t adc = 2500;, STM32 thực sự làm gì với số 2500 đó?

Nếu hiểu được bài này, những bài sau về ADC, PWM, Timer, UART, pointer, register sẽ dễ hơn rất nhiều.


1. Biến thực chất là gì?

Hãy tưởng tượng RAM của STM32 là một dãy các ngăn kéo.

RAM

Địa chỉ
0x20000000 ──┬── byte
0x20000001 ──┤
0x20000002 ──┤
0x20000003 ──┤
0x20000004 ──┤
0x20000005 ──┤
...

Mỗi ô có một địa chỉ.

Khi bạn viết:

uint8_t age = 25;

bạn đang yêu cầu máy tính:

“Hãy dành một vùng nhớ thích hợp để chứa giá trị 25, và gọi vùng đó là age.”

Có thể hình dung:

Tên biến       Địa chỉ       Giá trị
────────────────────────────────────
age            0x20000010       25

Điểm cực kỳ quan trọng:

agetên mà con người sử dụng, còn STM32 thực sự làm việc với địa chỉ + dữ liệu.


2. Bit và Byte

Trước khi học kiểu biến, phải hiểu hai đơn vị:

1 byte = 8 bit

Ví dụ một byte:

bit:    7 6 5 4 3 2 1 0
        ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓
        0 1 0 1 1 0 1 0

Có 8 bit nên có:

2^8 = 256

giá trị khác nhau:

0 → 255

Đó chính là lý do:

uint8_t

có thể chứa:

0 → 255

3. 10 kiểu biến bạn cần biết

Trong lập trình STM32, tôi muốn bạn làm quen với 10 kiểu sau:

char
unsigned char

int8_t
uint8_t
int16_t
uint16_t
int32_t
uint32_t
float
double

Nhưng cần lưu ý:

char, int, long, double có kích thước phụ thuộc compiler/kiến trúc; còn uint8_t, uint16_t, uint32_t được định nghĩa để biểu diễn kiểu số nguyên có độ rộng cụ thể khi nền tảng hỗ trợ.

Với STM32, các kiểu <stdint.h> như uint8_t, uint16_t, uint32_t đặc biệt hữu ích.


4. uint8_t — người bạn rất quan trọng

uint8_t x;

u = unsigned
int = integer
8 = 8 bit
_t = kiểu dữ liệu chuẩn hóa

Kích thước:

8 bit = 1 byte

Giá trị:

0 → 255

Ví dụ:

uint8_t led = 1;
uint8_t channel = 19;
uint8_t counter = 200;

Rất phù hợp với:

  • trạng thái ON/OFF
  • số thứ tự
  • dữ liệu byte
  • dữ liệu UART
  • dữ liệu SPI
  • dữ liệu I2C
  • giá trị 8-bit

Ví dụ:

uint8_t led_state = 1;

RAM có thể hình dung:

led_state
    ↓
┌─────────┐
│ 00000001│
└─────────┘
   1 byte

5. int8_t

Khác với uint8_t, int8_t có dấu.

int8_t temperature;

Giá trị thường là:

-128 → +127

Ví dụ:

int8_t temperature = -20;

Trong khi:

uint8_t temperature = -20;

là sai về mặt ý nghĩa vì uint8_t không biểu diễn số âm.


6. uint16_t

Đây là kiểu bạn sẽ gặp rất nhiều khi làm ADC.

uint16_t adc_value;

16 bit:

16 bit = 2 byte

Có:

2^16 = 65536

giá trị:

0 → 65535

Ví dụ ADC 12-bit:

0 → 4095

vì vậy:

uint16_t adc_value;

rất phù hợp.

Ví dụ:

uint16_t adc_value = 2500;

RAM:

Địa chỉ       Dữ liệu
────────────────────────
0x20000010    byte 1
0x20000011    byte 2

Hai byte được sử dụng để chứa một giá trị 16-bit.


7. int16_t

Có dấu:

int16_t temperature;

Khoảng giá trị:

-32768 → +32767

Ví dụ:

int16_t temperature = -500;

8. uint32_t

Đây là kiểu cực kỳ quan trọng trong STM32.

uint32_t counter;

32 bit:

32 bit = 4 byte

Khoảng:

0 → 4,294,967,295

Bạn sẽ gặp nó trong:

  • Timer
  • Counter
  • Tick
  • địa chỉ
  • thời gian
  • thanh ghi
  • bộ đếm
  • DMA
  • peripheral

Ví dụ:

uint32_t millis;

hoặc:

uint32_t counter = 0;

9. int32_t

Có dấu:

int32_t value;

Khoảng:

-2,147,483,648
→
+2,147,483,647

Dùng khi cần số nguyên lớn và có thể âm.


10. float

Đây là kiểu số thực.

Ví dụ:

float voltage = 3.3;

Không còn chỉ là số nguyên:

3
4
5

mà có thể:

3.3
4.75
1.25

Ví dụ với biến trở:

float voltage;

voltage = adc_value * 3.3 / 4095.0;

Nếu:

ADC = 2048

thì:

voltage ≈ 1.65 V

Tuy nhiên trong embedded, không phải lúc nào dùng float cũng là lựa chọn tốt. Nhiều phép tính có thể dùng số nguyên để tiết kiệm tài nguyên và tăng tốc.


11. double

Ví dụ:

double temperature;

double là kiểu số thực với độ chính xác cao hơn float theo chuẩn C.

Nhưng có một điểm rất đáng chú ý:

Trên nhiều dòng STM32 dùng ARM Cortex-M, kích thước/hiệu năng của double phụ thuộc toolchain và kiến trúc FPU; không nên mặc định rằng double luôn là “gấp đôi” float về mọi mặt.

Trong các bài học STM32 cơ bản, bạn nên ưu tiên hiểu:

float

trước.


12. Bảng ghi nhớ

Kiểu Số bit Số byte Giá trị thường gặp
uint8_t 8 1 0 → 255
int8_t 8 1 -128 → 127
uint16_t 16 2 0 → 65535
int16_t 16 2 -32768 → 32767
uint32_t 32 4 0 → 4.29 tỷ
int32_t 32 4 -2.147 tỷ → 2.147 tỷ
float thường 32 thường 4 số thực
double thường 64 thường 8 số thực độ chính xác cao
char 8 1 ký tự / số nguyên nhỏ
unsigned char 8 1 0 → 255 trên hệ thống thông thường

Không cần học thuộc bảng ngay. Quan trọng là hiểu tại sao kích thước khác nhau.


13. Bây giờ đến phần rất quan trọng: STM32 lưu biến thế nào?

Giả sử:

uint8_t a = 25;

Có thể hình dung:

RAM

0x20000010
┌──────────────┐
│ 00011001     │
└──────────────┘
      a

25 trong nhị phân:

25 = 00011001

Bây giờ:

uint16_t b = 1000;

1000 ở dạng hex:

0x03E8

Nó cần 16 bit:

00000011 11101000

Tức là 2 byte.

Trên STM32 Cortex-M, bạn còn sẽ gặp một khái niệm quan trọng gọi là little-endian: byte thấp thường được đặt ở địa chỉ thấp hơn.

Ví dụ minh họa:

b = 0x03E8

Địa chỉ       Nội dung
────────────────────────
0x20000020    E8
0x20000021    03

Đây là một trong những lý do sau này khi học pointer, UART, SPI, I2C, struct và register, bạn sẽ cần hiểu byte order.


14. Một biến có 3 thứ cần quan tâm

Khi nhìn:

uint16_t adc = 2500;

hãy tập suy nghĩ:

             adc
              │
       ┌──────┴──────┐
       ↓             ↓
    Kiểu dữ liệu    Giá trị
    uint16_t         2500
       │
       ↓
     2 byte
       │
       ↓
      RAM

Nhưng còn một thứ nữa:

             adc
              │
              ↓
          địa chỉ RAM

Do đó:

Tên biến → kiểu dữ liệu → địa chỉ RAM → dữ liệu

Đây chính là nền tảng của pointer.


15. Và đây là lý do tôi muốn bạn học Pointer ngay sau bài này

Giả sử:

uint16_t adc = 2500;

Nếu:

adc

giá trị:

2500

thì:

&adc

là:

địa chỉ của biến adc

Ví dụ tưởng tượng:

adc = 2500

RAM
─────────────────────────────
0x20000100    E4
0x20000101    09
              ↑
              adc

Khi đó:

&adc

có thể là:

0x20000100

16. Một câu lệnh cực kỳ quan trọng

uint16_t adc = 2500;

uint16_t *p = &adc;

Bây giờ:

adc
 ↓
2500

còn:

p
 ↓
địa chỉ của adc

Hình dung:

RAM

0x20000100
┌───────────────┐
│     adc       │
│     2500      │
└───────────────┘
       ↑
       │
       │ địa chỉ
       │
p ─────┘

Đây chính là pointer.

Và đây là một trong những khái niệm quan trọng nhất để chuyển từ:

“Tôi biết viết C”

sang:

“Tôi hiểu C đang điều khiển STM32 như thế nào.”


17. Thực hành ngay trên Blue Pill của bạn

Chúng ta có thể tạo project CubeIDE rất đơn giản.

Trong main.c:

#include "main.h"
#include <stdint.h>

int main(void)
{
    HAL_Init();

    uint8_t a = 25;
    uint16_t b = 1000;
    uint32_t c = 100000;
    int16_t d = -500;
    float e = 3.3f;

    while (1)
    {
    }
}

Chưa cần GPIO.

Chúng ta chỉ muốn debugger nhìn vào RAM.


18. Dùng Debugger để “nhìn thấy” biến

Đây là bài thực hành rất đáng làm.

Trong CubeIDE:

Build
  ↓
Debug
  ↓
Break tại while(1)
  ↓
Variables

Bạn sẽ thấy đại loại:

a = 25
b = 1000
c = 100000
d = -500
e = 3.300000

Nhưng điều thú vị hơn là mở:

Memory

và xem địa chỉ của biến.

Lúc đó bạn sẽ bắt đầu thấy C hoạt động bên trong STM32, thay vì chỉ nhìn code.


19. Bài tập số 1

Không chạy máy, hãy tự trả lời:

Câu 1

uint8_t x = 255;

Có hợp lệ không?

Câu 2

uint8_t x = 256;

Điều gì xảy ra?

Câu 3

int8_t x = -100;

Có hợp lệ không?

Câu 4

uint16_t x = 50000;

Có hợp lệ không?

Câu 5

uint16_t x = -1;

Có phù hợp không?

Câu 6

uint32_t counter = 0;
counter++;

counter dùng bao nhiêu byte?


20. Bài tập quan trọng hơn

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

uint16_t adc_value = 0;
uint8_t motor_speed = 0;
float voltage = 0.0f;
int16_t temperature = -10;
uint32_t counter = 0;

Hãy thử tự trả lời:

1. Có bao nhiêu biến?

2. Biến nào chiếm 1 byte?

3. Biến nào chiếm 2 byte?

4. Biến nào chiếm 4 byte?
(giả sử float là 32-bit trên toolchain của bạn)

5. Biến nào có thể chứa số âm?

6. Biến nào phù hợp nhất để chứa ADC 12-bit?

7. Biến nào phù hợp để đếm thời gian/số lần lặp rất lớn?


🎯 Sau Bài 1, bạn chỉ cần nhớ 5 điều

Đừng cố nhồi tất cả vào đầu. Chỉ cần nhớ:

1 byte = 8 bit
uint8_t  →  1 byte
uint16_t →  2 byte
uint32_t →  4 byte

và quan trọng nhất:

Tên biến
   ↓
Kiểu dữ liệu
   ↓
Vùng nhớ RAM
   ↓
Giá trị

Cuối cùng:

adc

giá trị của biến, còn:

&adc

địa chỉ của biến.

👉 Đây chính là chiếc cầu dẫn sang Bài 2: &, * và Pointer — tại sao HAL_GPIO_WritePin(&...) lại có dấu &, và tại sao STM32 có thể điều khiển phần cứng thông qua địa chỉ bộ nhớ.

About the author

lbtmicr06

Leave a Comment