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 51 – ROBOT 2 BÁNH ĐI THẲNG CHÍNH XÁC BẰNG 2 ENCODER + PID TRÊN STM32F103

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

BÀI 51 – ROBOT 2 BÁNH ĐI THẲNG CHÍNH XÁC BẰNG 2 ENCODER + PID TRÊN STM32F103

Bài 50 chúng ta đã có 2 motor độc lập + 2 encoder + 2 PID tốc độ.

Bài 51 nâng cấp hệ thống thành:

Ra lệnh cho robot đi một quãng đường cụ thể, đồng thời tự sửa khi bánh trái/phải chạy không đều.

Đây là bước rất quan trọng để tiến tới robot tự hành.


1. Mục tiêu của Bài 51

Ví dụ ta muốn:

Robot → đi thẳng 100 cm → dừng

Không còn điều khiển kiểu:

PWM = 60%
delay(5000)
STOP

mà sẽ là:

TARGET DISTANCE
       ↓
ENCODER POSITION
       ↓
POSITION CONTROL
       ↓
TARGET SPEED
       ↓
2 × SPEED PID
       ↓
2 × PWM
       ↓
2 MOTOR

Đồng thời:

Encoder Left
      ↕
  Synchronization
      ↕
Encoder Right

để robot cố gắng giữ đường thẳng.


2. Tại sao chỉ dùng 2 PID tốc độ vẫn chưa đủ?

Giả sử:

Target L = 100 RPM
Target R = 100 RPM

Hai PID làm rất tốt:

RPM L ≈ 100
RPM R ≈ 100

Nhưng sau 5 giây có thể xảy ra:

Left  = 5000 counts
Right = 4900 counts

Tại sao?

Encoder có thể có:

  • sai số đo;
  • bánh trượt;
  • đường kính bánh khác nhau;
  • tải khác nhau;
  • thời gian đáp ứng khác nhau.

Vì vậy:

Cùng RPM không đảm bảo hai bánh đi được cùng quãng đường trong thực tế.


3. Ta cần thêm Position Control

Bây giờ hệ thống có hai tầng:

             TARGET DISTANCE
                    ↓
             POSITION CONTROL
                    ↓
              TARGET SPEED
                    ↓
        ┌───────────┴───────────┐
        ↓                       ↓
    SPEED PID L             SPEED PID R
        ↓                       ↓
      PWM L                   PWM R
        ↓                       ↓
     MOTOR L                 MOTOR R
        ↓                       ↓
    ENCODER L               ENCODER R

Đây chính là kiến trúc cascade control mà chúng ta bắt đầu xây dựng ở Bài 49–50.


4. Đầu tiên phải hiểu counts

Encoder không đo:

cm

mà đo:

counts

Ví dụ:

Encoder = 400 counts/revolution

Bánh xe:

Diameter = 65 mm

Chu vi:

[
C=\pi D
]

[
C=3.1416\times65
]

≈

204.2 mm

Mỗi vòng bánh đi khoảng:

204.2 mm

5. Tính counts/mm

Nếu:

400 counts

cho một vòng:

204.2 mm

thì:

[
counts/mm =
\frac{400}{204.2}
]

≈

1.96 counts/mm

6. Từ mm → encoder counts

Muốn robot đi:

1000 mm

thì:

[
TargetCounts =
1000\times1.96
]

≈

1960 counts

Do đó:

Robot_MoveDistance(1000);

có thể chuyển thành:

target_counts = 1960;

7. Nhưng có một vấn đề

Thông số trên chỉ là lý thuyết.

Đường kính bánh ghi:

65 mm

không có nghĩa đường kính lăn thực tế chính xác 65 mm.

Có thể thực tế:

63.8 mm

hoặc:

64.5 mm

Ngoài ra encoder có thể không phải đúng:

400 counts/rev

theo cách mà chúng ta đang đếm.

Vì vậy robot cần calibration.


8. Calibration thực tế

Cho robot chạy:

1000 counts

Đo khoảng cách thật.

Ví dụ:

Encoder = 1000
Actual distance = 510 mm

thì:

[
counts/mm =
\frac{1000}{510}
]

≈

1.961

Đây là thông số thực tế tốt hơn nhiều.


9. Tạo cấu hình

#define COUNTS_PER_MM 1.961f

Sau đó:

int32_t Distance_To_Counts(float mm)
{
    return (int32_t)
           (mm * COUNTS_PER_MM);
}

Ví dụ:

Distance_To_Counts(1000.0f);

→ khoảng:

1961 counts

10. Cấu trúc Robot

Ta tạo:

