Được chứ. Chúng ta tiếp tục đúng mạch học C cho STM32, sau:
- Bài 1: 10 kiểu biến và STM32 lưu chúng trong RAM như thế nào.
- Bài 2: các toán tử, phép tính và cách C xử lý dữ liệu.
- Bài 3: cấu trúc điều khiển chương trình (
if,else,switch,for,while…).
🟦 BÀI 4 – HÀM (FUNCTION) TRONG C
“Biến một đoạn chương trình thành một khối có thể gọi đi gọi lại”
Đây là bài cực kỳ quan trọng đối với STM32.
Khi lập trình STM32 thực tế, chương trình của bạn có thể có hàng nghìn, thậm chí hàng chục nghìn dòng. Nếu không biết sử dụng hàm, code sẽ rất khó quản lý.
1. Hàm là gì?
Ví dụ bạn viết:
int a = 10;
int b = 20;
int c = a + b;
Nếu phép cộng này xuất hiện 100 lần thì sao?
Bạn có thể tạo một hàm:
int Cong(int a, int b)
{
return a + b;
}
Sau đó chỉ cần:
int c = Cong(10, 20);
Kết quả:
c = 30
Có thể hình dung:
10
\
\
┌─────────────┐
│ Cong │
│ │
│ a + b │
└──────┬──────┘
│
▼
30
Hàm giống như một chiếc máy:
INPUT → FUNCTION → OUTPUT
2. Cấu trúc một hàm
Một hàm C cơ bản:
int Cong(int a, int b)
{
return a + b;
}
Có 4 phần:
int Cong (int a, int b)
│ │ │
│ │ └── tham số
│ └──────────────── tên hàm
└────────────────────────── kiểu trả về
Phần:
return a + b;
là giá trị mà hàm trả về.
3. return cực kỳ quan trọng
Ví dụ:
int Cong(int a, int b)
{
return a + b;
}
Khi:
int x = Cong(5, 3);
quá trình xảy ra:
Cong(5,3)
↓
a = 5
b = 3
↓
a + b
↓
8
↓
x = 8
4. Hàm không trả về giá trị
Đây là loại hàm bạn sẽ gặp rất nhiều trong STM32.
Dùng:
void
Ví dụ:
void Led_On(void)
{
// bật LED
}
void có nghĩa:
Hàm này không trả về giá trị.
Gọi:
Led_On();
Không thể viết:
int x = Led_On();
vì Led_On() không trả về dữ liệu.
5. Hàm có tham số
Ví dụ:
void Led_Set(int state)
{
if(state == 1)
{
// bật LED
}
else
{
// tắt LED
}
}
Gọi:
Led_Set(1);
→ LED ON.
Gọi:
Led_Set(0);
→ LED OFF.
Ở đây:
int state
là parameter – tham số.
Còn:
1
hoặc:
0
là argument – đối số truyền vào.
6. Nhiều tham số
Ví dụ điều khiển LED với độ sáng:
void LED_SetBrightness(int brightness)
{
// đặt độ sáng
}
Gọi:
LED_SetBrightness(50);
hoặc:
LED_SetBrightness(100);
7. Hàm trả về float
Không nhất thiết phải là int.
Ví dụ:
float Voltage_Calculate(int adc)
{
return adc * 3.3 / 4095.0;
}
Gọi:
float voltage;
voltage = Voltage_Calculate(2048);
Ta có điện áp khoảng:
1.65 V
Đây chính là kiểu bài toán bạn sẽ gặp khi làm ADC + biến trở trên STM32.
8. Hàm trả về char
Ví dụ:
char Get_Status(void)
{
return 'A';
}
Gọi:
char status;
status = Get_Status();
Kết quả:
status = 'A'
9. Hàm trả về uint8_t
Khi làm STM32, bạn sẽ thường gặp:
uint8_t
uint16_t
uint32_t
Ví dụ:
uint8_t Get_Button(void)
{
return 1;
}
Hoặc:
uint16_t Read_ADC(void)
{
return 2048;
}
Đây là phong cách rất phổ biến trong embedded C.
10. Hàm không có tham số
Ví dụ:
void System_Init(void)
{
// khởi tạo hệ thống
}
Gọi:
System_Init();
Bạn sẽ thấy STM32 HAL sử dụng kiểu này rất nhiều:
HAL_Init();
Đây chính là một hàm.
11. Hàm có tham số nhưng không trả về
Ví dụ:
void LED_Write(uint8_t state)
{
// xử lý state
}
Gọi:
LED_Write(1);
Cấu trúc:
void
↓
không trả về
LED_Write
↓
tên hàm
(uint8_t state)
↓
tham số
12. Hàm vừa nhận dữ liệu vừa trả dữ liệu
Đây là loại rất quan trọng.
Ví dụ:
uint16_t ADC_To_PWM(uint16_t adc)
{
return adc / 4;
}
STM32 ADC 12-bit:
0 → 4095
PWM 8-bit:
0 → 255
Ta có thể chuyển:
ADC
4095
↓
ADC_To_PWM()
↓
255
Ví dụ:
uint16_t adc;
uint8_t pwm;
adc = 3000;
pwm = ADC_To_PWM(adc);
13. Hàm trong STM32 thực tế
Ví dụ bạn có biến trở 50 kΩ.
ADC đọc:
uint16_t adc_value;
Bạn muốn chuyển ADC thành 20 mức độ sáng.
Có thể viết:
uint8_t ADC_To_Level(uint16_t adc)
{
return (adc * 20) / 4096;
}
Sau đó:
uint8_t level;
level = ADC_To_Level(adc_value);
Chương trình chính trở nên rất dễ đọc:
Đọc ADC
↓
ADC_To_Level()
↓
độ sáng
↓
điều khiển LED
Đây chính là tư duy lập trình embedded chuyên nghiệp.
14. Một điều cực kỳ quan trọng: phạm vi biến
Xem:
void Test(void)
{
int x = 10;
}
int main(void)
{
Test();
x = 20; // SAI
}
Tại sao?
Vì x được tạo bên trong hàm Test().
Nó là local variable – biến cục bộ.
Test()
┌──────────────────┐
│ int x = 10; │
│ │
│ x chỉ tồn tại │
│ trong hàm này │
└──────────────────┘
Ra khỏi hàm:
x
↓
không còn truy cập được
15. Biến toàn cục
Nếu viết:
int x = 10;
void Test(void)
{
x = 20;
}
int main(void)
{
Test();
}
thì x là global variable.
Các hàm có thể truy cập nó.
x
│
┌──────┴──────┐
│ │
Test() main()
Tuy nhiên khi lập trình STM32, không nên lạm dụng biến global.
Sau này chúng ta sẽ học kỹ hơn về:
static
const
volatile
đặc biệt là volatile, rất quan trọng trong embedded.
16. Prototype – khai báo hàm
Đây là một thứ bạn cần bắt đầu làm quen.
Ví dụ:
int Cong(int a, int b);
Đây gọi là function prototype.
Sau đó:
int main(void)
{
int x;
x = Cong(10, 20);
}
và phía dưới:
int Cong(int a, int b)
{
return a + b;
}
Tại sao cần prototype?
Vì compiler cần biết trước:
Cong()
↓
nhận bao nhiêu tham số?
kiểu gì?
trả về kiểu gì?
17. Đây chính là kiểu tổ chức code STM32
Sau này bạn sẽ gặp:
main.c
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
while(1)
{
Read_ADC();
Control_LED();
}
}
Trong đó:
Read_ADC();
là một hàm.
Control_LED();
cũng là một hàm.
Bạn không nhét toàn bộ chương trình vào:
while(1)
mà chia thành các hàm nhỏ.
18. Tư duy rất quan trọng
Một chương trình STM32 tốt thường có dạng:
main()
│
├── Init()
│
├── Read_Input()
│
├── Process()
│
└── Output()
Ví dụ dự án biến trở + step motor của bạn:
main()
│
├── ADC_Read()
│
├── ADC_To_Speed()
│
├── Stepper_SetSpeed()
│
└── Stepper_Run()
Thay vì viết một đống code:
while(1)
{
// 100 dòng code
// 200 dòng code
// 300 dòng code
}
19. Bài tập thực hành Bài 4
Bạn hãy tự viết chương trình C này:
Bài 1
Viết hàm:
int Cong(int a, int b)
để cộng hai số.
Sau đó:
int x = Cong(15, 25);
Hỏi x bằng bao nhiêu?
Bài 2
Viết:
int Max(int a, int b)
để tìm số lớn hơn.
Ví dụ:
int x = Max(15, 25);
Kết quả:
x = 25
Bài 3
Viết:
int ADC_To_Level(uint16_t adc)
chuyển ADC 0–4095 thành 20 mức.
Ví dụ:
ADC = 0 → Level 0
ADC = 2048 → khoảng Level 10
ADC = 4095 → Level 19 hoặc 20 tùy cách thiết kế
Đây là bài rất đáng làm, vì nó nối trực tiếp kiến thức C với dự án STM32 biến trở mà bạn vừa làm.
🧠 Sau Bài 4, bạn cần nắm được
FUNCTION
│
├── kiểu trả về
│
├── tên hàm
│
├── tham số
│
├── thân hàm
│
├── return
│
├── void
│
├── prototype
│
└── local/global variable
Và quan trọng nhất:
Đừng học thuộc lòng hàm. Hãy hiểu cách một hàm nhận dữ liệu → xử lý → trả kết quả.
Đó là nền tảng để sang phần pointer, array, struct và register — những thứ thực sự bắt đầu làm C trên STM32 trở nên thú vị.
Bài 5 tôi đề xuất sẽ là: ARRAY + STRING – cách STM32 lưu một dãy dữ liệu trong RAM. Đây sẽ là bước nối rất tự nhiên từ biến đơn → hàm → mảng → sau đó tiến tới pointer, phần cực kỳ quan trọng của embedded C.