Dự án 13: Xe dò line - MakerEdu Inventor Kit for Micro:bit

From MakerLab Wiki
Jump to navigation Jump to search

Mức độ: Khó ★★★★★★★★★

Danh sách thiết bị

Sơ đồ kết nối

MakerEDU Shield Thiết bị
Port P0 [MKE-S10] - Dò line (mắt phải)
Port P1 [MKE-S10] - Dò line (mắt trái)
Motor_A Động cơ DC bên phải xe (bánh trước + bánh sau)
Motor_B Động cơ DC bên trái xe (bánh trước + bánh sau)

Mô tả dự án

Trong dự án này, các bạn sẽ học cách làm ra một chiếc xe dò line, mà chính bạn có thể tùy chỉnh cho phù hợp với đường line của riêng mình.

Mục tiêu là mình sẽ lập trình cho chiếc xe, bình thường khi không phát hiện ra đường line, xe hoàn toàn đứng yên.

Và chỉ khi nào nó phát hiện ra đường line, xe bắt đầu chạy bám theo đường line đó, cho đến khi ra khỏi điểm cuối đường line sẽ dừng lại.

Ngoài ra, bạn có thể bấm các nút A và B để cài đặt tăng giảm mức tốc độ di chuyển của cho xe.

Chuẩn bị

Trước tiên bạn cần khảo sát giá trị mà cảm biến trả về theo đường line bạn dùng.

Bạn có thể in giấy trắng đen cắt ghép nối lại, hay dùng băng keo đen (còn gọi là băng keo cách điện), hoặc dùng bất kỳ vật dụng có thể nào để dựng nên đường line.

Icon-Info-White.png Lưu ý:
Kích thước độ rộng đường line nên lớn khoảng cách giữa 2 mắt cảm biến.

Ở đây mình dùng băng keo đen để làm line, nên có độ rộng khoảng 15mm.

Vì đây là dạng cảm biến Analog, nên micro:bit sẽ cho trả về giá trị trong dãi số từ 0 đến 1023.

Qua khảo sát, mình thấy:

  • Giá trị dao động trong khoảng 40~50 khi cảm biến nằm ngoài đường line (outline).
  • Giá trị dao động trong khoảng 800~900 khi cảm biến nằm hoàn toàn trong đường line (inline).

Sau đó mình di chuyển mắt cảm biến từ bên trong line ra ngoài line và ngược lại, mình thấy có các "điểm mốc" đặc biệt:

  • Điểm A = 110.
    _ Khoảng từ 0 đến A"outline" vì mắt cảm biến nằm hoàn toàn ngoài line.
  • Điểm B = 500.
    _ Khoảng từ A đến B tuy là "inline" nhưng mắt cảm biến nằm khá sát ngoài mép line rồi.
  • Điểm C = 800.
    _ Khoảng từ B đến C"inline" khi mắt cảm biến dần tiến vào giữa đường line.
  • Điểm D = 910.
    _ Khoảng từ C đến D cũng là "inline" lúc này mắt cảm biến nằm hoàn toàn trong đường line.
  • Cuối cùng.
    _ Khoảng từ D đến 1023"outline" xảy ra khi khoảng cách độ cao giữa đường line và mắt cảm biến vượt quá ngoài phạm vi đo của cảm biến.
Icon-Attention-White.png Chú ý:
Các giá trị mình dùng mang tính chất tương đối, nó có thể phù hợp với đường line mình dùng, nhưng có thể không với các bạn.

Giá trị các "điểm mốc" khác nhau tùy thuộc vào nhiều yếu tố, như độ đậm nhạt đường line, màu sắc đường line, màu sắc màu nền quanh line, có bị ánh sáng mạnh như mặt trời rọi vào hay không, khoảng cách giữa đường line với cảm biến thế nào, ...

Vậy nên các bạn cần đo đạc lại cho đúng với đường line của mình.

Như vậy với mỗi khoảng giá trị này ta cần có hành động điều khiển cụ thể cho xe. Để đơn giản mình đặt một "giá trị điểm" tương ứng cho từng khoảng này:

Khoảng Analog Giá trị điểm Trường hợp xảy ra
[0 - A] 0 outline (xe ra ngoài line hoàn toàn)
[A - B] 1 inline (xe đi gần sát mép line)
[B - C] 2 inline (xe dần trở lại đúng giữa line)
[C - D] 2 inline (xe đi đúng giữa line)
[D - 1023] 0 outline (xe có thể đang được cầm lên)

Sau khi đã khảo sát giá trị rồi, ta bắt đầu lập trình.