typedef struct
{
    int32_t left_start;
    int32_t right_start;

    int32_t target_counts;

    float target_speed;

    uint8_t moving;

} RobotMove_t;

Khai báo:

RobotMove_t robot_move;

11. Khi bắt đầu di chuyển

Ví dụ:

Robot_MoveDistance(1000.0f);

ta lưu vị trí hiện tại:

robot_move.left_start =
    left_encoder.position;

robot_move.right_start =
    right_encoder.position;

Sau đó:

robot_move.target_counts =
    Distance_To_Counts(1000.0f);

12. Tại sao phải lưu vị trí bắt đầu?

Giả sử trước khi ra lệnh:

Left position  = 5000
Right position = 4900

Ta muốn đi thêm:

1000 mm

Không thể yêu cầu:

Position = 1961

vì đó là tọa độ tuyệt đối.

Ta cần:

Start + Distance

Ví dụ:

Left target
= 5000 + 1961
= 6961

13. Tính quãng đường đã đi

Sau khi chạy:

left_delta =
    left_encoder.position
    - robot_move.left_start;

right_delta =
    right_encoder.position
    - robot_move.right_start;

Ta có:

left_delta
right_delta

14. Không nên chỉ dùng bánh trái

Sai lầm:

distance = left_delta;

Nếu bánh trái trượt thì robot sẽ tính sai.

Ta có thể lấy trung bình:

[
distance =
\frac{left+right}{2}
]

Trong code:

float average_counts =
    ((float)left_delta +
     (float)right_delta) / 2.0f;

15. Đây là vị trí trung bình của robot

             Robot
       ┌──────────────┐
       │              │
       O              O
      LEFT           RIGHT

       │              │
       ↓              ↓
    1000 cnt       980 cnt

          ↓
     Average =
       990 cnt

Ta dùng:

Average Encoder

để quyết định robot đã đi được bao xa.


16. Position Error

Giả sử:

Target = 2000 counts
Actual = 1000 counts

thì:

error =
    target_counts -
    average_counts;

=

1000 counts

17. Position Controller

Giống Bài 49:

[
V_{target}=K_{pos}\times Error
]

Ví dụ:

#define POSITION_KP 0.05f

Nếu:

Error = 1000

thì:

Target speed = 50 RPM

18. Giới hạn tốc độ

#define MAX_MOVE_SPEED 100.0f

Sau đó:

if (target_speed >
    MAX_MOVE_SPEED)
{
    target_speed =
        MAX_MOVE_SPEED;
}

và:

if (target_speed <
    -MAX_MOVE_SPEED)
{
    target_speed =
        -MAX_MOVE_SPEED;
}

19. Nhưng robot vẫn có thể lệch

Ví dụ:

Left  = 1000 counts
Right = 900 counts

Robot đang có xu hướng lệch.

Ta cần:

Synchronization Controller

20. Tính sai lệch hai bánh

float sync_error =
    (float)left_delta -
    (float)right_delta;

Ví dụ:

Left  = 1000
Right = 900

thì:

sync_error = +100

21. Tạo Correction

#define SYNC_KP 0.02f

Ta tính:

float correction =
    SYNC_KP * sync_error;

Ví dụ:

100 × 0.02

=

2 RPM

22. Điều chỉnh tốc độ

Nếu bánh trái đi xa hơn:

Left = 1000
Right = 900

ta muốn:

Left chậm lại
Right nhanh lên

Do đó:

target_left_rpm =
    base_speed - correction;

target_right_rpm =
    base_speed + correction;

Kết quả:

Left  = 98 RPM
Right = 102 RPM

23. Đây là phần cực kỳ quan trọng

Hệ thống hiện tại có 3 tầng logic:

TARGET DISTANCE
       │
       ▼
POSITION CONTROLLER
       │
       ▼
BASE SPEED
       │
       ▼
SYNC CONTROLLER
    ┌──┴──┐
    ▼     ▼
 SPEED L SPEED R
    │     │
    ▼     ▼
 PID L   PID R
    │     │
    ▼     ▼
 PWM L   PWM R

Đây là kiến trúc rất gần với robot thực tế.


24. Tuy nhiên có một vấn đề với SYNC_KP

Nếu correction quá lớn:

Base speed = 50
Correction = 80

ta có:

Left  = -30
Right = 130

Robot có thể quay rất mạnh.

Vì vậy phải giới hạn correction:

#define MAX_SYNC_CORRECTION 20.0f

25. Clamp correction

if (correction >
    MAX_SYNC_CORRECTION)
{
    correction =
        MAX_SYNC_CORRECTION;
}

if (correction <
    -MAX_SYNC_CORRECTION)
{
    correction =
        -MAX_SYNC_CORRECTION;
}

