Được. Chúng ta sẽ đi đúng chuỗi này. Trước hết là BÀI 40 – OUTPUT COMPARE CHUYÊN SÂU, sau đó lần lượt 41 → 42 → 43.
BÀI 40 – OUTPUT COMPARE CHUYÊN SÂU TRÊN STM32F103
Timer tự tạo sự kiện theo mốc thời gian mà không cần CPU polling
Ở Bài 39, chúng ta đã làm điều ngược lại:
Tín hiệu bên ngoài
↓
Input Capture
↓
CCR
↓
Biết thời điểm tín hiệu xảy ra
Còn hôm nay:
CNT
↓
so sánh với CCR
↓
Compare Match
↓
Tạo sự kiện
Đây là Output Compare.
1. Output Compare thực sự là gì?
Câu định nghĩa quan trọng nhất:
Output Compare là chế độ trong đó Timer liên tục so sánh giá trị
CNTvớiCCR. Khi hai giá trị đạt điều kiện so sánh, phần cứng tạo ra một Compare Event.
Ví dụ:
CNT:
0
1
2
3
...
99
100 ← CCR1
101
...
999
Nếu:
TIM2->CCR1 = 100;
thì khi:
CNT == 100
Timer tạo:
CC1 Event
2. Output Compare khác Input Capture thế nào?
Đây là hai khái niệm bạn phải phân biệt thật rõ.
Input Capture
GPIO event
↓
chụp CNT
↓
CCR
Nói cách khác:
Bên ngoài → Timer
Output Compare
CNT
↓
so sánh CCR
↓
Compare Event
↓
Output
Nói cách khác:
Timer → sự kiện/output
3. Một cách nhớ rất dễ
| Chế độ | Hướng |
|---|---|
| Input Capture | Ngoài → Timer |
| Output Compare | Timer → Event |
| PWM | Timer → xung |
4. Output Compare không nhất thiết phải điều khiển GPIO
Đây là một điểm rất quan trọng.
Khi:
CNT == CCR1
Timer có thể:
Compare Event
mà không cần xuất tín hiệu ra chân GPIO.
Ta có thể dùng Event đó để:
→ Interrupt
→ Trigger
→ thay đổi trạng thái output
→ điều khiển PWM
5. CCR1 chính là “mốc thời gian”
Ví dụ:
Timer tick = 1 µs
và:
CCR1 = 5000;
thì Compare xảy ra khi:
CNT = 5000
tức:
5 ms
Bạn có thể coi:
CCR1 = thời điểm cần thực hiện một sự kiện
Đây là tư duy cực kỳ hữu ích.
6. Output Compare giống một “bộ hẹn giờ phần cứng”
Ví dụ bạn muốn:
sau 10 ms → làm việc A
sau 25 ms → làm việc B
sau 50 ms → làm việc C
Ta có thể hình dung:
CNT
│
├── 10 ms → CCR1
│
├── 25 ms → CCR2
│
└── 50 ms → CCR3
Một Timer có nhiều Channel nên có thể tạo nhiều mốc.
7. Đây là ưu điểm rất lớn
Thay vì:
while (1)
{
if (millis() >= 10)
taskA();
if (millis() >= 25)
taskB();
if (millis() >= 50)
taskC();
}
Timer phần cứng có thể tạo:
CC1 → Task A
CC2 → Task B
CC3 → Task C
CPU chỉ xử lý khi có Event.
8. Các thanh ghi quan trọng
Trong Output Compare, chúng ta đặc biệt quan tâm:
TIMx->PSC
TIMx->CNT
TIMx->ARR
TIMx->CCR1
TIMx->CCMR1
TIMx->CCER
TIMx->DIER
TIMx->SR
9. CCMR1
Thanh ghi:
TIM2->CCMR1
chứa cấu hình cho:
CH1
CH2
Trong Output Compare, phần quan trọng là:
OC1M
Output Compare Mode.
10. OC1M
OC1M quyết định Timer sẽ làm gì khi xảy ra Compare Match.
Một số chế độ quan trọng:
Frozen
Set output
Clear output
Toggle output
PWM
Đây là phần cực kỳ quan trọng.
11. Chế độ Toggle
Một ứng dụng rất dễ hiểu:
CNT == CCR1
↓
Toggle output
Nếu chân đang:
LOW
thì:
HIGH
Nếu đang:
HIGH
thì:
LOW
12. Vì sao Toggle rất hữu ích?
Giả sử:
Compare mỗi 500 µs
Mỗi Compare:
LOW → HIGH
HIGH → LOW
thì ta tạo được:
┌───┐ ┌───┐
──────┘ └───┘ └──
Đó chính là một tín hiệu vuông.
13. Tần số tín hiệu
Nếu Toggle mỗi:
500 µs
thì:
HIGH = 500 µs
LOW = 500 µs
Chu kỳ:
T = 1000 µs
→
f = 1000 Hz
Đây là cách Timer có thể tự tạo clock/square wave.
14. Không cần delay()
Không cần:
GPIOA->ODR |= ...
delay_us(500);
GPIOA->ODR &= ...
delay_us(500);
Timer phần cứng làm:
CNT → CCR
↓
Toggle
↓
CNT → CCR mới
↓
Toggle
CPU gần như không phải làm gì.
15. Output Compare Interrupt
Nếu không muốn Timer trực tiếp điều khiển output mà muốn CPU xử lý:
TIM2->DIER |= TIM_DIER_CC1IE;
Khi Compare:
CC1IF = 1
và:
TIM2_IRQHandler()
được gọi.
16. Sự khác nhau giữa Hardware Output và Interrupt
Hardware
CNT == CCR
↓
GPIO Toggle
CPU:
không cần tham gia
Interrupt
CNT == CCR
↓
CC1IF
↓
ISR
↓
CPU
CPU phải xử lý.
17. Khi nào dùng Hardware?
Dùng khi cần:
- xung chính xác;
- PWM;
- clock;
- toggle chân;
- timing rất ổn định.
18. Khi nào dùng Interrupt?
Dùng khi:
- cần thực hiện code;
- cần cập nhật biến;
- cần điều khiển logic;
- cần thay đổi CCR;
- cần phối hợp nhiều thiết bị.
19. Ví dụ thực hành: Toggle LED bằng Output Compare
Giả sử:
TIM2 = 72 MHz
Ta muốn:
1 tick = 1 µs
Dùng:
TIM2->PSC = 71;
Sau đó:
TIM2->ARR = 9999;
TIM2->CCR1 = 5000;
Compare tại:
5 ms
20. Cấu hình CH1 Output Compare
Về nguyên lý:
TIM2->CCMR1
phải đặt CH1 ở Output Compare.
Ví dụ:
TIM2->CCMR1 &= ~(TIM_CCMR1_CC1S);
CC1S = 00:
CH1 = Output
21. Chọn Toggle
Ta đặt:
OC1M = 011
Tức:
Toggle on match
Có thể viết:
TIM2->CCMR1 &= ~(TIM_CCMR1_OC1M);
TIM2->CCMR1 |= (3 << TIM_CCMR1_OC1M_Pos);
22. Enable CH1
TIM2->CCER |= TIM_CCER_CC1E;
Sau đó Timer có thể điều khiển output của Channel 1.
23. Nhưng cần nhớ chân GPIO
TIM2_CH1 mặc định:
PA0
Vì vậy:
PA0 = TIM2_CH1
Nếu muốn nhìn tín hiệu bằng oscilloscope/logic analyzer:
PA0 → oscilloscope
24. GPIO PA0 phải là Alternate Function Output
Khác Input Capture.
Input Capture:
PA0 → Input
Output Compare:
PA0 → Alternate Function Output
Ví dụ:
GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |= (0xB << 0);
0xB:
MODE = 11
CNF = 10
tức:
Output 50 MHz
Alternate Function Push-Pull
25. Code minh họa
#include "stm32f10x.h"
void GPIO_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/*
PA0 = TIM2_CH1
Alternate Function Push-Pull
50 MHz
*/
GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |= (0xB << 0);
}
void TIM2_OC_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
/*
72 MHz / 72
= 1 MHz
1 tick = 1 us
*/
TIM2->PSC = 71;
/*
Timer period = 10 ms
*/
TIM2->ARR = 9999;
/*
Compare every 5 ms
*/
TIM2->CCR1 = 5000;
/*
CH1 = Output Compare
*/
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;
/*
Toggle on Compare
OC1M = 011
*/
TIM2->CCMR1 &= ~TIM_CCMR1_OC1M;
TIM2->CCMR1 |= (3 << TIM_CCMR1_OC1M_Pos);
/*
Enable CH1
*/
TIM2->CCER |= TIM_CCER_CC1E;
/*
Start Timer
*/
TIM2->CR1 |= TIM_CR1_CEN;
}
int main(void)
{
GPIO_Init();
TIM2_OC_Init();
while (1)
{
}
}
26. Nhưng code trên có một điểm cần hiểu
Với:
ARR = 9999
CCR1 = 5000
Timer sẽ Compare tại:
CNT = 5000
sau đó đến:
CNT = 9999
Timer Update.
Sau khi quay về:
CNT = 0
nó lại đi tới:
CNT = 5000
và Toggle lần nữa.
Do đó output sẽ có dạng:
5ms 5ms
HIGH ─────┐ ┌────
│ │
LOW └─────┘
Chu kỳ:
10 ms
Tần số:
100 Hz
27. Đây là chỗ rất dễ nhầm
Bạn có thể nghĩ:
Compare xảy ra mỗi 5 ms nên tần số là 200 Hz.
Không.
Vì mỗi Compare chỉ đảo trạng thái một lần.
Compare 1 → HIGH
Compare 2 → LOW
Compare 3 → HIGH
Do đó:
1 chu kỳ = 2 lần Toggle
Nếu Toggle mỗi 5 ms:
T = 10 ms
f = 100 Hz
28. Làm thế nào để tạo 1 kHz?
Muốn:
f = 1 kHz
thì:
T = 1 ms
Mỗi Toggle:
0.5 ms
Với:
1 tick = 1 µs
thì:
CCR1
phải được cập nhật mỗi:
500 ticks
29. Đây là lúc kỹ thuật CCR += delta xuất hiện
Thay vì cố định:
CCR1 = 500;
ta làm:
TIM2->CCR1 += 500;
Mỗi Compare:
CCR1
500
↓
1000
↓
1500
↓
2000
↓
2500
...
Như vậy Timer tạo sự kiện đều đặn.
30. Đây thực chất là một Hardware Scheduler
Bạn có:
CCR1 = thời điểm kế tiếp
Mỗi khi sự kiện xảy ra:
CCR1 = CCR1 + khoảng_thời_gian
Đây chính là một dạng:
Deadline scheduling bằng phần cứng.
31. Tại sao CCR += delta tốt hơn CCR = CNT + delta?
Giả sử:
TIM2->CCR1 = TIM2->CNT + 500;
Nếu ISR/hệ thống bị trễ:
CNT đã chạy thêm
thời điểm có thể bị trôi.
Trong khi:
TIM2->CCR1 += 500;
giữ lịch theo mốc Timer ban đầu.
Đây là một kỹ thuật rất hay để tránh timing drift.
32. Ví dụ
Lịch lý tưởng:
1000
1500
2000
2500
3000
Nếu CPU xử lý trễ ở mốc:
1500
thì:
CCR += 500
vẫn hướng tới:
2000
chứ không phải:
CNT hiện tại + 500
33. Output Compare có thể điều khiển nhiều Channel
Ví dụ:
TIM2
CH1 → CCR1 = 100
CH2 → CCR2 = 300
CH3 → CCR3 = 500
CH4 → CCR4 = 800
Một chu kỳ:
0
│
100 → Event 1
│
300 → Event 2
│
500 → Event 3
│
800 → Event 4
│
999 → Update
Đây là một trong những lý do Timer STM32 rất mạnh.
34. Output Compare và PWM
Đến đây bạn có thể bắt đầu thấy:
Output Compare
↓
CNT so với CCR
↓
Output thay đổi
PWM thực chất cũng dựa trên ý tưởng này.
Khác biệt là PWM được Timer tổ chức thành một chế độ tự động tạo xung tuần hoàn.
35. Một ví dụ PWM đơn giản
Giả sử:
ARR = 999
CCR1 = 250
thì:
CNT:
0 ───────── 250 ───────── 999
↑
Compare
Tùy PWM mode:
Output
████████
██
██
ta thu được duty tương ứng.
36. Từ Output Compare sang PWM
Hãy nhớ:
Output Compare:
CNT == CCR
↓
Event
PWM:
CNT + ARR + CCR
↓
tự động tạo xung tuần hoàn
Do đó:
Hiểu Output Compare thật chắc sẽ khiến PWM dễ hiểu hơn rất nhiều.
37. Output Compare có 4 Channel
Với TIM2:
TIM2_CH1
TIM2_CH2
TIM2_CH3
TIM2_CH4
Các thanh ghi:
CCR1
CCR2
CCR3
CCR4
Mỗi Channel có thể có Compare riêng.
38. Nhưng không phải chân nào cũng tùy ý
Đây là điều cần nhớ khi làm phần cứng.
Timer Channel được nối với những chân GPIO cụ thể theo Alternate Function Mapping.
Ví dụ TIM2 mặc định:
CH1 → PA0
CH2 → PA1
CH3 → PA2
CH4 → PA3
Nếu remap TIM2 thì chân có thể thay đổi.
Vì vậy khi thiết kế PCB STM32:
Không nên chọn chân Timer/PWM tùy ý trước khi kiểm tra Alternate Function.
39. Output Compare Interrupt và CC1IF
Nếu dùng interrupt:
if (TIM2->SR & TIM_SR_CC1IF)
{
...
}
sau đó phải xử lý flag.
Ví dụ:
TIM2->SR &= ~TIM_SR_CC1IF;
Nếu quên xử lý đúng:
ISR có thể bị gọi liên tục
Đây là lỗi rất thường gặp khi học Timer.
40. Một ứng dụng rất hay: tạo nhiều nhịp thời gian
Ví dụ Timer tick:
1 µs
Bạn có thể lập lịch:
CCR1 → 1000 µs
CCR2 → 2000 µs
CCR3 → 5000 µs
CCR4 → 10000 µs
Như vậy một Timer có thể tạo nhiều mốc.
41. So sánh với millis()
Software Scheduler
TIM interrupt
↓
millis++
↓
CPU kiểm tra
↓
Task
Output Compare
Timer CNT
↓
CCR
↓
Compare Hardware
↓
Event
Output Compare chính xác và ít phụ thuộc vào thời gian CPU chạy code.
42. Nhưng Output Compare không thay thế Scheduler
Đừng hiểu nhầm rằng:
“Từ giờ không cần millis nữa.”
Không.
Mỗi thứ có mục đích:
millis()
→ quản lý task mức phần mềm
Output Compare
→ timing chính xác mức phần cứng
Ví dụ:
Robot
├── millis → đọc cảm biến mỗi 50 ms
├── millis → cập nhật LCD mỗi 200 ms
└── Timer PWM → điều khiển motor
43. Đây là kiến trúc rất giống robot của bạn
Robot tránh vật cản của bạn có thể phân chia:
STM32
│
┌──────────┼──────────┐
│ │ │
Timer GPIO UART
│
├── PWM → Motor
│
└── Input Capture → HC-SR04
Đây chính là cách tư duy phần cứng mà chúng ta đang xây dựng từ Bài 36 đến Bài 43.
44. Bài tập 1
Cho:
TIM2 = 72 MHz
PSC = 71
Hỏi:
Một Timer tick bao nhiêu?
Đáp án:
72 MHz / 72
= 1 MHz
→
1 tick = 1 µs
45. Bài tập 2
Cho:
CCR1 = 2500
Compare xảy ra sau:
2500 µs
hay:
2.5 ms
46. Bài tập 3
Cho:
ARR = 9999
CCR1 = 5000
Timer tick:
1 µs
Hỏi chu kỳ Timer?
10000 µs
= 10 ms
Hỏi tần số nếu Toggle một lần mỗi Compare?
T = 10 ms
f = 100 Hz
47. Bài tập 4
Muốn tạo square wave:
1 kHz
với:
1 µs/tick
hãy tìm khoảng thời gian giữa hai lần Toggle.
Ta có:
T = 1 ms
Một chu kỳ có 2 lần Toggle:
500 µs
Do đó:
CCR_next = CCR_current + 500;
48. Bài tập 5
Hãy trả lời:
Input Capture và Output Compare khác nhau ở đâu?
Câu trả lời chuẩn:
Input Capture:
External Event
↓
capture CNT
↓
CCR
Còn:
Output Compare:
CNT
↓
compare CCR
↓
Event / Output
Nếu bạn hiểu được hai sơ đồ này thì bạn đã nắm được cốt lõi của bài.
49. Tóm tắt Bài 40
Bạn cần nhớ:
PSC
↓
tạo tốc độ CNT
CNT
↓
bộ đếm
CCR
↓
mốc so sánh
CNT == CCR
↓
Compare Event
Compare Event có thể:
→ Interrupt
→ Toggle
→ Set
→ Clear
→ PWM
Và kỹ thuật quan trọng:
CCR1 += interval;
cho phép Timer tự tạo các mốc thời gian định kỳ.
50. Chuỗi kiến thức tiếp theo
Bây giờ chúng ta đã có:
Bài 38
Timer Architecture
↓
Bài 39
Input Capture
↓
Bài 40
Output Compare
↓
Bước tiếp theo là:
BÀI 41 – PWM TỪ TIMER TRÊN STM32F103
Ở bài đó chúng ta sẽ giải quyết một câu hỏi rất quan trọng:
Làm thế nào chỉ bằng
PSC,ARR,CCR, Timer có thể tự tạo ra một xung PWM mà CPU không phải liên tục bật/tắt GPIO?
Sau đó chúng ta sẽ dùng chính PWM đó để học:
Bài 41
PWM
↓
Bài 42
Servo SG90 / MG90S
↓
Bài 43
HC-SR04 + Input Capture
Và khi hoàn thành Bài 43, bạn sẽ có đủ nền tảng Timer để hiểu khá sâu motor PWM + servo + ultrasonic + encoder trên STM32F103.