Giai đoạn 1

Bước đầu bạn cần tạo một số BIẾN để sử dụng cho từng mục đích cụ thể.

Tất cả biến trong chương trình dự án này gồm:

  • "valueL", "valueR".
  • "pointL", "pointR".
  • "A", "B", "C", "D".
  • "enable", "first", "tilt", "count", "threshold".
  • "speed", "speedL", "speedR".

Giải thích

Cặp biến "valueL""valueR" :
→ Dùng để lưu giá trị Analog đọc được từ cảm biến dò line.
→ Trong đó "valueL" lưu giá trị mắt bên trái (đang kết nối với port P1).
→ Và "valueR" lưu giá trị đọc từ mắt bên phải (đang kết nối với port P0).

Cặp biến "pointL""pointR" :
→ Dùng để lưu "giá trị điểm" chuyển đổi từ giá trị Analog.
→ Với "pointL" là chuyển từ "valueL".
→ Và "pointR" là chuyển từ "valueR".

Bộ 4 biến "A", "B", "C", "D" :
→ Dùng để lưu các "điểm mốc" trên thang trục Analog.
→ Giúp việc chuyển đổi giá trị Analog sang "giá trị điểm" dễ hơn.
→ Bạn cũng dễ dàng thay đổi thông số 4 biến này để tinh chỉnh chương trình cho xe.

MakeCode Kit 12 point line.png

Các biến "enable", "first", "tilt", "count", "threshold" :
→ Dùng để thực hiện 2 tính năng quan trọng.
→ Một, khi xe có quán tính lớn làm chạy lệch hoàn toàn ra ngoài line, nó sẽ giúp xe biết cách quay lại đường line để đi tiếp.
→ Hai, khi xe chạy tới cuối đường line, sau vài lần đo cảm biến xác nhận đã ra ngoài line hoàn toàn, sẽ cho xe dừng lại.

Các biến "speed", "speedL", "speedR" :
→ Dùng để lưu giá trị "tốc độ", đơn vị (%).
→ Biến "speed" lưu ngưỡng tốc độ tối đa do người dùng cài đặt.
→ Biến "speedL" để điều khiển tốc độ quay của cặp bánh xe bên trái.
→ Biến "speedR" để điều khiển tốc độ quay của cặp bánh xe bên phải.

MakeCode Kit 12 set speed.png

Giai đoạn 2

. Tạo hàm stop → Hàm này điều khiển tắt 2 kênh Motor_A và Motor_B để dừng xe.

. Tạo khối on button A pressed → Khối này thực hiện khi nút A được nhấn.

  • Làm tăng giá trị tốc độ (%) của biến "speed" lên 10 đơn vị.
  • Đồng thời hiển thị mức tốc độ lên Led Matrix.
  • Và giảm giá trị biến "threshold" đi 3 đơn vị.

. Tạo khối on button B pressed → Khối này thực hiện khi nút B được nhấn.

  • Làm giảm giá trị tốc độ (%) của biến "speed" xuống 10 đơn vị.
  • Đồng thời hiển thị mức tốc độ lên Led Matrix.
  • Và tăng giá trị biến "threshold" đi 3 đơn vị.

. Tạo khối on start → Khối này được thực hiện 1 lần mỗi khi micro:bit khởi động.

  • Trước tiên điều khiển Driver để đảm bảo xe không chạy khi mới bật nguồn.
  • Tiếp cài đặt tốc độ mặc định ban đầu là 50%, tương ứng hiển thị mức tốc độ [3] trên Led Matrix.
  • Cài đặt giá trị "điểm mốc" cho các biến "A", "B", "C", "D" và một số biến khác.

Giải thích

Icon-Done-White.png Tốc độ xe:
Trong chương trình này, tốc độ chậm nhất của xe được đặt là 20% và biến "speed" là tốc độ tối đa cho phép xe đi, có thể tùy chỉnh trong 5 mức từ 30% đến 70%. Từng mức hiển thị trên Led Matrix, được tính bằng công thức (speed / 10) - 2, tương ứng với (%) tốc độ sau:
  • [1] = 30%
  • [2] = 40%
  • [3] = 50%
  • [4] = 60%
  • [5] = 70%

"enable" : biến này có chức năng cho phép hoặc không cho micro:bit điều khiển xe.

  • enable = 0 → các hàm điều khiển xe bị vô hiệu.
  • enable = 1 → micro:bit có thể dùng các hàm điều khiển xe.

