Skip to content

Công nghệ 9 giây

“Website hướng dẫn, thử nghiệm và giải thích công nghệ theo cách dễ hiểu cho mọi người.”

Menu
  • Home
  • Công nghệ
  • STM32
  • ESP32
  • Altium Desinger
  • Kiến thức điện máy
Menu

BÀI 45 – TIMER ENCODER MODE TRÊN STM32F103

Posted on August 23, 2026August 23, 2026 by congnghe9s

BÀI 45 – TIMER ENCODER MODE TRÊN STM32F103

Đọc encoder bằng phần cứng Timer → xác định chiều → tính RPM → tính quãng đường

Đây là một bài rất quan trọng nếu mục tiêu của bạn là tiến tới lập trình robot di chuyển chính xác.

Ở các bài trước, chúng ta đã dùng Timer để:

Bài 36 → Timer Interrupt
Bài 39 → Input Capture
Bài 41 → PWM
Bài 42 → Servo
Bài 43 → HC-SR04
Bài 44 → HC-SR04 + State Machine

Bây giờ chúng ta dùng chính Timer cho một nhiệm vụ khác:

Đọc encoder trực tiếp bằng phần cứng Timer mà không cần CPU đếm từng xung.


1. Encoder là gì?

Encoder là cảm biến biến chuyển động quay thành tín hiệu điện.

Ví dụ một encoder có:

A
B

Hai tín hiệu này lệch pha nhau:

A: ──┐    ┌────┐    ┌────
     └────┘    └────┘

B: ─────┐    ┌────┐    ┌──
        └────┘    └────┘

Hai tín hiệu này gọi là:

Quadrature Encoder


2. Tại sao cần hai tín hiệu A và B?

Nếu chỉ có:

A

ta biết:

có quay

nhưng khó biết:

quay trái hay quay phải

Có hai tín hiệu:

A
B

ta có thể xác định chiều.


3. Nhìn vào hai tín hiệu

Ví dụ chiều 1:

A:  ┌───┐   ┌───┐
────┘   └───┘   └──

B:    ┌───┐   ┌───┐
──────┘   └───┘   └

Chiều ngược lại:

B:  ┌───┐   ┌───┐
────┘   └───┘   └──

A:    ┌───┐   ┌───┐
──────┘   └───┘   └

Sự lệch pha giữa A và B cho phép Timer xác định chiều quay.


4. Encoder Mode là gì?

STM32F103 có một chức năng rất hay:

TIMx
 ├── CH1 ← Encoder A
 └── CH2 ← Encoder B

Sau đó Timer tự:

đếm xung
+
xác định chiều

Kết quả nằm trong:

TIMx->CNT

CPU không cần:

if (A) count++;

cho từng xung.


5. Đây là điểm khác biệt rất lớn

Cách thủ công

Encoder
   ↓
GPIO Interrupt
   ↓
CPU
   ↓
đọc A/B
   ↓
count++

Nếu encoder có rất nhiều xung:

CPU bận rất nhiều

Encoder Mode

Encoder
   ↓
TIM Encoder Interface
   ↓
CNT

Phần cứng Timer tự đếm.

CPU chỉ cần:

count = TIM2->CNT;

6. Sơ đồ

Ví dụ sử dụng:

TIM2

với:

PA0 = TIM2_CH1
PA1 = TIM2_CH2

Kết nối:

Encoder A ─────► PA0
Encoder B ─────► PA1

Encoder GND ───► GND
Encoder VCC ────► nguồn phù hợp

7. ⚠️ Chú ý điện áp encoder

Nếu encoder của bạn chạy:

5V

thì không được mặc định đưa tín hiệu 5V vào STM32.

Cần kiểm tra loại output:

Open collector
Push-pull
Line driver

và mức logic thực tế.

Với encoder 5V, nếu output có thể lên 5V, hãy dùng:

level shifting

hoặc mạch phù hợp trước khi đưa vào STM32.


8. Timer nào?

Trong bài học này:

TIM2

và:

PA0 → CH1
PA1 → CH2

rất thuận tiện.

             STM32F103

        PA0 ───────── Encoder A
         │
      TIM2_CH1

        PA1 ───────── Encoder B
         │
      TIM2_CH2

