BÀI 38 – TIMER INPUT/OUTPUT COMPARE TRÊN STM32F103
Hiểu sâu CNT, PSC, ARR, CCR và cách Timer tự tạo sự kiện
Ở Bài 36–37, chúng ta đã dùng TIM2 để tạo:
TIM2
↓
1 ms
↓
Interrupt
↓
millis++
↓
Software Timer
↓
Nhiều task
Bây giờ chúng ta đi sâu hơn vào bản chất thật sự của Timer STM32.
Bài này đặc biệt quan trọng vì từ đây bạn sẽ gặp những khái niệm:
CNT
PSC
ARR
CCR
Input Capture
Output Compare
PWM
Và chúng liên kết với nhau thành một hệ thống rất mạnh.
1. Timer STM32 thực sự làm gì?
Đừng nghĩ Timer chỉ là:
“Một cái đồng hồ để tạo delay.”
Timer của STM32 thực chất là một bộ đếm phần cứng có khả năng tự phát hiện và tạo sự kiện.
Có thể hình dung:
Clock
↓
Prescaler
↓
Counter CNT
↓
Compare
↓
Capture / Output / Interrupt
CPU không nhất thiết phải liên tục kiểm tra Timer.
2. Bốn thanh ghi quan trọng
Trong bài này, hãy đặc biệt nhớ:
PSC
CNT
ARR
CCR
PSC – Prescaler
TIM2->PSC
Dùng để chia clock trước khi đưa vào bộ đếm.
CNT – Counter
TIM2->CNT
Là giá trị hiện tại của bộ đếm.
ARR – Auto Reload Register
TIM2->ARR
Quy định giới hạn đếm.
CCR – Capture/Compare Register
Ví dụ:
TIM2->CCR1
Dùng cho:
- Output Compare
- Input Capture
- PWM
3. Mối quan hệ giữa chúng
Sơ đồ:
TIM2 CLOCK
│
▼
PSC
│
▼
CNT tăng
│
┌──────┴──────┐
│ │
▼ ▼
ARR CCR1
│ │
│ │
▼ ▼
Update Event Compare Event
Đây chính là cấu trúc quan trọng của Timer.
4. CNT là gì?
Giả sử:
TIM2->CNT
đang tăng:
0
1
2
3
4
5
6
...
Nếu:
TIM2->ARR = 999;
thì Timer đếm:
0 → 999
sau đó tạo Update Event và quay lại:
0
5. PSC quyết định tốc độ CNT
Ví dụ Timer clock:
72 MHz
Nếu:
TIM2->PSC = 7199;
thì:
[
72MHz/(7199+1)=10kHz
]
Tức:
CNT tăng 10.000 lần/giây
Mỗi bước:
100 µs
6. ARR quyết định chu kỳ
Nếu:
TIM2->ARR = 99;
thì:
100 ticks
Mỗi tick:
100 µs
Do đó:
[
100 \times 100\mu s=10ms
]
Timer tạo Update Event mỗi:
10 ms
7. CCR lại khác
Giả sử:
TIM2->CCR1 = 30;
Timer có:
CNT = 0
1
2
...
30
Khi:
CNT == CCR1
Timer phát hiện:
Compare Match
Đây gọi là:
Output Compare
8. So sánh ARR và CCR
| Thanh ghi | Ý nghĩa |
|---|---|
PSC |
Chia clock |
CNT |
Giá trị đếm hiện tại |
ARR |
Mốc kết thúc chu kỳ |
CCR1 |
Mốc so sánh |
Ví dụ:
PSC = 7199
ARR = 99
CCR1 = 30
Timer:
CNT
0
│
│
30 ← Compare Event
│
│
99 ← Update Event
│
└──→ 0
9. Output Compare là gì?
Hiểu đơn giản:
Timer tự theo dõi CNT và khi CNT đạt đến CCR, nó tạo ra một sự kiện.
CPU không cần:
if (TIM2->CNT == 30)
liên tục.
Phần cứng Timer tự làm.
Đây là điểm cực kỳ quan trọng.
10. Tại sao điều này tốt hơn polling?
Cách polling:
while (1)
{
if (TIM2->CNT >= 30)
{
DoSomething();
}
}
CPU phải liên tục kiểm tra.
Output Compare:
CNT
↓
CCR
↓
Compare Event
↓
Hardware
Timer tự phát hiện.
Nếu bật interrupt:
Compare Event
↓
CC1 Interrupt
↓
TIM2_IRQHandler()
11. Thanh ghi CCR1
Với Channel 1:
TIM2->CCR1
Channel 2:
TIM2->CCR2
Channel 3:
TIM2->CCR3
Channel 4:
TIM2->CCR4
STM32F103 Timer có thể có nhiều Compare/Capture Channel.
12. Timer có thể có nhiều mốc thời gian
Ví dụ:
ARR = 999
và:
CCR1 = 100
CCR2 = 300
CCR3 = 500
CCR4 = 800
Một chu kỳ Timer:
0
│
100 ← CC1
│
300 ← CC2
│
500 ← CC3
│
800 ← CC4
│
999 ← Update
│
0
Đây là một khả năng rất mạnh.
13. Output Compare có thể tạo interrupt
Ta cần bật:
TIM2->DIER |= TIM_DIER_CC1IE;
CC1IE:
Capture/Compare 1 Interrupt Enable
Sau đó NVIC:
NVIC_EnableIRQ(TIM2_IRQn);
14. ISR
Trong ISR:
void TIM2_IRQHandler(void)
{
if (TIM2->SR & TIM_SR_CC1IF)
{
TIM2->SR &= ~TIM_SR_CC1IF;
// Compare event
}
}
CC1IF:
Capture/Compare 1 Interrupt Flag.
15. Ví dụ: tạo sự kiện mỗi 10 ms
Ta cấu hình:
TIM2 clock = 72 MHz
PSC = 7199
→
10 kHz
Chọn:
ARR = 99;
→ chu kỳ:
10 ms
Nếu:
CCR1 = 50;
thì Compare xảy ra tại:
50 × 100 µs
= 5 ms
Timeline:
0 ms
│
├──────── 5 ms
│ ↑
│ CCR1
│ │
│ └── Compare Interrupt
│
├────────────────── 10 ms
↑
ARR
│
└── Update Event
16. Đây là điểm rất hay
Một Timer có thể đồng thời tạo:
5 ms → Compare Event
10 ms → Update Event
mà không cần hai Timer.
17. Ví dụ thực hành: Toggle LED bằng Compare Interrupt
Ta muốn:
TIM2
↓
Compare
↓
LED Toggle
Cấu hình:
TIM2->PSC = 7199;
TIM2->ARR = 999;
TIM2->CCR1 = 500;
Khi:
CNT == 500
CC1 interrupt xảy ra.
18. Nhưng có một vấn đề
Nếu:
ARR = 999
CCR1 = 500
thì Compare chỉ xảy ra một lần mỗi chu kỳ Timer.
Nếu muốn tạo sự kiện đều đặn, chúng ta có thể cập nhật:
TIM2->CCR1 += ...
Đây là một kỹ thuật rất quan trọng.
19. Output Compare theo kiểu “lập lịch”
Ví dụ Timer tick:
100 µs
Ta muốn event mỗi:
5 ms
5 ms tương đương:
50 ticks
Sau mỗi Compare:
TIM2->CCR1 += 50;
Timeline:
CCR1 = 50
↓
Compare
↓
CCR1 = 100
↓
Compare
↓
CCR1 = 150
↓
Compare
↓
CCR1 = 200
Timer phần cứng đang trở thành một scheduler chính xác theo tick.
20. Đây khác millis() thế nào?
millis():
TIM2
↓
mỗi 1 ms
↓
millis++
↓
CPU kiểm tra
Output Compare:
TIM2
↓
CNT == CCR
↓
Hardware Compare
↓
Interrupt
Output Compare cho phép:
Phần cứng lập lịch một sự kiện tại một thời điểm cụ thể của Timer.
21. Input Capture là gì?
Bây giờ chúng ta chuyển sang phần còn lại của bài:
Input Capture
Ý tưởng ngược lại.
Output Compare:
Timer → so sánh với CCR
Input Capture:
GPIO event → chụp giá trị CNT vào CCR
22. Hình dung
Giả sử một tín hiệu ngoài đi vào:
PA0
│
▼
TIM2_CH1
Tín hiệu:
HIGH ──────┐ ┌──────
│ │
LOW └──────┘
↑
cạnh lên
Khi cạnh xảy ra:
GPIO edge
↓
Timer Capture
↓
CCR1 = CNT
Timer chụp lại thời điểm xảy ra cạnh.
23. Đây là ý tưởng cực kỳ quan trọng
Ví dụ:
CNT = 1000
tín hiệu cạnh lên xảy ra.
Hardware tự động:
CCR1 = 1000
Sau đó:
CNT = 1500
cạnh tiếp theo:
CCR1 = 1500
Khoảng thời gian:
1500 - 1000 = 500 ticks
Ta đo được độ rộng/thời gian tín hiệu.
24. Input Capture dùng để làm gì?
Rất nhiều thứ:
- đo tần số;
- đo chu kỳ;
- đo độ rộng xung;
- đo PWM từ thiết bị khác;
- đo thời gian Echo của HC-SR04;
- đọc encoder;
- đo tốc độ động cơ;
- đo tín hiệu cảm biến.
Đặc biệt:
HC-SR04 mà bạn từng làm với robot chính là một ứng dụng rất phù hợp để hiểu Input Capture.
25. Ví dụ HC-SR04
HC-SR04 tạo:
TRIG
↓
phát siêu âm
Sau đó chân:
ECHO
có một xung HIGH.
Ví dụ:
ECHO
LOW ─────┐
│
└──────────────┐
│
└──── LOW
↑ ↑
Rising Falling
Ta có thể dùng Timer Input Capture:
Rising edge
↓
CCR1 = thời điểm bắt đầu
Falling edge
↓
CCR1 = thời điểm kết thúc
Sau đó:
[
t = t_{falling}-t_{rising}
]
Từ thời gian Echo:
[
Distance \approx \frac{t \times 0.0343}{2}
]
với t tính bằng microsecond.
26. Bạn thấy sự liên kết chưa?
Bài 38 đang chuẩn bị trực tiếp cho:
Timer
↓
Input Capture
↓
HC-SR04
Và sau đó:
Timer
↓
PWM
↓
Servo
và:
Timer
↓
PWM
↓
Motor
27. Timer Channel
Một Timer có các channel:
TIM2_CH1
TIM2_CH2
TIM2_CH3
TIM2_CH4
Mỗi channel có:
Capture/Compare Register
Ví dụ:
CH1 → CCR1
CH2 → CCR2
CH3 → CCR3
CH4 → CCR4
28. CCR là thanh ghi rất đa năng
Đây là điều bạn cần nhớ:
CCR
│
├── Output Compare
│
├── Input Capture
│
└── PWM
Tùy chế độ Timer Channel.
29. Bây giờ hãy hiểu sâu hơn về PWM
PWM mà bạn sẽ học sau này cũng dựa trên:
CNT
ARR
CCR
Ví dụ:
ARR = 999
CCR1 = 250
Có thể tạo:
Duty ≈ 25%
Nếu:
CCR1 = 500
thì:
Duty ≈ 50%
Nếu:
CCR1 = 750
thì:
Duty ≈ 75%
30. Một hình ảnh tư duy cực kỳ quan trọng
Hãy tưởng tượng:
ARR
│
▼
0 ───────────────────────── 999
│
│─────── CCR1
│
└───────────────
ARR quyết định:
chu kỳ
CCR quyết định:
mốc so sánh / độ rộng xung tùy chế độ
Đây chính là nền tảng của PWM.
31. Tổng hợp Timer
Bạn có thể nhớ bằng bảng:
| Thành phần | Chức năng |
|---|---|
PSC |
Chia clock |
CNT |
Bộ đếm |
ARR |
Chu kỳ |
CCR |
Mốc Capture/Compare |
| Update | CNT đạt ARR |
| Compare | CNT đạt CCR |
| Capture | GPIO event → lưu CNT |
| PWM | Dùng CNT + CCR để tạo xung |
32. Thí nghiệm Output Compare đơn giản
Bây giờ chúng ta tạo:
TIM2
↓
Compare CH1
↓
Interrupt
↓
Toggle PC13
Mục tiêu:
LED toggle mỗi 100 ms
33. Tính Timer
Giữ:
TIM2 = 72 MHz
PSC = 7199
Timer tick:
100 µs
Muốn:
100 ms
cần:
1000 ticks
Có thể:
ARR = 9999;
nhưng khi dùng Compare, ta có thể chọn mốc CCR.
Ví dụ:
CCR1 = 1000;
→ Compare tại:
100 ms
nếu CNT bắt đầu từ 0 và tick 100 µs.
34. Code minh họa Output Compare
#include "stm32f10x.h"
void GPIO_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
/* PC13 output */
GPIOC->CRH &= ~(0xF << 20);
GPIOC->CRH |= (0x2 << 20);
}
void TIM2_OC_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
/*
TIM2 = 72 MHz
72 MHz / 7200
= 10 kHz
1 tick = 100 us
*/
TIM2->PSC = 7199;
/*
Timer period = 1 second
10000 ticks
*/
TIM2->ARR = 9999;
/* First compare after 100 ms */
TIM2->CCR1 = 1000;
/* Enable CC1 interrupt */
TIM2->DIER |= TIM_DIER_CC1IE;
NVIC_SetPriority(TIM2_IRQn, 2);
NVIC_EnableIRQ(TIM2_IRQn);
TIM2->CNT = 0;
TIM2->CR1 |= TIM_CR1_CEN;
}
void TIM2_IRQHandler(void)
{
if (TIM2->SR & TIM_SR_CC1IF)
{
TIM2->SR &= ~TIM_SR_CC1IF;
GPIOC->ODR ^= (1 << 13);
/*
Schedule next compare
100 ms later
*/
TIM2->CCR1 += 1000;
}
}
int main(void)
{
GPIO_Init();
TIM2_OC_Init();
while (1)
{
}
}
35. Nhưng hãy chú ý một vấn đề
Ở đây:
TIM2->CCR1 += 1000;
sẽ tạo:
100 ms
200 ms
300 ms
400 ms
...
cho đến khi:
CNT
và:
CCR1
quay vòng theo Timer.
Với:
ARR = 9999
sau 1 giây:
CNT → 0
và CCR1 cũng cần được quản lý phù hợp.
Trong bài học đầu tiên, điều quan trọng nhất là hiểu cơ chế:
Compare Event xảy ra khi CNT đạt CCR.
36. Một cách đơn giản hơn để học
Chúng ta có thể đặt:
ARR = 999;
CCR1 = 500;
và quan sát:
CNT = 0
↓
CNT = 500
↓
CC1 interrupt
↓
CNT = 999
↓
Update
↓
CNT = 0
Đây là mô hình dễ hiểu nhất.
37. So sánh ba kỹ thuật
Polling
CPU → đọc CNT → đọc CNT → đọc CNT
Interrupt
Timer → Event → CPU ISR
Hardware Output
Timer → Event → GPIO hardware
Càng về sau:
CPU càng ít phải can thiệp.
Đây là xu hướng rất quan trọng trong thiết kế embedded.
38. Ví dụ cao hơn
Trong PWM:
CPU
↓
CCR = giá trị mới
↓
Timer tự tạo PWM
↓
GPIO
CPU không cần:
ON
delay
OFF
delay
ON
delay
Phần cứng Timer tự làm.
Đây là lý do PWM bằng Timer tốt hơn rất nhiều so với tự viết GPIO bằng software.
39. Từ đây bạn sẽ gặp CCER
Khi đi sâu vào Input Capture/Output Compare, bạn sẽ gặp:
TIM2->CCER
Capture/Compare Enable Register.
Nó quyết định channel:
- enable/disable;
- polarity;
- input/output configuration.
Ví dụ khái niệm:
CC1E
cho phép Channel 1.
Và:
CC1P
liên quan đến polarity.
Chúng ta sẽ học kỹ ở bài chuyên sâu hơn, chưa cần học thuộc ngay hôm nay.
40. Tại sao bài 38 quan trọng?
Vì từ đây:
Timer
không còn chỉ là:
delay
mà trở thành:
Timer
├── Time base
├── Interrupt
├── Output Compare
├── Input Capture
└── PWM
Một Timer phần cứng có thể làm được rất nhiều việc.
41. Sơ đồ tổng quát
Hãy ghi sơ đồ này:
TIMER
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
CNT ARR CCR
│ │
│ ┌─────┴─────┐
│ │ │
▼ ▼ ▼
Update Compare Capture
│ │ │
▼ ▼ ▼
Interrupt Output Input Event
│
▼
PWM
42. Bài tập 1
Hãy cấu hình:
TIM2 clock = 72 MHz
PSC = 7199
Tính:
Timer tick bao nhiêu microsecond?
Đáp án:
[
72MHz/7200=10kHz
]
nên:
1 tick = 100 µs
43. Bài tập 2
Nếu:
PSC = 7199;
ARR = 99;
thì Timer period bao nhiêu?
Có:
100 ticks
mỗi tick:
100 µs
nên:
10 ms
44. Bài tập 3
Nếu:
CCR1 = 25;
thì Compare xảy ra sau bao lâu?
Có:
25 ticks
mỗi:
100 µs
→ khoảng:
2.5 ms
45. Bài tập 4 – cực kỳ quan trọng
Cho:
TIM2CLK = 72 MHz
PSC = 7199
ARR = 999
CCR1 = 250
Hãy xác định:
1. Timer tick
2. Update period
3. Compare time
Đáp án:
Timer tick = 100 µs
Update:
1000 × 100 µs
= 100 ms
Compare:
250 × 100 µs
= 25 ms
46. Bài tập 5 – suy nghĩ
Nếu:
ARR = 999
CCR1 = 250
thì:
CNT = 249
có Compare chưa?
Chưa.
Khi:
CNT = 250
thì:
Compare Event
được tạo.
Đây chính là bản chất của Output Compare.
47. Điều bạn cần thuộc lòng sau Bài 38
PSC
↓
chia clock
CNT
↓
đang đếm
ARR
↓
giới hạn / chu kỳ
CCR
↓
mốc Capture / Compare
Và:
CNT == ARR
↓
Update Event
trong mô hình đếm lên thông thường.
Còn:
CNT == CCR
↓
Compare Event
48. Lộ trình tiếp theo
Chúng ta đã đi:
Bài 35
Debounce + EXTI + Timer
↓
Bài 36
Timer Interrupt + millis
↓
Bài 37
Software Timer + Scheduler
↓
Bài 38
CNT + PSC + ARR + CCR
↓
Bước tiếp theo rất hợp lý là:
BÀI 39 – INPUT CAPTURE TRÊN STM32F103: ĐO THỜI GIAN, ĐO TẦN SỐ VÀ ĐO XUNG BẰNG TIMER
Ở Bài 39 chúng ta sẽ thực sự cấu hình một chân Timer, đưa một tín hiệu vuông vào nó và để phần cứng tự chụp:
Cạnh lên
↓
CCR1 = CNT
Cạnh lên tiếp theo
↓
CCR1 = CNT
ΔCNT
↓
Thời gian
↓
Tần số
Sau đó có thể áp dụng trực tiếp vào HC-SR04 để đo Echo, rồi từ đó chuyển sang đo tốc độ động cơ/encoder.