"threshold" : biến này là số lần micro:bit kiểm tra lại giá trị từ mắt dò line, kể từ khi phát hiện xe bị "outline". Tức trường hợp "giá trị điểm" của cả 2 mắt đều là 0 lần đầu tiên (pointL = 0 AND pointR = 0).
_ Lúc này micro:bit sẽ điều khiển xe xoay trái hoặc xoay phải để cố gắng tìm lại đường line. Nếu sau quá số lần kiểm tra lại, mà vẫn không tìm thấy đường line, micro:bit sẽ cho dừng xe.
_ Vậy nên, khi nhấn nút A để tăng tốc độ tối đa của xe, nên giảm giá trị biến "threshold" xuống và ngược lại.

Blocks

MakeCode Kit 12A.png

Giai đoạn 3

. Tạo hàm go
→ Hàm này điều khiển 2 kênh Motor_A và Motor_B quay thuận cùng chiều cho xe đi hướng tới.

. Tạo hàm turnLeft
→ Hàm này điều khiển 2 kênh Motor_A quay thuận và Motor_B quay ngược làm xe quay trái.

. Tạo hàm turnRight
→ Hàm này điều khiển 2 kênh Motor_A quay ngược và Motor_B quay thuận làm xe quay phải.

. Tạo hàm check_stop
→ Hàm này kiểm tra để ra quyết định khi nào phải dừng xe, kể từ lúc phát hiện xe "outline".

. Tạo hàm set_speed → Hàm này giúp tính toán tốc độ quay phù hợp theo đường line của cả bánh xe bên trái và bên phải.

  • 2 tham số "fast""slow" nhận giá trị của 2 "điểm mốc" trong khoảng Analog để chuyển đổi sang thang giá trị tốc độ (%).
  • Với "speed" là tốc độ tối đa và 20% là tốc độ nhỏ nhất.

. Tạo hàm detect_line → Hàm này dựa trên giá trị Analog đọc từ cảm biến dò line, và chuyển sang "giá trị điểm" tương ứng.

  • Tham số "num" chủ yếu nhận từ biến "valueL""valueR".
  • Giá trị trả về cũng chủ yếu lưu vào biến "pointL""pointR".

Giải thích

Icon-Done-White.png Điều khiển hướng xe đi:
Có tất cả 3 hàm điều khiển xe di chuyển, các hàm đều có 2 tham số "SL""SR" để nhận giá trị tốc độ, điều khiển cho kênh Motor_B và Motor_A.
Thông thường các hàm này nhận giá trị từ biến "speedL""speedR".
Và các hàm chỉ sử dụng được khi biến "enable" được kích hoạt (tức enable = 1).
Trong đó, hàm điều khiển xe quay trái (turnLeft) và quay phải (turnRight) sẽ phải truy cập thêm hàm check_stop khi xe bị "outline".

Cho dừng xe:
Nếu xe đang di chuyển (tức "inline") và sau đó phát hiện "outline", biến "first" được kích hoạt (first = 1).
Lúc này chỉ có 2 trường hợp xảy ra:

  1. Xe di chuyển lệch ra ngoài line.
  2. Xe đã đi đến cuối đường line.

Xe sẽ quay trái hoặc phải để tìm line, đồng thời bật bộ đếm.
Khi bộ đếm "count" vượt ngưỡng "threshold".
Micro:bit cho dừng xe và vô hiệu hóa các hàm điều khiển xe (enable = 0).

Blocks

MakeCode Kit 12B.png
MakeCode Kit 12C.png

Giai đoạn 4

. Tạo hàm control_car → Đây là hàm chức năng quan trọng nhất trong dự án này, giúp micro:bit ra những quyết định điểu khiển xe phù hợp.

Giải thích

Hàm dựa trên giá trị của 2 cặp biến "pointL""pointR" để điều khiển. Với "giá trị điểm" có tất cả 3 loại là 0, 1, 2. Ta có được bảng các trường hợp sau:

pointL pointR Ý nghĩa Điều khiển
0 0 outline stop hoặc ...
  • turnLeft (tilt = 0) với tốc độ max
  • turnRight (tilt = 1) với tốc độ max
0 1 lệch trái nhiều (tilt = 1) turnRight
  • với tốc độ max → min trong đoạn [B - A]
0 2 lệch trái nhiều (tilt = 1) turnRight
  • với tốc độ max → min trong đoạn [C - D]
1 0 lệch phải nhiều (tilt = 0) turnLeft
  • với tốc độ max → min trong đoạn [B - A]
1 1 inline go
  • với tốc độ max → min trong đoạn [B - A]