9. Encoder Mode có những chế độ nào?

Thanh ghi:

TIM2->SMCR

có phần:

SMS

để chọn Slave Mode.

Encoder Interface có các mode chính:

SMS = 001
SMS = 010
SMS = 011

Trong thực tế ta thường dùng:

Encoder Mode 1
Encoder Mode 2
Encoder Mode 3

10. Encoder Mode 3

Đây là mode rất hay:

TI1 + TI2

Timer có thể đếm trên cả hai tín hiệu.

A ──► CH1
B ──► CH2

Khi đó độ phân giải tăng.


11. Ví dụ encoder 20 PPR

Giả sử:

20 pulse/revolution

Nếu dùng:

x1

thì:

20 count/rev

Nếu x2:

40 count/rev

Nếu x4:

80 count/rev

Đây là lý do quadrature encoder rất hữu ích.


12. PPR và CPR rất dễ gây nhầm

Bạn cần phân biệt:

PPR
CPR
Counts

Một encoder ghi:

100 PPR

không phải lúc nào cũng có nghĩa:

100 count/revolution

Nếu đọc quadrature x4:

100 × 4
=
400 counts/revolution

Vì vậy khi tính RPM, phải biết chính xác nhà sản xuất định nghĩa PPR/CPR như thế nào.


13. Cấu hình GPIO

Encoder A/B là input.

Ví dụ dùng Pull-up:

void Encoder_GPIO_Init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

    /*
        PA0 = TIM2_CH1
        Input Pull-up
    */

    GPIOA->CRL &= ~(0xF << 0);
    GPIOA->CRL |=  (0x8 << 0);

    GPIOA->ODR |= GPIO_ODR_ODR0;


    /*
        PA1 = TIM2_CH2
        Input Pull-up
    */

    GPIOA->CRL &= ~(0xF << 4);
    GPIOA->CRL |=  (0x8 << 4);

    GPIOA->ODR |= GPIO_ODR_ODR1;
}

Ở đây:

CNF = 10
MODE = 00

tức:

Input Pull-up / Pull-down

và:

ODR = 1

chọn:

Pull-up

14. Cấu hình TIM2

Đầu tiên bật clock:

RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

Reset Timer:

TIM2->CR1 = 0;
TIM2->CR2 = 0;
TIM2->SMCR = 0;
TIM2->CCMR1 = 0;
TIM2->CCER = 0;

15. Cấu hình CH1 và CH2

Cả hai channel phải ở Input.

TIM2->CCMR1 |= TIM_CCMR1_CC1S_0;
TIM2->CCMR1 |= TIM_CCMR1_CC2S_0;

Tức:

CH1 → TI1
CH2 → TI2

16. Enable Capture channels

TIM2->CCER |= TIM_CCER_CC1E;
TIM2->CCER |= TIM_CCER_CC2E;

17. Chọn Encoder Mode

Ví dụ:

TIM2->SMCR |= TIM_SMCR_SMS_0;
TIM2->SMCR |= TIM_SMCR_SMS_1;

Ta có:

SMS = 011

→ Encoder Mode 3.


18. ARR

Để Counter chạy 16-bit:

TIM2->ARR = 0xFFFF;

Timer:

0
1
2
...
65535
0
1
...

19. Counter bắt đầu từ 0

TIM2->CNT = 0;

Sau đó:

TIM2->CR1 |= TIM_CR1_CEN;

Timer bắt đầu đếm encoder.


20. Hàm Init hoàn chỉnh

