BÀI 39 – INPUT CAPTURE TRÊN STM32F103
Đo thời gian, đo tần số và đo độ rộng xung bằng Timer
Ở Bài 38, chúng ta đã hiểu:
PSC → chia clock
CNT → bộ đếm
ARR → giới hạn chu kỳ
CCR → Capture / Compare
Bây giờ chúng ta dùng CCR theo chế độ Input Capture.
Đây là một trong những bài quan trọng nhất của phần Timer, bởi vì từ nó bạn có thể làm:
- đo tần số;
- đo chu kỳ;
- đo độ rộng xung;
- đo PWM từ thiết bị bên ngoài;
- đo Echo của HC-SR04;
- đo tốc độ động cơ;
- đọc encoder;
- đo tín hiệu cảm biến.
1. Input Capture là gì?
Hãy bắt đầu bằng một câu cực kỳ đơn giản:
Input Capture là cơ chế để Timer tự động chụp giá trị
CNTtại thời điểm một sự kiện xảy ra trên chân GPIO.
Ví dụ Timer đang đếm:
CNT:
0
1
2
3
4
5
6
7
8
9
10
...
Một tín hiệu bên ngoài xuất hiện cạnh lên:
↑
──────────┘
Rising Edge
Phần cứng Timer lập tức:
CNT = 1250
↓
CCR1 = 1250
CPU không cần phải đứng đó đọc CNT liên tục.
2. Đây là điểm khác biệt rất lớn
Trong polling, bạn phải làm:
while (1)
{
if (GPIOA->IDR & (1 << 0))
{
time = TIM2->CNT;
}
}
CPU phải liên tục kiểm tra GPIO.
Trong Input Capture:
Tín hiệu
↓
TIM2_CH1
↓
Capture Hardware
↓
CCR1 = CNT
↓
Interrupt
↓
CPU xử lý
Phần cứng Timer làm công việc chính.
3. Hãy hình dung một đồng hồ bấm giờ
Input Capture giống như:
START
↓
Timer bắt đầu đếm
↓
───────────────
↑
Event
↓
Timer chụp CNT
Ví dụ:
Start:
CNT = 100
Event:
CNT = 750
Ta có:
ΔCNT = 750 - 100
= 650
Nếu mỗi tick là:
1 µs
thì:
Δt = 650 µs
4. Cấu trúc Input Capture
Sơ đồ:
STM32F103
│
│ GPIO
▼
TIM2_CH1 Input
│
▼
Capture Logic
│
▼
CCR1
│
▼
CNT
│
▼
Interrupt
Bạn cần nhớ:
Input Capture không tạo ra thời gian. Nó ghi lại thời điểm xảy ra sự kiện.
5. Timer vẫn phải chạy
Ví dụ:
TIM2 clock
↓
PSC
↓
CNT tăng
Nếu Timer không chạy:
CNT = 0
thì Input Capture chẳng có gì để chụp.
6. Chọn tốc độ Timer
Đây là bước rất quan trọng.
Giả sử STM32F103 chạy:
72 MHz
Ta muốn:
1 tick = 1 µs
Cần:
[
72MHz / 1MHz = 72
]
Do PSC dùng:
[
PSC+1
]
nên:
TIM2->PSC = 71;
Kết quả:
72 MHz / 72
= 1 MHz
Do đó:
1 tick = 1 µs
Đây là cấu hình rất đẹp để đo thời gian.
7. Ví dụ
Timer:
PSC = 71;
thì:
CNT = 0
CNT = 1 → 1 µs
CNT = 2 → 2 µs
CNT = 3 → 3 µs
...
CNT = 1000 → 1000 µs
Hay:
1000 µs = 1 ms
8. Đo chu kỳ tín hiệu
Giả sử tín hiệu:
┌───────┐ ┌───────┐
───────┘ └───────┘ └──────
↑ ↑
A B
Cạnh lên đầu tiên:
CCR1 = 1000
Cạnh lên tiếp theo:
CCR1 = 3000
Ta có:
3000 - 1000 = 2000
Vì:
1 tick = 1 µs
nên:
T = 2000 µs
= 2 ms
9. Từ chu kỳ suy ra tần số
Công thức:
[
f = \frac{1}{T}
]
Nếu:
T = 2 ms
thì:
[
f=\frac{1}{0.002}=500Hz
]
Vậy Input Capture cho phép:
đo thời gian
↓
đo chu kỳ
↓
đo tần số
10. Đo độ rộng xung
Đây là ứng dụng còn thú vị hơn.
Giả sử:
┌────────────┐
─────────┘ └─────────
↑ ↑
Rising Falling
Ta cần biết:
HIGH time
Nếu:
Rising → CCR1 = 1000
Falling → CCR1 = 2500
thì:
HIGH width = 2500 - 1000
= 1500 µs
11. Độ rộng xung và Duty Cycle
Nếu chu kỳ:
T = 5000 µs
và thời gian HIGH:
Ton = 1500 µs
thì:
[
Duty=\frac{Ton}{T}\times100%
]
[
Duty=\frac{1500}{5000}\times100=30%
]
Vậy Input Capture có thể đo:
Frequency
+
Duty Cycle
của tín hiệu PWM bên ngoài.
12. Rising Edge và Falling Edge
Có hai loại cạnh cơ bản:
Rising edge
LOW → HIGH
Falling edge
HIGH → LOW
Input Capture có thể được cấu hình để bắt:
Rising
hoặc:
Falling
hoặc tùy cấu hình có thể xử lý cả hai.
13. Thanh ghi quan trọng: CCMR1
Đối với TIM2 Channel 1/2, chúng ta sẽ gặp:
TIM2->CCMR1
Nó cấu hình Capture/Compare Mode.
Đối với Input Capture Channel 1:
CC1S
xác định Channel 1 hoạt động như Input Capture.
14. Thanh ghi CCER
Chúng ta đã nhắc tới nó ở Bài 38:
TIM2->CCER
Nó quyết định:
- Channel enable;
- polarity;
- input/output behavior.
Ví dụ ý tưởng:
CC1E → enable CH1
CC1P → polarity
15. Thanh ghi CCR1
Khi Input Capture xảy ra:
TIM2->CCR1
sẽ chứa giá trị CNT được chụp.
Ví dụ:
CNT = 2456
External edge
↓
Capture
↓
CCR1 = 2456
Đây chính là “ảnh chụp thời gian”.
16. Thanh ghi SR
Timer báo sự kiện bằng flag:
TIM2->SR
Đối với Channel 1:
TIM_SR_CC1IF
Nếu:
TIM2->SR & TIM_SR_CC1IF
đúng, nghĩa là:
Channel 1 vừa xảy ra Capture/Compare event.
17. Thanh ghi DIER
Muốn Capture tạo interrupt:
TIM2->DIER |= TIM_DIER_CC1IE;
Trong đó:
CC1IE
=
Capture/Compare 1 Interrupt Enable.
18. Đường đi hoàn chỉnh
Khi tín hiệu ngoài xuất hiện:
GPIOA
↓
TIM2_CH1
↓
Input Capture
↓
CNT được chụp
↓
CCR1
↓
CC1IF = 1
↓
NVIC
↓
TIM2_IRQHandler()
Bạn nên thuộc sơ đồ này.
19. Ví dụ cấu hình TIM2_CH1
Trên STM32F103, TIM2_CH1 mặc định nằm trên:
PA0
Do đó chúng ta có thể dùng:
PA0 → TIM2_CH1
để nhận tín hiệu.
20. Cấu hình GPIO PA0
PA0 cần được cấu hình làm Input.
Ví dụ input floating:
void GPIO_InputCapture_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/* PA0 = Input floating */
GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |= (0x4 << 0);
}
Trong đó:
CNF = 01
MODE = 00
→ Input floating.
21. Cấu hình Timer 1 µs/tick
void TIM2_InputCapture_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
/*
72 MHz / 72
= 1 MHz
1 tick = 1 us
*/
TIM2->PSC = 71;
TIM2->ARR = 0xFFFF;
TIM2->CNT = 0;
}
TIM2 là Timer 32-bit trên STM32F103, nhưng để học Input Capture cơ bản, bạn có thể coi CNT như một bộ đếm đủ lớn và tập trung vào cơ chế Capture.
22. Cấu hình Channel 1 Input Capture
Đây là phần quan trọng.
Ta cần đặt:
CC1S = 01
để:
CH1 → Input Capture
Có thể cấu hình:
TIM2->CCMR1 &= ~(TIM_CCMR1_CC1S);
TIM2->CCMR1 |= TIM_CCMR1_CC1S_0;
23. Chọn Rising Edge
Ta muốn bắt:
LOW → HIGH
Mặc định polarity là rising.
Enable channel:
TIM2->CCER |= TIM_CCER_CC1E;
24. Enable interrupt
TIM2->DIER |= TIM_DIER_CC1IE;
NVIC:
NVIC_SetPriority(TIM2_IRQn, 2);
NVIC_EnableIRQ(TIM2_IRQn);
Cuối cùng:
TIM2->CR1 |= TIM_CR1_CEN;
Timer bắt đầu chạy.
25. ISR cơ bản
volatile uint32_t capture_value = 0;
void TIM2_IRQHandler(void)
{
if (TIM2->SR & TIM_SR_CC1IF)
{
capture_value = TIM2->CCR1;
TIM2->SR &= ~TIM_SR_CC1IF;
}
}
Mỗi khi có cạnh lên:
PA0
↓
Capture
↓
CCR1
↓
capture_value
26. Nhưng như vậy mới chỉ đọc được một thời điểm
Ví dụ:
Capture 1 → 1000
Capture 2 → 3000
Chúng ta cần lưu:
uint32_t capture1;
uint32_t capture2;
và tính:
period = capture2 - capture1;
27. Code đo chu kỳ
volatile uint32_t capture1 = 0;
volatile uint32_t capture2 = 0;
volatile uint32_t period = 0;
volatile uint8_t capture_state = 0;
void TIM2_IRQHandler(void)
{
if (TIM2->SR & TIM_SR_CC1IF)
{
uint32_t current;
current = TIM2->CCR1;
if (capture_state == 0)
{
capture1 = current;
capture_state = 1;
}
else
{
capture2 = current;
period = capture2 - capture1;
capture_state = 0;
}
TIM2->SR &= ~TIM_SR_CC1IF;
}
}
28. Có một vấn đề quan trọng: Timer overflow
Giả sử:
capture1 = 65000
capture2 = 100
Nếu bạn nghĩ:
100 - 65000
là sai thì không hoàn toàn đúng.
Với kiểu:
uint16_t
phép trừ unsigned có thể tự xử lý vòng quay.
Ví dụ:
uint16_t delta;
delta = capture2 - capture1;
sẽ cho khoảng thời gian đúng nếu khoảng đo nằm trong một vòng Timer.
29. Với uint32_t
Bạn cũng có thể dùng:
uint32_t delta;
delta = capture2 - capture1;
Miễn là biến là unsigned.
Đây chính là kỹ thuật chúng ta đã gặp ở millis():
(uint32_t)(now - last)
30. Đây là một kỹ thuật cực kỳ quan trọng trong Embedded C
Timer chạy:
0 → MAX → 0 → MAX → ...
Không phải:
0 → MAX → chết
Do đó chương trình phải được thiết kế để chịu được overflow.
31. Đo tần số
Nếu Timer tick:
1 µs
và:
period = 2000
thì:
T = 2000 µs
Đổi sang giây:
0.002 s
Tần số:
f = 1 / 0.002
= 500 Hz
32. Có thể tính trực tiếp
Nếu:
Timer frequency = 1 MHz
thì:
[
f_{signal}=\frac{1,000,000}{period}
]
Ví dụ:
period = 2000
thì:
f = 1,000,000 / 2000
= 500 Hz
33. Đo độ rộng xung bằng cách đổi polarity
Ta có:
Rising
↓
CCR1 = start
Sau đó chuyển sang:
Falling
↓
CCR1 = end
Rồi:
width = end - start;
34. Đây chính là cách đo Echo HC-SR04
Hãy nhớ hình này:
TRIG
____┌──┐________________
│ │
└──┘
ECHO
________┌────────────────┐________
↑ ↑
Rising Falling
Timer:
Rising
↓
start = CCR
Falling
↓
end = CCR
Sau đó:
echo_time = end - start
35. Từ Echo Time đến khoảng cách
Công thức:
[
Distance = \frac{Time \times 0.0343}{2}
]
Nếu Time tính bằng µs:
Distance(cm)
≈ Time(µs) / 58
Ví dụ:
Echo = 1740 µs
thì:
Distance ≈ 1740 / 58
≈ 30 cm
Đây chính là con số 30 cm mà bạn từng dùng trong robot tránh vật cản.
36. Vì vậy Input Capture rất phù hợp cho robot
Thay vì:
while (ECHO == LOW);
start = micros();
while (ECHO == HIGH);
end = micros();
CPU phải chờ.
Input Capture:
ECHO Rising
↓
Hardware Capture
ECHO Falling
↓
Hardware Capture
CPU
↓
tính khoảng cách
CPU không phải đứng chờ toàn bộ thời gian Echo.
37. So sánh hai cách
Cách polling
CPU
↓
chờ ECHO
↓
chờ ECHO
↓
chờ ECHO
↓
đọc thời gian
Input Capture
Timer Hardware
↓
chờ cạnh
↓
capture tự động
↓
CPU xử lý khi cần
Đây là tư duy embedded rất quan trọng:
Đẩy công việc thời gian chính xác sang phần cứng.
38. Nhưng có một điều bạn cần đặc biệt chú ý
Trên Blue Pill:
STM32F103C8T6
Timer Clock không phải lúc nào cũng đơn giản là:
72 MHz
Trong cấu hình chuẩn thường gặp:
SYSCLK = 72 MHz
APB1 = 36 MHz
Nhưng Timer trên APB1 có cơ chế:
Nếu APB1 prescaler khác 1, clock Timer có thể được nhân 2.
Vì vậy:
PCLK1 = 36 MHz
TIM2CLK = 72 MHz
Đây là lý do chúng ta thường sử dụng:
TIM2 = 72 MHz
trong cấu hình Blue Pill 72 MHz.
39. Đây là chỗ người mới rất dễ sai
Bạn nhìn:
APB1 = 36 MHz
rồi tính:
TIM2 = 36 MHz
→ có thể sai.
Phải xem clock tree.
40. Bài tập tính toán
Giả sử:
SYSCLK = 72 MHz
APB1 = 36 MHz
APB1 Prescaler = 2
thì:
TIM2CLK = 72 MHz
Muốn:
1 µs/tick
ta dùng:
PSC = 71;
vì:
[
72MHz/(71+1)=1MHz
]
41. Một vấn đề khác: tín hiệu quá chậm
Nếu bạn dùng:
1 µs/tick
thì Timer có thể đo được khoảng:
0 → 65535 µs
nếu sử dụng bộ đếm 16-bit.
Tức:
≈ 65.5 ms
Nếu tín hiệu chậm hơn:
100 ms
có thể xảy ra overflow.
Khi đó bạn có thể:
- giảm tốc độ Timer;
- dùng Timer 32-bit;
- đếm số lần overflow;
- dùng interrupt Update;
- hoặc kết hợp nhiều Timer.
42. Ví dụ
Nếu:
tick = 10 µs
thì:
65535 × 10 µs
≈ 655 ms
Phạm vi đo rộng hơn.
Nhưng độ phân giải giảm.
43. Đây là bài toán Resolution vs Range
Timer:
1 µs/tick
Ưu điểm:
độ phân giải cao
Nhược điểm:
dễ overflow với tín hiệu dài
Timer:
10 µs/tick
Ưu điểm:
đo được khoảng thời gian dài hơn
Nhược điểm:
độ phân giải thấp hơn
Kỹ sư phải chọn phù hợp ứng dụng.
44. Một ví dụ thực tế
HC-SR04
Khoảng cách:
2 cm → 400 cm
Echo tối đa khoảng:
23 ms
Dùng:
1 µs/tick
hoàn toàn hợp lý.
45. Đo tốc độ động cơ
Giả sử động cơ có:
1 xung / vòng
Input Capture đo:
T = 20 ms
thì:
f = 50 Hz
Nếu:
1 xung = 1 vòng
thì:
RPM = 50 × 60
= 3000 RPM
Nếu encoder có:
20 xung/vòng
thì:
RPM = 50 × 60 / 20
= 150 RPM
46. Vì vậy Input Capture xuất hiện ở rất nhiều hệ thống
Encoder
↓
Input Capture
↓
Frequency
↓
RPM
hoặc:
PWM từ thiết bị
↓
Input Capture
↓
Period + Ton
↓
Frequency + Duty
47. Một khái niệm rất hay: Hardware Timestamp
CCR1 thực chất có thể được hiểu là:
Hardware timestamp
Ví dụ:
Event A → timestamp = 10000
Event B → timestamp = 12500
Khoảng cách thời gian:
2500 ticks
Đây là tư duy cực kỳ hữu ích khi lập trình embedded.
48. Input Capture không cần CPU đọc liên tục
Đây là một trong những điều quan trọng nhất của Bài 39:
❌ Không cần:
while(1)
{
đọc GPIO;
đọc CNT;
}
Thay vào đó:
✅ Hardware:
GPIO edge
↓
Capture
↓
CCR
↓
Interrupt
↓
CPU xử lý
49. Code khung hoàn chỉnh để học
Dưới đây là một firmware tối giản để bạn nghiên cứu cấu trúc:
#include "stm32f10x.h"
volatile uint32_t capture1 = 0;
volatile uint32_t capture2 = 0;
volatile uint32_t period = 0;
volatile uint8_t capture_state = 0;
/* =====================================================
GPIO PA0 = TIM2_CH1
===================================================== */
void GPIO_Init(void)
{
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
/*
PA0 = Input Floating
*/
GPIOA->CRL &= ~(0xF << 0);
GPIOA->CRL |= (0x4 << 0);
}
/* =====================================================
TIM2 INPUT CAPTURE
===================================================== */
void TIM2_InputCapture_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
/*
TIM2 clock = 72 MHz
72 MHz / 72
= 1 MHz
1 tick = 1 us
*/
TIM2->PSC = 71;
/*
TIM2 is 32-bit on STM32F103,
but this example uses a large period.
*/
TIM2->ARR = 0xFFFFFFFF;
TIM2->CNT = 0;
/*
CC1S = 01
CH1 configured as Input Capture
*/
TIM2->CCMR1 &= ~TIM_CCMR1_CC1S;
TIM2->CCMR1 |= TIM_CCMR1_CC1S_0;
/*
Rising edge
*/
TIM2->CCER &= ~TIM_CCER_CC1P;
/*
Enable Channel 1
*/
TIM2->CCER |= TIM_CCER_CC1E;
/*
Enable CC1 interrupt
*/
TIM2->DIER |= TIM_DIER_CC1IE;
/*
NVIC
*/
NVIC_SetPriority(TIM2_IRQn, 2);
NVIC_EnableIRQ(TIM2_IRQn);
/*
Start Timer
*/
TIM2->CR1 |= TIM_CR1_CEN;
}
/* =====================================================
TIM2 INTERRUPT
===================================================== */
void TIM2_IRQHandler(void)
{
if (TIM2->SR & TIM_SR_CC1IF)
{
uint32_t current;
current = TIM2->CCR1;
if (capture_state == 0)
{
capture1 = current;
capture_state = 1;
}
else
{
capture2 = current;
period = capture2 - capture1;
capture_state = 0;
}
/*
Clear Capture flag
*/
TIM2->SR &= ~TIM_SR_CC1IF;
}
}
/* =====================================================
MAIN
===================================================== */
int main(void)
{
GPIO_Init();
TIM2_InputCapture_Init();
while (1)
{
/*
period contains signal period in us.
frequency:
f = 1000000 / period
*/
}
}
50. Một lưu ý về code trên
Đây là code học nguyên lý, chưa phải driver Input Capture hoàn chỉnh cho sản phẩm.
Khi làm hệ thống thực tế, chúng ta còn phải xử lý:
Overflow
Noise
Glitch
Invalid signal
Timeout
Multiple captures
Interrupt race
Signal polarity
Filter
Đặc biệt là:
IC Filter
mà chúng ta sẽ gặp khi đi sâu hơn.
51. Bài tập thực hành rất đáng làm
Nếu chưa có signal generator, bạn có thể dùng một Timer khác để tạo PWM/tín hiệu rồi đưa sang PA0.
Ví dụ:
Timer tạo xung
│
▼
PA?
│
│ dây nối
▼
PA0
│
▼
TIM2_CH1 Input Capture
Sau đó kiểm tra:
period
frequency
52. Một bài thực hành cực kỳ phù hợp với bạn
Vì bạn đang có:
STM32 Blue Pill
+
HC-SR04
hãy hướng tới cấu trúc:
STM32F103
│
┌────────┴────────┐
│ │
TRIG ECHO
│ │
▼ ▼
GPIO OUT TIM2_CH1
│
▼
Input Capture
│
▼
CCR1
│
▼
Echo time
│
▼
Distance
│
▼
cm
Đây sẽ là bài thực hành tuyệt vời để chuyển kiến thức Timer từ lý thuyết sang phần cứng thật.
53. Tổng kết Bài 39
Bạn cần nhớ 7 ý sau:
① Timer vẫn chạy bằng:
PSC → CNT
② Input Capture xảy ra khi:
External Edge
↓
Capture
③ Khi Capture:
CCR1 = CNT
④ Hai lần Capture:
CCR2 - CCR1
cho ta khoảng thời gian.
⑤ Từ khoảng thời gian:
Period → Frequency
⑥ Rising + Falling:
Rising → start
Falling → end
cho ta:
Pulse Width
⑦ Ứng dụng:
HC-SR04
Encoder
RPM
PWM measurement
Frequency meter
Pulse measurement
54. Chuỗi kiến thức Timer của chúng ta lúc này
Bài 36
TIM Interrupt
↓
Bài 37
millis + Software Scheduler
↓
Bài 38
PSC + CNT + ARR + CCR
↓
Bài 39
INPUT CAPTURE
↓
Bài 40
OUTPUT COMPARE
↓
Bài 41
PWM
↓
Bài 42
SERVO bằng PWM
↓
Bài 43
HC-SR04 bằng Input Capture
Tuy nhiên, Bài 40 có thể học trước hoặc sau PWM. Với mục tiêu học STM32 thật chắc, tôi khuyên đi theo hướng:
Bài 40 – OUTPUT COMPARE chuyên sâu → Bài 41 – PWM từ TIM → Bài 42 – Servo → Bài 43 – HC-SR04 bằng Input Capture.
Như vậy bạn sẽ hiểu được toàn bộ chuỗi:
STM32 TIMER
│
┌──────────┼──────────┐
│ │ │
CNT │ CCR
│ │ │
ARR │ ┌────┴────┐
│ │ │ │
Time Base │ Compare Capture
│ │ │
│ ▼ ▼
│ Output Input
│ │ │
│ ▼ ▼
│ PWM đo thời gian
│ │
│ ▼
│ HC-SR04
│
▼
Timer
Interrupt
Đây chính là lúc Timer STM32 bắt đầu trở thành một “thiết bị phần cứng thông minh” chứ không còn đơn thuần là bộ tạo delay nữa.