1 2 inline (lệch trái ít) go
  • bánh trái tốc độ max → min trong đoạn [B - A]
  • bánh phải dừng trong đoạn [B - C]
  • bánh phải tốc độ 50% max trong đoạn [C - D]
2 0 lệch phải nhiều (tilt = 0) turnLeft
  • với tốc độ max → min trong đoạn [C - D]
2 1 inline (lệch phải ít) go
  • bánh phải tốc độ max → min trong đoạn [B - A]
  • bánh trái dừng trong đoạn [B - C]
  • bánh trái tốc độ 50% max trong đoạn [C - D]
2 2 inline go
  • với tốc độ max trong đoạn [B - C]
  • với tốc độ max → min trong đoạn [C - D]
Icon-Done-White.png 0 AND 0
Đây là trường hợp đặc biệt nhất.

Nếu xe đang di chuyển:
_ Lúc này biến "enable" vẫn đang kích hoạt (enable = 1) vì xe mới từ "inline" ra "outline".
_ Biến "first" sẽ được kích hoạt (first = 1) để bật bộ đếm "count" trong hàm check_stop.
_ Đồng thời dựa trên giá trị biến "tilt" để biết lần cuối khi xe còn trong line đang bị lệch về phía nào và điều khiển xe quay lại đường line.

  • tilt = 1 → xe lệch trái → cho xoay phải với tốc độ nhanh nhất.
  • tilt = 0 → xe lệch phải → cho xoay trái với tốc độ nhanh nhất.

_ Lúc này có 2 trường hợp:

  1. Bộ đếm chưa chạm ngưỡng "threshold" mà xe đã tìm lại được line ... tình huống xe chạy lệch line!
    → Biến "first" được tắt (first = 0) và xóa giá trị bộ đếm (count = 0).
    → Biến "enable" vẫn được duy trì.
  2. Bộ đếm vượt ngưỡng "threshold" mà xe vẫn không thấy line ... tình huống xe chạy đến cuối line!
    → Hàm stop được thực hiện để dừng xe.
    → Biến "enable" được tắt (enable = 0).

Nếu xe đang đứng yên:
_ Lúc này biến "enable" đã tắt (enable = 0) và có 2 trường hợp có thể xảy ra tiếp:

  1. Vẫn nhận được cặp giá trị 0 → Xe vẫn đứng yên và không làm gì cả.
  2. Chỉ cần một trong hai khác 0 → Biến "enable" được kích hoạt lại và cho di chuyển xe.

Blocks

MakeCode Kit 12D.png

Giai đoạn 5

. Tạo khối forever → Khối này được thực hiện lặp lại liên tục sau khi micro:bit khởi động xong.

  • Bước 1: đọc giá trị Analog từ 2 mắt cảm biến dò line trái và phải.
  • Bước 2: chuyển đổi giá trị Analog sang "giá trị điểm".
  • Bước 3: ra quyết định điều khiển xe trên 2 kênh Motor_A và Motor_B.

Blocks

MakeCode Kit 12E.png

Javascript

Icon-Info-White.png Tip:
Dự án này có lượng khối khá nhiều, thay vì tạo từng khối theo hình.

Bạn có thể copy đoạn code dưới và paste vào mục JavaScript, rồi quay lại mục Blocks để xem code dưới dạng khối.

let valueL = 0
let valueR = 0

let pointL = 0
let pointR = 0

let A = 0
let B = 0
let C = 0
let D = 0

let enable = 0
let first = 0
let tilt = 0
let count = 0
let threshold = 0

let speed = 0
let speedL = 0
let speedR = 0

/* ------------------------------------------------------------------------- */

function stop() {
  l9110.pauseMotor(l9110.Motor.MotorA)
  l9110.pauseMotor(l9110.Motor.MotorB)
}

function go(SL: number, SR: number) {
  if (enable) {
    l9110.controlMotor(l9110.Motor.MotorA, l9110.Rotate.Forward, SR)
    l9110.controlMotor(l9110.Motor.MotorB, l9110.Rotate.Forward, SL)
  }
}

function turnLeft(SL: number, SR: number) {
  if (enable) {
    l9110.controlMotor(l9110.Motor.MotorA, l9110.Rotate.Forward, SR)
    l9110.controlMotor(l9110.Motor.MotorB, l9110.Rotate.Backward, SL)
    check_stop()
  }
}

function turnRight(SL: number, SR: number) {
  if (enable) {
    l9110.controlMotor(l9110.Motor.MotorA, l9110.Rotate.Backward, SR)
    l9110.controlMotor(l9110.Motor.MotorB, l9110.Rotate.Forward, SL)
    check_stop()
  }
}