void Encoder_TIM2_Init(void)
{
    /*
        Clock GPIOA
    */

    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

    /*
        Clock TIM2
    */

    RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;


    /*
        PA0 = CH1
        PA1 = CH2

        Input Pull-up
    */

    GPIOA->CRL &= ~(0xFF);

    GPIOA->CRL |=
        (0x8 << 0) |
        (0x8 << 4);

    GPIOA->ODR |=
        GPIO_ODR_ODR0 |
        GPIO_ODR_ODR1;


    /*
        Reset Timer
    */

    TIM2->CR1 = 0;
    TIM2->SMCR = 0;
    TIM2->CCMR1 = 0;
    TIM2->CCER = 0;


    /*
        CH1 = TI1
        CH2 = TI2
    */

    TIM2->CCMR1 |= TIM_CCMR1_CC1S_0;
    TIM2->CCMR1 |= TIM_CCMR1_CC2S_0;


    /*
        Enable CH1 + CH2
    */

    TIM2->CCER |=
        TIM_CCER_CC1E |
        TIM_CCER_CC2E;


    /*
        Encoder Mode 3

        SMS = 011
    */

    TIM2->SMCR |=
        TIM_SMCR_SMS_0 |
        TIM_SMCR_SMS_1;


    /*
        Counter range
    */

    TIM2->ARR = 0xFFFF;

    /*
        Start from zero
    */

    TIM2->CNT = 0;

    /*
        Start Timer
    */

    TIM2->CR1 |= TIM_CR1_CEN;
}

21. Đọc encoder

Bây giờ chỉ cần:

uint16_t Encoder_GetCount(void)
{
    return TIM2->CNT;
}

Ví dụ:

uint16_t count;

count = Encoder_GetCount();

22. Điều kỳ diệu nằm ở đây

Bạn không viết:

if (A)
{
    count++;
}

Không cần:

attachInterrupt()

Không cần ISR cho từng xung.

Phần cứng làm:

A/B
 ↓
TIM2
 ↓
CNT

23. Khi quay thuận

Ví dụ:

CNT:

0
1
2
3
4
5
...

24. Khi quay ngược

Counter có thể:

100
99
98
97
96
...

Tức Timer tự biết chiều dựa trên quan hệ pha A/B.


25. Đây là Encoder Mode thực sự

Bạn có thể kiểm tra:

int16_t count =
    (int16_t)TIM2->CNT;

Ví dụ:

+500

→ quay một chiều.

-500

→ chiều ngược lại.


26. Tại sao dùng int16_t?

TIM2->CNT là:

16-bit unsigned

nhưng khi dùng encoder, ta thường xem Counter như:

signed position

nên:

int16_t

rất tiện.

Ví dụ:

0x0005 → +5
0xFFFB → -5

Đây chính là two’s complement mà bạn đã học trong phần C và biểu diễn số.


27. Overflow không phải lỗi

Giả sử:

CNT = 65535

thêm một count:

CNT = 0

Nếu nhìn dưới dạng int16_t:

32767
→
-32768

Điều này có thể rất hữu ích khi tính delta count.


28. Đo vị trí bằng Encoder

Giả sử:

counts_per_rev = 400

Counter:

CNT = 100

thì góc:

[
Angle =
\frac{100}{400}\times360
]

[
Angle=90^\circ
]


29. Công thức tổng quát

Angle =
Count × 360 / CountsPerRevolution

Ví dụ:

float angle =
    count * 360.0f /
    COUNTS_PER_REV;

30. Đo RPM

Bây giờ đến phần rất quan trọng.

RPM:

Revolutions Per Minute – số vòng/phút.

Giả sử trong khoảng thời gian:

100 ms

ta đếm được:

40 counts

Encoder:

400 counts/rev

31. Tính số vòng

40 / 400
=
0.1 revolution

Trong:

0.1 second

32. Đổi sang RPM

0.1 vòng trong 0.1 s.

Trong 1 giây:

1 rev/s

Trong 1 phút:

60 rev/min

→

60 RPM

33. Công thức RPM

Nếu:

Δcount = số count thay đổi
Δt = thời gian đo tính bằng giây
CPR = counts/revolution

thì:

[
RPM=
\frac{\Delta count}{CPR}
\times
\frac{60}{\Delta t}
]


34. Ví dụ

Δcount = 40
CPR = 400
Δt = 0.1 s

[
RPM=
\frac{40}{400}
\times
\frac{60}{0.1}
]

[
RPM=60
]


35. Không nên đo RPM bằng cách đọc CNT một lần

Sai:

rpm = TIM2->CNT;

CNT chỉ cho biết:

vị trí tương đối

Muốn biết tốc độ phải biết:

count thay đổi bao nhiêu
trong bao lâu

36. Dùng SysTick 1 ms

Bạn đã có hệ thống:

