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 57 – TRAJECTORY GENERATION: TỰ SINH QUỸ ĐẠO CHO ROBOT 2 BÁNH

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

BÀI 57 – TRAJECTORY GENERATION

TỰ SINH QUỸ ĐẠO CHO ROBOT 2 BÁNH BẰNG STM32F103

Ở Bài 56, robot nhận một Path[] gồm nhiều waypoint rồi dùng Pure Pursuit để bám đường.

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

Nếu muốn robot đi từ A → B thật mượt, chúng ta có thực sự muốn tự tạo 100, 200 hay 500 waypoint không?

Không.

Ở Bài 57, chúng ta học cách để STM32 tự sinh quỹ đạo theo công thức toán học.


1. Path và Trajectory khác nhau thế nào?

Đây là khái niệm cực kỳ quan trọng.

Path

Path chỉ nói:

Robot phải đi qua đâu?

Ví dụ:

A ●──────────────● B

Ta có:

X(t)
Y(t)

nhưng chưa quan tâm robot đi nhanh hay chậm.


Trajectory

Trajectory nói:

Robot phải đi qua đâu và vào thời điểm nào?

Ví dụ:

t = 0s       → A
t = 1s       → P1
t = 2s       → P2
t = 3s       → P3
t = 4s       → B

Do đó trajectory có:

[
x(t),y(t),\theta(t)
]

và thậm chí:

[
v(t),\omega(t)
]


2. Hãy hình dung như thế này

Path:

A ●────────────────────● B

Robot có thể đi:

10 giây
20 giây
100 giây

miễn là đi đúng đường.

Trajectory:

A ●──●──●──●──●──●──● B
   1s 2s 3s 4s 5s 6s

Bây giờ ta quy định luôn thời gian.


3. Tại sao Trajectory Generation quan trọng?

Trong robot thực tế, ta thường muốn:

không giật
không tăng tốc đột ngột
không giảm tốc đột ngột
không đổi hướng quá nhanh

Ví dụ không tốt:

0 mm/s
   │
   │
   └────────────── 500 mm/s

Robot sẽ:

GIẬT

Tốt hơn:

0
   ╱
  ╱
 ╱
╱────────────

Tức là:

[
V(t)
]

thay đổi từ từ.


4. Ba tầng chúng ta cần phân biệt

Từ Bài 53–57, hệ thống robot đã tiến hóa:

Odometry
   ↓
Path
   ↓
Path Tracking
   ↓
Trajectory Generation
   ↓
Trajectory Tracking

Cụ thể:

Odometry

Encoder
 ↓
X,Y,θ

Path Tracking

X,Y,θ
 ↓
bám đường

Trajectory Generation

A → B
 ↓
tính quỹ đạo

Trajectory Tracking

X,Y,θ thực tế
       ↓
X,Y,θ mong muốn
       ↓
Controller

5. Quỹ đạo đơn giản nhất: đường thẳng

Giả sử robot đi từ:

A = (0,0)

đến:

B = (1000,500)

Ta có:

[
\Delta x=1000
]

[
\Delta y=500
]

Độ dài:

[
L=\sqrt{1000^2+500^2}
]


6. Góc của đường thẳng

[
\theta_d=
atan2(\Delta y,\Delta x)
]

Trong ví dụ:

[
\theta_d=atan2(500,1000)
]

≈ 26.565°.


7. Nội suy tuyến tính

Đặt:

[
s\in[0,1]
]

thì:

[
x(s)=x_0+s(x_f-x_0)
]

[
y(s)=y_0+s(y_f-y_0)
]

Ví dụ:

s=0
→ A

s=0.5
→ giữa

s=1
→ B

8. Code

typedef struct
{
    float x;
    float y;
    float theta;

} TrajectoryPoint_t;

Hàm:

TrajectoryPoint_t Trajectory_Line(
    float x0,
    float y0,
    float xf,
    float yf,
    float s)
{
    TrajectoryPoint_t p;

    p.x =
        x0 +
        s * (xf - x0);

    p.y =
        y0 +
        s * (yf - y0);

    p.theta =
        atan2f(
            yf - y0,
            xf - x0);

    return p;
}

9. Nhưng s chưa phải thời gian

Đây là điểm rất quan trọng.

Nếu:

s = 0.5

chúng ta chỉ biết robot ở giữa quãng đường.

Chưa biết:

t = ?

Do đó ta cần thêm time parameterization.


10. Cách đơn giản nhất

Cho tổng thời gian:

T = 5.0f;

thì:

[
s=\frac{t}{T}
]

với:

