2. แพ็กเก็ต I2C ทีละไบต์
อ่านแพ็กเก็ต 6 ไบต์ m_i2cData ตัวนับ และตาราง F_* จาก protocol.cpp และ protocol.hpp
ทุกครั้งที่คุณเรียก drone.takeOff() หรือ drone.fw(250) โค้ดของชมรมไม่ได้ "ส่ง" อะไรออกไปทันที มันเขียนค่าลงตัวแปรในหน่วยความจำของ Arduino แล้วรอให้ Flight Controller มา อ่าน
คิดเหมือนกระดานสั่งอาหารที่ห้องครัว พนักงานเขียนรายการลงกระดานแล้วเปลี่ยนเลขใบสั่งที่มุมกระดาน ห้องครัวเดินมาดูเมื่อสะดวก ถ้าเลขใบสั่งต่างจากที่จำไว้ แปลว่ามีรายการใหม่ ถ้าเลขเท่าเดิม ก็ไม่ต้องทำซ้ำ
หน้าตาของแพ็กเก็ต
protocol.cpp ประกาศแพ็กเก็ตไว้ 6 ไบต์ ส่วน protocol.hpp ประกาศรหัสคำสั่ง
char Arduino::m_i2cData[6];
void Arduino::requestEvent(void) {
Wire.write(m_i2cData); // ใครมาอ่าน ก็ส่ง 6 ไบต์นี้ให้
}| ไบต์ | หน้าที่ |
|---|---|
[0] | ตัวนับ บวกหนึ่งทุกครั้งที่มีคำสั่งใหม่ (m_i2cData[0]++) |
[1] | รหัสคำสั่ง F_* |
[2] ถึง [5] | พารามิเตอร์ ใช้ตามคำสั่ง |
ตัวนับเป็น char จึงวนกลับเมื่อเต็ม ไม่เป็นปัญหา เพราะสิ่งที่ฝั่งอ่านสนใจคือ "ต่างจากครั้งก่อนไหม" โค้ดฝั่ง Flight Controller ไม่มีให้ดู ข้อนี้เป็นการอ่านเจตนาจากรูปแบบโค้ด ไม่ใช่การยืนยันจากฝั่ง FC
ตัวอย่างทีละคำสั่ง
take_off() — ขึ้นบิน
- n+1ตัวนับ
- 01F_ARM
- –[2]
- –[3]
- –[4]
- –[5]
set_direction(1, 2) — เดินหน้า ความเร็วปกติ
- n+1ตัวนับ
- 0FF_DIRECTION
- 01ทิศ
- 02ความเร็ว
- –[4]
- –[5]
set_height(120) — สูง 120 ซม. (120 = 0x0078)
- n+1ตัวนับ
- 0EF_POS
- 78z ต่ำ
- 00z สูง
- –[4]
- –[5]
set_yaw(1, 90) — หมุน 90° (90 = 0x005A)
- n+1ตัวนับ
- 10F_YAW_ROTATE
- 01ทิศหมุน
- 5Aมุม ต่ำ
- 00มุม สูง
- –[5]
motor(1, 50) — มอเตอร์ 1 ที่ 50
- n+1ตัวนับ
- 0CF_MOTOR
- 01หมายเลข
- 32ความเร็ว
- –[4]
- –[5]
ช่อง "–" คือไบต์ที่คำสั่งนั้นไม่ได้เขียน มันคงค่าเดิมจากคำสั่งก่อนหน้า (ไม่ใช่ศูนย์) ซึ่งเป็นพฤติกรรมของโค้ด เพราะฟังก์ชันเขียนทับเฉพาะไบต์ที่ใช้
ค่า 16 บิต เช่นความสูงและมุม แบ่งเป็นไบต์ต่ำก่อนไบต์สูง (little-endian)
void Arduino::set_height(uint16_t z) {
m_i2cData[0]++;
m_i2cData[1] = Arduino::F_POS;
m_i2cData[2] = (uint16_t)(z & 0xffff); // ไบต์ต่ำ
m_i2cData[3] = (uint16_t)(z & 0xffff) >> 8; // ไบต์สูง
}ตาราง F_*
จาก enum _FUNCTION_SEND ใน protocol.hpp ค่าที่ไม่ได้ระบุเลขตรงๆ นับต่อจากตัวก่อนหน้า
| ชื่อ | เลข | ฟังก์ชันที่ใช้ | เขียนไบต์ |
|---|---|---|---|
F_NONE | 0 | ไม่ใช้ | |
F_ARM | 1 | take_off() | [1] |
F_DISARM | 2 | stop_motor() | [1] |
F_AUTO_LAND | 3 | auto_land() | [1] |
F_CALIBRATION | 4 | calibration() | [1] |
F_ROTATE | 5 | rotate() | [1] |
F_FLIP | 6 | flip() ว่างเปล่า ไม่ส่งแพ็กเก็ต | |
F_SPEED | 7 | ประกาศไว้ ไม่มีฟังก์ชันเรียก | |
F_CIRCLE | 8 | circle_360() | [1] |
F_JUMP | 9 | jump() | [1] |
F_MOTOR | 12 | motor(num, speed) | [2], [3] |
F_POS | 14 | set_pos(x, y) และ set_height(z) | [2]–[5] |
F_DIRECTION | 15 | set_direction(dir, speed) | [2], [3] |
F_YAW_ROTATE | 16 | set_yaw(rotate, angle) | [2]–[4] |
F_PICTURE | 31 | picture() | [1] |
F_VIDEO_BEGIN | 32 | video_begin() | [1] |
F_VIDEO_END | 33 | video_end() | [1] |
F_CLOSE_JUMP | 37 | jump_end() | [1] |
สองข้อน่าสังเกต
flip()ในprotocol.cppมีตัวฟังก์ชันแต่ ไม่มีโค้ดข้างใน เรียกdrone.flip()แล้วไม่มีอะไรเกิดขึ้นset_pos(x, y)กับset_height(z)ใช้รหัสF_POSเดียวกัน และเขียนไบต์ [2],[3] ที่เดียวกัน (x กับ z ทับกัน) Flight Controller แยกออกอย่างไรไม่มีข้อมูล จึงไม่ควรเรียกสองอย่างนี้สลับกันโดยไม่ทดสอบ
รหัสทิศและความเร็ว
ค่า dir และ speed ที่ set_direction(dir, speed) ใช้ ตามคอมเมนต์ใน protocol.cpp
dir | ความหมาย | ที่มา |
|---|---|---|
| 1 | เดินหน้า | move_forward_cm |
| 2 | ถอย | move_backward_cm |
| 3 | ซ้าย | move_left_cm |
| 4 | ขวา | move_right_cm |
| 5 | ขึ้น | up() |
| 6 | ลง | down() |
| 7 | หยุดนิ่ง ล็อกตำแหน่ง | poshold() |
ความเร็ว 1, 2, 3 คือ ช้า ปกติ เร็ว (คอมเมนต์ภาษาจีนของ set_direction) ส่วนที่ Flight Controller ทำกับค่าเหล่านี้ ไม่มีข้อมูล
ขากลับ: อ่านความสูง
ทิศตรงข้าม Arduino เป็นฝ่ายอ่าน
static void iicRecv() {
const uint8_t FC_ADDR = 0x34;
const uint8_t LEN = 8;
Wire.requestFrom(FC_ADDR, LEN);
// อ่านเข้า m_externData[0..7] เติม 0 ถ้าไม่ครบ
}
uint16_t Arduino::get_height_cm() const {
return (uint16_t)m_externData[0] | ((uint16_t)m_externData[1] << 8);
}เรียก drone.poll() ก่อน แล้วค่อย drone.heightCm() ถ้าไม่ poll() จะได้ค่าเก่า ไบต์ [2]–[7] ของ m_externData ไม่มีฟังก์ชันตัวใดอ่านต่อ ความหมายไม่ได้ระบุในโค้ด
ปุ่มจากรีโมท
อีกทิศหนึ่ง receiveEvent เก็บไบต์สุดท้ายที่ได้รับเป็นหมายเลขปุ่ม (m_remoteKey) และ keyDo ใช้เทียบกับ KEY_1…KEY_14 ที่ onKey1…14 ส่งเข้ามา
ลองทำ
- เขียนแพ็กเก็ตของ
set_direction(4, 3)เป็นเลขฐานสิบหกทั้ง 6 ไบต์ (ไบต์ [0] ใส่ n+1 ได้) - คำนวณไบต์ต่ำ/สูงของ
set_height(305)(คำตอบ: 305 =0x0131ต่ำ31สูง01) - อ่าน
Arduino::set_yawในprotocol.cppแล้วถามตัวเองว่า ทำไมไบต์ [5] ไม่ถูกเขียน - เปรียบเทียบตาราง F_* นี้กับรหัสคำสั่งของรีโมทใน คำสั่งเดินทางยังไง
พังตรงไหนบ่อย
- คิดว่าเรียกแล้วส่งทันที คำสั่งอยู่ในหน่วยความจำจนกว่า FC จะมาอ่าน ถ้าสั่งสองคำสั่งติดกันโดยไม่รอ คำสั่งแรกถูกเขียนทับก่อนถูกอ่าน เพราะมีกระดานเดียว (นี่คือเหตุผลที่โค้ดชมรมคั่นด้วย
delay) - ตัวนับกับเนื้อหาไม่ตรงกัน ฟังก์ชันบวกตัวนับก่อนแล้วเขียนไบต์ที่เหลือ ถ้า FC มาอ่านกลางคันในทางทฤษฎีอาจได้ตัวนับใหม่กับรหัสเก่า ยังไม่ปรากฏว่าเกิดจริง ไม่มีหลักฐานสองทาง
- ใช้
set_posกับset_heightสลับกัน ใช้ไบต์เดียวกัน - เรียก
flip()ไม่มีผล - ไม่เรียก
poll()แล้วอ่านความสูง ได้ค่าเก่าหรือ 0 ที่ยังไม่มีข้อมูล - เชื่อว่ารู้ว่า FC ทำอะไรกับเลขพวกนี้ รู้แค่ว่าเราเขียนอะไรลงไป