26. Tạo hàm Clamp

Để code sạch hơn:

float Clamp(
    float value,
    float min,
    float max)
{
    if (value < min)
        return min;

    if (value > max)
        return max;

    return value;
}

Sau đó:

correction =
    Clamp(
        correction,
        -MAX_SYNC_CORRECTION,
        MAX_SYNC_CORRECTION);

27. Hàm Robot_MoveDistance()

Ta có thể viết:

void Robot_MoveDistance(
    float distance_mm)
{
    robot_move.left_start =
        left_encoder.position;

    robot_move.right_start =
        right_encoder.position;

    robot_move.target_counts =
        Distance_To_Counts(
            distance_mm);

    robot_move.moving = 1;
}

Ví dụ:

Robot_MoveDistance(1000.0f);

→ robot đi khoảng 1 mét.


28. Hàm kiểm tra hoàn thành

uint8_t Robot_MoveFinished(void)
{
    int32_t left_delta;
    int32_t right_delta;

    float average;

    left_delta =
        left_encoder.position -
        robot_move.left_start;

    right_delta =
        right_encoder.position -
        robot_move.right_start;

    average =
        ((float)left_delta +
         (float)right_delta)
        / 2.0f;

    if (average >=
        robot_move.target_counts)
    {
        return 1;
    }

    return 0;
}

29. Khi đến đích

Không nên ngay lập tức:

PWM = 0;

vì robot đang chạy.

Ví dụ:

100 RPM
   ↓
STOP PWM
   ↓
quán tính
   ↓
robot vẫn chạy
   ↓
vượt đích

Do đó position controller phải giảm tốc trước khi tới đích.

Đây chính là lý do chúng ta dùng:

Target Speed = Kp × Position Error

30. Ví dụ giảm tốc

Giả sử:

Target = 2000

Còn 1000 counts

Speed = 50 RPM

Còn 500 counts

Speed = 25 RPM

Còn 100 counts

Speed = 5 RPM

Còn 20 counts

Speed = 1 RPM

Robot sẽ tiến tới đích nhẹ nhàng hơn.


31. Vùng dừng

Ta tạo:

#define POSITION_TOLERANCE 5

Nếu:

abs(error) <= 5

thì:

target_left_rpm = 0;
target_right_rpm = 0;

và:

robot_move.moving = 0;

32. Nhưng với robot thực tế nên có STOP_ZONE

Ví dụ:

#define STOP_ZONE_COUNTS 5

và:

#define SLOW_ZONE_COUNTS 300

Khi còn:

> 300 counts

→ chạy nhanh.

Khi:

< 300 counts

→ giảm tốc.

Khi:

< 5 counts

→ dừng.


33. State Machine

Bài này nên dùng:

typedef enum
{
    ROBOT_IDLE,
    ROBOT_MOVING,
    ROBOT_SLOWING,
    ROBOT_REACHED
} RobotState_t;

34. ROBOT_IDLE

Không chạy
target_left_rpm = 0;
target_right_rpm = 0;

35. ROBOT_MOVING

Error lớn

Robot chạy về phía target.


36. ROBOT_SLOWING

Khi:

abs(error) < SLOW_ZONE

Position controller tự giảm tốc.


37. ROBOT_REACHED

Khi:

abs(error) <= TOLERANCE

thì:

Speed target = 0

38. Code State Machine

void Robot_PositionControl(void)
{
    float left_delta;
    float right_delta;
    float average;
    float error;
    float base_speed;
    float sync_error;
    float correction;

    left_delta =
        left_encoder.position -
        robot_move.left_start;

    right_delta =
        right_encoder.position -
        robot_move.right_start;

    average =
        (left_delta +
         right_delta) / 2.0f;

    error =
        robot_move.target_counts -
        average;

    if (error < 0)
    {
        error = 0;
    }

    if (error <= POSITION_TOLERANCE)
    {
        robot_move.moving = 0;

        target_left_rpm = 0;
        target_right_rpm = 0;

        return;
    }

    base_speed =
        POSITION_KP * error;

    base_speed =
        Clamp(
            base_speed,
            0,
            MAX_MOVE_SPEED);

    sync_error =
        left_delta -
        right_delta;

    correction =
        SYNC_KP * sync_error;

    correction =
        Clamp(
            correction,
            -MAX_SYNC_CORRECTION,
            MAX_SYNC_CORRECTION);

    target_left_rpm =
        base_speed -
        correction;

    target_right_rpm =
        base_speed +
        correction;
}

Đây là phiên bản đầu tiên để học, sau này ta sẽ cải tiến nhiều.


