Được. Chúng ta tiếp tục Bài 9 trong lộ trình học C để lập trình STM32.
🟢 BÀI 9 – struct nâng cao: Struct + Con trỏ + Hàm
Ở Bài 7, bạn đã học struct.
Ở Bài 8, bạn học hàm.
Bây giờ ta ghép chúng lại. Đây là kiến thức cực kỳ quan trọng khi chuyển sang lập trình STM32 thực tế, vì trong chương trình STM32 bạn sẽ gặp rất nhiều kiểu dữ liệu dạng cấu trúc.
1. Ôn lại struct
Ví dụ ta tạo thông tin của một LED:
struct LED
{
int pin;
int state;
};
Tạo biến:
struct LED led1;
Gán dữ liệu:
led1.pin = 13;
led1.state = 1;
Truy cập:
printf("%d", led1.pin);
printf("%d", led1.state);
Ta có:
led1
├── pin = 13
└── state = 1
2. Struct truyền vào hàm
Đây là phần rất quan trọng.
Ta có:
struct LED
{
int pin;
int state;
};
Viết hàm:
void LED_On(struct LED led)
{
led.state = 1;
}
Sau đó:
struct LED led1;
led1.pin = 13;
led1.state = 0;
LED_On(led1);
Bạn có thể nghĩ:
“Sau khi gọi
LED_On(led1),led1.statesẽ thành 1?”
Không!
Bởi vì:
LED_On(led1);
đang truyền bản sao của led1 vào hàm.
Hình dung:
led1 gốc
|
| copy
↓
led trong hàm
Hàm thay đổi bản copy, không thay đổi biến gốc.
3. Muốn hàm thay đổi Struct gốc → dùng con trỏ
Đây chính là lý do con trỏ + struct rất quan trọng.
Ta viết:
void LED_On(struct LED *led)
{
led->state = 1;
}
Gọi:
LED_On(&led1);
Ở đây:
&led1
nghĩa là:
địa chỉ của
led1.
Hàm nhận:
struct LED *led
nghĩa là:
ledlà con trỏ trỏ tới một biếnstruct LED.
4. Tại sao lại có ->?
Đây là cú pháp bạn cần nhớ:
Nếu có biến struct bình thường:
led1.state
Nếu có con trỏ tới struct:
led->state
Hai cách này tương đương:
led->state
và:
(*led).state
Ví dụ:
void LED_On(struct LED *led)
{
(*led).state = 1;
}
hoàn toàn tương đương:
void LED_On(struct LED *led)
{
led->state = 1;
}
Trong thực tế người ta thường dùng:
led->state
vì dễ đọc hơn.
5. Ví dụ hoàn chỉnh
#include <stdio.h>
struct LED
{
int pin;
int state;
};
void LED_On(struct LED *led)
{
led->state = 1;
}
void LED_Off(struct LED *led)
{
led->state = 0;
}
int main()
{
struct LED led1;
led1.pin = 13;
led1.state = 0;
printf("Before: %d\n", led1.state);
LED_On(&led1);
printf("After ON: %d\n", led1.state);
LED_Off(&led1);
printf("After OFF: %d\n", led1.state);
return 0;
}
Kết quả:
Before: 0
After ON: 1
After OFF: 0
6. Đây chính là tư duy lập trình STM32
Sau này bạn sẽ gặp kiểu code như:
GPIO_Init(&GPIO_InitStruct);
Bạn thấy:
&GPIO_InitStruct
không?
Đó chính là địa chỉ của một struct được truyền vào hàm.
Ví dụ tưởng tượng:
struct GPIO
{
int pin;
int mode;
int speed;
};
void GPIO_Init(struct GPIO *config)
{
...
}
Ta tạo:
struct GPIO config;
config.pin = 13;
config.mode = 1;
config.speed = 2;
Sau đó:
GPIO_Init(&config);
Đây chính là mô hình mà các thư viện STM32 sử dụng rất nhiều.
7. Một ví dụ gần với STM32 hơn
Giả sử ta mô tả một chân GPIO:
struct GPIO_Config
{
int pin;
int mode;
int pull;
};
Tạo cấu hình:
struct GPIO_Config led;
led.pin = 13;
led.mode = 1;
led.pull = 0;
Hàm:
void GPIO_Init(struct GPIO_Config *config)
{
printf("Pin = %d\n", config->pin);
printf("Mode = %d\n", config->mode);
printf("Pull = %d\n", config->pull);
}
Gọi:
GPIO_Init(&led);
8. Một bước quan trọng hơn: typedef struct
Trong C, bạn thường thấy:
typedef struct
{
int pin;
int state;
} LED;
Bây giờ không cần viết:
struct LED led1;
mà chỉ cần:
LED led1;
Ví dụ:
typedef struct
{
int pin;
int state;
} LED;
Sau đó:
LED led1;
LED led2;
LED led3;
Hàm:
void LED_On(LED *led)
{
led->state = 1;
}
Gọi:
LED_On(&led1);
Cách này bạn sẽ gặp rất nhiều trong STM32 HAL.
9. Struct có thể chứa nhiều kiểu dữ liệu
Ví dụ một thiết bị:
typedef struct
{
uint8_t id;
uint16_t adc;
uint32_t time;
float voltage;
uint8_t state;
} Device;
Tạo:
Device dev1;
Gán:
dev1.id = 1;
dev1.adc = 2048;
dev1.time = 1000;
dev1.voltage = 2.5;
dev1.state = 1;
Bạn đang gom rất nhiều thông tin liên quan vào một đối tượng dữ liệu.
Đây là tư duy cực kỳ hữu ích trong embedded.
10. Struct chứa con trỏ
Đây là phần nâng cao hơn một chút.
Ví dụ:
typedef struct
{
int pin;
int *value;
} Sensor;
Ta có:
int temperature = 30;
Sensor sensor1;
sensor1.pin = 5;
sensor1.value = &temperature;
Sau đó:
printf("%d", *(sensor1.value));
Kết quả:
30
Sơ đồ:
sensor1
├── pin
│ └── 5
│
└── value
│
↓
temperature
│
└── 30
Đây là lúc bạn bắt đầu thấy struct + pointer mạnh như thế nào.
11. Vì sao kiến thức này quan trọng với STM32?
Sau này bạn sẽ gặp các cấu trúc kiểu:
GPIO_InitTypeDef
UART_HandleTypeDef
TIM_HandleTypeDef
ADC_HandleTypeDef
I2C_HandleTypeDef
Ví dụ:
UART_HandleTypeDef huart1;
Đây là một struct.
Rồi truyền địa chỉ:
HAL_UART_Transmit(&huart1, ...);
Ta có:
UART_HandleTypeDef huart1
│
│ &
↓
&huart1
│
↓
hàm HAL_UART_Transmit()
Vì vậy nếu bạn hiểu chắc:
struct
↓
pointer
↓
struct pointer
↓
function
↓
struct + pointer + function
thì khi bước vào STM32 HAL bạn sẽ đỡ bị choáng rất nhiều.
🎯 12. Bài tập Bài 9
Bạn hãy tự viết chương trình sau.
Tạo:
typedef struct
{
int pin;
int state;
int brightness;
} LED;
Tạo 2 LED:
LED led1;
LED led2;
Yêu cầu:
LED1
pin = 13
state = 0
brightness = 50
LED2
pin = 8
state = 1
brightness = 100
Viết 2 hàm:
void LED_On(LED *led);
và:
void LED_Off(LED *led);
Trong main():
- Khởi tạo LED1, LED2.
- In trạng thái ban đầu.
- Gọi
LED_On(&led1). - Gọi
LED_Off(&led2). - In trạng thái sau khi thay đổi.
🧠 Sau Bài 9 bạn cần nắm chắc 6 thứ
1. struct
2. typedef struct
3. struct truyền vào hàm
4. con trỏ tới struct
5. toán tử ->
6. truyền địa chỉ struct bằng &
Đặc biệt hãy thuộc cặp này:
LED led1;
LED_On(&led1);
và:
void LED_On(LED *led)
{
led->state = 1;
}
Đây là một trong những mẫu cú pháp bạn sẽ gặp liên tục khi lập trình STM32.
Bài tiếp theo nên là Bài 10 – enum, typedef, const, static và volatile: 4 từ khóa cực kỳ quan trọng trong C nhúng.