BÀI 8 – HÀM TRONG C: CHIA CHƯƠNG TRÌNH THÀNH NHỮNG “KHỐI NÃO” 🧠
Sau Bài 7 – struct, chúng ta chuyển sang một kiến thức cực kỳ quan trọng nếu mục tiêu của bạn là lập trình STM32 bằng C:
Hàm (function).
Trong chương trình STM32 thực tế, bạn sẽ không viết tất cả mọi thứ trong main(). Bạn sẽ chia chương trình thành rất nhiều hàm:
void LED_On(void);
void LED_Off(void);
void Motor_SetSpeed(uint8_t speed);
uint16_t ADC_Read(void);
void LCD_ShowNumber(uint16_t value);
Có thể hiểu đơn giản:
CHƯƠNG TRÌNH
│
├── khởi tạo()
├── đọc_ADC()
├── xử_lý_nút_nhấn()
├── điều_khiển_motor()
├── cập_nhật_LCD()
└── main()
1. Hàm là gì?
Ví dụ bạn muốn bật LED.
Thay vì viết:
GPIOA->BSRR = GPIO_PIN_8;
ở 20 chỗ khác nhau, bạn tạo một hàm:
void LED_On(void)
{
GPIOA->BSRR = GPIO_PIN_8;
}
Sau đó chỉ cần:
LED_On();
Đây chính là tư duy rất quan trọng trong lập trình nhúng:
Một chức năng → một hàm.
2. Cấu trúc cơ bản của một hàm
Một hàm C có dạng:
kiểu_trả_về tên_hàm(tham_số)
{
// câu lệnh
}
Ví dụ:
int Add(int a, int b)
{
return a + b;
}
Ở đây:
int → kiểu dữ liệu trả về
Add → tên hàm
int a → tham số 1
int b → tham số 2
return → trả kết quả
Sử dụng:
int result;
result = Add(10, 20);
Sau đó:
result = 30
3. Hàm không trả về giá trị – void
Đây là loại hàm bạn sẽ sử dụng rất nhiều trên STM32.
Ví dụ:
void LED_On(void)
{
GPIOA->BSRR = GPIO_PIN_8;
}
void trước tên hàm có nghĩa:
Hàm này không trả về kết quả.
Gọi:
LED_On();
Một ví dụ đơn giản hơn:
void SayHello(void)
{
printf("Hello STM32\n");
}
Gọi:
SayHello();
4. Hàm có tham số
Đây là phần rất quan trọng.
Ví dụ:
void LED_Set(int state)
{
if(state == 1)
{
LED_On();
}
else
{
LED_Off();
}
}
Ta có thể gọi:
LED_Set(1);
hoặc:
LED_Set(0);
Ở đây:
int state
là tham số.
Còn:
1
và:
0
là đối số truyền vào hàm.
5. Nhiều tham số
Ví dụ:
int Add(int a, int b)
{
return a + b;
}
Gọi:
int x;
x = Add(5, 7);
Ta có:
a = 5
b = 7
5 + 7 = 12
x = 12
Một ví dụ rất giống những gì bạn sẽ gặp trong điều khiển motor:
void Motor_Set(uint8_t direction, uint8_t speed)
{
// xử lý direction
// xử lý speed
}
Gọi:
Motor_Set(1, 80);
Có thể hiểu:
direction = 1
speed = 80
6. Hàm trả về giá trị
Ví dụ:
int Square(int x)
{
return x * x;
}
Gọi:
int result;
result = Square(5);
Kết quả:
result = 25
Một ví dụ rất gần với STM32:
uint16_t ADC_GetValue(void)
{
uint16_t value;
// đọc ADC
return value;
}
Sau đó:
uint16_t adc;
adc = ADC_GetValue();
Đây là kiểu thiết kế cực kỳ phổ biến:
ADC_GetValue()
↓
trả về số
↓
adc
7. return cực kỳ quan trọng
Ví dụ:
int Max(int a, int b)
{
if(a > b)
{
return a;
}
else
{
return b;
}
}
Gọi:
int x;
x = Max(10, 20);
Kết quả:
x = 20
Khi chương trình chạy đến:
return 20;
hàm kết thúc và trả 20 về nơi gọi hàm.
8. Hàm main() cũng chính là một hàm
Bạn đã nhìn thấy:
int main(void)
{
}
Thực ra:
int
↓
kiểu trả về
main
↓
tên hàm
(void)
↓
không có tham số
{ }
↓
thân hàm
Vì vậy main() không phải là thứ gì “bí mật”.
Nó cũng là một hàm đặc biệt.
9. Prototype – khai báo hàm trước
Đây là phần rất quan trọng khi viết chương trình STM32 lớn.
Ví dụ:
#include <stdio.h>
int Add(int a, int b);
int main(void)
{
int result;
result = Add(10, 20);
printf("%d\n", result);
return 0;
}
int Add(int a, int b)
{
return a + b;
}
Dòng:
int Add(int a, int b);
gọi là function prototype.
Nó nói cho compiler biết:
“Phía dưới sẽ có một hàm tên
Add, nhận haiintvà trả về mộtint.”
10. Tại sao STM32 rất cần prototype?
Khi chương trình nhỏ:
main.c
thì có thể chưa thấy vấn đề.
Nhưng chương trình STM32 thực tế có thể có:
main.c
gpio.c
gpio.h
adc.c
adc.h
motor.c
motor.h
lcd.c
lcd.h
uart.c
uart.h
Ví dụ:
motor.h
#ifndef MOTOR_H
#define MOTOR_H
void Motor_Init(void);
void Motor_Start(void);
void Motor_Stop(void);
void Motor_SetSpeed(uint8_t speed);
#endif
motor.c
#include "motor.h"
void Motor_Init(void)
{
// khởi tạo motor
}
void Motor_Start(void)
{
// chạy motor
}
void Motor_Stop(void)
{
// dừng motor
}
void Motor_SetSpeed(uint8_t speed)
{
// điều chỉnh tốc độ
}
main.c
#include "motor.h"
int main(void)
{
Motor_Init();
Motor_SetSpeed(50);
Motor_Start();
while(1)
{
}
}
Đây chính là cách chương trình STM32 được tổ chức.
11. Hàm + con trỏ
Đây là nơi kiến thức Bài 5 – con trỏ bắt đầu kết nối với Bài 8.
Ví dụ:
void SetValue(int *p)
{
*p = 100;
}
Trong main():
int x = 10;
SetValue(&x);
Sau đó:
x = 100
Tại sao?
Ta truyền:
&x
tức là địa chỉ của x.
Hàm nhận:
int *p
Sau đó:
*p = 100;
thay đổi giá trị nằm tại địa chỉ đó.
12. Đây là kiến thức cực kỳ quan trọng đối với STM32
Ví dụ bạn có:
uint16_t adc;
Một hàm có thể trả về ADC:
uint16_t ADC_Read(void)
{
return adc_value;
}
Nhưng đôi khi bạn muốn một hàm ghi kết quả vào biến bên ngoài:
void ADC_Read(uint16_t *value)
{
*value = 1234;
}
Sử dụng:
uint16_t adc;
ADC_Read(&adc);
Sau đó:
adc = 1234
Đây chính là sự kết hợp:
HÀM
+
CON TRỎ
=
truyền dữ liệu giữa các module
13. Hàm nhận struct
Bài 7 vừa học struct, nên bây giờ ta kết hợp hai kiến thức.
Ví dụ:
struct Motor
{
uint8_t direction;
uint8_t speed;
};
Ta có thể tạo hàm:
void Motor_Set(struct Motor *motor)
{
printf("Direction = %d\n", motor->direction);
printf("Speed = %d\n", motor->speed);
}
Sử dụng:
struct Motor m1;
m1.direction = 1;
m1.speed = 80;
Motor_Set(&m1);
Bạn thấy không?
Chúng ta đang ghép:
Bài 5
Con trỏ
↓
Bài 7
struct
↓
Bài 8
function
Đây chính là nền móng của C dùng trong embedded.
14. Một ví dụ STM32 thực tế
Giả sử Blue Pill của bạn có:
PA8 → LED
ADC → biến trở 50K
Ta có thể tổ chức chương trình như sau:
void LED_Set(uint8_t state)
{
if(state)
{
// bật LED
}
else
{
// tắt LED
}
}
Đọc ADC:
uint16_t ADC_Read(void)
{
// đọc ADC
return 0;
}
Chuyển ADC thành tốc độ:
uint8_t ADC_To_Speed(uint16_t adc)
{
return (adc * 100) / 4095;
}
Sau đó:
int main(void)
{
uint16_t adc;
uint8_t speed;
while(1)
{
adc = ADC_Read();
speed = ADC_To_Speed(adc);
if(speed > 50)
{
LED_Set(1);
}
else
{
LED_Set(0);
}
}
}
Đây đã bắt đầu giống một chương trình embedded thực tế.
15. Một lỗi rất hay gặp
Bạn viết:
int Add(int a, int b)
{
a + b;
}
Sai.
Vì bạn chưa trả kết quả.
Phải là:
int Add(int a, int b)
{
return a + b;
}
16. Một lỗi khác
Bạn viết:
void LED_On(void)
{
return 10;
}
Sai vì:
void
không được trả về giá trị.
Đúng:
void LED_On(void)
{
// bật LED
}
17. Bài tập Bài 8
Bạn hãy tự viết, chưa cần STM32.
Bài 1
Viết hàm:
int Add(int a, int b)
trả về tổng hai số.
Bài 2
Viết:
int Max(int a, int b)
trả về số lớn hơn.
Ví dụ:
Max(10, 20)
→ 20
Bài 3
Viết:
int Square(int x)
trả về bình phương của x.
Bài 4
Viết:
void LED_Set(int state)
Nếu:
state = 1 → ON
state = 0 → OFF
Chưa cần điều khiển GPIO thật, chỉ cần printf():
LED ON
LED OFF
Bài 5 – quan trọng nhất
Viết:
void ChangeValue(int *p)
Hàm này biến giá trị bên ngoài thành 500.
Ví dụ:
int x = 10;
ChangeValue(&x);
Sau đó:
x = 500
🎯 Sau Bài 8 bạn cần nắm chắc 6 thứ
1. Hàm là gì?
2. void là gì?
3. Tham số là gì?
4. return là gì?
5. Prototype là gì?
6. Truyền con trỏ vào hàm để thay đổi biến bên ngoài
Và đặc biệt hãy nhớ chuỗi kiến thức chúng ta đang đi:
Bài 1 → Kiểu dữ liệu / RAM
Bài 2 → Biến / toán tử
Bài 3 → Cấu trúc điều khiển
Bài 4 → Mảng
Bài 5 → Con trỏ
Bài 6 → Mảng + con trỏ
Bài 7 → struct
Bài 8 → HÀM
↓
Bài tiếp theo → kết hợp các kiến thức này
↓
STM32 thực tế
Điểm quan trọng: từ Bài 8 trở đi, bạn sẽ bắt đầu thấy C của STM32 khác hẳn kiểu C “làm bài tập”. Ta sẽ tập xây dựng các module như ADC, GPIO, Motor, LCD, UART bằng chính hàm + con trỏ + struct.