0 ≤ t ≤ T

Code:

float s;

s = t / T;

if (s > 1.0f)
    s = 1.0f;

11. Trajectory lúc này

Ta có:

[
x(t)=x_0+\frac{t}{T}(x_f-x_0)
]

[
y(t)=y_0+\frac{t}{T}(y_f-y_0)
]

Robot sẽ chạy đều.

Nhưng vẫn có vấn đề:

Robot bắt đầu ngay lập tức ở tốc độ cao.


12. Trapezoidal Velocity Profile

Đây là một trong những profile quan trọng nhất trong điều khiển chuyển động.

Thay vì:

V
│──────────────
│
└────────────── t

ta tạo:

V
│       ┌──────────┐
│      /            \
│     /              \
│____/                \____
└────────────────────────── t
     accel    cruise decel

Ba giai đoạn:

1. ACCEL
2. CRUISE
3. DECEL

13. Ví dụ

Robot cần đi:

L = 1000 mm

với:

Vmax = 300 mm/s
Amax = 100 mm/s²

Thời gian tăng tốc:

[
t_a=\frac{V_{max}}{A_{max}}
]

[
t_a=3s
]

Quãng đường tăng tốc:

[
d_a=\frac12 A t_a^2
]

[
d_a=450mm
]

Hai bên tăng tốc + giảm tốc:

900 mm

vẫn còn:

100 mm

để chạy cruise.


14. Nếu quãng đường quá ngắn?

Ví dụ:

L = 500 mm

nhưng:

d_accel + d_decel > L

thì robot không thể đạt Vmax.

Khi đó profile trở thành:

      /\
     /  \
    /    \
___/      \___

Đây gọi là:

Triangular Velocity Profile


15. So sánh

Trapezoidal

      ┌───────┐
     /         \
____/           \____

Triangular

       /\
      /  \
_____/    \_____

Đây là nền tảng của rất nhiều hệ thống motion control.


16. Tạo cấu trúc Motion Profile

typedef struct
{
    float distance;

    float vmax;

    float accel;

    float decel;

    float t_accel;

    float t_cruise;

    float t_decel;

    float total_time;

    float v_peak;

} MotionProfile_t;

17. Khởi tạo profile

void MotionProfile_Init(
    MotionProfile_t *p,
    float distance,
    float vmax,
    float accel,
    float decel)
{
    float t1;
    float d1;

    float t3;
    float d3;

    p->distance = distance;
    p->vmax = vmax;
    p->accel = accel;
    p->decel = decel;

    t1 =
        vmax / accel;

    d1 =
        0.5f *
        accel *
        t1 *
        t1;

    t3 =
        vmax / decel;

    d3 =
        0.5f *
        decel *
        t3 *
        t3;

    if ((d1 + d3) <= distance)
    {
        /*
         * Trapezoidal
         */

        p->v_peak = vmax;

        p->t_accel = t1;
        p->t_decel = t3;

        p->t_cruise =
            (distance - d1 - d3)
            / vmax;
    }
    else
    {
        /*
         * Triangular
         */

        p->v_peak =
            sqrtf(
                2.0f *
                distance *
                accel *
                decel /
                (accel + decel));

        p->t_accel =
            p->v_peak / accel;

        p->t_decel =
            p->v_peak / decel;

        p->t_cruise = 0.0f;
    }

    p->total_time =
        p->t_accel +
        p->t_cruise +
        p->t_decel;
}

18. Hàm lấy vận tốc tại thời điểm t

Đây là phần rất quan trọng.

float MotionProfile_GetVelocity(
    MotionProfile_t *p,
    float t)
{
    float t2;

    if (t <= 0.0f)
        return 0.0f;

    /*
     * ACCEL
     */

    if (t < p->t_accel)
    {
        return
            p->accel * t;
    }

    /*
     * CRUISE
     */

    t2 =
        t -
        p->t_accel;

    if (t2 < p->t_cruise)
    {
        return p->v_peak;
    }

    /*
     * DECEL
     */

    t2 =
        t -
        p->t_accel -
        p->t_cruise;

    if (t2 < p->t_decel)
    {
        return
            p->v_peak -
            p->decel * t2;
    }

    return 0.0f;
}

19. Lấy quãng đường đã đi

Ta cần:

[
s(t)
]

ACCEL

[
s=\frac12at^2
]

CRUISE

[
s=d_a+V_{max}t_c
]

DECEL

[
s=d_a+d_c+
V_{peak}t_d-\frac12at_d^2
]


20. Code