SysTick = 1 ms

Ta có thể lấy mẫu mỗi:

100 ms

Ví dụ:

if (millis - last_measure >= 100)
{
    ...
}

37. Thuật toán đo RPM

t0
│
├── đọc CNT = count0
│
│       100 ms
│
├── đọc CNT = count1
│
└── delta = count1 - count0

Sau đó:

delta
 ↓
CPR
 ↓
RPM

38. Code đo RPM

int16_t Encoder_GetDelta(void)
{
    static int16_t last_count = 0;

    int16_t current_count =
        (int16_t)TIM2->CNT;

    int16_t delta =
        current_count - last_count;

    last_count =
        current_count;

    return delta;
}

Sau mỗi:

100 ms

ta gọi:

delta = Encoder_GetDelta();

39. Tính RPM

float Encoder_GetRPM(
    int16_t delta,
    float sample_time_s,
    uint16_t counts_per_rev)
{
    return
        ((float)delta /
         counts_per_rev)
        *
        (60.0f /
         sample_time_s);
}

Ví dụ:

rpm =
    Encoder_GetRPM(
        delta,
        0.1f,
        400
    );

40. Nhưng hãy cẩn thận với kiểu dữ liệu

Nếu:

delta < 0

thì:

RPM < 0

Ví dụ:

+60 RPM

→ thuận.

-60 RPM

→ ngược.

Đây là cách rất thuận tiện để đưa thông tin encoder vào điều khiển motor.


41. Đo quãng đường

Nếu encoder gắn vào bánh xe, ta có thể tính quãng đường.

Giả sử bánh xe:

đường kính = 65 mm

Chu vi:

[
C=\pi D
]

[
C\approx204.2mm
]


42. Với 400 counts/rev

Mỗi count tương ứng:

[
204.2/400
]

≈

0.51 mm

43. Nếu đếm được 4000 counts

Số vòng:

[
4000/400=10
]

Quãng đường:

[
10\times204.2
]

≈

2042 mm

≈

2.04 m

44. Công thức quãng đường

Distance =
Count / CPR × Wheel Circumference

hoặc:

[
Distance=
\frac{Count}{CPR}
\times
\pi D
]


45. Đây là nền tảng odometry

Khi robot có:

Encoder bánh trái
Encoder bánh phải

ta có:

Left Count
Right Count

từ đó có thể tính:

Left Distance
Right Distance

và suy ra:

Robot đã đi bao xa
Robot quay bao nhiêu
Robot đang ở đâu

Đây là odometry.


46. Robot hai bánh

Ví dụ:

         FRONT
           ↑

   ┌───────────────┐
   │               │
  (L)             (R)
   │               │
   └───────────────┘

Có:

Encoder Left
Encoder Right

Nếu:

Left = Right

→ đi thẳng.

Nếu:

Left > Right

→ robot cong về một phía.

Nếu:

Left < Right

→ cong về phía còn lại.


47. Đây là lý do encoder quan trọng hơn chỉ PWM

PWM nói:

Motor chạy 60%

nhưng không đảm bảo:

tốc độ thực = 60%

Vì:

tải
ma sát
điện áp
pin
mặt sàn

thay đổi.

Encoder cho biết:

Motor thực sự đang quay nhanh bao nhiêu.

48. Từ đó ta có Closed-loop Control

Không encoder:

PWM
 ↓
Motor

Đây là:

Open Loop

Có encoder:

         ┌──────────────┐
         │              │
PWM ───► Motor ───► Encoder
         ▲              │
         │              │
         └── Controller ◄┘

Đây là:

Closed-loop control


49. Ví dụ điều khiển tốc độ

Muốn:

Target = 100 RPM

Đo được:

Actual = 80 RPM

Sai số:

[
Error=100-80=20
]

Controller tăng PWM.

Nếu:

Actual = 110 RPM

thì:

[
Error=100-110=-10
]

Controller giảm PWM.

Đây chính là nền tảng của:

PID

mà chúng ta có thể học sau này.


50. Bài tập thực hành cực kỳ quan trọng

Nếu bạn có encoder, hãy thử:

Bước 1

Để bánh xe đứng yên.

CNT = 0