39. Một vấn đề với code trên

Đoạn:

if (error < 0)
    error = 0;

chỉ phù hợp với:

Robot đi về phía trước

Nếu muốn:

Robot_MoveDistance(-1000);

để đi lùi, phải xử lý dấu.

Đây là một bài học quan trọng:

Không được tùy tiện biến error âm thành 0 nếu hệ thống cần chạy hai chiều.


40. Phiên bản hỗ trợ tiến/lùi

Ta lưu:

int32_t target_counts;

có thể âm hoặc dương.

Ví dụ:

+1960 → tiến
-1960 → lùi

Sau đó:

error =
    target_counts -
    average;

Ta không ép:

if(error < 0)
    error = 0;

mà xử lý dấu.


41. Tính base speed

base_speed =
    POSITION_KP * error;

Ví dụ:

error = -500

→

base_speed = -25 RPM

Robot tự lùi.


42. Nhưng synchronization cũng phải xét chiều

Nếu robot đang lùi:

Left  = -100
Right = -90

thì cần tránh viết correction một cách máy móc.

Ở phiên bản nâng cao, ta sẽ dùng signed traveled distance và quy ước encoder thật chặt chẽ.

Đây là lý do Bài 51 trước hết nên test đi thẳng tiến, sau đó mới mở rộng sang lùi.


43. Chu kỳ điều khiển

Ta vẫn sử dụng Timer từ các bài trước:

Timer tick = 1 ms

Ví dụ:

1 ms
 ↓
system_tick++

10 ms
 ↓
Encoder_Update()
 ↓
Calculate_RPM()
 ↓
Speed_PID()

20 ms
 ↓
Position_Control()

44. Tại sao Position Control có thể chậm hơn Speed PID?

Vì:

Speed

thay đổi nhanh.

Trong khi:

Position

thay đổi tương đối chậm.

Ví dụ:

Speed PID      = 100 Hz
Position loop  = 50 Hz

tức:

10 ms
20 ms

45. Luồng hoàn chỉnh

                    TARGET
                 1000 mm
                     │
                     ▼
              mm → Encoder
                     │
                     ▼
             Position Controller
                     │
                 Base Speed
                     │
              ┌──────┴──────┐
              │             │
              ▼             ▼
          Sync Control   Sync Control
              │             │
              ▼             ▼
          Target L       Target R
              │             │
              ▼             ▼
           PID LEFT      PID RIGHT
              │             │
              ▼             ▼
           PWM LEFT      PWM RIGHT
              │             │
              ▼             ▼
          MOTOR LEFT    MOTOR RIGHT
              │             │
              ▼             ▼
          ENC LEFT      ENC RIGHT
              │             │
              └──────┬──────┘
                     ▼
                 Position

46. Ví dụ thực tế

Giả sử:

Wheel diameter = 65 mm
Encoder = 400 counts/rev

Robot nhận:

Robot_MoveDistance(1000.0f);

Mục tiêu:

1000 mm

Ban đầu:

Left  = 0
Right = 0

Position error:

1960 counts

Position controller:

Base speed = 100 RPM

Sau một thời gian:

Left  = 800
Right = 760

Average:

780

Error:

1180

Base speed:

59 RPM

Sync error:

40

Correction:

0.8 RPM

Target:

Left  = 58.2 RPM
Right = 59.8 RPM

Bánh phải được tăng nhẹ để bắt kịp.


47. Gần đích

Left  = 1850
Right = 1830

Average:

1840

Error:

120

Base:

6 RPM

Sync correction:

0.4 RPM

Target:

Left  = 5.6 RPM
Right = 6.4 RPM

Robot đi rất chậm.


48. Đích

Left  = 1958
Right = 1959

Average:

1958.5

Nếu tolerance:

5 counts

→ đạt.

Target RPM L = 0
Target RPM R = 0

→ dừng.


49. Điều rất quan trọng: robot không nhất thiết phải có hai encoder giống hệt nhau

Ví dụ:

Left encoder:
400 CPR

Right encoder:
500 CPR

vẫn có thể sử dụng.

Chỉ cần:

LEFT_COUNTS_PER_REV
RIGHT_COUNTS_PER_REV

được cấu hình riêng.


50. Đường kính hai bánh cũng có thể khác

Ta có:

#define LEFT_WHEEL_DIAMETER_MM  65.0f
#define RIGHT_WHEEL_DIAMETER_MM 64.5f

Khi đó:

LEFT COUNTS/MM
RIGHT COUNTS/MM

khác nhau.

Đây chính là lý do calibration từng bánh rất hữu ích.


51. Calibration chuyên nghiệp hơn