float MotionProfile_GetPosition(
    MotionProfile_t *p,
    float t)
{
    float d_accel;
    float t2;
    float d_cruise;

    d_accel =
        0.5f *
        p->accel *
        p->t_accel *
        p->t_accel;

    if (t <= 0.0f)
        return 0.0f;

    /*
     * ACCEL
     */

    if (t < p->t_accel)
    {
        return
            0.5f *
            p->accel *
            t *
            t;
    }

    /*
     * CRUISE
     */

    t2 =
        t -
        p->t_accel;

    d_cruise =
        p->v_peak *
        p->t_cruise;

    if (t2 < p->t_cruise)
    {
        return
            d_accel +
            p->v_peak *
            t2;
    }

    /*
     * DECEL
     */

    t2 =
        t -
        p->t_accel -
        p->t_cruise;

    return
        d_accel +
        d_cruise +
        p->v_peak *
        t2 -
        0.5f *
        p->decel *
        t2 *
        t2;
}

21. Bây giờ chúng ta có gì?

Chỉ cần:

t

ta lấy được:

s(t)
V(t)

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


22. Kết hợp với đường thẳng

Ta có:

A(x0,y0)
       \
        \
         \
          B(xf,yf)

Tính:

dx = xf - x0;
dy = yf - y0;

và:

L =
sqrtf(dx * dx + dy * dy);

Sau đó:

s = MotionProfile_GetPosition(&profile, t);

23. Chuẩn hóa s

[
u=\frac{s}{L}
]

float u;

u = s / L;

if (u > 1.0f)
    u = 1.0f;

Sau đó:

x =
    x0 +
    u * dx;

y =
    y0 +
    u * dy;

24. Hàm hoàn chỉnh

TrajectoryPoint_t Trajectory_Line_Profile(
    float x0,
    float y0,
    float xf,
    float yf,
    MotionProfile_t *profile,
    float t)
{
    TrajectoryPoint_t p;

    float dx;
    float dy;

    float length;
    float s;
    float u;

    dx = xf - x0;
    dy = yf - y0;

    length =
        sqrtf(dx * dx + dy * dy);

    s =
        MotionProfile_GetPosition(
            profile,
            t);

    u =
        s / length;

    if (u > 1.0f)
        u = 1.0f;

    p.x =
        x0 +
        u * dx;

    p.y =
        y0 +
        u * dy;

    p.theta =
        atan2f(dy, dx);

    return p;
}

25. Đây chính là Trajectory Generation

Ví dụ:

t=0
x=0
y=0
V=0

t=1
x=50
y=25
V=100

t=2
x=200
y=100
V=200

t=3
x=400
y=200
V=300

t=4
x=650
y=325
V=250

t=5
x=900
y=450
V=100

t=6
x=1000
y=500
V=0

Robot không còn:

START → FULL SPEED

mà:

0
 ↓
tăng tốc
 ↓
Vmax
 ↓
giảm tốc
 ↓
0

26. Nhưng robot 2 bánh cần thêm ω

Đường thẳng:

[
\omega=0
]

nên:

[
V_L=V_R=V
]

Rất đơn giản.

Nhưng nếu là đường cong:

      ______
    /
   /
  /
 /

thì:

ω ≠ 0

27. Quỹ đạo đường tròn

Đây là quỹ đạo cực kỳ quan trọng.

Cho:

Center = (cx, cy)
Radius = R

Ta có:

[
x(t)=c_x+R\cos\phi
]

[
y(t)=c_y+R\sin\phi
]


28. Nếu robot chạy với vận tốc V

Ta có:

[
\omega=\frac{V}{R}
]

và:

[
\phi(t)=\phi_0+\omega t
]

Do đó:

[
x(t)=c_x+R\cos(\phi_0+\omega t)
]

[
y(t)=c_y+R\sin(\phi_0+\omega t)
]


29. Góc robot

Nếu robot chạy theo chiều ngược kim đồng hồ:

[
\theta(t)=\phi(t)+\frac{\pi}{2}
]

Nếu chạy theo chiều kim đồng hồ:

[
\theta(t)=\phi(t)-\frac{\pi}{2}
]


30. Code Circle

TrajectoryPoint_t Trajectory_Circle(
    float cx,
    float cy,
    float radius,
    float phi0,
    float omega,
    float t)
{
    TrajectoryPoint_t p;

    float phi;

    phi =
        phi0 +
        omega * t;

    p.x =
        cx +
        radius * cosf(phi);

    p.y =
        cy +
        radius * sinf(phi);

    if (omega >= 0.0f)
    {
        p.theta =
            phi +
            M_PI / 2.0f;
    }
    else
    {
        p.theta =
            phi -
            M_PI / 2.0f;
    }

    return p;
}