Bước 2

Quay bánh bằng tay một vòng.

Xem:

CNT

Bước 3

Quay ngược lại.

Xem:

CNT

Bạn phải thấy:

chiều thuận → CNT tăng
chiều ngược → CNT giảm

51. Bài tập 2

Giả sử encoder:

100 PPR

và Timer Encoder Mode x4.

Hỏi:

Counts/revolution = ?

Đáp án:

[
100\times4=400
]

→ 400 counts/revolution


52. Bài tập 3

Encoder:

400 CPR

Trong:

100 ms

đếm:

80 counts

RPM:

[
RPM=
\frac{80}{400}
\times
\frac{60}{0.1}
]

[
RPM=120
]


53. Bài tập 4

Bánh xe:

D = 70 mm

Encoder:

400 CPR

Robot đi:

2000 counts

Khoảng cách:

[
2000/400=5 vòng
]

Chu vi:

[
\pi\times70
\approx219.9mm
]

Quãng đường:

[
5\times219.9
\approx1099.5mm
]

≈

1.10 m

54. Một vấn đề thực tế: Encoder nhiễu

Encoder cơ khí có thể bị:

bounce
noise

Nếu tín hiệu xấu:

A ──┐ ┌┐ ┌──┐
    └─┘└─┘  └

Timer có thể đếm những chuyển đổi không mong muốn.


55. STM32 có Input Filter

Đây là một tính năng rất đáng học.

Trong:

TIM2->CCMR1

có:

IC1F
IC2F

để lọc tín hiệu Input Capture.

Ví dụ:

Encoder
 ↓
Digital Filter
 ↓
Timer

Điều này đặc biệt hữu ích với encoder cơ khí.


56. Nhưng không nên bật Filter một cách mù quáng

Filter càng mạnh:

noise ↓

nhưng:

đáp ứng chậm hơn

Nếu encoder tốc độ cao, filter quá lớn có thể làm mất xung.

Vì vậy:

Filter phải được chọn dựa trên tần số encoder thực tế.


57. Một lỗi rất thường gặp

Người mới thường tính:

RPM = CNT × ...

Điều này sai nếu Counter không được reset.

Đúng phải là:

ΔCNT = CNT_now - CNT_old

rồi:

ΔCNT / Δt

58. Kiến trúc driver nên như thế nào?

Sau Bài 44, chúng ta tiếp tục giữ nguyên tư duy module:

Encoder.c
Encoder.h

API:

Encoder_Init();

Encoder_GetCount();

Encoder_GetDelta();

Encoder_GetRPM();

Encoder_GetDistance();

Application không cần biết:

TIM2->SMCR
TIM2->CCMR1
TIM2->CCER

59. Sơ đồ phần mềm

                  APPLICATION
                       │
              ┌────────┴────────┐
              │                 │
          Robot Control      Display
              │
              ▼
         Encoder Driver
              │
              ▼
            TIM2
          ┌───────┐
          │       │
        CH1       CH2
         │         │
         A         B
         │         │
       Encoder

60. Liên hệ với Bài 41 – PWM

Bây giờ chúng ta có hai chiều:

PWM

STM32
 ↓
TIM PWM
 ↓
Motor

Encoder

Motor
 ↓
Encoder
 ↓
TIM Encoder
 ↓
STM32

Ghép lại:

                ┌─────────────┐
                │             │
PWM ───────────►│    MOTOR    │
                │             │
                └──────┬──────┘
                       │
                       ▼
                   ENCODER
                       │
                       ▼
                   TIM2 CNT
                       │
                       ▼
                  Controller
                       │
                       └────► PWM

Đây chính là vòng điều khiển tốc độ.


61. Một điều cần đặc biệt nhớ về TIM2

Trong bài này:

TIM2

được sử dụng cho:

Encoder Mode

Điều đó có nghĩa nếu bạn đồng thời muốn dùng:

TIM2_CH1 PWM

thì sẽ có xung đột tài nguyên.

Đây là một bài học rất thực tế:

Timer là tài nguyên phần cứng hữu hạn.

Bạn phải lập sơ đồ phân bổ Timer trước khi thiết kế firmware lớn.


62. Ví dụ phân bổ Timer cho robot

