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

STM32 – BÀI 1 10 kiểu biến và cách STM32 thực sự lưu chúng trong RAM

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

Được bạn. Chúng ta tiếp tục đúng lộ trình nhé. 😄

🟦 STM32 – BÀI 2

ĐỊA CHỈ BỘ NHỚ, CON TRỎ *, TOÁN TỬ & VÀ REGISTER

Nếu Bài 1 giúp bạn hiểu:

“Biến được lưu trong RAM như thế nào?”

thì Bài 2 sẽ trả lời câu hỏi quan trọng hơn:

“Làm thế nào CPU biết phải đọc/ghi đúng ô nhớ nào?”

Và đây chính là cánh cửa để hiểu GPIO, ADC, Timer, UART, SPI, I2C của STM32.


1. Hãy tưởng tượng RAM là một dãy nhà

Hãy tưởng tượng RAM của STM32 giống như một dãy nhà.

Mỗi căn nhà có một địa chỉ:

Địa chỉ             Dữ liệu

0x20000000          ?
0x20000001          ?
0x20000002          ?
0x20000003          ?
0x20000004          ?
...

CPU muốn lấy dữ liệu ở đâu thì phải biết:

Địa chỉ của dữ liệu đó.

Ví dụ:

0x20000000

đây là một địa chỉ bộ nhớ.


2. Biến có địa chỉ

Ví dụ:

int number = 100;

Bạn đã biết number chứa:

100

Nhưng nó cũng có một địa chỉ.

Ví dụ giả định:

number
   │
   ▼
0x20000010

Ta có:

Tên biến       Giá trị       Địa chỉ

number           100        0x20000010

Đây là một khái niệm cực kỳ quan trọng.


3. Toán tử &

Trong C:

&number

có nghĩa:

Lấy địa chỉ của biến number.

Ví dụ:

int number = 100;

Serial.println((uint32_t)&number, HEX);

Bạn có thể nhận được một địa chỉ kiểu:

20000010

Địa chỉ thực tế có thể khác.

Điều quan trọng không phải con số cụ thể.

Điều quan trọng là:

number
   ↓
giá trị

&number
   ↓
địa chỉ

4. Đây là lúc con trỏ xuất hiện

Ta viết:

int *ptr;

Đây là:

Một biến con trỏ dùng để lưu địa chỉ của một biến int.

Ví dụ:

int number = 100;

int *ptr;

ptr = &number;

Bây giờ:

number
┌─────────────┐
│     100     │
└─────────────┘
      ▲
      │
      │
    ptr

ptr không chứa 100.

Nó chứa:

địa chỉ nơi 100 đang được lưu.


5. Phân biệt cực kỳ quan trọng

Có hai thứ:

number

và

&number

Ví dụ:

number
   ↓
100

nhưng:

&number
   ↓
0x20000010

Do đó:

ptr = &number;

nghĩa là:

ptr = địa chỉ của number

6. Toán tử * khi dùng với con trỏ

Đây là phần dễ gây rối nhất lúc mới học C.

Ví dụ:

int number = 100;

int *ptr = &number;

Bây giờ:

*ptr

có nghĩa:

Lấy dữ liệu tại địa chỉ mà ptr đang trỏ tới.

Vì ptr đang trỏ tới number, nên:

*ptr

chính là:

100

7. Hình dung như thế này

                 RAM

        địa chỉ 0x20000010
                 │
                 ▼
             ┌─────────┐
number ─────►│   100   │
             └─────────┘
                 ▲
                 │
                 │
                ptr

ptr chứa:

0x20000010

Còn:

*ptr

đọc dữ liệu tại:

0x20000010

và nhận:

100

8. *ptr không chỉ đọc được

Bạn còn có thể ghi:

*ptr = 500;

Khi đó:

number

sẽ trở thành:

500

Bởi vì:

ptr
 ↓
địa chỉ của number
 ↓
ghi 500 vào đó

Ví dụ hoàn chỉnh:

int number = 100;

int *ptr = &number;

*ptr = 500;

Kết quả:

number = 500

9. Đây chính là sức mạnh của con trỏ

Bạn có thể thao tác dữ liệu thông qua địa chỉ.

Ví dụ:

int a = 10;
int b = 20;

int *p;

p = &a;
*p = 100;

p = &b;
*p = 200;

Sau chương trình:

a = 100
b = 200

10. Nhưng STM32 dùng con trỏ ở đâu?

Đây mới là phần quan trọng.

STM32 có rất nhiều thanh ghi phần cứng – register.

Ví dụ:

GPIO
ADC
TIMER
USART
SPI
I2C

Mỗi peripheral có các thanh ghi nằm tại những địa chỉ bộ nhớ cụ thể.

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

0x40010800 → GPIOA
0x40010C00 → GPIOB
...

Đây không phải RAM thông thường.

Đó là vùng địa chỉ mà CPU sử dụng để giao tiếp với phần cứng.


11. Đây được gọi là Memory-Mapped I/O

Tên này rất quan trọng:

Memory-Mapped I/O

Có thể hiểu đơn giản:

CPU
 │
 │ đọc/ghi địa chỉ
 ▼
Memory Map
 │
 ├── RAM
 ├── Flash
 └── Peripheral Registers
       ├── GPIO
       ├── ADC
       ├── TIMER
       ├── UART
       └── SPI

Vì peripheral được ánh xạ vào không gian địa chỉ, CPU có thể dùng các lệnh đọc/ghi bộ nhớ để điều khiển phần cứng.


12. Ví dụ cực kỳ quan trọng

Giả sử có một register ở:

0x4001080C

Ta có thể tưởng tượng:

*(volatile uint32_t *)0x4001080C

Đây là một biểu thức rất quan trọng.

Hãy tách nó ra.


13. 0x4001080C là gì?

Đây là một số hexadecimal.

0x4001080C

được xem như một địa chỉ.


14. (uint32_t *)

Phần này nói:

Hãy xem địa chỉ đó như một con trỏ tới dữ liệu uint32_t.

Tức:

(uint32_t *)0x4001080C

có thể hiểu là:

con trỏ
   ↓
địa chỉ 0x4001080C
   ↓
một vùng dữ liệu 32-bit

15. * phía trước

Tiếp theo:

*(volatile uint32_t *)0x4001080C

* có nghĩa:

Truy cập dữ liệu nằm tại địa chỉ đó.

Và nếu ghi:

*(volatile uint32_t *)0x4001080C = 1;

thì CPU sẽ thực hiện một thao tác ghi tới địa chỉ đó.

Nếu địa chỉ đó là register điều khiển GPIO, thao tác này có thể làm thay đổi trạng thái phần cứng.


16. volatile là gì?

Đây là từ khóa bạn sẽ gặp rất nhiều trong STM32.

Ví dụ:

volatile uint32_t register_value;

volatile nói với compiler đại ý rằng:

Giá trị này có thể thay đổi bởi thứ bên ngoài luồng code bình thường, vì vậy đừng tự ý tối ưu hóa việc đọc/ghi nó.

Điều này cực kỳ quan trọng đối với:

GPIO register
ADC register
Timer register
UART status register
Interrupt flag

17. Tại sao register phải volatile?

Ví dụ UART có một thanh ghi trạng thái.

Phần cứng có thể tự thay đổi:

CPU                 UART Hardware

 │                       │
 │                       │
 │                       ├── nhận dữ liệu
 │                       │
 │                       ▼
 │                  Status = READY
 │                       │
 ▼                       │
đọc register ◄───────────┘

CPU không phải là thứ duy nhất thay đổi giá trị.

Phần cứng cũng có thể thay đổi.

Vì vậy compiler phải biết:

“Đừng giả định giá trị này luôn giống lần đọc trước.”


18. Đây chính là lý do bạn thấy những thứ như

Khi đọc code STM32 HAL/LL hoặc CMSIS, bạn sẽ gặp:

volatile uint32_t

hoặc các định nghĩa register rất dài.

Đừng sợ.

Bây giờ bạn đã biết nền tảng:

volatile
   +
pointer
   +
address
   +
register

19. Ví dụ với GPIO

Giả sử chúng ta có:

GPIOA

GPIOA có các register.

Ví dụ khái niệm:

GPIOA
│
├── CRL
├── CRH
├── IDR
├── ODR
├── BSRR
├── BRR
└── LCKR

Bạn chưa cần học hết chúng ngay.

Hôm nay chỉ cần hiểu:

GPIO không phải là một hàm thần bí. GPIO cuối cùng vẫn được CPU điều khiển bằng cách đọc/ghi các register.


20. Ví dụ bật LED

Trên Blue Pill, LED onboard thường nối với:

PC13

Nếu dùng Arduino framework, bạn có thể viết:

digitalWrite(PC13, LOW);

Rất đơn giản.

Nhưng phía dưới abstraction đó, cuối cùng phần cứng vẫn phải thực hiện:

CPU
 ↓
GPIOC register
 ↓
bit tương ứng PC13
 ↓
GPIO output
 ↓
LED

