Được 👍 Chúng ta tiếp tục Bài 7 trong chuỗi học C để lập trình STM32.
🟦 BÀI 7 – STRUCT, ENUM VÀ TYPEDEF TRONG C
Ở các bài trước, bạn đã đi qua:
- Kiểu dữ liệu và cách biến nằm trong RAM.
- Toán tử, biểu thức và điều kiện.
- Vòng lặp.
- Hàm.
- Con trỏ.
- Mảng và con trỏ.
Bây giờ chúng ta học một thứ cực kỳ quan trọng khi lập trình STM32 thực tế:
struct– gom nhiều biến liên quan thành một đối tượng dữ liệu.
Đây là thứ bạn sẽ gặp liên tục trong STM32 HAL, driver, UART, GPIO, Timer, ADC…
1. Vấn đề của việc dùng nhiều biến rời rạc
Ví dụ bạn muốn lưu thông tin một LED:
int led_pin = 13;
int led_state = 1;
int led_brightness = 80;
Ba biến này cùng mô tả một LED.
Nhưng nếu có 10 LED:
int led1_pin;
int led1_state;
int led1_brightness;
int led2_pin;
int led2_state;
int led2_brightness;
int led3_pin;
int led3_state;
int led3_brightness;
Rất nhanh trở nên khó quản lý.
struct giải quyết vấn đề này.
2. struct là gì?
Ta có thể tạo một kiểu dữ liệu mới:
struct LED
{
int pin;
int state;
int brightness;
};
Bây giờ:
struct LED led1;
led1 có 3 thành phần:
led1
├── pin
├── state
└── brightness
Gán dữ liệu:
led1.pin = 13;
led1.state = 1;
led1.brightness = 80;
Truy cập:
printf("%d", led1.pin);
3. Hãy đặc biệt chú ý dấu .
Với struct, dấu:
.
dùng để truy cập thành viên.
Ví dụ:
led1.pin
led1.state
led1.brightness
Có thể hiểu:
led1.pin
│ │
│ └── thành viên
└────── biến struct
4. Khởi tạo struct
Có thể viết:
struct LED led1 = {13, 1, 80};
Tương đương:
led1.pin = 13;
led1.state = 1;
led1.brightness = 80;
Thứ tự phải đúng:
struct LED
{
int pin; // 13
int state; // 1
int brightness; // 80
};
5. Nhiều biến struct
Đây mới là điểm mạnh.
struct LED led1;
struct LED led2;
struct LED led3;
Sau đó:
led1.pin = 13;
led2.pin = 8;
led3.pin = 9;
Ta đang có:
LED1
├── pin
├── state
└── brightness
LED2
├── pin
├── state
└── brightness
LED3
├── pin
├── state
└── brightness
6. Struct kết hợp với mảng
Đây là phần rất quan trọng.
Ta có thể tạo:
struct LED leds[3];
Khi đó:
leds[0]
leds[1]
leds[2]
Mỗi phần tử lại chứa:
pin
state
brightness
Ví dụ:
leds[0].pin = 13;
leds[0].state = 1;
leds[1].pin = 8;
leds[1].state = 0;
leds[2].pin = 9;
leds[2].state = 1;
Đây là cấu trúc rất gần với cách bạn sẽ quản lý 19 kênh trong project Arduino/STM32.
7. Struct + vòng lặp
Ta có thể làm:
for(int i = 0; i < 3; i++)
{
leds[i].state = 0;
}
Tức là tắt cả 3 LED.
Hoặc:
for(int i = 0; i < 3; i++)
{
leds[i].brightness = 100;
}
8. typedef – giúp code dễ đọc hơn
Cách trên hơi dài:
struct LED led1;
Ta có thể dùng:
typedef struct
{
int pin;
int state;
int brightness;
} LED;
Bây giờ chỉ cần:
LED led1;
LED led2;
LED led3;
Dễ đọc hơn rất nhiều.
9. enum – tạo các trạng thái có tên
Giả sử LED có ba trạng thái:
OFF
ON
ERROR
Không nên viết:
led.state = 0;
led.state = 1;
led.state = 2;
Vì nhìn vào không biết 2 nghĩa là gì.
Ta dùng enum:
enum LED_STATE
{
LED_OFF,
LED_ON,
LED_ERROR
};
Bây giờ:
enum LED_STATE state;
state = LED_OFF;
hoặc:
state = LED_ON;
Code dễ hiểu hơn rất nhiều.
10. enum thực chất vẫn liên quan đến số
Mặc định:
enum LED_STATE
{
LED_OFF,
LED_ON,
LED_ERROR
};
thường tương ứng:
LED_OFF = 0
LED_ON = 1
LED_ERROR = 2
Có thể tự đặt:
enum LED_STATE
{
LED_OFF = 0,
LED_ON = 10,
LED_ERROR = 20
};
11. Kết hợp typedef + struct + enum
Đây là lúc mọi thứ bắt đầu giống code embedded thực tế.
typedef enum
{
LED_OFF,
LED_ON,
LED_ERROR
} LED_STATE;
Sau đó:
typedef struct
{
int pin;
LED_STATE state;
int brightness;
} LED;
Tạo LED:
LED led1;
Gán:
led1.pin = 13;
led1.state = LED_ON;
led1.brightness = 80;
Nhìn vào code ta biết ngay:
pin → chân GPIO
state → trạng thái LED
brightness → độ sáng
12. Đây chính là tư duy quan trọng
Thay vì:
int pin;
int state;
int brightness;
hãy bắt đầu suy nghĩ:
“Ba biến này thuộc về cùng một đối tượng.”
→ tạo struct.
Ví dụ với STM32:
typedef struct
{
uint16_t pin;
uint8_t state;
uint8_t brightness;
} LED;
13. Nhưng STM32 còn dùng struct ở mức cao hơn
Ví dụ khi làm HAL, bạn sẽ gặp kiểu:
GPIO_InitTypeDef GPIO_InitStruct;
Rồi:
GPIO_InitStruct.Pin = GPIO_PIN_13;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
Bạn thấy quen không?
Đó chính là tư duy:
GPIO_InitStruct
├── Pin
├── Mode
├── Pull
└── Speed
Tức là:
STM32 HAL sử dụng struct rất nhiều để gom các thông số cấu hình của phần cứng.
14. Struct và con trỏ
Đây là cầu nối trực tiếp từ Bài 5 – Con trỏ sang lập trình STM32.
Nếu:
LED led1;
ta có con trỏ:
LED *p;
Cho:
p = &led1;
Muốn truy cập:
(*p).pin
Nhưng C cung cấp cách viết ngắn hơn:
p->pin
Hai cách này tương đương:
(*p).pin
và:
p->pin
Đây là một cú pháp bạn bắt buộc phải quen khi học STM32.
15. Ví dụ cực kỳ quan trọng
typedef struct
{
int pin;
int state;
} LED;
LED led1;
LED *p = &led1;
p->pin = 13;
p->state = 1;
Luồng dữ liệu:
led1
│
├── pin = 13
└── state = 1
p
│
└──────► led1
p không chứa LED.
p chứa địa chỉ của LED.
16. Bài tập Bài 7
Bài tập 1
Tạo struct:
MOTOR
có:
speed
direction
enable
Tạo:
motor1
và gán:
speed = 100
direction = 1
enable = 1
Bài tập 2
Tạo:
typedef struct
{
int pin;
int state;
} LED;
Tạo:
LED leds[5];
Dùng vòng for để:
- LED 1 pin = 8
- LED 2 pin = 9
- LED 3 pin = 10
- LED 4 pin = 11
- LED 5 pin = 12
Bài tập 3 – quan trọng nhất
Tạo:
typedef struct
{
int pin;
int state;
} LED;
Sau đó:
LED led1;
LED *p;
Cho p trỏ tới led1.
Dùng -> để:
pin = 13
state = 1
🎯 Mục tiêu sau Bài 7
Bạn chưa cần học thuộc lòng tất cả.
Chỉ cần thật chắc 5 thứ:
struct
typedef
enum
.
và đặc biệt:
->
Trong đó struct + con trỏ + -> là bộ ba cực kỳ quan trọng để bước sang code STM32 thực tế.
Bài 8 chúng ta có thể đi vào một phần rất quan trọng khác: const, volatile, static và tại sao volatile đặc biệt quan trọng với thanh ghi phần cứng, interrupt và STM32.