Một thiết kế có thể là:

TIM1
 ├─ PWM Motor Left
 └─ PWM Motor Right

TIM2
 ├─ Encoder Left A
 └─ Encoder Left B

TIM3
 ├─ Encoder Right A
 └─ Encoder Right B

TIM4
 └─ Servo / auxiliary

SysTick
 └─ 1 ms scheduler

Đây là một kiến trúc rất mạnh cho robot hai bánh.


63. Nhưng STM32F103C8T6 còn giới hạn chân

Vì vậy khi thiết kế robot thật, phải tính:

GPIO
Timer
Channel
AFIO
Remap
PWM
Encoder
UART
I2C
SPI

trước khi chốt PCB.

Đây chính là lý do kiến thức:

STM32 Pin Mapping
+
Timer
+
Alternate Function

rất quan trọng.


64. Tóm tắt Bài 45

Bạn cần nhớ:

Encoder A
     ↓
TIM CH1

Encoder B
     ↓
TIM CH2

        ↓

Encoder Mode

        ↓

      CNT

        ↓
 ┌──────┼────────┐
 ▼      ▼        ▼
Count  Direction  ΔCount
                  │
                  ▼
                 RPM
                  │
                  ▼
              Distance

65. Công thức quan trọng nhất

Số vòng:

[
Revolution=
\frac{Count}{CPR}
]

Góc:

[
Angle=
\frac{Count}{CPR}\times360^\circ
]

RPM:

[
RPM=
\frac{\Delta Count}{CPR}
\times
\frac{60}{\Delta t}
]

Quãng đường:

[
Distance=
\frac{Count}{CPR}
\times
\pi D
]


66. Và kiến thức quan trọng nhất

Đừng chỉ nhớ:

TIM2->SMCR = ...

Hãy hiểu bản chất:

Encoder Mode biến Timer thành một bộ đếm quadrature bằng phần cứng.

Thay vì:

xung → CPU → đếm

ta có:

A/B → Timer Hardware → CNT

CPU chỉ đọc kết quả khi cần.


Bài 46 – bước tiếp theo

Sau khi hiểu Encoder Mode, bài tiếp theo rất đáng học là:

BÀI 46 – ĐIỀU KHIỂN TỐC ĐỘ MOTOR DC BẰNG ENCODER + PWM

Ta sẽ ghép trực tiếp:

PWM ───────► Motor
              │
              ▼
           Encoder
              │
              ▼
        TIM Encoder Mode
              │
              ▼
          Đo RPM thực
              │
              ▼
       Target RPM
              │
              ▼
          Sai số
              │
              ▼
        PWM điều chỉnh

và từ đó bắt đầu xây dựng bộ điều khiển tốc độ vòng kín, trước khi tiến tới PID tốc độ cho robot.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Recent Posts

  • Bạn có thể hướng dẫn tôi quy trình sửa chữa một mainboard laptop cũng như mainboard của máy tính để bàn không?
  • Có những mức nguồn DC nào trên một bo mạch mainboard laptop?Kỹ thuật đốt dòng để kiểm tra nguồn laptop được thực hiện như thế nào?
  • bài tiếp theo hợp lý là: BÀI 63 – SENSOR FUSION THỰC CHIẾN: ENCODER + IMU MPU6050 + EKF + ODOMETRY X,Y,θ TRÊN STM32F103
  • BÀI 62 – EKF (EXTENDED KALMAN FILTER) CHO ROBOT 2 BÁNH: FUSION ENCODER + IMU + ƯỚC LƯỢNG X, Y, θ, V, ω
  • BÀI 61 – KALMAN FILTER CHO ENCODER + IMU: ƯỚC LƯỢNG TỐC ĐỘ, GÓC θ VÀ SENSOR FUSION

Recent Comments

No comments to show.

Archives

  • September 2026
  • August 2026
  • July 2026
  • June 2026
  • May 2026
  • February 2026

Categories

  • Altium Desinger
  • Công nghệ
  • ESP32
  • Khác
  • Kiến thức điện máy
  • STM32
Live 3D Globes Visitor
©2026 Công nghệ 9 giây | Design: Newspaperly WordPress Theme