Đây chính là điểm khác biệt giữa:

Cấp cao

digitalWrite(PC13, LOW);

và:

Cấp register

ghi bit vào GPIO register

21. Tại sao phải học register?

Bởi vì khi bạn đã hiểu register, bạn sẽ bắt đầu hiểu:

HAL_GPIO_WritePin()

không còn là “phép thuật”.

Bạn sẽ hiểu:

HAL
 ↓
CMSIS
 ↓
Register
 ↓
Hardware

22. Một ví dụ cực đơn giản về register

Hãy tưởng tượng có:

REGISTER_A

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

Nếu bit 3 điều khiển một thiết bị:

00001000

thì thiết bị bật.

Nếu:

00000000

thì thiết bị tắt.

Đây là lý do bit manipulation cực kỳ quan trọng trong STM32.


23. Và đây là cây kiến thức bạn đang bắt đầu xây

C LANGUAGE
    │
    ├── Variable
    │
    ├── Data type
    │
    ├── Address
    │
    ├── Pointer
    │
    ├── Bit
    │
    └── Operator
            │
            ▼
      MEMORY / REGISTER
            │
            ▼
       STM32 PERIPHERAL
            │
      ┌─────┼─────┐
      ▼     ▼     ▼
    GPIO   ADC   TIMER
      │
      ▼
   HARDWARE

Bạn đang đi đúng hướng để chuyển từ:

“lập trình STM32 bằng cách gọi hàm”

sang:

“hiểu STM32 thực sự hoạt động bên dưới.”


🧪 BÀI THỰC HÀNH 1

Bạn hãy chạy đoạn này trên Blue Pill bằng Arduino IDE:

int number = 123;

int *ptr = &number;

void setup()
{
    Serial.begin(115200);

    Serial.print("number = ");
    Serial.println(number);

    Serial.print("address = 0x");
    Serial.println((uint32_t)&number, HEX);

    Serial.print("*ptr = ");
    Serial.println(*ptr);

    *ptr = 456;

    Serial.print("number after = ");
    Serial.println(number);
}

void loop()
{
}

Bạn sẽ thấy đại khái:

number = 123
address = 0x2000....
*ptr = 123
number after = 456

Điều cần quan sát là:

number = 123

sau:

*ptr = 456;

thì:

number = 456

🧪 BÀI THỰC HÀNH 2 – RẤT QUAN TRỌNG

Thử chương trình:

uint8_t a = 10;
uint16_t b = 500;
uint32_t c = 100000;

void setup()
{
    Serial.begin(115200);

    Serial.print("a address = 0x");
    Serial.println((uint32_t)&a, HEX);

    Serial.print("b address = 0x");
    Serial.println((uint32_t)&b, HEX);

    Serial.print("c address = 0x");
    Serial.println((uint32_t)&c, HEX);
}

void loop()
{
}

Bạn sẽ bắt đầu thấy rằng:

a → một địa chỉ
b → một địa chỉ khác
c → một địa chỉ khác

và mỗi biến chiếm số byte khác nhau.


🎯 6 điều bạn cần nắm sau Bài 2

Đừng cố học thuộc cả bài. Chỉ cần hiểu chắc 6 câu này:

① &

&x

→ lấy địa chỉ của x

② *

Nếu p là con trỏ:

*p

→ truy cập dữ liệu tại địa chỉ mà p đang trỏ tới

③ Con trỏ

int *p;

→ p là biến dùng để chứa địa chỉ của một int.

④ Register

Register là các vùng thanh ghi mà CPU/peripheral sử dụng để điều khiển và theo dõi phần cứng.

⑤ Memory-mapped I/O

Peripheral STM32 được ánh xạ vào không gian địa chỉ, nên CPU có thể giao tiếp với chúng thông qua các thao tác đọc/ghi địa chỉ.

⑥ volatile

Dùng để báo compiler rằng giá trị có thể thay đổi ngoài luồng thực thi thông thường, đặc biệt quan trọng với hardware register và biến liên quan đến interrupt.


🔜 BÀI 3

Nếu tiếp tục theo lộ trình này, Bài 3 mình đề nghị học:

“BIT – BYTE – HEX – toán tử & | ^ ~ << >> và cách STM32 bật/tắt đúng 1 bit của một register”

Đây sẽ là bài cực kỳ quan trọng.

Sau Bài 3, chúng ta sẽ có thể tự tay phân tích kiểu:

GPIOC->ODR |= (1 << 13);

và bạn sẽ hiểu từng ký tự trong dòng lệnh đó có ý nghĩa gì, thay vì học thuộc lòng.

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