📘 BÀI 14 – STRUCT + ENUM + TYPEDEF: ĐƯA VÀO LẬP TRÌNH STM32
Ở các bài trước, bạn đã đi qua:
- Bài 1: Kiểu dữ liệu C và RAM
- Bài 2–4: Biến, toán tử, điều kiện, vòng lặp
- Bài 5: Con trỏ
- Bài 6: Mảng + con trỏ
- Bài 7–9:
struct, hàm và cách tổ chức dữ liệu - Bài 10–13:
enum,typedef,structkết hợp
👉 Bài 14 chúng ta bắt đầu chuyển từ “học C” sang “dùng C để lập trình vi điều khiển”.
1. Tư duy quan trọng nhất của Bài 14
Khi lập trình STM32, bạn sẽ không muốn viết kiểu:
int temperature;
int motorSpeed;
int motorDirection;
int motorState;
int error;
rồi chương trình ngày càng dài.
Thay vào đó, ta gom các thông tin liên quan thành một đối tượng:
typedef struct
{
int temperature;
int motorSpeed;
int motorDirection;
int motorState;
int error;
} Motor_t;
Sau đó:
Motor_t motor1;
Ta có một “đối tượng dữ liệu” đại diện cho motor.
Đây chính là tư duy rất quan trọng trong embedded C.
2. STRUCT + ENUM
Ví dụ một động cơ có các trạng thái:
typedef enum
{
MOTOR_STOP,
MOTOR_RUN,
MOTOR_ERROR
} MotorState_t;
Ta có:
MOTOR_STOP = 0
MOTOR_RUN = 1
MOTOR_ERROR = 2
Sau đó tạo struct:
typedef struct
{
int speed;
MotorState_t state;
} Motor_t;
Tạo biến:
Motor_t motor1;
Gán:
motor1.speed = 100;
motor1.state = MOTOR_RUN;
3. Tại sao cách này cực kỳ hữu ích với STM32?
Hãy tưởng tượng bạn có hệ thống:
Motor 1
Motor 2
Fan
Pump
Valve
Sensor
Alarm
Mỗi thiết bị có nhiều thông tin.
Ví dụ:
typedef enum
{
DEVICE_OFF,
DEVICE_ON,
DEVICE_ERROR
} DeviceState_t;
typedef struct
{
DeviceState_t state;
int speed;
int errorCode;
} Device_t;
Bây giờ:
Device_t motor1;
Device_t motor2;
Device_t fan;
Device_t pump;
Bạn có thể quản lý cả hệ thống rất gọn.
4. Đây chính là kiểu tư duy dùng trong firmware thực tế
Ví dụ máy giặt:
typedef enum
{
WASH_IDLE,
WASH_FILL,
WASH_WASHING,
WASH_DRAIN,
WASH_SPIN,
WASH_FINISH,
WASH_ERROR
} WashState_t;
Sau đó:
typedef struct
{
WashState_t state;
int waterLevel;
int motorSpeed;
int temperature;
int doorClosed;
int errorCode;
} WashingMachine_t;
Tạo một biến:
WashingMachine_t machine;
Ví dụ:
machine.state = WASH_WASHING;
machine.waterLevel = 60;
machine.motorSpeed = 800;
machine.temperature = 35;
machine.doorClosed = 1;
machine.errorCode = 0;
Bây giờ machine chứa toàn bộ trạng thái của máy giặt.
5. Một điều rất quan trọng: ENUM thực chất là số
Ví dụ:
typedef enum
{
OFF = 0,
ON = 1,
ERROR = 2
} State_t;
Ta có:
State_t state;
state = ON;
Trong bộ nhớ, về bản chất vẫn là một giá trị số.
Có thể hình dung:
state
│
▼
┌───────────┐
│ 1 │
└───────────┘
Trong firmware, điều này rất quan trọng vì CPU cuối cùng vẫn xử lý bit và số.
6. STRUCT cũng chỉ là vùng RAM
Ví dụ:
typedef struct
{
uint8_t state;
uint16_t speed;
uint8_t error;
} Motor_t;
Khi khai báo:
Motor_t motor1;
RAM có thể hình dung:
motor1
┌──────────────┐
│ state │ 1 byte
├──────────────┤
│ speed │ 2 byte
├──────────────┤
│ error │ 1 byte
└──────────────┘
Đây là điểm nối rất quan trọng giữa:
C → RAM → STM32
7. STRUCT + POINTER
Đây là phần cực kỳ quan trọng.
Ta có:
typedef struct
{
int speed;
int state;
} Motor_t;
Motor_t motor1;
Có thể truyền địa chỉ của motor1 vào hàm:
void Motor_Update(Motor_t *motor)
{
motor->speed = 1000;
motor->state = 1;
}
Gọi:
Motor_Update(&motor1);
Chú ý:
motor->speed
tương đương với:
(*motor).speed
Nhưng cách viết:
motor->speed
dễ đọc hơn rất nhiều.
8. Đây là mẫu cực kỳ phổ biến trong STM32
Bạn sẽ gặp kiểu:
void Motor_Init(Motor_t *motor)
{
motor->speed = 0;
motor->state = MOTOR_STOP;
}
void Motor_Start(Motor_t *motor)
{
motor->state = MOTOR_RUN;
}
void Motor_Stop(Motor_t *motor)
{
motor->state = MOTOR_STOP;
motor->speed = 0;
}
Sau đó:
Motor_t motor1;
Motor_Init(&motor1);
Motor_Start(&motor1);
Đây chính là cách xây dựng module firmware.
9. Một ví dụ gần với Blue Pill của bạn
Giả sử chúng ta điều khiển LED PC13.
Ta định nghĩa:
typedef enum
{
LED_OFF,
LED_ON
} LedState_t;
Tạo cấu trúc:
typedef struct
{
LedState_t state;
} Led_t;
Tạo biến:
Led_t led;
Hàm:
void LED_On(Led_t *led)
{
led->state = LED_ON;
}
void LED_Off(Led_t *led)
{
led->state = LED_OFF;
}
Sau đó:
LED_On(&led);
Lúc này:
led.state = LED_ON
Nhưng lưu ý:
led.state mới chỉ là trạng thái trong RAM.
Nó chưa tự động làm chân PC13 sáng.
Ta cần một tầng điều khiển phần cứng:
void LED_Update(Led_t *led)
{
if (led->state == LED_ON)
{
// GPIO PC13 ON
}
else
{
// GPIO PC13 OFF
}
}
Đây chính là tư duy kiến trúc firmware:
APPLICATION
│
▼
led.state
│
▼
LED_Update()
│
▼
GPIO
│
▼
PC13
│
▼
LED
10. Một kiến trúc lớn hơn
Sau này khi lập trình STM32, bạn sẽ thường gặp dạng:
main.c
│
├── GPIO
├── UART
├── ADC
├── Timer
├── Motor
├── Sensor
└── Application
Ví dụ:
typedef struct
{
uint16_t adcValue;
float voltage;
} Sensor_t;
typedef enum
{
MOTOR_STOP,
MOTOR_RUN
} MotorState_t;
typedef struct
{
MotorState_t state;
uint16_t speed;
} Motor_t;
Sau đó chương trình có:
Sensor_t sensor;
Motor_t motor;
11. Bài tập Bài 14
Bài tập 1
Tạo enum:
typedef enum
{
LED_OFF,
LED_ON,
LED_ERROR
} LedState_t;
Sau đó tạo:
typedef struct
{
LedState_t state;
uint8_t brightness;
} Led_t;
Tạo:
Led_t led1;
Thiết lập:
led1.state = LED_ON;
led1.brightness = 80;
Hãy tự trả lời:
Trong RAM, led1 đang chứa những gì?
Bài tập 2
Viết hàm:
void LED_SetState(Led_t *led, LedState_t state)
để có thể gọi:
LED_SetState(&led1, LED_ON);
và:
LED_SetState(&led1, LED_OFF);
Bài tập 3 – quan trọng
Tạo:
typedef enum
{
MOTOR_STOP,
MOTOR_RUN,
MOTOR_ERROR
} MotorState_t;
và:
typedef struct
{
MotorState_t state;
uint16_t speed;
uint8_t errorCode;
} Motor_t;
Tạo:
Motor_t motor1;
Sau đó viết 3 hàm:
Motor_Start()
Motor_Stop()
Motor_Error()
sử dụng pointer + struct + enum.
🎯 Mục tiêu sau Bài 14
Bạn cần thực sự hiểu được chuỗi này:
enum
↓
typedef
↓
struct
↓
pointer
↓
function
↓
RAM
↓
STM32 peripheral
Nếu bạn nắm chắc chuỗi này, chúng ta có thể bước sang Bài 15: static, const, volatile – 3 từ khóa C cực kỳ quan trọng trong STM32.
Đặc biệt, volatile là thứ bạn bắt buộc phải hiểu khi làm GPIO interrupt, timer, UART, ADC, DMA và RTOS sau này.