Dự án 13: Xe dò line - MakerEdu Inventor Kit for Micro:bit
Mức độ: Khó ★★★★★★★★★☆
Danh sách thiết bị
- 3x [MKE-S10] - Dò line
- 1x Khung xe
- 1x MakerEdu Shield for micro:bit
- 1x Micro:bit
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.
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 là "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 là "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 là "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.
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" và "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" và "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.
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.
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
Blocks
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" và "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" và "valueR".
- Giá trị trả về cũng chủ yếu lưu vào biến "pointL" và "pointR".
Giải thích
Blocks
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" và "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 ...
|
0 | 1 | lệch trái nhiều (tilt = 1) | turnRight
|
0 | 2 | lệch trái nhiều (tilt = 1) | turnRight
|
1 | 0 | lệch phải nhiều (tilt = 0) | turnLeft
|
1 | 1 | inline | go
|
1 | 2 | inline (lệch trái ít) | go
|
2 | 0 | lệch phải nhiều (tilt = 0) | turnLeft
|
2 | 1 | inline (lệch phải ít) | go
|
2 | 2 | inline | go
|
Blocks
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
Javascript
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
- Kết nối [MKE-S10] - Dò line (mắt phải) đến Port P0 của MakerEdu Shield.
- Kết nối [MKE-S10] - Dò line (mắt trái) đến Port P1 của MakerEdu Shield.
- Nhấp vào Download để nạp code của bạn sang micro:bit.
- 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. - Kết nối 2 cặp Động cơ DC đến port domino Motor_A và Motor_B của MakerEdu Shield.
- Kết nối nguồn riêng 5V qua cổng microUSB trên Shield.
- Đặ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.