Được chứ. 👍 Và mình nghĩ nên học lại Bài 1 từ đầu, nhưng lần này mình sẽ trình bày theo hướng rất thực tế cho STM32 Blue Pill + lập trình C, để sau này bạn chuyển sang CubeMX/CubeIDE cũng không bị “ngợp”.
🟦 STM32 – BÀI 1
10 kiểu biến và cách STM32 thực sự lưu chúng trong RAM
Mục tiêu của bài này không phải là bắt bạn học thuộc lòng một đống câu lệnh.
Điều quan trọng nhất là bạn phải hình dung được:
Khi viết một biến trong C, STM32 thực sự đặt cái gì vào RAM?
1. Trước hết: biến là gì?
Ví dụ:
int speed = 100;
Bạn có thể hiểu đơn giản:
speed
↓
┌──────────────┐
│ 100 │
└──────────────┘
RAM
speed là tên mà chương trình dùng để gọi một vùng nhớ.
Nhưng STM32 không hiểu chữ speed.
CPU chỉ làm việc với:
Địa chỉ RAM
↓
các bit 0/1
Ví dụ tưởng tượng:
RAM
0x20000000 → 64
0x20000001 → 00
0x20000002 → 00
0x20000003 → 00
Nếu speed là int 32-bit thì nó có thể chiếm 4 byte.
2. Điều đầu tiên cần nhớ: bit và byte
Bit
Một bit chỉ có:
0
1
1 byte
Một byte = 8 bit.
Ví dụ:
01001101
Có 8 bit.
Một byte có thể biểu diễn:
0 → 255
nếu là số không dấu.
3. STM32F103C8T6 của bạn có CPU 32-bit
Blue Pill STM32F103C8T6 sử dụng:
ARM Cortex-M3
Đây là CPU 32-bit.
Điều này rất quan trọng.
CPU có thể xử lý dữ liệu 32-bit rất tự nhiên.
Ví dụ:
uint32_t counter;
thường là:
32 bit
=
4 byte
4. Kiểu số nguyên đầu tiên: char
char a = 100;
Thông thường:
char
↓
8 bit
↓
1 byte
Ví dụ:
RAM
┌─────────┐
│ 100 │
└─────────┘
1 byte
Nếu dùng char như số có dấu:
-128 → +127
Nếu dùng unsigned:
unsigned char a = 250;
thì:
0 → 255
5. short
short temperature = 25;
Thông thường:
short
↓
16 bit
↓
2 byte
Phạm vi signed thường là:
-32768 → 32767
Unsigned:
unsigned short x;
thường:
0 → 65535
6. int
Đây là kiểu cực kỳ quan trọng.
Trên STM32F103:
int
thường là:
32 bit
=
4 byte
Ví dụ:
int speed = 1000;
RAM có thể chứa:
┌───────┬───────┬───────┬───────┐
│ byte0 │ byte1 │ byte2 │ byte3 │
└───────┴───────┴───────┴───────┘
4 byte
7. long
Trên STM32 với ARM GCC, long thường là:
32 bit
=
4 byte
Ví dụ:
long distance = 100000;
Nhưng đây là lý do chúng ta không nên quá phụ thuộc vào int, long nếu cần kích thước chính xác.
Trong embedded C, bạn sẽ thường gặp:
uint8_t
uint16_t
uint32_t
int8_t
int16_t
int32_t
8. Đây mới là nhóm bạn nên đặc biệt ghi nhớ
Trong STM32:
#include <stdint.h>
cho phép dùng các kiểu có kích thước rõ ràng.
uint8_t
uint8_t x;
nghĩa là:
unsigned
8 bit
1 byte
Phạm vi:
0 → 255
uint16_t
uint16_t adc;
nghĩa là:
unsigned
16 bit
2 byte
Phạm vi:
0 → 65535
Cái này rất hữu ích khi bạn làm ADC.
Ví dụ STM32F103 có ADC 12-bit:
ADC:
0
...
4095
Do đó:
uint16_t adc_value;
rất hợp lý.
uint32_t
uint32_t counter;
nghĩa là:
unsigned
32 bit
4 byte
Phạm vi:
0 → 4,294,967,295
9. Nhóm có dấu
Tương tự:
int8_t
int16_t
int32_t
Ví dụ:
int16_t temperature;
có thể chứa:
-32768 → +32767
10. float
Bây giờ đến kiểu rất quan trọng.
float voltage = 3.3;
float thường:
32 bit
=
4 byte
Nhưng khác int.
int lưu số nguyên:
10
20
100
float lưu số thực:
3.14
1.25
0.001
Ví dụ:
float voltage = 3.3;
11. double
double temperature;
Trên môi trường ARM GCC thông thường, double là:
64 bit
=
8 byte
Nó có độ chính xác cao hơn float.
Nhưng trên STM32F103 Cortex-M3, việc tính toán double có thể tốn tài nguyên hơn đáng kể so với số nguyên.
Vì vậy trong embedded:
Không phải cứ dùng
doublelà tốt hơn.
12. bool
bool motor_on = true;
Cần:
#include <stdbool.h>
Có hai giá trị:
true
false
Rất tiện để biểu diễn trạng thái:
bool motor_running = false;
bool alarm_active = true;
bool button_pressed = false;
13. Bảng tổng hợp
Bạn nên ghi nhớ bảng này:
| Kiểu | Kích thước thường gặp | Ví dụ |
|---|---|---|
char |
8 bit | char c |
uint8_t |
8 bit | uint8_t x |
int8_t |
8 bit | int8_t x |
short |
16 bit | short x |
uint16_t |
16 bit | ADC |
int16_t |
16 bit | nhiệt độ |
int |
32 bit | int x |
long |
32 bit | long x |
uint32_t |
32 bit | counter |
int32_t |
32 bit | số có dấu |
float |
32 bit | 3.14 |
double |
64 bit | số thực chính xác cao |
bool |
thường 1 byte | true/false |
Lưu ý: kích thước chính xác của các kiểu C như int, long, double phụ thuộc compiler/ABI; các kiểu uint8_t, uint16_t, uint32_t mới là cách tốt để yêu cầu kích thước chính xác.
14. Nhưng điều quan trọng hơn kích thước
Giả sử:
uint8_t a = 10;
uint16_t b = 1000;
uint32_t c = 100000;
Bạn có thể tưởng tượng RAM:
RAM
│
├── a
│ └── 1 byte
│
├── b
│ └── 2 byte
│
└── c
└── 4 byte
Tức là:
uint8_t → 1 byte
uint16_t → 2 byte
uint32_t → 4 byte
Đây là nền tảng cực kỳ quan trọng khi lập trình STM32.
15. Tại sao phải quan tâm RAM?
STM32F103C8T6 mà bạn đang học có RAM khoảng:
20 KB
Nghe có vẻ nhiều.
Nhưng nếu bạn tạo:
uint8_t buffer[1000];
thì:
1000 byte
đã bị sử dụng.
Nếu:
uint32_t buffer[1000];
thì:
1000 × 4
= 4000 byte
Tức là gấp 4 lần.
Đây chính là lý do trong các dự án embedded lớn, việc chọn kiểu dữ liệu rất quan trọng.
16. Một ví dụ rất thực tế với dự án của bạn
Bạn có biến trở 50 kΩ và đọc ADC.
STM32F103 ADC 12-bit:
0 → 4095
Bạn có thể viết:
uint16_t adc_value;
Sau đó:
adc_value = analogRead(PA0);
về mặt ý tưởng.
Vì:
4095 < 65535
nên uint16_t hoàn toàn đủ.
Không cần:
uint32_t
nếu chỉ lưu giá trị ADC.
17. Một ví dụ khác: điều khiển step motor
Bạn có:
28BYJ-48
Giả sử muốn lưu vị trí:
int32_t position;
Ví dụ:
position = 0
position = 512
position = -512
Nếu cần cả chiều âm và dương thì int32_t phù hợp.
Còn nếu chỉ lưu trạng thái:
bool motor_running;
thì không cần int.
18. Bài tập đầu tiên
Không cần viết chương trình phức tạp.
Hãy thử đo kích thước các kiểu:
#include <Arduino.h>
#include <stdint.h>
void setup()
{
Serial.begin(115200);
Serial.println(sizeof(char));
Serial.println(sizeof(short));
Serial.println(sizeof(int));
Serial.println(sizeof(long));
Serial.println(sizeof(float));
Serial.println(sizeof(double));
Serial.println(sizeof(uint8_t));
Serial.println(sizeof(uint16_t));
Serial.println(sizeof(uint32_t));
}
void loop()
{
}
Sau khi chạy, bạn sẽ thấy STM32 thực tế đang dùng bao nhiêu byte cho từng kiểu.
19. Nhưng có một điều tôi muốn bạn đặc biệt nhớ
Đừng học C STM32 theo kiểu:
“
uint32_tlà 4 byte,uint16_tlà 2 byte, học thuộc.”
Hãy hình thành tư duy:
C code
│
▼
kiểu dữ liệu
│
▼
kích thước bộ nhớ
│
▼
RAM
│
▼
CPU đọc/xử lý
│
▼
GPIO / ADC / Timer
Đây mới là tư duy của lập trình embedded.
🎯 Sau Bài 1, bạn chỉ cần nắm chắc 6 thứ
Nếu hôm nay chỉ nhớ được 6 thứ thì hãy nhớ:
1. bit = 0 hoặc 1
2. 1 byte = 8 bit
3. uint8_t = 1 byte
4. uint16_t = 2 byte
5. uint32_t = 4 byte
6. STM32F103 là CPU 32-bit
Và thêm một câu cực kỳ quan trọng:
Tên biến không phải là dữ liệu; tên biến chỉ là cách chương trình truy cập vùng nhớ chứa dữ liệu.
🔜 Bài 2 chúng ta sẽ học gì?
Sau khi hiểu biến và RAM, Bài 2 nên đi thẳng vào:
“Địa chỉ bộ nhớ, con trỏ *, toán tử & và tại sao GPIO của STM32 thực chất cũng chỉ là những địa chỉ trong bộ nhớ.”
Đây là bài cực kỳ quan trọng.
Khi hiểu bài đó, bạn sẽ bắt đầu nhìn STM32 theo kiểu:
GPIO
ADC
TIMER
UART
I2C
SPI
không còn là những thứ “bí hiểm”, mà bắt đầu thấy chúng đều liên quan đến thanh ghi (register) + địa chỉ bộ nhớ + bit.
Và từ đó chúng ta mới đi vào phần C dành riêng cho STM32 một cách thật sự.