BÀI 41 – PWM TỪ TIMER TRÊN STM32F103
Từ PSC + ARR + CCR đến PWM thực sự hoạt động như thế nào?
Đây là một bài rất quan trọng trong quá trình học STM32 của bạn.
Sau Bài 39 và 40, chúng ta đã biết:
Bài 39 – Input Capture
Tín hiệu ngoài → Timer → CCR
Bài 40 – Output Compare
CNT → so sánh CCR → tạo Event
Bây giờ chúng ta ghép các ý tưởng đó lại:
TIMER
│
┌──────────┼──────────┐
│ │ │
PSC ARR CCR
│ │ │
└──────────┼──────────┘
↓
PWM
↓
GPIO
Và đây chính là nền tảng để sau này điều khiển:
- LED sáng tối;
- động cơ DC;
- quạt;
- servo;
- robot;
- nguồn switching;
- inverter;
- BLDC;
- các cơ cấu chấp hành.
1. PWM là gì?
PWM = Pulse Width Modulation
Tiếng Việt:
Điều chế độ rộng xung.
Một tín hiệu PWM có dạng:
┌──────┐ ┌──────┐
│ │ │ │
────────┘ └────────┘ └──────
←Ton→
←────── T ──────→
Có hai đại lượng quan trọng:
T = chu kỳ
Ton = thời gian HIGH
Và:
[
Duty=\frac{T_{ON}}{T}\times100%
]
2. Ví dụ Duty Cycle
Duty 0%
────────────────────────
Luôn LOW.
Duty 25%
┌───┐ ┌───┐
──────┘ └───────────┘ └────
HIGH khoảng 25% thời gian.
Duty 50%
┌─────┐ ┌─────┐
─────┘ └───────┘ └────
HIGH 50%.
Duty 75%
┌──────────┐ ┌──────────┐
─────┘ └───┘ └──
HIGH 75%.
Duty 100%
──────────────────────── HIGH
3. PWM không phải là thay đổi điện áp
Đây là một hiểu nhầm rất phổ biến.
Nếu MCU chạy:
3.3 V
thì PWM vẫn có mức:
LOW = 0 V
HIGH = 3.3 V
Nó không tạo ra 1.65 V vật lý khi Duty = 50%.
Thay vào đó:
0 V
3.3 V
0 V
3.3 V
...
rất nhanh.
4. Vậy tại sao motor/LED lại “thấy” mức trung bình?
Bởi vì tải và mạch điện có đáp ứng theo thời gian.
Ví dụ LED:
PWM 3.3V
Duty 50%
↓
Mắt người
↓
thấy sáng khoảng 50%
Motor:
PWM
↓
MOSFET
↓
Motor
↓
quán tính cơ học
↓
tốc độ trung bình
5. PWM trong STM32 không cần CPU bật/tắt GPIO
Đây là điều quan trọng nhất.
Cách rất tệ:
while(1)
{
GPIO_HIGH();
delay_us(500);
GPIO_LOW();
delay_us(500);
}
CPU phải liên tục làm việc.
STM32 Timer có thể làm:
TIMER
│
┌─────┴─────┐
│ │
ARR CCR
│ │
└─────┬─────┘
↓
PWM
↓
GPIO
CPU chỉ cần cấu hình một lần.
6. Ba thanh ghi quan trọng
Để tạo PWM, hãy nhớ:
PSC
ARR
CCR
Đây chính là “bộ ba thần thánh” của PWM.
7. PSC quyết định tốc độ đếm
Giả sử:
Timer Clock = 72 MHz
Muốn:
1 tick = 1 µs
ta dùng:
TIM2->PSC = 71;
vì:
[
72MHz/(71+1)=1MHz
]
Kết quả:
1 tick = 1 µs
8. ARR quyết định chu kỳ PWM
Giả sử:
1 tick = 1 µs
và:
TIM2->ARR = 999;
Timer đếm:
0
1
2
...
998
999
→ reset
Có:
1000 ticks
nên:
[
T=1000\mu s=1ms
]
Do đó:
[
f=1kHz
]
9. Công thức tần số PWM
Công thức tổng quát:
[
f_{PWM}=
\frac{f_{TIM}}
{(PSC+1)(ARR+1)}
]
Ví dụ:
fTIM = 72 MHz
PSC = 71
ARR = 999
ta có:
[
f_{PWM}
\frac{72,000,000}
{72\times1000}
]
[
=1000Hz
]
→ 1 kHz
10. CCR quyết định Duty Cycle
Đây là phần cực kỳ quan trọng.
Giả sử:
ARR = 999
và:
CCR1 = 500
thì Duty khoảng:
[
50%
]
Hình dung:
CNT
0 500 999
│──────────│───────────────│
███████████
PWM HIGH trong khoảng đầu chu kỳ.
11. Công thức Duty Cycle
Trong PWM mode cơ bản:
[
Duty \approx
\frac{CCR}{ARR+1}\times100%
]
Ví dụ:
ARR = 999
CCR = 250
thì:
[
Duty=
\frac{250}{1000}\times100
=25%
]
12. Bảng rất quan trọng
Giả sử:
ARR = 999
| CCR1 | Duty gần đúng |
|---|---|
| 0 | 0% |
| 100 | 10% |
| 250 | 25% |
| 500 | 50% |
| 750 | 75% |
| 900 | 90% |
| 999 | ~100% |
Đây là cách bạn điều khiển độ rộng xung.
13. PWM Mode 1
STM32F103 có nhiều chế độ PWM.
Hai chế độ thường gặp:
PWM Mode 1
PWM Mode 2
Chúng ta tập trung trước vào:
PWM Mode 1
Trong PWM Mode 1:
CNT < CCR
↓
Output HIGH
và khi:
CNT >= CCR
output chuyển trạng thái theo quy tắc PWM.
Hình dung:
CNT:
0 CCR ARR
│────────│────────────────│
PWM:
█████████
└────────────────
14. OC1M chính là nơi chọn PWM
Trong TIM2->CCMR1, Channel 1 có:
OC1M
PWM Mode 1 thường được cấu hình:
OC1M = 110
tức:
PWM Mode 1
Đây là điểm bạn cần phân biệt với Bài 40:
OC1M = 011
→ Toggle
OC1M = 110
→ PWM Mode 1
15. PWM không phải là một loại GPIO riêng
GPIO vẫn là chân GPIO.
Nhưng thay vì CPU điều khiển:
GPIOA->ODR
Timer điều khiển chân thông qua:
Alternate Function
Ví dụ:
TIM2_CH1
↓
PA0
16. Vì vậy PA0 phải được cấu hình AF Output
Không phải:
GPIO Output thường
mà là:
Alternate Function Push-Pull
Ví dụ:
GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |= (0xB << 0);
17. Cấu hình PWM hoàn chỉnh
Chúng ta sẽ dùng:
STM32F103
TIM2_CH1
PA0
Mục tiêu:
PWM = 1 kHz
Duty = 50%
18. Bước 1 – bật clock GPIO
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
19. Bước 2 – cấu hình PA0
GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |= (0xB << 0);
PA0:
Alternate Function Push-Pull
50 MHz
20. Bước 3 – bật clock TIM2
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
21. Bước 4 – chọn Prescaler
TIM2->PSC = 71;
Nếu:
TIM2CLK = 72 MHz
thì:
Timer tick = 1 MHz
hay:
1 tick = 1 µs
22. Bước 5 – chọn ARR
Muốn:
PWM = 1 kHz
thì:
T = 1 ms
với:
1 tick = 1 µs
cần:
TIM2->ARR = 999;
23. Bước 6 – chọn CCR
Muốn:
Duty = 50%
thì:
TIM2->CCR1 = 500;
24. Bước 7 – chọn Output Compare
Channel 1 phải là output:
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;
CC1S = 00.
25. Bước 8 – chọn PWM Mode 1
TIM2->CCMR1 &= ~TIM_CCMR1_OC1M;
TIM2->CCMR1 |=
(6 << TIM_CCMR1_OC1M_Pos);
6 nhị phân:
110
→ PWM Mode 1.
26. Bước 9 – Enable preload
Nên bật:
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;
OC1PE:
Output Compare 1 Preload Enable.
Điều này rất quan trọng khi thay đổi Duty trong lúc Timer đang chạy.
27. Bước 10 – Enable Channel
TIM2->CCER |= TIM_CCER_CC1E;
28. Bước 11 – Auto Reload Preload
TIM2->CR1 |= TIM_CR1_ARPE;
29. Bước 12 – tạo Update Event
Sau khi cấu hình:
TIM2->EGR |= TIM_EGR_UG;
Điều này buộc Timer cập nhật các giá trị preload.
30. Bước 13 – Start Timer
TIM2->CR1 |= TIM_CR1_CEN;
Vậy là PWM bắt đầu chạy.
31. Firmware PWM đầu tiên
#include "stm32f10x.h"
void GPIO_PWM_Init(void)
{
/* Enable GPIOA clock */
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/*
PA0 = TIM2_CH1
Alternate Function Push-Pull
50 MHz
*/
GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |= (0xB << 0);
}
void TIM2_PWM_Init(void)
{
/* Enable TIM2 clock */
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
/*
TIM2CLK = 72 MHz
72 MHz / 72
= 1 MHz
1 tick = 1 us
*/
TIM2->PSC = 71;
/*
PWM frequency = 1 kHz
1000 us period
*/
TIM2->ARR = 999;
/*
50% duty
*/
TIM2->CCR1 = 500;
/*
CH1 = Output
*/
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;
/*
PWM Mode 1
OC1M = 110
*/
TIM2->CCMR1 &= ~TIM_CCMR1_OC1M;
TIM2->CCMR1 |=
(6 << TIM_CCMR1_OC1M_Pos);
/*
Enable preload
*/
TIM2->CCMR1 |= TIM_CCMR1_OC1PE;
/*
Enable CH1
*/
TIM2->CCER |= TIM_CCER_CC1E;
/*
Auto reload preload
*/
TIM2->CR1 |= TIM_CR1_ARPE;
/*
Generate update event
*/
TIM2->EGR |= TIM_EGR_UG;
/*
Start Timer
*/
TIM2->CR1 |= TIM_CR1_CEN;
}
int main(void)
{
GPIO_PWM_Init();
TIM2_PWM_Init();
while (1)
{
}
}
32. Nếu đo PA0 bằng oscilloscope
Bạn sẽ thấy:
┌──────┐ ┌──────┐
│ │ │ │
──────┘ └──────┘ └──────
←500µs→
←──────── 1ms ────────→
Điện áp:
LOW ≈ 0 V
HIGH ≈ 3.3 V
Tần số:
1 kHz
Duty:
50%
33. Thay đổi Duty
Đây mới là điều thú vị.
Chỉ cần:
TIM2->CCR1 = 250;
→ khoảng:
25%
Hoặc:
TIM2->CCR1 = 750;
→ khoảng:
75%
Tần số vẫn:
1 kHz
Chỉ Duty thay đổi.
34. Đây là nguyên tắc cực kỳ quan trọng
PSC + ARR
↓
TẦN SỐ
CCR
↓
DUTY
Hãy nhớ câu này.
35. Ví dụ điều khiển LED
Nếu nối:
PA0 → điện trở → LED → GND
thì:
TIM2->CCR1 = 100;
LED khá tối.
TIM2->CCR1 = 500;
LED sáng khoảng 50%.
TIM2->CCR1 = 900;
LED rất sáng.
36. Nhưng PWM điều khiển LED có một vấn đề
Nếu tần số quá thấp:
10 Hz
mắt có thể nhận thấy:
sáng → tối → sáng → tối
Vì vậy LED thường dùng PWM đủ cao.
Ví dụ:
500 Hz
1 kHz
2 kHz
...
tùy ứng dụng.
37. PWM điều khiển motor DC
Ví dụ:
STM32
│
PA0 PWM
│
▼
MOSFET
│
▼
Motor
Không được nối motor trực tiếp vào PA0.
PA0 chỉ tạo tín hiệu điều khiển:
3.3 V logic
MOSFET/driver mới cấp dòng cho motor.
38. Duty và tốc độ motor
Một cách đơn giản:
Duty tăng
↓
điện áp/công suất trung bình tăng
↓
motor có xu hướng chạy nhanh hơn
Ví dụ:
20% → chậm
40% → nhanh hơn
60% → nhanh
80% → rất nhanh
Nhưng thực tế tốc độ không nhất thiết tỷ lệ tuyến tính tuyệt đối với Duty.
39. PWM và servo
Đây chính là cầu nối sang Bài 42.
Servo RC thường dùng tín hiệu:
~50 Hz
Chu kỳ:
20 ms
và độ rộng xung:
~1 ms → vị trí này
~1.5 ms → vị trí giữa
~2 ms → vị trí kia
Hãy nhìn:
20 ms
┌──────┐
│ │
└──────┴────────────────────
1 ms
Đây là PWM, nhưng cách sử dụng khác PWM LED/motor.
40. Ví dụ Servo
Nếu:
Timer tick = 1 µs
muốn:
T = 20 ms
thì:
ARR = 19999
Bởi vì:
20000 × 1 µs
= 20 ms
Sau đó:
1 ms
CCR1 = 1000;
1.5 ms
CCR1 = 1500;
2 ms
CCR1 = 2000;
Đây sẽ là nội dung chính của Bài 42.
41. Một điểm rất quan trọng: PWM không chỉ có một loại
Chúng ta còn có:
Edge-aligned PWM
Center-aligned PWM
Bài hôm nay tập trung:
Edge-aligned PWM
Đây là loại dễ hiểu nhất.
42. Edge-aligned PWM
Timer:
0 → ARR
PWM được tạo dựa trên:
CNT < CCR
Dạng:
┌───────┐
│ │
└───────┴──────────────────
0 CCR ARR
43. Center-aligned PWM
Timer có thể đếm:
0 → ARR → 0
hoặc các biến thể:
Up
Down
Up
Down
PWM đối xứng quanh tâm.
Nó đặc biệt quan trọng trong:
- điều khiển motor;
- inverter;
- power electronics;
- giảm một số thành phần hài.
Sau này khi học STM32 nâng cao, chúng ta sẽ quay lại phần này.
44. PWM Resolution
Giả sử:
ARR = 999
ta có khoảng:
1000 mức
Duty có thể điều chỉnh với bước khoảng:
[
1/1000=0.1%
]
Nếu:
ARR = 99
chỉ có:
100 mức
→ bước:
1%
45. Đây là bài toán Resolution vs Frequency
Muốn:
Tần số cao
thường phải giảm:
ARR
→ ít mức Duty hơn.
Muốn:
Resolution cao
cần:
ARR lớn
→ tần số PWM có thể thấp hơn.
Đây là trade-off rất quan trọng.
46. Ví dụ
Timer:
72 MHz
Muốn:
20 kHz
và:
PSC = 71
thì Timer tick:
1 MHz
Chu kỳ:
50 µs
Do đó:
ARR ≈ 49
Chỉ có khoảng:
50 mức Duty
→ resolution khoảng 2%.
Nếu muốn resolution cao hơn, ta phải chọn PSC nhỏ hơn để Timer chạy nhanh hơn.
47. Ví dụ tốt hơn
Không nhất thiết phải dùng:
PSC = 71
Ta có thể chọn:
PSC = 0
Timer:
72 MHz
Muốn 20 kHz:
[
ARR+1 =
\frac{72MHz}{20kHz}
=3600
]
nên:
ARR = 3599
Bây giờ có:
3600 mức
→ resolution tốt hơn rất nhiều.
Đây là cách kỹ sư Timer lựa chọn PSC/ARR.
48. Vì vậy đừng học thuộc một bộ PSC/ARR
Không nên nghĩ:
PWM = PSC 71
ARR 999
Đó chỉ là một ví dụ.
Bạn cần hiểu công thức:
[
f_{PWM}
\frac{f_{TIM}}
{(PSC+1)(ARR+1)}
]
Sau đó tự chọn:
PSC
ARR
phù hợp với:
frequency
resolution
49. Thay đổi Duty trong khi Timer đang chạy
Ví dụ:
TIM2->CCR1 = 200;
rồi:
TIM2->CCR1 = 700;
Bạn có thể thay đổi Duty ngay trong while(1).
Nhưng tại sao chúng ta bật:
OC1PE
?
Vì nó giúp giá trị CCR mới được cập nhật ở thời điểm Update Event thích hợp, tránh một số trường hợp xung bị thay đổi giữa chu kỳ.
50. Đây là lý do Preload rất quan trọng
Không preload:
CPU ghi CCR
↓
Timer đang chạy
↓
có thể thay đổi giữa chu kỳ
Có preload:
CPU ghi CCR
↓
Shadow/Preload register
↓
Update Event
↓
CCR hoạt động
PWM ổn định hơn.
51. PWM nhiều Channel
TIM2 có:
CH1
CH2
CH3
CH4
Bạn có thể tạo:
CH1 → PWM 20%
CH2 → PWM 40%
CH3 → PWM 60%
CH4 → PWM 80%
Tất cả sử dụng cùng:
Timer frequency
nhưng:
CCR1
CCR2
CCR3
CCR4
khác nhau.
52. Đây chính là nền tảng điều khiển robot
Ví dụ robot hai bánh:
TIM2_CH1 → Motor Left
TIM2_CH2 → Motor Right
Bạn có thể:
CCR1 = 700
CCR2 = 500
→ hai motor có mức PWM khác nhau.
Khi rẽ:
Motor trái = 40%
Motor phải = 70%
→ robot đổi hướng.
Đây là cách chúng ta sau này có thể chuyển firmware robot của bạn từ kiểu Arduino sang STM32.
53. PWM + Direction
Motor DC thường cần ít nhất:
PWM
DIR
Ví dụ:
STM32
┌───────┐
PWM ───►│ │
DIR ───►│ Motor │───► Driver
└───────┘
PWM quyết định:
công suất/tốc độ
DIR quyết định:
chiều quay
54. PWM không tự quyết định chiều motor
Đây cũng là điểm cần nhớ:
PWM ≠ Direction
PWM chỉ là tín hiệu điều chế.
Chiều quay phụ thuộc:
- H-bridge;
- DIR pins;
- phase sequence đối với motor khác loại.
55. PWM và 28BYJ-48 của bạn
Đối với:
28BYJ-48
cần phân biệt:
PWM
với:
Step sequence
28BYJ-48 thường được điều khiển bằng trình tự kích các cuộn dây thông qua driver ULN2003.
Không phải cứ đưa PWM vào một chân là điều khiển được vị trí như servo.
Đây là một khác biệt rất quan trọng giữa:
DC Motor
Servo
Stepper
56. Ba loại motor – ba cách điều khiển
DC Motor
PWM + Direction
Servo
PWM pulse width
Stepper
Step sequence / STEP-DIR
hoặc:
4-phase sequence
Đây là kiến thức rất đáng nhớ.
57. Bài tập thực hành trên Blue Pill
Bạn có thể làm ngay:
STM32F103
│
└── PA0
│
├── Oscilloscope
│
└── LED + resistor
Firmware:
PWM = 1 kHz
Duty = 50%
Sau đó lần lượt:
CCR1 = 100
CCR1 = 250
CCR1 = 500
CCR1 = 750
CCR1 = 900
Quan sát LED và oscilloscope.
58. Bài tập nâng cao
Hãy thử tạo:
PWM = 2 kHz
với:
TIM2CLK = 72 MHz
PSC = 71
Timer tick:
1 MHz
Chu kỳ:
500 µs
Do đó:
ARR = 499
Nếu muốn:
Duty = 30%
thì:
CCR1 ≈ 150
59. Bài tập tính ngược
Cho:
TIM2CLK = 72 MHz
PSC = 71
ARR = 999
CCR1 = 250
Hãy tự tính:
Timer tick
1 µs
PWM frequency
1 kHz
Duty
25%
Nếu bạn tính được ba thứ này thì phần cơ bản của PWM đã rất chắc.
60. Công thức cần thuộc lòng
Timer frequency:
[
f_{CNT}=
\frac{f_{TIM}}{PSC+1}
]
PWM frequency:
[
f_{PWM}=
\frac{f_{TIM}}
{(PSC+1)(ARR+1)}
]
PWM period:
[
T_{PWM}=
\frac{(PSC+1)(ARR+1)}
{f_{TIM}}
]
Duty:
[
Duty\approx
\frac{CCR}{ARR+1}\times100%
]
61. Sơ đồ tư duy của toàn bộ Bài 41
TIMER
│
Timer Clock
│
PSC
│
▼
CNT frequency
│
▼
CNT
│
┌───────┴───────┐
│ │
ARR CCR
│ │
│ │
Chu kỳ PWM Duty PWM
│ │
└───────┬───────┘
▼
PWM Logic
│
▼
TIMx_CHx
│
▼
GPIO
62. Ba câu bạn phải nhớ sau Bài 41
Câu 1
PSC quyết định tốc độ Timer đếm.
Câu 2
ARR quyết định chu kỳ PWM.
Câu 3
CCR quyết định độ rộng xung/Duty.
Nếu ba câu này đã rõ thì chúng ta đã hoàn thành phần cốt lõi của Bài 41.
63. Chuỗi kiến thức tiếp theo
Bây giờ chúng ta đã có:
Bài 39
INPUT CAPTURE
↓
đo thời gian
đo tần số
đo xung
Bài 40
OUTPUT COMPARE
↓
CNT == CCR
↓
Event
Bài 41
PWM
↓
PSC → frequency
ARR → period
CCR → duty
Tiếp theo:
BÀI 42 – ĐIỀU KHIỂN SERVO BẰNG TIMER PWM TRÊN STM32F103
Ở Bài 42, chúng ta sẽ lấy chính công thức vừa học và biến nó thành một servo thật:
STM32F103
│
│ TIM PWM
▼
PA0
│
▼
SG90/MG90S
│
▼
0° ←── 90° ──→ 180°
Quan trọng nhất, tôi sẽ giải thích tại sao servo lại dùng chu kỳ khoảng 20 ms nhưng vị trí lại được quyết định chủ yếu bởi độ rộng xung khoảng 1–2 ms, và chúng ta sẽ tự tính PSC, ARR, CCR thay vì dùng thư viện Servo có sẵn.