31. Đây là một điểm rất quan trọng

Trong Bài 56:

Path Tracker

robot có thể chạy:

circle

bằng Pure Pursuit.

Nhưng trong Bài 57:

Trajectory Generator

chúng ta biết trước:

x(t)
y(t)
θ(t)
V(t)
ω(t)

Do đó controller có nhiều thông tin hơn.


32. So sánh

Pure Pursuit

Robot
 ↓
nhìn phía trước
 ↓
tính curvature
 ↓
V,ω

Trajectory Tracking

Time t
 ↓
Trajectory Generator
 ↓
x_d,y_d,θ_d
V_d,ω_d
 ↓
Controller
 ↓
V,ω

Đây là bước chuyển từ:

“Bám đường”

sang:

“Bám chuyển động mong muốn theo thời gian.”


33. Controller của Trajectory Tracking

Ta có sai số:

[
e_x=x_d-x
]

[
e_y=y_d-y
]

[
e_\theta=\theta_d-\theta
]

Nhưng không nên điều khiển trực tiếp ex, ey trong hệ tọa độ toàn cục.

Ta chuyển sang hệ tọa độ robot.


34. Sai số trong hệ robot

        y_robot
           ↑
           |
           |
           ●────→ x_robot
         robot

Ta tính:

[
e_x^r

\cos\theta e_x+
\sin\theta e_y
]

[
e_y^r

-\sin\theta e_x+
\cos\theta e_y
]


35. Sai số góc

[
e_\theta=
normalize(\theta_d-\theta)
]

Ví dụ:

θd = 179°
θ  = -179°

phải ra:

eθ = 2°

chứ không phải:

358°

36. Controller đơn giản

Một controller cơ bản:

[
V=
V_d+K_xe_x
]

[
\omega=
\omega_d+
K_y e_y+
K_\theta e_\theta
]

Đây là một controller rất thú vị.


37. Code

void Trajectory_Controller(
    Pose_t *actual,
    TrajectoryPoint_t *desired,
    float Vd,
    float omega_d,
    float *V,
    float *omega)
{
    float dx;
    float dy;

    float ex;
    float ey;
    float etheta;

    dx =
        desired->x -
        actual->x;

    dy =
        desired->y -
        actual->y;

    ex =
        cosf(actual->theta) * dx +
        sinf(actual->theta) * dy;

    ey =
        -sinf(actual->theta) * dx +
        cosf(actual->theta) * dy;

    etheta =
        NormalizeAngleRad(
            desired->theta -
            actual->theta);

    *V =
        Vd +
        KX * ex;

    *omega =
        omega_d +
        KY * ey +
        KTHETA * etheta;
}

38. Nhưng không được quên giới hạn

if (*V > MAX_SPEED)
    *V = MAX_SPEED;

if (*V < -MAX_SPEED)
    *V = -MAX_SPEED;

if (*omega > MAX_OMEGA)
    *omega = MAX_OMEGA;

if (*omega < -MAX_OMEGA)
    *omega = -MAX_OMEGA;

39. Sau đó lại quay về Inverse Kinematics

V
ω
 ↓
VL = V - ωW/2
VR = V + ωW/2

rồi:

VL → PID motor trái
VR → PID motor phải

40. Toàn bộ hệ thống Bài 57

              TIME t
                 │
                 ▼
      ┌────────────────────┐
      │ Trajectory         │
      │ Generator          │
      └─────────┬──────────┘
                │
       ┌────────┼────────┐
       ↓        ↓        ↓
      Xd       Yd       θd
       │        │        │
       └────────┼────────┘
                ↓
             Vd,ωd
                │
                ▼
       Trajectory Controller
                ▲
                │
             X,Y,θ
                │
             Odometry
                ▲
                │
             Encoders
                │
                ▼
       ┌─────────────────┐
       │ Inverse         │
       │ Kinematics      │
       └──────┬────┬─────┘
              ↓    ↓
             VL    VR
              ↓    ↓
             PID  PID
              ↓    ↓
           Motor  Motor

41. Một ví dụ hoàn chỉnh

Robot bắt đầu:

X = 0
Y = 0
θ = 0

Mục tiêu:

X = 1000 mm
Y = 500 mm

Tạo profile:

MotionProfile_t profile;

MotionProfile_Init(
    &profile,
    1118.0f,
    300.0f,
    100.0f,
    100.0f);

Vì:

[
L=
\sqrt{1000^2+500^2}
\approx1118mm
]


42. Trong Timer 1 ms

Bài 36 của chúng ta đã tạo:

millis()

Vậy ta có thể:

float t;

t =
    (millis() -
     trajectory_start_time)
    / 1000.0f;

Sau đó:

desired =
    Trajectory_Line_Profile(
        0,
        0,
        1000,
        500,
        &profile,
        t);

43. Đây là sự kết nối rất đẹp với các bài trước

Bạn đã học:

Bài 36

Timer
 ↓
millis 1 ms

Bài 45

Encoder

Bài 46

Motor speed PID

Bài 48

Position control

Bài 53

Odometry

Bài 54

Inverse Kinematics

Bài 55

Trajectory Tracking

Bài 56

Pure Pursuit

Bài 57

Trajectory Generation

Tất cả đang bắt đầu ghép thành một hệ thống robot hoàn chỉnh.


44. Một lưu ý rất quan trọng với STM32F103

STM32F103C8T6 không có FPU phần cứng.

Các hàm:

sqrtf()
sinf()
cosf()
atan2f()

đều sử dụng tính toán floating-point bằng phần mềm.

Trong bài học chúng ta vẫn dùng float vì dễ hiểu và dễ kiểm tra.

Khi robot chạy thực tế với vòng điều khiển nhanh, chúng ta có thể tối ưu sau bằng:

lookup table
CORDIC
fixed-point
giảm số lần tính sin/cos

Đừng tối ưu quá sớm.


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

Hãy tạo robot đi:

(0,0)
   ↓
(1000,0)

với:

Vmax = 300 mm/s
A = 100 mm/s²
D = 100 mm/s²

Quan sát UART:

TIME
Xd
Yd
Vd
θd

Bạn sẽ thấy:

TIME    Xd       Yd       Vd
0.0     0        0        0
0.5     ...      0        50
1.0     ...      0        100
2.0     ...      0        200
3.0     ...      0        300
...

46. Bài thực hành số 2 – Circle

Cho:

R = 300 mm
V = 150 mm/s

thì:

[
\omega=\frac{150}{300}
]

[
\omega=0.5rad/s
]

Chu kỳ:

[
T=\frac{2\pi}{0.5}
]

≈ 12.57 s.

Robot sẽ hoàn thành một vòng trong khoảng:

12.57 s

47. Bài thực hành số 3 – kiểm tra Tracking Error

UART:

Xd
Yd
θd

X
Y
θ

ex
ey
etheta

Ví dụ:

Xd=500
Yd=200
θd=0.8

X=480
Y=190
θ=0.75

ex=20
ey=10
etheta=0.05

Đây mới là cách debug robot chuyên nghiệp.


48. Điều quan trọng nhất của Bài 57

Bạn cần nhớ:

PATH

là:

Robot đi đâu?

Còn:

TRAJECTORY

là:

Robot đi đâu + đi lúc nào + với vận tốc nào?

Và:

TRAJECTORY TRACKING

là:

Robot thực tế phải bám theo trạng thái mong muốn theo thời gian.


49. Sơ đồ tư duy cuối bài

                 ROBOT 2 BÁNH
                       │
        ┌──────────────┴──────────────┐
        │                             │
     PATH                         TRAJECTORY
        │                             │
   x(s), y(s)                  x(t),y(t),θ(t)
        │                             │
 Pure Pursuit                 V(t),ω(t)
        │                             │
        └──────────────┬──────────────┘
                       │
                  CONTROLLER
                       │
                     V,ω
                       │
              INVERSE KINEMATICS
                 ↙           ↘
               VL             VR
                ↓              ↓
             PID L          PID R
                ↓              ↓
             MOTOR L         MOTOR R
                 ↘           ↙
                   ENCODER
                      │
                   ODOMETRY
                      │
                    X,Y,θ
                      │
                      └──── feedback

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

Sau Bài 57, bài rất hợp lý tiếp theo là:

BÀI 58 – TRAJECTORY TRACKING NÂNG CAO: CONTROLLER X,Y,θ + FEEDFORWARD + FEEDBACK

Ở đó chúng ta sẽ không chỉ dùng:

[
V=V_d+K_xe_x
]

mà tách thành:

[
\boxed{
u=u_{feedforward}+u_{feedback}
}
]

tức là robot biết trước mình cần chạy thế nào từ trajectory, đồng thời dùng encoder + odometry để sửa sai số thực tế. Đây là bước rất quan trọng để tiến tới các controller robot thực tế như kinematic control, dynamic control và sau này là MPC.

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