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 31 – XÂY DỰNG GPIO DRIVER HOÀN CHỈNH: GPIO_Init(), GPIO_WritePin(), GPIO_ReadPin(), GPIO_TogglePin()

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

Được chứ. Nếu Bài 30 – Tự viết GPIO Driver cho STM32F103 đã giúp bạn đi từ việc thao tác trực tiếp thanh ghi sang tự xây dựng một lớp driver, thì Bài 31 rất phù hợp để bước tiếp theo là:

BÀI 31 – XÂY DỰNG GPIO DRIVER HOÀN CHỈNH: GPIO_Init(), GPIO_WritePin(), GPIO_ReadPin(), GPIO_TogglePin()

Mục tiêu của bài này là để bạn bắt đầu viết GPIO theo kiểu giống tư duy của HAL/LL, nhưng driver là do chính bạn viết.


1. Bài 31 chúng ta sẽ học gì?

Sau bài này bạn sẽ có thể viết:

GPIO_Init(GPIOA, GPIO_PIN_0, GPIO_OUTPUT_PP, GPIO_SPEED_2MHz);

GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);

GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_RESET);

GPIO_TogglePin(GPIOA, GPIO_PIN_0);

uint8_t state = GPIO_ReadPin(GPIOA, GPIO_PIN_0);

Tức là thay vì chương trình của bạn đầy những dòng:

GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |=  (0x2 << 0);

GPIOA->BSRR = (1 << 0);

ta sẽ tạo ra một GPIO Driver riêng.

Kiến trúc sẽ bắt đầu giống thế này:

main.c
   │
   ▼
GPIO Driver
   │
   ├── GPIO_Init()
   ├── GPIO_WritePin()
   ├── GPIO_ReadPin()
   └── GPIO_TogglePin()
   │
   ▼
STM32F103 Register
   │
   ├── CRL
   ├── CRH
   ├── IDR
   ├── ODR
   └── BSRR

Đây là một bước rất quan trọng khi bạn muốn chuyển từ “học STM32” sang “lập trình firmware STM32”.


2. Trước tiên: GPIO Driver thực chất là gì?

Bạn có thể hiểu rất đơn giản:

GPIO Driver là lớp code đứng giữa ứng dụng và thanh ghi phần cứng.

Ví dụ ứng dụng muốn bật LED:

GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

Ứng dụng không cần biết bên trong phải làm:

GPIOC->BSRR = ...

Driver sẽ lo việc đó.

Đây chính là tư duy:

Application
     ↓
Driver
     ↓
Register
     ↓
Hardware

3. Tại sao không viết trực tiếp thanh ghi?

Bạn đã học ở các bài trước:

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

Cách này hoàn toàn đúng.

Nhưng nếu chương trình lớn lên, bạn sẽ gặp vấn đề.

Ví dụ:

GPIOA->ODR |= (1 << 0);
GPIOA->ODR &= ~(1 << 1);
GPIOB->ODR |= (1 << 5);
GPIOC->ODR &= ~(1 << 13);

Sau này bạn có:

  • LED
  • button
  • relay
  • UART
  • SPI
  • I2C
  • LCD
  • motor
  • sensor

thì code sẽ đầy các thao tác thanh ghi.

Driver giúp bạn biến chúng thành:

GPIO_WritePin(GPIOA, GPIO_PIN_0, GPIO_PIN_SET);
GPIO_WritePin(GPIOA, GPIO_PIN_1, GPIO_PIN_RESET);
GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

Dễ đọc hơn rất nhiều.


4. Thiết kế GPIO Driver

Ta tạo hai file:

GPIO.h
GPIO.c

Trong dự án STM32 của bạn:

Core
│
├── Inc
│   └── GPIO.h
│
└── Src
    └── GPIO.c

Nếu dùng Arduino IDE thì cũng có thể đặt:

GPIO.h
GPIO.cpp

Nhưng trong bài học này tôi muốn bạn bắt đầu làm quen với cách tổ chức kiểu firmware C:

GPIO.h
GPIO.c

5. Tạo các kiểu dữ liệu cho GPIO

Đầu tiên:

typedef enum
{
    GPIO_PIN_RESET = 0,
    GPIO_PIN_SET = 1
} GPIO_PinState;

Bây giờ ta có:

GPIO_PIN_RESET

và:

GPIO_PIN_SET

Thay vì:

0
1

Code sẽ dễ đọc hơn.


6. Định nghĩa các chân GPIO

Ta có thể định nghĩa:

#define GPIO_PIN_0      (1U << 0)
#define GPIO_PIN_1      (1U << 1)
#define GPIO_PIN_2      (1U << 2)
#define GPIO_PIN_3      (1U << 3)
#define GPIO_PIN_4      (1U << 4)
#define GPIO_PIN_5      (1U << 5)
#define GPIO_PIN_6      (1U << 6)
#define GPIO_PIN_7      (1U << 7)

#define GPIO_PIN_8      (1U << 8)
#define GPIO_PIN_9      (1U << 9)
#define GPIO_PIN_10     (1U << 10)
#define GPIO_PIN_11     (1U << 11)
#define GPIO_PIN_12     (1U << 12)
#define GPIO_PIN_13     (1U << 13)
#define GPIO_PIN_14     (1U << 14)
#define GPIO_PIN_15     (1U << 15)

Điều này cực kỳ quan trọng.

Ví dụ:

GPIO_PIN_13

thực chất là:

0000 0000 0000 0000
             ↑
            bit13

hay:

0x2000

7. Định nghĩa chế độ GPIO

STM32F103 có một điểm rất đặc biệt.

GPIO được cấu hình thông qua:

CRL
CRH

Mỗi chân cần 4 bit cấu hình.

Ví dụ PA0:

CRL

bit 0 ─┐
bit 1  ├── MODE0 + CNF0
bit 2  │
bit 3 ─┘

PA1:

bit 4 → bit 7

PA2:

bit 8 → bit 11

…

PA7:

bit 28 → bit 31

Còn PA8 đến PA15 nằm trong:

CRH

8. Các mode cơ bản

Trong bài này chúng ta tạo:

#define GPIO_INPUT_ANALOG        0x0
#define GPIO_INPUT_FLOATING      0x4
#define GPIO_INPUT_PULL          0x8

#define GPIO_OUTPUT_PP           0x0
#define GPIO_OUTPUT_OD           0x4
#define GPIO_AF_PP               0x8
#define GPIO_AF_OD               0xC

Nhưng bạn phải hiểu:

Đây chưa phải toàn bộ 4 bit cấu hình.

Còn có:

MODE

để xác định tốc độ output.


9. MODE của STM32F103

STM32F103 có:

MODE = 00 → Input
MODE = 01 → Output 10 MHz
MODE = 10 → Output 2 MHz
MODE = 11 → Output 50 MHz

Ta định nghĩa:

#define GPIO_MODE_INPUT       0x0
#define GPIO_SPEED_10MHZ      0x1
#define GPIO_SPEED_2MHZ       0x2
#define GPIO_SPEED_50MHZ      0x3

Nhưng hãy chú ý:

MODE + CNF

mới tạo thành 4-bit configuration.


10. Ví dụ PA0 Output Push-Pull 2 MHz

Ta muốn:

PA0
MODE = 10
CNF  = 00

Ghép lại:

CNF   MODE
00    10

thành:

0010

hay:

0x2

Vì PA0 sử dụng 4 bit đầu của CRL:

GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |=  (0x2 << 0);

Đây chính là thứ Driver sẽ tự động làm.


11. GPIO_Init()

Bây giờ chúng ta thiết kế hàm:

GPIO_Init(GPIOA,
          GPIO_PIN_0,
          GPIO_OUTPUT_PP,
          GPIO_SPEED_2MHZ);

Ý nghĩa:

GPIOA
  │
  └── PA0
       │
       ├── Output
       ├── Push-Pull
       └── 2 MHz

