3. ยิงลูกดอกใส่เป้า
servoHold, การกวาดซ้ายขวา และการรอให้ความสูงนิ่งก่อนยิง จาก shooting.ino
ภารกิจยิงเป้า (Drone Innovation Challenge) ให้ยิงลูกดอกใส่เป้ากลม 4 จุด ยิงได้ 1 ลูกต่อเป้า วงกลางสุดได้ 20 คะแนน วงนอกสุด 10 คะแนน และโดรนต้องอยู่ห่างเป้า อย่างน้อย 1 เมตร ก่อนยิง
ชุดยิงของชมรมมีเซอร์โวสองตัว (SHOOT1_PIN = PORT4, SHOOT2_PIN = PORT5 ใน config.hpp) ตัวหนึ่งใช้ยิงด้านหน้า อีกตัวใช้ยิงด้านหลัง (ตามคอมเมนต์ใน Drone_Innovation_Challenge.ino) โค้ดสั่งเซอร์โวไปสามตำแหน่ง คือ ปิด ซ้าย ขวา โครงกลไกที่ใช้ปลดลูกดอกไม่ได้อยู่ในโค้ด หน้านี้อธิบายเฉพาะการสั่งเซอร์โว
เหมือนก้านปัดน้ำฝน ปัดไปสุดทาง ค้างครู่หนึ่ง แล้วกลับมาจอดที่เดิม
1. กวาดซ้าย กวาดขวา
ค่าตำแหน่งสามค่าอยู่ใน config.hpp
const int S_CLOSE = 1500;
const int S_LEFT = 1200;
const int S_RIGHT = 2000;
const uint16_t FIRE_HOLD_MS = 220;
const uint16_t RESET_HOLD_MS = 150;
const uint16_t POST_DELAY_MS = 60;ตัวส่งพัลส์อยู่ใน shooting.ino สองชั้น ชั้นล่างส่งพัลส์ 1 ครั้ง ชั้นบนวนซ้ำจนครบเวลา
static inline void servoPulse50Hz(int pin, int pulse_us) {
pulse_us = clampPulse(pulse_us); // จำกัดที่ 600–2400 µs
noInterrupts();
digitalWrite(pin, HIGH);
delayMicroseconds(pulse_us);
digitalWrite(pin, LOW);
interrupts();
delayMicroseconds(20000 - pulse_us);
}
static inline void servoHold(int pin, int pulse_us, uint16_t hold_ms) {
const uint32_t t0 = millis();
while (millis() - t0 < hold_ms) servoPulse50Hz(pin, pulse_us);
}servoHoldค้างที่ตำแหน่งหนึ่งตามเวลาที่กำหนด (หน่วย ms ไม่ใช่ 1.5 วินาทีตายตัวแบบrelease_servo_angle)clampPulseตัดค่าที่เกินช่วง 600–2400 µs เพื่อไม่ให้เซอร์โวชนขอบnoInterrupts()ปิดเฉพาะช่วงพัลส์ HIGH ไม่กี่ร้อยไมโครวินาที โค้ดตัวอย่างเก่าในไฟล์Servo.inoเขียนคอมเมนต์ไว้ว่าเพื่อกันพัลส์ถูกยืด ส่วนที่เหลือของเฟรมเปิด interrupt ให้ I2C ทำงานได้
ท่ากวาดสี่ท่าประกอบจากสองฟังก์ชันนี้
static void swipe_left_S1() {
servoHold(SHOOT1_PIN, S_LEFT, FIRE_HOLD_MS);
servoHold(SHOOT1_PIN, S_CLOSE, RESET_HOLD_MS);
delay(POST_DELAY_MS);
}อีกสามท่า (swipe_right_S1, swipe_left_S2, swipe_right_S2) ต่างกันแค่พินและทิศ รวมเวลาครั้งละ 220 + 150 + 60 = 430 ms ส่วน Test_ball.ino มี target_shooting() เรียกทั้งสี่ท่าเรียงกันไว้ทดสอบบนโต๊ะ
2. ทำไมยิงแล้วต้องรอความสูงนิ่ง
ลูกดอกพุ่งตามทิศที่โดรนหันและความสูงขณะนั้น ถ้าโดรนยังกำลังขึ้นหรือลง ลูกจะพลาดวง shooting.ino จึงมีฟังก์ชันสั่งความสูงเป้าหมาย (HEIGHT_SHOOT = 120 ซม.) แล้ว รอจนอ่านความสูงนิ่งจริง ก่อนปล่อยให้ยิง
แบบรอเวลาแล้วเช็กทีเดียว: ensure_shoot_height
void ensure_shoot_height() {
pArduino->set_height(HEIGHT_SHOOT);
delay(200);
uint16_t current_h = read_height_cm();
if (current_h < (HEIGHT_SHOOT - 10) || current_h > (HEIGHT_SHOOT + 10)) {
pArduino->set_height(HEIGHT_SHOOT);
delay(500);
hold_now(300);
} else {
hold_now(400);
}
}ถ้าความสูงอยู่ในช่วง ±10 ซม. จากเป้า แค่ถือนิ่ง 400 ms ถ้าไม่ใช่ สั่งซ้ำ รอ 500 ms แล้วถือ 300 ms
แบบวนจนนิ่ง: reach_height_shoot_stable
ฟังก์ชันนี้สั่งความสูงซ้ำๆ แล้วอ่านค่ากลับ นิ่งเมื่ออยู่ในช่วงคลาดเคลื่อน (TOL = 4 ซม.) ต่อเนื่อง 500 ms
constexpr uint16_t TARGET = HEIGHT_SHOOT;
constexpr uint8_t TOL = 4;
constexpr uint16_t MAX_WAIT_MS = 3500;
constexpr uint16_t SAMPLE_DELAY = 80;
constexpr uint16_t STABLE_NEED_MS = 500;
while (millis() - t0 < MAX_WAIT_MS) {
pArduino->set_height(TARGET);
delay(SAMPLE_DELAY);
pArduino->poll();
uint16_t h = read_height_cm();
if (h == 0 || h > 300) { stableSince = 0; continue; }
int diff = (int)h - (int)TARGET;
if (abs(diff) <= TOL) {
if (stableSince == 0) stableSince = millis();
if (millis() - stableSince >= STABLE_NEED_MS) break;
} else {
stableSince = 0;
}
}ชื่อเล่นของลูกเล่นนี้คือ stableSince จดเวลาที่เริ่มนิ่ง ถ้าหลุดช่วงเมื่อไร รีเซ็ตเป็น 0 แล้วเริ่มนับใหม่ ผ่านครบ 500 ms จึงออกจากลูป ถ้ารอนานเกิน 3.5 วินาทียอมแพ้แล้วยิงต่อ ไม่ให้ภารกิจค้าง
ฟังก์ชันตระกูลเดียวกันมีให้เลือกตามเวลาที่มี
| ฟังก์ชัน | เป้า (ซม.) | ช่วงคลาด | รอสูงสุด | ต้องนิ่งนาน |
|---|---|---|---|---|
reach_height_shoot_stable | 120 | ±4 | 3500 ms | 500 ms |
reach_height_shoot_fastb | 127 | ±6 | 2000 ms | 350 ms |
reach_height_shoot_fast | 125 | ±6 | 2000 ms | 350 ms |
reach_height_shoot_fast2 | 120 | ไม่เช็ก | รอ 700 ms ตายตัว | ไม่มี |
3. โค้ดภารกิจจริงยิงอย่างไร
มีสองทางในโฟลเดอร์ และไม่เหมือนกัน
shooting.inoมีservoHoldกับswipe_*แต่ตอนนี้ถูกเรียกจากTest_ball.inoที่ทดสอบบนโต๊ะเท่านั้นDrone_Innovation_Challenge.ino(ภารกิจจริง) ยิงด้วยdrone.servoAngle(SHOOT1_PIN, S_LEFT_ANGLE)ที่ใช้ค่าองศา 62 / 88 / 132 แล้วผ่านrelease_servo_angleซึ่งส่งพัลส์ 1.5 วินาทีและบล็อกโปรแกรม
drone.holdHeight(132);
drone.wait(700);
drone.setHeight(135);
drone.wait(1500); // รอให้นิ่ง
drone.servoAngle(SHOOT1_PIN, S_LEFT_ANGLE); // ยิงด้านหน้าสองทางไปจบที่ตำแหน่งเซอร์โวใกล้กัน เพราะ S_LEFT_ANGLE = 62 แปลงเป็นพัลส์ได้ 1199 µs ใกล้กับ S_LEFT = 1200 (ดูวิธีคำนวณในบท จูนเซอร์โวด้วยองศา) ภารกิจจริงจึงรอความสูงด้วย delay ตายตัว (700 + 1500 ms) ไม่ได้วนอ่านเซนเซอร์เหมือน reach_height_shoot_stable
ลองทำ
- รวมเวลาที่
target_shooting()ใช้ทั้งสี่ท่า (430 ms × 4) แล้วเทียบกับเวลาที่ภารกิจจริงใช้ต่อการยิงหนึ่งครั้ง (รอ 700 + 1500 + 1500) - ดูในตัวจำลอง โปรแกรม
innovation-fourยิงที่ความสูง 140 ซม. ได้วง 20 ทุกเป้า ลองแก้เป็นsh(150)ดูว่าคะแนนลดลงเท่าไร - เขียนฟังก์ชันที่เลือก
reach_height_shoot_stableกับreach_height_shoot_fast2ตามเวลาที่เหลือในงบ
พังตรงไหนบ่อย
- ยิงแล้วเป้าเลยไปข้างบน ความสูงยังไม่นิ่ง ใช้แบบ
stableแทนfast2 - ก้านค้างไม่กลับ เวลา
RESET_HOLD_MSสั้นเกินไป ก้านยังไปไม่ถึงตำแหน่งปิด ก่อนโดรนขยับต่อ - ยิงแล้วโดรนส่าย การกระแทกของลูกดอกทำให้ท่าทางเปลี่ยน รอ
POST_DELAY_MSแล้วค่อยบินต่อ อย่ายกเลิกบรรทัดนั้น - ปรับ
S_LEFT/S_RIGHTทีละมากREADME.inoเตือนให้ปรับทีละน้อยแล้วทดสอบใหม่ ค่าที่ไม่ตรงทำให้ลูกดอกไม่หลุดหรือหลุดทั้งสองข้าง - อยู่ใกล้เป้าเกินไป กติกากำหนดห่างอย่างน้อย 1 เมตร ถ้าใกล้กว่านั้นไม่นับ
- ยิงเกิน 1 ลูกต่อเป้า กติกานับลูกที่คะแนนน้อยที่สุด ยิงซ้ำมีแต่เสีย