Thay vì:

COUNTS_PER_MM

một giá trị chung, ta dùng:

float left_counts_per_mm;
float right_counts_per_mm;

Khi robot đi:

1000 mm

ta có:

Left target counts
Right target counts

khác nhau nếu cần.


52. Nhưng ở Bài 51 hãy bắt đầu đơn giản

Trình tự học nên là:

1. Hai encoder đọc đúng
2. Hai RPM đúng
3. Hai PID tốc độ ổn định
4. Robot chạy cùng RPM
5. Position control
6. Synchronization
7. Calibration
8. Đi thẳng chính xác

Đừng đưa tất cả vào cùng một lúc.


53. Những lỗi thường gặp

Lỗi 1 – Robot quay thay vì đi thẳng

Kiểm tra:

Motor direction
Encoder direction

Lỗi 2 – PID tăng PWM liên tục

Có thể:

Encoder direction sai

Lỗi 3 – Robot đến gần đích rồi dao động

Giảm:

POSITION_KP

hoặc giảm:

MAX_MOVE_SPEED

Lỗi 4 – Robot luôn lệch một phía

Kiểm tra:

Left/Right encoder

và:

SYNC_KP

Lỗi 5 – Robot vượt đích

Có thể:

MAX_MOVE_SPEED quá cao

hoặc:

POSITION_KP quá lớn

hoặc PID tốc độ chưa được tune tốt.


54. Bài thực hành số 1

Cho:

Target = 500 mm

Robot phải:

Đi 500 mm
↓
Dừng

Đo:

Actual distance

Ví dụ:

Target = 500 mm
Actual = 527 mm

Sai số:

+27 mm

55. Bài thực hành số 2

Đi:

1000 mm

ghi:

Left counts
Right counts

Ví dụ:

Left  = 1960
Right = 1910

Ta thấy:

Difference = 50 counts

→ synchronization chưa đủ tốt hoặc có sai lệch cơ khí.


56. Bài thực hành số 3

Cho robot:

1000 mm

và:

SYNC_KP = 0

Ghi lại độ lệch.

Sau đó:

SYNC_KP = 0.01

rồi:

SYNC_KP = 0.02

So sánh.

Bạn sẽ thực sự nhìn thấy tác dụng của feedback control.


57. Một khái niệm rất quan trọng từ Bài 51

Bạn đang bắt đầu chuyển từ:

"Điều khiển motor"

sang:

"Điều khiển chuyển động của robot"

Hai khái niệm này hoàn toàn khác nhau.


58. Bài 50

Ta nói:

Motor trái chạy 100 RPM
Motor phải chạy 100 RPM

Đó là:

Motor control


59. Bài 51

Ta nói:

Robot đi 1000 mm

Đó là:

Motion control

Đây là một bước tiến rất lớn.


60. Kiến trúc cuối của Bài 51

                         STM32F103
                             │
                             ▼
                    TARGET DISTANCE
                         1000 mm
                             │
                             ▼
                    POSITION CONTROLLER
                             │
                         BASE SPEED
                             │
                             ▼
                   SYNC / STRAIGHT CONTROL
                      ┌──────┴──────┐
                      ▼             ▼
                 TARGET L      TARGET R
                      │             │
                      ▼             ▼
                   PID LEFT      PID RIGHT
                      │             │
                      ▼             ▼
                   PWM LEFT      PWM RIGHT
                      │             │
                      ▼             ▼
                  MOTOR LEFT    MOTOR RIGHT
                      │             │
                      ▼             ▼
                  ENCODER L      ENCODER R
                      │             │
                      └──────┬──────┘
                             │
                             ▼
                      ROBOT POSITION

Đây là nền tảng rất quan trọng cho robot 2 bánh.


61. Và từ đây Bài 52 sẽ còn thú vị hơn

Sau khi robot có thể:

đi 100 mm
đi 500 mm
đi 1000 mm

và tự giữ hai bánh tương đối đồng tốc, bước tiếp theo hợp lý là:

BÀI 52 – ĐIỀU KHIỂN GÓC QUAY ROBOT BẰNG 2 ENCODER

Chúng ta sẽ học cách ra lệnh:

Đi thẳng 500 mm
        ↓
Quay trái 90°
        ↓
Đi thẳng 1000 mm
        ↓
Quay phải 90°

và từ đó bắt đầu xây dựng khái niệm:

DISTANCE
+
ANGLE
+
ENCODER
+
PID
        ↓
ODOMETRY

Đó chính là bước đầu tiên để STM32 biết robot đang ở vị trí (X, Y) và góc hướng θ trên mặt phẳng.

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