Driver sẽ tự:

  1. xác định pin
  2. xác định CRL hay CRH
  3. xác định vị trí 4 bit
  4. xóa cấu hình cũ
  5. tạo cấu hình mới
  6. ghi vào thanh ghi

12. Công thức cực kỳ quan trọng

Đây là phần bạn nên hiểu thật kỹ.

Với pin:

pin = 0

vị trí:

shift = 0 × 4 = 0

Pin 1:

shift = 1 × 4 = 4

Pin 2:

shift = 2 × 4 = 8

Pin 3:

shift = 3 × 4 = 12

…

Pin 7:

shift = 7 × 4 = 28

Công thức:

shift = pin_number * 4;

Đây là một trong những công thức quan trọng nhất của GPIO STM32F1.


13. CRL hay CRH?

Nếu:

pin <= 7

thì:

CRL

Nếu:

pin >= 8

thì:

CRH

Với pin 8:

CRH bit 0 → 3

Pin 9:

CRH bit 4 → 7

Vì vậy:

if (pin_number < 8)
{
    // CRL
}
else
{
    // CRH
}

14. Ví dụ PA13

PA13 không nằm trong CRL.

Nó nằm trong:

GPIOA->CRH

Vị trí của PA13:

13 - 8 = 5

Do mỗi pin chiếm 4 bit:

5 × 4 = 20

Vậy PA13 sử dụng:

CRH[23:20]

Đây là lý do khi bạn nhìn datasheet STM32F103 sẽ thấy PA13 cấu hình ở các bit:

20 → 23

15. GPIO_WritePin()

Sau khi cấu hình GPIO, ta cần điều khiển output.

Ta tạo:

void GPIO_WritePin(GPIO_TypeDef *GPIOx,
                   uint16_t GPIO_Pin,
                   GPIO_PinState PinState);

Ví dụ:

GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET);

16. Vì sao nên dùng BSRR?

Bạn vừa học BSRR ở Bài 29 nên đây chính là lúc áp dụng kiến thức đó.

Không nên ưu tiên:

GPIOC->ODR |= GPIO_PIN_13;

hoặc:

GPIOC->ODR &= ~GPIO_PIN_13;

mà Driver có thể dùng:

GPIOC->BSRR = GPIO_PIN_13;

để SET.

Và:

GPIOC->BSRR = ((uint32_t)GPIO_PIN_13 << 16);

để RESET.


17. Tại sao BSRR rất hay?

Ví dụ:

GPIOC->BSRR = GPIO_PIN_13;

nghĩa là:

BSRR bit13 = 1
       ↓
PC13 = SET

Còn:

GPIOC->BSRR = GPIO_PIN_13 << 16;

thì:

BSRR bit29 = 1
       ↓
PC13 = RESET

Bạn không cần:

read
modify
write

như với ODR.


18. GPIO_ReadPin()

Input thì chúng ta đọc:

GPIOx->IDR

Ví dụ:

if (GPIOA->IDR & GPIO_PIN_0)
{
    // PA0 = HIGH
}

Driver có thể đóng gói thành:

GPIO_PinState GPIO_ReadPin(GPIO_TypeDef *GPIOx,
                           uint16_t GPIO_Pin);

Sau đó chương trình chỉ cần:

if (GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
{
    ...
}

19. GPIO_TogglePin()

Đây là một hàm rất hữu ích.

Ví dụ:

GPIO_TogglePin(GPIOC, GPIO_PIN_13);

LED đang:

OFF → ON

lần sau:

ON → OFF

Ta có thể thực hiện bằng ODR:

GPIOx->ODR ^= GPIO_Pin;

Đây cũng là lúc bạn thấy sự khác nhau:

BSRR
 ↓
SET / RESET an toàn

ODR
 ↓
đọc trạng thái hiện tại rồi đảo

20. Bộ API đầu tiên của GPIO Driver

Sau bài này, driver của chúng ta sẽ có bộ hàm:

GPIO_Init();

GPIO_WritePin();

GPIO_ReadPin();

GPIO_TogglePin();

Đây chính là một GPIO abstraction layer.


21. Ví dụ ứng dụng LED PC13

Giả sử Blue Pill của bạn có LED trên:

PC13

Ta có thể viết:

int main(void)
{
    GPIO_Init(GPIOC,
              GPIO_PIN_13,
              GPIO_OUTPUT_PP,
              GPIO_SPEED_2MHZ);

    while (1)
    {
        GPIO_TogglePin(GPIOC, GPIO_PIN_13);

        delay_ms(500);
    }
}

Điều rất đáng chú ý là main() bây giờ không còn quan tâm CRH.

Nó không cần biết:

GPIOC->CRH

cũng không cần biết:

bit 20
bit 21
bit 22
bit 23

Toàn bộ chuyện đó nằm trong Driver.


22. Đây chính là tư duy Driver

Bạn hãy nhìn sự tiến hóa:

Cấp 1 – thao tác thanh ghi

GPIOC->CRH &= ~(0xF << 20);
GPIOC->CRH |=  (0x2 << 20);

GPIOC->BSRR = (1 << 29);

↓

Cấp 2 – tự viết hàm

GPIO_Init(...);

GPIO_WritePin(...);

↓

Cấp 3 – Driver

Application
     ↓
GPIO API
     ↓
GPIO Driver
     ↓
STM32 Register
     ↓
Hardware

Đây chính là hướng mà firmware thực tế thường đi.


23. Một bài tập rất quan trọng cho bạn

Đừng chỉ copy code.

Hãy tự viết Driver để điều khiển:

PC13 LED

và:

PA0 button

Sơ đồ:

        STM32F103
       ┌───────────┐
       │           │
PA0 ───┤ INPUT     │
       │           │
PC13 ──┤ OUTPUT    │
       │           │
       └───────────┘

Yêu cầu:

PA0 = 0 → PC13 bật
PA0 = 1 → PC13 tắt

Bạn phải sử dụng:

GPIO_Init()
GPIO_ReadPin()
GPIO_WritePin()

không được viết trực tiếp GPIOA->CRL, GPIOC->CRH trong main.c.


24. Một điều tôi muốn bạn đặc biệt nhớ

Ở giai đoạn hiện tại, bạn không cần học thuộc code Driver.

Bạn cần hiểu 5 thứ:

1. GPIOx->CRL
2. GPIOx->CRH
3. GPIOx->IDR
4. GPIOx->ODR
5. GPIOx->BSRR

và mối quan hệ:

CRL/CRH
   ↓
Cấu hình GPIO

IDR
   ↓
Đọc GPIO

ODR
   ↓
Đọc/ghi trạng thái Output

BSRR
   ↓
SET/RESET Output

Nếu 5 thanh ghi này bạn thực sự hiểu, việc đọc GPIO của STM32F1 sẽ nhẹ đi rất nhiều.


25. Chuỗi bài tiếp theo

Tôi đề xuất chúng ta tiếp tục theo đúng mạch đang học:

Bài 30
Tự viết GPIO Driver
        ↓
Bài 31
GPIO Driver hoàn chỉnh
        ↓
Bài 32
GPIO Input + Pull-up/Pull-down
        ↓
Bài 33
EXTI – External Interrupt
        ↓
Bài 34
NVIC – Interrupt Controller
        ↓
Bài 35
Timer cơ bản
        ↓
Bài 36
SysTick
        ↓
Bài 37
Delay không blocking
        ↓
Bài 38
UART
        ↓
Bài 39
Tự viết UART Driver

Bài 32 tôi đặc biệt muốn dạy bạn về Input Pull-up/Pull-down và cách STM32F103 dùng ODR để chọn Pull-up hay Pull-down. Đây là một chỗ rất thú vị vì nó khiến nhiều người mới học STM32 bị nhầm: “ODR là thanh ghi output, tại sao lại liên quan đến INPUT?”

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