/* ------------------------------------------------------------------------- */

function detect_line(num: number) {
  if (num < A) {
    return 0
  } else if (num <= B) {
    return 1
  } else if (num <= D) {
    return 2
  } else {
    return 0
  }
}

function check_stop() {
  if (first) {
    count += 1
    if (count > threshold) {
      stop()
      enable = 0
    }
  }
}

function set_speed(fast: number, slow: number) {
  speedL = Math.round(Math.map(valueL, fast, slow, speed, 20))
  speedR = Math.round(Math.map(valueR, fast, slow, speed, 20))
}

function control_car() {
  if (pointL == 0 && pointR == 0) {
    if (enable) {
      first = 1
      if (tilt) {
        turnRight(speed, speed)
      } else {
        turnLeft(speed, speed)
      }
    }
  } else {
    enable = 1
    first = 0
    count = 0
    if (pointL == 0 && pointR == 1) {
      tilt = 1
      set_speed(B, A)
      turnRight(speedL, speedR)
    } else if (pointL == 0 && pointR == 2) {
      tilt = 1
      set_speed(C, D)
      turnRight(speedL, speedR)
    } else if (pointL == 1 && pointR == 0) {
      tilt = 0
      set_speed(B, A)
      turnLeft(speedL, speedR)
    } else if (pointL == 1 && pointR == 1) {
      set_speed(B, A)
      go(speedL, speedR)
    } else if (pointL == 1 && pointR == 2) {
      set_speed(B, A)
      if (valueR < C) {
        speedR = 0
      } else {
        speedR = Math.round(speed / 2)
      }
      go(speedL, speedR)
    } else if (pointL == 2 && pointR == 0) {
      tilt = 0
      set_speed(C, D)
      turnLeft(speedL, speedR)
    } else if (pointL == 2 && pointR == 1) {
      set_speed(B, A)
      if (valueL < C) {
        speedL = 0
      } else {
        speedL = Math.round(speed / 2)
      }
      go(speedL, speedR)
    } else if (pointL == 2 && pointR == 2) {
      set_speed(C, D)
      if (valueL < C) {
        speedL = speed
      }
      if (valueR < C) {
        speedR = speed
      }
      go(speedL, speedR)
    }
  }
}

/* ------------------------------------------------------------------------- */

input.onButtonPressed(Button.A, function () {
  speed = Math.constrain(speed + 10, 30, 70)
  basic.showNumber(speed / 10 - 2)
  threshold += -3
})


input.onButtonPressed(Button.B, function () {
  speed = Math.constrain(speed - 10, 30, 70)
  basic.showNumber(speed / 10 - 2)
  threshold += 3
})

/* ------------------------------------------------------------------------- */

stop()
speed = 50
A = 110
B = 500
C = 800
D = 910
basic.showNumber(speed / 10 - 2)
enable = 0
threshold = 10

basic.forever(function () {
  valueR = pins.analogReadPin(AnalogPin.P0)
  valueL = pins.analogReadPin(AnalogPin.P1)
  pointR = detect_line(valueR)
  pointL = detect_line(valueL)
  control_car()
})

Nạp code

  1. Kết nối [MKE-S10] - Dò line (mắt phải) đến Port P0 của MakerEdu Shield.
  2. Kết nối [MKE-S10] - Dò line (mắt trái) đến Port P1 của MakerEdu Shield.
  3. Nhấp vào Download để nạp code của bạn sang micro:bit.
  4. Gắn micro:bit lên MakerEdu Shield.
    Lưu ý phải ngắt kết nối micro:bit với máy tính.
  5. Kết nối 2 cặp Động cơ DC đến port domino Motor_AMotor_B của MakerEdu Shield.
  6. Kết nối nguồn riêng 5V qua cổng microUSB trên Shield.
  7. Đặt xe vào đường line và xem xe chạy có như mong đợi không.

Kết quả

... hình dự án

Bài tập nâng cao thêm

  • Dựa theo nguyên lý trên, các bạn hãy thử kết hợp với điều khiển xe qua Bluetooth.
    → Như vậy mình có thể điều khiển cho xe đi đến đường line, và khi thấy line xe sẽ tự đi tiếp.
  • Bạn cũng có thể tìm hiểu thêm phần truyền dữ liệu qua Bluetooth, để bạn có thể chỉnh sửa các thông số cho chiếc xe dò line trực tiếp qua điện thoại, mà không cần phải nạp lại chương trình.
    → Cách này giúp bạn dễ dàng tinh chỉnh hoàn thiện độ chính xác của chiếc xe nhanh hơn rất nhiều.