Table of Contents

การใช้งานโมดูลเซ็นเซอร์ 9-DOF IMU Sensor BNO055 BNO085 BNO086 รุ่น Halley Bosch CEVA by Massmore

ข้อมูลทั่วไปสินค้า (Product Introduction)

บทนำสู่เซ็นเซอร์ IMU 9-DOF

Inertial Measurement Unit (IMU) หรือหน่วยวัดแรงเฉื่อย เป็นหัวใจสำคัญของระบบอิเล็กทรอนิกส์สมัยใหม่ที่ต้องการรับรู้การเคลื่อนไหวและการวางแนวในพื้นที่สามมิติ เซ็นเซอร์ IMU แบบ 9 องศาอิสระ (9-Degrees-of-Freedom: 9-DOF) คืออุปกรณ์ที่รวมเซ็นเซอร์สามชนิดไว้ในชิพเดียว ได้แก่ Accelerometer (มาตรวัดความเร่ง) 3 แกน, Gyroscope (ไจโรสโคป) 3 แกน และ Magnetometer (มาตรวัดสนามแม่เหล็ก) 3 แกน การมีอยู่ของเซ็นเซอร์ทั้งสามชนิดนี้ทำให้สามารถวัดค่าต่างๆ ที่เกี่ยวข้องกับการเคลื่อนที่ได้อย่างครบถ้วน ตั้งแต่ความเร่งเชิงเส้น, ความเร็วเชิงมุม ไปจนถึงทิศทางของสนามแม่เหล็กโลก

อย่างไรก็ตาม ความท้าทายที่แท้จริงไม่ได้อยู่ที่การวัดค่าดิบจากเซ็นเซอร์แต่ละตัว แต่อยู่ในกระบวนการที่เรียกว่า “Sensor Fusion” ซึ่งเป็นกระบวนการทางคณิตศาสตร์และอัลกอริธึมที่ซับซ้อนในการหลอมรวมข้อมูลจากเซ็นเซอร์ทั้งสาม เพื่อสร้างข้อมูลการวางแนว (Orientation) ที่มีความเสถียร, แม่นยำ และปราศจากข้อผิดพลาดที่เกิดจากเซ็นเซอร์แต่ละชนิดโดยลำพัง เช่น การสะสมความคลาดเคลื่อน (drift) ของ Gyroscope หรือการถูกรบกวนจากสนามแม่เหล็กภายนอกของ Magnetometer

ภาพรวมของ BNO Sensor Family

เพื่อแก้ไขปัญหาความซับซ้อนของ Sensor Fusion และลดภาระการประมวลผลของไมโครคอนโทรลเลอร์หลัก (Host MCU) จึงได้มีการพัฒนาเซ็นเซอร์อัจฉริยะ (Smart Sensor) ตระกูล BNO ขึ้น

BNO055: พัฒนาโดย Bosch Sensortec ถือเป็นเซ็นเซอร์รุ่นบุกเบิกที่ปฏิวัติวงการ IMU โดยการผนวกรวมหน่วยประมวลผล 32-bit ARM Cortex-M0+ เข้ามาภายในชิพ เพื่อทำหน้าที่ประมวลผลอัลกอริธึม Sensor Fusion ของ Bosch โดยเฉพาะ ผลลัพธ์ที่ได้คือ BNO055 สามารถส่งข้อมูลการวางแนวที่ผ่านการประมวลผลแล้ว เช่น Euler angles หรือ Quaternions ออกมาให้ Host MCU นำไปใช้งานได้ทันที

BNO08x Series (BNO085, BNO086): เป็นเซ็นเซอร์รุ่นถัดมาที่เกิดจากความร่วมมือระหว่าง Bosch Sensortec และ CEVA (โดยผ่านหน่วยธุรกิจ Hillcrest Labs) แม้ว่า BNO08x Series จะใช้แพลตฟอร์มฮาร์ดแวร์พื้นฐานเดียวกันกับ BNO055 คือมีเซ็นเซอร์ MEMS และหน่วยประมวลผล ARM Cortex-M0+ เหมือนกัน 8 แต่จุดแตกต่างที่สำคัญที่สุดและเป็นหัวใจของประสิทธิภาพที่เหนือกว่าคือ

เฟิร์มแวร์ BNO08x Series ทำงานบนเฟิร์มแวร์ SH-2 และอัลกอริธึม MotionEngine™ ของ CEVA ซึ่งเป็นอัลกอริธึม Sensor Fusion ที่มีความก้าวหน้าและทรงพลังกว่าอย่างมีนัยสำคัญ ดังนั้น การเปรียบเทียบระหว่าง BNO055 และ BNO08x จึงไม่ใช่แค่การเปรียบเทียบฮาร์ดแวร์รุ่นใหม่กับรุ่นเก่า แต่เป็นการเปรียบเทียบระหว่างสองระบบนิเวศซอฟต์แวร์ที่ทำงานบนแพลตฟอร์มฮาร์ดแวร์เดียวกัน ซึ่งส่งผลโดยตรงต่อความแม่นยำ, ความเร็ว และความสามารถของเซ็นเซอร์

ข้อมูลทางเทคนิค (Technical Specifications)

ข้อมูลจำเพาะทางเทคนิคเป็นปัจจัยพื้นฐานที่จำเป็นต้องพิจารณาในการเลือกเซ็นเซอร์ให้เหมาะสมกับงานออกแบบวงจรและข้อกำหนดของระบบ ตารางต่อไปนี้รวบรวมคุณสมบัติทางเทคนิคที่สำคัญของเซ็นเซอร์ทั้งสามรุ่นจากเอกสารข้อมูลของผู้ผลิตและผู้จัดจำหน่าย

ตารางที่ 1: เปรียบเทียบคุณสมบัติทางเทคนิค

คุณลักษณะ (Feature)BNO055BNO085BNO086
ผู้ผลิตฮาร์ดแวร์Bosch SensortecBosch SensortecBosch Sensortec
ผู้พัฒนาเฟิร์มแวร์Bosch SensortecCEVA / Hillcrest LabsCEVA / Hillcrest Labs
เฟิร์มแวร์/อัลกอริธึมBosch Fusion AlgorithmCEVA SH-2 MotionEngine™CEVA SH-2 MotionEngine™
หน่วยประมวลผลภายใน32-bit ARM® Cortex™-M0+32-bit ARM® Cortex™-M0+32-bit ARM® Cortex™-M0+
แรงดันไฟฟ้า (VDD)2.4V – 3.6V1.7V – 3.6V1.7V – 3.6V
แรงดันไฟฟ้า (VDDIO)1.7V – 3.6V1.7V – 3.6V1.7V – 3.6V
การเชื่อมต่อ (Interface)I2C, UARTI2C, SPI, UARTI2C, SPI, UART
แพ็คเกจ (Package)28-LGA (5.2×3.8 mm)28-LGA (5.2×3.8 mm)28-LGA (5.2×3.8 mm)
ช่วงอุณหภูมิทำงาน-40°C ~ 85°C-40°C ~ 85°C-40°C ~ 85°C
Accelerometer Range±2g, ±4g, ±8g, ±16gN/A (จัดการโดยเฟิร์มแวร์)N/A (จัดการโดยเฟิร์มแวร์)
Gyroscope Range±125°/s ถึง ±2000°/sN/A (จัดการโดยเฟิร์มแวร์)N/A (จัดการโดยเฟิร์มแวร์)
สถานะการผลิตNot For New Designs (NRND)ActiveActive

เปรียบเทียบ(Comparison) : BNO055 vs. BNO085 vs. BNO086

การเลือกระหว่างเซ็นเซอร์ทั้งสามรุ่นนี้จำเป็นต้องพิจารณาให้ลึกกว่าข้อมูลจำเพาะพื้นฐาน โดยต้องทำความเข้าใจถึงความแตกต่างในเชิงประสิทธิภาพและฟังก์ชันการทำงาน ซึ่งเป็นผลโดยตรงจากอัลกอริธึม Sensor Fusion ที่แตกต่างกัน

อัลกอริธึม Sensor Fusion และประสิทธิภาพ

BNO055: ใช้อัลกอริธึมดั้งเดิมของ Bosch ซึ่งให้ข้อมูลฟิวชั่นพื้นฐานที่อัตราการส่งข้อมูลสูงสุด 100 Hz  แม้จะเพียงพอสำหรับงานทั่วไป แต่ก็มีข้อจำกัดที่สำคัญ คือมีแนวโน้มที่จะเกิดปัญหา “runaway calibration” หรือการที่ค่าสอบเทียบพื้นฐานค่อยๆ ผิดเพี้ยนไปเมื่อมีการเคลื่อนไหวที่เป็นรูปแบบซ้ำๆ ต่อเนื่องเป็นเวลานาน 8นอกจากนี้ การส่งข้อมูล Euler angles (yaw, pitch, roll) โดยตรงจากชิพนั้น แม้จะดูสะดวก แต่ก็มีพฤติกรรมที่ไม่เสถียรและอาจให้ค่าที่ไม่ถูกต้องเมื่อเซ็นเซอร์มีการเอียง (tilt) เกิน 45 องศา 

BNO08x Series (BNO085/BNO086): การทำงานบนเฟิร์มแวร์ SH-2 และ MotionEngine™ ของ CEVA ทำให้ BNO08x มีประสิทธิภาพที่เหนือกว่า BNO055 อย่างก้าวกระโดดในหลายมิติ 

  • อัตราการส่งข้อมูล (Data Rate): รองรับอัตราการส่งข้อมูลที่สูงกว่ามาก โดยสามารถส่งข้อมูล Gyro Rotation Vector ได้สูงถึง 1 kHz และข้อมูล Fusion ทั่วไปที่ 400 Hz  ซึ่งจำเป็นอย่างยิ่งสำหรับแอปพลิเคชันที่ต้องการการตอบสนองที่รวดเร็วและมีความหน่วงต่ำ (low latency) เช่น อุปกรณ์ Augmented Reality (AR) และ Virtual Reality (VR)
  • ความแม่นยำและเสถียรภาพ: อัลกอริธึมที่ก้าวหน้ากว่าของ CEVA สามารถจัดการกับปัญหาการสะสมความคลาดเคลื่อน (drift) และการ runaway calibration ได้ดีกว่า BNO055 อย่างมีนัยสำคัญ ทำให้ได้ข้อมูลการวางแนวที่มีความเสถียรและน่าเชื่อถือมากกว่าในระยะยาว 
  • ข้อมูลเอาท์พุต: BNO08x เน้นการส่งข้อมูลในรูปแบบ Quaternions ซึ่งเป็นรูปแบบทางคณิตศาสตร์ที่ใช้ในการแสดงการหมุนในสามมิติ มีความแม่นยำสูงกว่าและไม่เกิดปัญหา “Gimbal Lock” เหมือน Euler angles 4 BNO08x จะไม่ส่งค่า Euler angles ออกมาโดยตรง แต่แนะนำให้ผู้ใช้นำค่า Quaternions ที่ได้ไปคำนวณเป็น Euler angles ที่ฝั่ง Host MCU เอง เพื่อความถูกต้องสูงสุด 

ความสามารถในการสอบเทียบ (Calibration Capabilities)

BNO055: มีกระบวนการสอบเทียบที่ผู้ใช้ต้องเคลื่อนไหวเซ็นเซอร์ในท่าทางต่างๆ เพื่อให้เซ็นเซอร์เรียนรู้และปรับค่าชดเชย อย่างไรก็ตาม BNO055 ไม่มีฟังก์ชันในการบันทึกค่าการสอบเทียบที่ได้ลงในหน่วยความจำถาวรของชิพโดยตรง ซึ่งหมายความว่าอาจจำเป็นต้องทำการสอบเทียบใหม่ทุกครั้งที่เปิดใช้งานอุปกรณ์

BNO08x Series (BNO085/BNO086): มาพร้อมกับระบบ “Dynamic Calibration” ที่มีความสามารถสูงกว่า  จุดเด่นที่สำคัญที่สุดคือ

สามารถบันทึกข้อมูลการสอบเทียบ (เรียกว่า DCD file) ลงในหน่วยความจำแฟลชภายในตัวชิพได้ ทั้งแบบอัตโนมัติและตามคำสั่ง คุณสมบัตินี้ช่วยลดความยุ่งยากและประหยัดเวลาได้อย่างมาก เนื่องจากไม่จำเป็นต้องทำการสอบเทียบใหม่ทุกครั้งที่เปิดเครื่อง

BNO086 (Interactive Calibration): BNO086 มีฟีเจอร์พิเศษที่เรียกว่า “Interactive Calibration” ซึ่งเป็นความสามารถที่เหนือกว่า BNO085 และออกแบบมาสำหรับแอปพลิเคชันหุ่นยนต์โดยเฉพาะ  ฟีเจอร์นี้ไม่ได้ทำงานโดยอาศัยข้อมูลจากเซ็นเซอร์เพียงอย่างเดียว แต่เป็นการทำงานร่วมกันระหว่างเซ็นเซอร์และ Host MCU โดย Host MCU ซึ่งมีความรู้เกี่ยวกับสถานะการเคลื่อนที่ของหุ่นยนต์ (เช่น “ตอนนี้หุ่นยนต์กำลังหยุดนิ่ง” หรือ “กำลังเคลื่อนที่เป็นเส้นตรง”) สามารถส่งข้อมูลสถานะนี้กลับไปยัง BNO086 ได้ อัลกอริธึมภายใน BNO086 จะใช้ข้อมูล “ground truth” นี้เพื่อทำการสอบเทียบตัวเองได้อย่างรวดเร็วและแม่นยำยิ่งขึ้น โดยเฉพาะการชดเชย Gyroscope drift ซึ่งส่งผลให้ได้ค่าทิศทาง (Heading) ที่แม่นยำสูงสุด เหมาะอย่างยิ่งสำหรับหุ่นยนต์ที่ใช้ระบบนำทางอัจฉริยะ เช่น SLAM (Simultaneous Localization and Mapping) 

คุณสมบัติขั้นสูงและฟังก์ชันเพิ่มเติม

BNO08x Series มาพร้อมกับฟังก์ชันการทำงานขั้นสูงมากมายที่ไม่มีใน BNO055:

  • Rotation Vectors เฉพาะทาง: BNO08x มี Rotation Vector หลายประเภทที่ปรับให้เหมาะกับการใช้งานที่แตกต่างกัน เช่น Game Rotation Vector ซึ่งไม่ใช้ข้อมูลจาก Magnetometer เพื่อป้องกันการรบกวนจากสนามแม่เหล็กภายนอก เหมาะสำหรับอุปกรณ์ VR/AR, และ AR/VR Stabilized Rotation Vector ที่ถูกปรับแต่งมาเพื่อลดความหน่วงและ Jitter 
  • Activity Classification: BNO08x สามารถจำแนกกิจกรรมของผู้ใช้ได้ เช่น การอยู่นิ่ง, การเดิน, การวิ่ง และยังมาพร้อมกับฟังก์ชัน Step Counter (ตัวนับก้าว), Tap Detector (ตัวตรวจจับการเคาะ), และ Shake Detector (ตัวตรวจจับการสั่น) 
  • TARE Function: เป็นฟังก์ชันที่ช่วยให้สามารถตั้งค่าการวางแนวปัจจุบันให้เป็น “ศูนย์” หรือเป็นทิศทางอ้างอิงใหม่ได้ทันทีตามต้องการ ซึ่งมีประโยชน์มากในการรีเซ็ตมุมมองในแอปพลิเคชัน VR หรือการกำหนดทิศทางเริ่มต้นของหุ่นยนต์ 
  • UART-RVC Mode: เป็นโหมดการทำงานพิเศษที่ออกแบบมาเพื่อลดความซับซ้อนในการใช้งานสำหรับหุ่นยนต์ภาคพื้นดิน (เช่น หุ่นยนต์ดูดฝุ่น)  เมื่อเปิดใช้งานโหมดนี้ BNO08x จะส่งข้อมูล Heading และ Acceleration ที่ผ่านการประมวลผลและสอบเทียบแล้วออกมาทางพอร์ต UART อย่างต่อเนื่องที่อัตรา 100 Hz โดยที่ผู้ใช้แทบไม่ต้องเขียนโค้ดเพื่อตั้งค่าหรือสอบเทียบใดๆ 

สรุปและข้อเสนอแนะ

ตารางที่ 2: สรุปข้อแตกต่างเชิงคุณลักษณะและการใช้งาน

คุณลักษณะ (Feature)BNO055BNO085BNO086
อัลกอริธึม FusionStandard Bosch FusionCEVA SH-2 MotionEngine™CEVA SH-2 MotionEngine™
การบันทึกค่า Calibrationไม่รองรับรองรับ (DCD File)รองรับ (DCD File)
Interactive Calibrationไม่มีไม่มีมี
อัตราข้อมูลสูงสุด100 Hz~400 Hz (1 kHz สำหรับ Gyro)~400 Hz (1 kHz สำหรับ Gyro)
คุณสมบัติพิเศษพื้นฐานActivity Classifier, TARE, UART-RVC, Specialized Vectorsเหมือน BNO085 + Interactive Calibration
กรณีใช้งานที่แนะนำโปรเจกต์ทั่วไป, การเรียนรู้, ต้นแบบที่ไม่ต้องการความแม่นยำสูงAR/VR, อุปกรณ์สวมใส่, HIDs, หุ่นยนต์ประสิทธิภาพสูงหุ่นยนต์อัตโนมัติขั้นสูง, โดรน, งานที่ต้องการ Heading แม่นยำสูงสุด
ข้อได้เปรียบหลักใช้งานง่าย, ให้ค่า Euler โดยตรงประสิทธิภาพสูง, บันทึก Calibration ได้, ฟีเจอร์หลากหลายความแม่นยำสูงสุดสำหรับหุ่นยนต์
  • เลือก BNO055 เมื่อต้องการเซ็นเซอร์ที่ใช้งานง่ายสำหรับโปรเจกต์ทั่วไป, งานอดิเรก, หรือการสร้างต้นแบบที่ไม่ต้องการความแม่นยำสูงสุด และมีข้อจำกัดด้านงบประมาณ (ปัจจุบันมีสถานะเป็น Not For New Designs หรือ NRND ซึ่งหมายความว่าไม่แนะนำสำหรับงานออกแบบใหม่) 
  • เลือก BNO085 เมื่อต้องการประสิทธิภาพและความแม่นยำที่สูงกว่า BNO055 อย่างชัดเจน เป็นตัวเลือกที่คุ้มค่าและทรงพลังสำหรับงานส่วนใหญ่ ตั้งแต่อุปกรณ์สวมใส่, อุปกรณ์ควบคุมเกม, AR/VR ไปจนถึงหุ่นยนต์ประสิทธิภาพสูง
  • เลือก BNO086 เมื่อต้องการประสิทธิภาพสูงสุดสำหรับงานเฉพาะทาง โดยเฉพาะอย่างยิ่งในงานหุ่นยนต์อัตโนมัติ, โดรน, หรือแอปพลิเคชันใดๆ ที่ความแม่นยำของทิศทาง (Heading) เป็นสิ่งสำคัญที่สุด และสามารถใช้ประโยชน์จากฟีเจอร์ Interactive Calibration ได้

Connection Diagram

การเชื่อมต่อเซ็นเซอร์กับไมโครคอนโทรลเลอร์ เช่น Arduino สามารถทำได้หลายวิธี ขึ้นอยู่กับอินเทอร์เฟซที่เลือกใช้

การเชื่อมต่อแบบ I2C (BNO055/BNO08x)

เป็นวิธีที่นิยมที่สุดเนื่องจากใช้สายสัญญาณเพียง 2 เส้น (ไม่รวมสายไฟ)

  • VIN/VCC -> 3.3V หรือ 5V ของ Arduino
  • GND -> GND ของ Arduino
  • SCL -> SCL ของ Arduino (A5 บน Uno)
  • SDA -> SDA ของ Arduino (A4 บน Uno)

การเชื่อมต่อแบบ SPI (BNO08x เท่านั้น)

ให้ความเร็วในการสื่อสารที่สูงกว่า I2C แต่ใช้สายสัญญาณมากกว่า

  • VIN/VCC -> 3.3V หรือ 5V ของ Arduino
  • GND -> GND ของ Arduino
  • SCL/SCK -> SCK ของ Arduino (ขา 13 บน Uno)
  • SDA/MISO -> MISO ของ Arduino (ขา 12 บน Uno)
  • DI/MOSI -> MOSI ของ Arduino (ขา 11 บน Uno)
  • CS -> Digital Pin ของ Arduino (เช่น ขา 10)
  • INT -> Digital Pin ของ Arduino (เช่น ขา 9)
  • RST -> Digital Pin ของ Arduino (เช่น ขา 5)

หมายเหตุสำคัญ: สำหรับการเชื่อมต่อแบบ SPI กับ BNO08x จำเป็นต้องต่อขา INT และ RST เพื่อให้การทำงานมีความเสถียร 

การเชื่อมต่อแบบ UART (รวมถึงโหมด UART-RVC)

เป็นอีกทางเลือกหนึ่งสำหรับการสื่อสารแบบอนุกรม

  • VIN/VCC -> 3.3V หรือ 5V ของ Arduino
  • GND -> GND ของ Arduino
  • SCL/TX (ของเซ็นเซอร์) -> RX ของ Arduino (เช่น ขา 0)
  • SDA/RX (ของเซ็นเซอร์) -> TX ของ Arduino (เช่น ขา 1)

คำเตือนที่สำคัญ: ปัญหาความเข้ากันได้ของ BNO08x กับ ESP32 ผ่าน I2C

มีรายงานที่ได้รับการยืนยันจากหลายแหล่งว่า การใช้งาน BNO08x Series (BNO085/BNO086) กับไมโครคอนโทรลเลอร์ในตระกูล Espressif (ESP32, ESP32-S3) ผ่านอินเทอร์เฟซ I2C มีปัญหาด้านความเสถียร  เฟิร์มแวร์ของ BNO08x มีการละเมิดโปรโตคอล I2C ในบางสถานการณ์ ซึ่งทำให้การสื่อสารกับชิพตระกูล ESP32 ไม่น่าเชื่อถือและอาจล้มเหลวได้

ข้อเสนอแนะ: หากมีการวางแผนที่จะใช้เซ็นเซอร์ BNO085 หรือ BNO086 ร่วมกับบอร์ด ESP32 ขอแนะนำอย่างยิ่งให้หลีกเลี่ยงการเชื่อมต่อผ่าน I2C และให้เลือกใช้อินเทอร์เฟซ SPI หรือ UART แทน เพื่อให้ระบบทำงานได้อย่างมีเสถียรภาพและหลีกเลี่ยงปัญหาที่ยากต่อการแก้ไขในภายหลัง

Interface Description

I2C Interface

  • BNO055: มี I2C Address เริ่มต้นที่ 0x28 สามารถเปลี่ยนเป็น
    0x29 ได้โดยการให้แรงดันไฟฟ้าที่ขา ADR
  • BNO08x: มี I2C Address เริ่มต้นที่ 0x4A สามารถเปลี่ยนเป็น
    0x4B ได้โดยการให้แรงดันไฟฟ้าที่ขา DI 

SPI Interface (BNO08x เท่านั้น)

เป็นอินเทอร์เฟซแบบ 4-wire ที่ต้องการขา CS (Chip Select) เพื่อเลือกอุปกรณ์, INT (Interrupt) เพื่อแจ้งเตือน Host MCU ว่ามีข้อมูลพร้อม และ RST (Reset) เพื่อรีเซ็ตฮาร์ดแวร์  การเลือกโหมด SPI ทำได้โดยการตั้งค่าที่ขา PS0 และ PS1

UART Interface

เป็นการสื่อสารแบบอนุกรม 2 สาย (TX, RX) BNO055 รองรับ UART ที่ความเร็ว 115200 bps  สำหรับ BNO08x สามารถเลือกโหมดการทำงานของ UART ได้ผ่านการตั้งค่าฮาร์ดแวร์ที่ขา PS0 และ PS1 :

  • PS1 = Low, PS0 = Low (MODE PIN): I2C (ค่าเริ่มต้น)
  • PS1 = Low, PS0 = High (MODE PIN) : UART-RVC (โหมดสำหรับหุ่นยนต์)
  • PS1 = High, PS0 = Low: UART-SHTP (โหมดมาตรฐาน)
  • PS1 = High, PS0 = High: SPI

ตัวอย่างการใช้งาน (Usage Examples)

การแสดงผลการวางแนว 3 มิติ

สามารถนำข้อมูล Quaternions หรือ Euler angles ที่ได้จากเซ็นเซอร์ไปใช้ร่วมกับซอฟต์แวร์บนคอมพิวเตอร์ เช่น Processing หรือแอปพลิเคชันที่เขียนด้วย Python เพื่อสร้างภาพวัตถุ 3 มิติบนหน้าจอที่สามารถหมุนและเคลื่อนที่ตามการวางแนวของเซ็นเซอร์จริงได้  สิ่งนี้มีประโยชน์อย่างยิ่งในการทดสอบและทำความเข้าใจการทำงานของเซ็นเซอร์ในเบื้องต้น

การควบคุมทิศทางหุ่นยนต์ด้วย UART-RVC

สำหรับโครงการหุ่นยนต์ภาคพื้นดิน การใช้ BNO08x ในโหมด UART-RVC เป็นวิธีที่ง่ายและมีประสิทธิภาพสูง  เพียงเชื่อมต่อขา TX ของเซ็นเซอร์เข้ากับขา RX ของไมโครคอนโทรลเลอร์ ก็สามารถอ่านค่าทิศทาง (yaw) ที่มีความเสถียรสูงออกมาได้โดยตรง ค่านี้สามารถนำไปใช้ในอัลกอริธึมควบคุม เช่น PID Control เพื่อสั่งให้หุ่นยนต์เคลื่อนที่เป็นเส้นตรงหรือเลี้ยวไปยังทิศทางที่ต้องการได้อย่างแม่นยำ โดยไม่ต้องกังวลกับความซับซ้อนของ Sensor Fusion

การสร้างอุปกรณ์นับก้าว

ด้วยฟังก์ชัน Activity Classifier ที่มีในตัว BNO08x สามารถพัฒนาอุปกรณ์สวมใส่ (Wearable Device) พื้นฐานได้อย่างง่ายดาย  เพียงเปิดใช้งานรายงาน Step Counter ผ่านไลบรารี ก็จะสามารถอ่านจำนวนก้าวที่ผู้ใช้เดินได้โดยตรง ซึ่งสามารถนำไปแสดงผลบนจอ LCD หรือส่งข้อมูลผ่าน Bluetooth ไปยังสมาร์ทโฟนได้

ตัวอย่างโปรแกรม github (GitHub Program Examples)

ไลบรารีโอเพนซอร์สจากผู้ผลิตบอร์ดทดลอง เช่น Adafruit และ SparkFun เป็นเครื่องมือสำคัญที่ช่วยให้นักพัฒนาสามารถเริ่มต้นใช้งานเซ็นเซอร์เหล่านี้ได้อย่างรวดเร็ว

BNO055 (Arduino)

ไลบรารีที่ได้รับความนิยมสูงสุดคือ Adafruit BNO055 ซึ่งเป็นส่วนหนึ่งของระบบ Adafruit Unified Sensor ทำให้มีรูปแบบการเรียกใช้งานที่เป็นมาตรฐานและเข้าใจง่าย

				
					#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>

/* Set the delay between fresh samples */
uint16_t BNO055_SAMPLERATE_DELAY_MS = 100;

// Check I2C device address and correct line below (by default address is 0x29 or 0x28)
//                                   id, address
Adafruit_BNO055 bno = Adafruit_BNO055(55, 0x28, &Wire);

void setup(void)
{
  Serial.begin(115200);
  Wire.begin(21, 22);
  while (!Serial) delay(10);  // wait for serial port to open!

  Serial.println("Orientation Sensor Test"); Serial.println("");

  /* Initialise the sensor */
  if (!bno.begin())
  {
    /* There was a problem detecting the BNO055 ... check your connections */
    Serial.print("Ooops, no BNO055 detected ... Check your wiring or I2C ADDR!");
    while (1);
  }

  delay(1000);
}

void loop(void)
{
  //could add VECTOR_ACCELEROMETER, VECTOR_MAGNETOMETER,VECTOR_GRAVITY...
  sensors_event_t orientationData , angVelocityData , linearAccelData, magnetometerData, accelerometerData, gravityData;
  bno.getEvent(&orientationData, Adafruit_BNO055::VECTOR_EULER);
  bno.getEvent(&angVelocityData, Adafruit_BNO055::VECTOR_GYROSCOPE);
  bno.getEvent(&linearAccelData, Adafruit_BNO055::VECTOR_LINEARACCEL);
  bno.getEvent(&magnetometerData, Adafruit_BNO055::VECTOR_MAGNETOMETER);
  bno.getEvent(&accelerometerData, Adafruit_BNO055::VECTOR_ACCELEROMETER);
  bno.getEvent(&gravityData, Adafruit_BNO055::VECTOR_GRAVITY);

  printEvent(&orientationData);
  printEvent(&angVelocityData);
  printEvent(&linearAccelData);
  printEvent(&magnetometerData);
  printEvent(&accelerometerData);
  printEvent(&gravityData);

  int8_t boardTemp = bno.getTemp();
  Serial.println();
  Serial.print(F("temperature: "));
  Serial.println(boardTemp);

  uint8_t system, gyro, accel, mag = 0;
  bno.getCalibration(&system, &gyro, &accel, &mag);
  Serial.println();
  Serial.print("Calibration: Sys=");
  Serial.print(system);
  Serial.print(" Gyro=");
  Serial.print(gyro);
  Serial.print(" Accel=");
  Serial.print(accel);
  Serial.print(" Mag=");
  Serial.println(mag);

  Serial.println("--");
  delay(BNO055_SAMPLERATE_DELAY_MS);
}

void printEvent(sensors_event_t* event) {
  double x = -1000000, y = -1000000 , z = -1000000; //dumb values, easy to spot problem
  if (event->type == SENSOR_TYPE_ACCELEROMETER) {
    Serial.print("Accl:");
    x = event->acceleration.x;
    y = event->acceleration.y;
    z = event->acceleration.z;
  }
  else if (event->type == SENSOR_TYPE_ORIENTATION) {
    Serial.print("Orient:");
    x = event->orientation.x;
    y = event->orientation.y;
    z = event->orientation.z;
  }
  else if (event->type == SENSOR_TYPE_MAGNETIC_FIELD) {
    Serial.print("Mag:");
    x = event->magnetic.x;
    y = event->magnetic.y;
    z = event->magnetic.z;
  }
  else if (event->type == SENSOR_TYPE_GYROSCOPE) {
    Serial.print("Gyro:");
    x = event->gyro.x;
    y = event->gyro.y;
    z = event->gyro.z;
  }
  else if (event->type == SENSOR_TYPE_ROTATION_VECTOR) {
    Serial.print("Rot:");
    x = event->gyro.x;
    y = event->gyro.y;
    z = event->gyro.z;
  }
  else if (event->type == SENSOR_TYPE_LINEAR_ACCELERATION) {
    Serial.print("Linear:");
    x = event->acceleration.x;
    y = event->acceleration.y;
    z = event->acceleration.z;
  }
  else if (event->type == SENSOR_TYPE_GRAVITY) {
    Serial.print("Gravity:");
    x = event->acceleration.x;
    y = event->acceleration.y;
    z = event->acceleration.z;
  }
  else {
    Serial.print("Unk:");
  }

  Serial.print("\tx= ");
  Serial.print(x);
  Serial.print(" |\ty= ");
  Serial.print(y);
  Serial.print(" |\tz= ");
  Serial.println(z);
}
				
			

BNO085 / BNO086 (ESP32 Arduino – I2C Standard Mode)

มีไลบรารีที่ยอดเยี่ยมจาก SparkFun ซึ่งจัดการกับการสื่อสารโปรโตคอล SH-2 ที่ซับซ้อนให้ทั้งหมด

				
					
/*
  Using the BNO08x IMU

  Example : Euler Angles

  Feel like supporting our work? Buy a board from SparkFun!
  https://www.sparkfun.com/products/22857
*/

#include <Wire.h>

#include "SparkFun_BNO08x_Arduino_Library.h"  // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BNO08x
BNO08x myIMU;

// For the most reliable interaction with the SHTP bus, we need
// to use hardware reset control, and to monitor the H_INT pin.
// The H_INT pin will go low when its okay to talk on the SHTP bus.
// Note, these can be other GPIO if you like.
// Define as -1 to disable these features.
//#define BNO08X_INT  17
#define BNO08X_INT  -1
//#define BNO08X_RST  16
#define BNO08X_RST  -1
//
//#define BNO08X_ADDR 0x4B  // Alternate address if ADR jumper is closed
#define BNO08X_ADDR 0x4A //

void setup() {
  Serial.begin(115200);
  
  while(!Serial) delay(10); // Wait for Serial to become available.
  // Necessary for boards with native USB (like the SAMD51 Thing+).
  // For a final version of a project that does not need serial debug (or a USB cable plugged in),
  // Comment out this while loop, or it will prevent the remaining code from running.
  
  Serial.println();
  Serial.println("BNO08x Read Example");

  Wire.begin(21,22);

  //if (myIMU.begin() == false) {  // Setup without INT/RST control (Not Recommended)
  if (myIMU.begin(BNO08X_ADDR, Wire, BNO08X_INT, BNO08X_RST) == false) {
    Serial.println("BNO08x not detected at default I2C address. Check your jumpers and the hookup guide. Freezing...");
    while (1)
      ;
  }
  Serial.println("BNO08x found!");

  // Wire.setClock(400000); //Increase I2C data rate to 400kHz

  setReports();

  Serial.println("Reading events");
  delay(100);
}

// Here is where you define the sensor outputs you want to receive
void setReports(void) {
  Serial.println("Setting desired reports");
  if (myIMU.enableRotationVector() == true) {
    Serial.println(F("Rotation vector enabled"));
    Serial.println(F("Output in form roll, pitch, yaw"));
  } else {
    Serial.println("Could not enable rotation vector");
  }
}

void loop() {
  delay(10);

  if (myIMU.wasReset()) {
    Serial.print("sensor was reset ");
    setReports();
  }

  // Has a new event come in on the Sensor Hub Bus?
  if (myIMU.getSensorEvent() == true) {

    // is it the correct sensor data we want?
    if (myIMU.getSensorEventID() == SENSOR_REPORTID_ROTATION_VECTOR) {

    float roll = (myIMU.getRoll()) * 180.0 / PI; // Convert roll to degrees
    float pitch = (myIMU.getPitch()) * 180.0 / PI; // Convert pitch to degrees
    float yaw = (myIMU.getYaw()) * 180.0 / PI; // Convert yaw / heading to degrees

    Serial.print(roll, 1);
    Serial.print(F(","));
    Serial.print(pitch, 1);
    Serial.print(F(","));
    Serial.print(yaw, 1);

    Serial.println();
    }
  }
}
				
			

ตัวอย่างการประยุกต์ใช้งานกับบอร์ด ESP32 Breakout Board by Massmore

ตัวอย่างโปรแกรม GitHub
https://github.com/Massmore/BNO0xx_SKU-1010

ไลบรารีและไดรเวอร์จากผู้ผลิตและชุมชนนักพัฒนาเป็นแหล่งข้อมูลสำคัญสำหรับการนำเซ็นเซอร์ไปใช้งาน

ตัวอย่างการใช้งาน BNO055

การต่อวงจรใช้งาน Diagrams ทั้ง 3 โมดูลสามารถต่อใช้งาน I2C ได้เหมือนกันทุกประการ

โปรแกรมตัวอย่าง การอ่านค่าเซ็นเซอร์ BNO055 แสดงผลผ่านทางหน้าจอ TFT_LCD

				
					/*
 * โปรแกรม ESP32 อ่านค่า BNO055 (Addr 0x28)
 * แสดงผล Euler Angles และเข็มทิศบนจอ ST7789 (240x240)
 */

#include <Wire.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>

// --- สร้าง Object สำหรับไลบรารี ---
TFT_eSPI tft = TFT_eSPI();
// ระบุ Address เป็น 0x28
Adafruit_BNO055 bno055 = Adafruit_BNO055(-1, 0x28);

// ตัวแปรสำหรับเก็บค่ามุม Yaw ของรอบก่อนหน้า (สำหรับลบเข็มทิศเก่า)
float last_yaw = 0;

// --- ฟังก์ชันสำหรับวาด UI พื้นฐาน ---
void drawUiLayout() {
  tft.fillScreen(TFT_BLACK); // พื้นหลังสีน้ำเงินเข้ม
  tft.setTextColor(TFT_WHITE, TFT_BLACK);

  // หัวข้อ
  tft.setTextDatum(MC_DATUM);
  tft.drawString("BNO055 9-DOF IMU Sensor", tft.width() / 2, 15, 2);

  // วาดกรอบสำหรับค่า Roll & Pitch
  tft.drawRect(10, 35, tft.width() - 20, 50, TFT_WHITE);
  tft.drawString("Roll", tft.width() / 4, 45, 2);
  tft.drawString("Pitch", tft.width() * 3 / 4, 45, 2);

  // วาดกรอบสำหรับเข็มทิศ Yaw
  tft.drawRect(10, 95, tft.width() - 20, 135, TFT_WHITE);
  tft.drawString("Heading (Yaw)", tft.width() / 2, 105, 2);
  // วาดวงกลมเข็มทิศ
  tft.drawCircle(tft.width() / 2, 165, 50, TFT_DARKGREY);
  tft.drawCircle(tft.width() / 2, 165, 51, TFT_DARKGREY);
  // ขีดบอกทิศ
  tft.drawString("N", tft.width()/2, 122, 2);
}

// --- ฟังก์ชันสำหรับอัปเดตเข็มทิศ ---
void updateCompass(float yaw) {
  int center_x = tft.width() / 2;
  int center_y = 165;
  int radius = 50;

  // --- ลบเข็มเก่า ---
  // คำนวณตำแหน่งปลายเข็มเก่า
  float old_rad = last_yaw * PI / 180.0; // แปลงองศาเป็นเรเดียน
  int old_x = center_x + radius * sin(old_rad);
  int old_y = center_y - radius * cos(old_rad);
  // วาดทับด้วยสีพื้นหลัง
  tft.drawLine(center_x, center_y, old_x, old_y, TFT_BLACK);
  
  // --- วาดเข็มใหม่ ---
  // คำนวณตำแหน่งปลายเข็มใหม่
  float new_rad = yaw * PI / 180.0;
  int new_x = center_x + radius * sin(new_rad);
  int new_y = center_y - radius * cos(new_rad);
  // วาดเข็มสีแดง
  tft.drawLine(center_x, center_y, new_x, new_y, TFT_RED);

  last_yaw = yaw; // อัปเดตค่ามุมเก่า
}

void setup() {
  Serial.begin(115200);
  Wire.begin();

  // --- เริ่มต้นจอภาพ ---
  tft.init();
  tft.setRotation(0);
  drawUiLayout();

  // --- เริ่มต้น BNO055 ---
  if (!bno055.begin()) {
    Serial.println("BNO055 not detected. Check wiring/address.");
    tft.setTextDatum(MC_DATUM);
    tft.drawString("BNO055 FAIL", tft.width() / 2, tft.height() / 2, 4);
    while (1);
  }
  
  delay(1000); // รอให้เซ็นเซอร์นิ่ง
  bno055.setExtCrystalUse(true); // ใช้ Crystal ภายนอกเพื่อความแม่นยำ
  Serial.println("BNO055 Found!");
}

void loop() {
  // --- อ่านค่า Euler Angles จากเซ็นเซอร์ ---
  imu::Vector<3> euler = bno055.getVector(Adafruit_BNO055::VECTOR_EULER);
  // Adafruit library map: x=Yaw, y=Roll, z=Pitch
  float yaw = euler.x();
  float roll = euler.y();
  float pitch = euler.z();
  
  // --- อัปเดตค่าบนหน้าจอ ---
  tft.setTextDatum(MC_DATUM);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);

  // --- แสดงค่า Roll ---
  // 1. วาดสี่เหลี่ยมสีพื้นหลังทับของเก่าก่อน
  tft.fillRect(20, 55, 80, 30, TFT_BLACK); // x, y, width, height, color
  // 2. วาดค่าใหม่
  tft.drawFloat(roll, 1, tft.width() / 4, 70, 4);

  // --- แสดงค่า Pitch ---
  // 1. วาดสี่เหลี่ยมสีพื้นหลังทับของเก่าก่อน
  tft.fillRect(140, 55, 80, 30, TFT_BLACK);
  // 2. วาดค่าใหม่
  tft.drawFloat(pitch, 1, tft.width() * 3 / 4, 70, 4);
  
  // --- แสดงค่า Yaw (ตัวเลข) ---
  // 1. วาดสี่เหลี่ยมสีพื้นหลังทับของเก่าก่อน
  tft.fillRect(80, 200, 80, 30, TFT_BLACK);
  // 2. วาดค่าใหม่
  tft.drawFloat(yaw, 1, tft.width() / 2, 215, 4);

  // อัปเดตกราฟิกเข็มทิศ (ส่วนนี้ทำงานได้ดีอยู่แล้ว)
  updateCompass(yaw);

  delay(75); // อัปเดตหน้าจอประมาณ 50 ครั้งต่อวินาที
}
				
			

ตัวอย่างการใช้งาน BNO08x 

การต่อวงจรใช้งาน Diagrams เหมือนกันกับ BNO055 และโปรแกรมของ BNO085/BNO086 สามารถใช้โปรแกรมเดียวกันได้

				
					/*
   โปรแกรม ESP32 อ่านค่าเซ็นเซอร์ BNO085
   แสดงผล Euler Angles (Roll, Pitch, Yaw) บนจอ ST7789 (240x240)
   พร้อม UI ที่สวยงามและกราฟิกเข็มทิศ
*/

#include <Wire.h>
#include <SPI.h>
#include <TFT_eSPI.h>
#include "SparkFun_BNO08x_Arduino_Library.h"

//#define BNO08X_ADDR 0x4B  // JUMP
#define BNO08X_ADDR 0x4A

// --- สร้าง Object สำหรับไลบรารี ---
TFT_eSPI tft = TFT_eSPI();
BNO08x bno085;

// --- ตัวแปรสำหรับเก็บค่ามุม และค่าเก่าสำหรับลบภาพ ---
float roll = 0.0;
float pitch = 0.0;
float yaw = 0.0;
float last_yaw = 0.0; // สำหรับลบเข็มทิศเก่า

// --- ฟังก์ชันสำหรับวาด UI พื้นฐาน (วาดครั้งเดียว) ---
void drawUiLayout() {
  tft.fillScreen(TFT_BLACK);

  // วาดหัวข้อ
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(2);
  tft.drawString("BNO085 IMU Monitor", tft.width() / 2, 20);

  // วาดกรอบสำหรับข้อมูล Roll & Pitch
  tft.drawRect(10, 45, tft.width() - 20, 70, TFT_DARKCYAN);

  // วาดกรอบสำหรับข้อมูล Yaw และเข็มทิศ
  tft.drawRect(10, 125, tft.width() - 20, 105, TFT_DARKGREEN);

  // วาดป้ายกำกับ (Labels)
  tft.setTextDatum(TL_DATUM);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE);
  tft.drawString("Roll:", 20, 60);
  tft.drawString("Pitch:", 20, 90);
  tft.drawString("Heading (Yaw):", 20, 140);

  // วาดหน้าปัดเข็มทิศ
  int centerX = 180;
  int centerY = 178;
  int radius = 45;
  tft.drawCircle(centerX, centerY, radius, TFT_DARKGREY);
  tft.drawCircle(centerX, centerY, radius - 1, TFT_DARKGREY);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(1);
  tft.drawString("N", centerX, centerY - (radius - 10));
}

void setup() {
  Serial.begin(115200);
  Wire.begin();

  // --- เริ่มต้นจอภาพ ---
  tft.init();
  tft.setRotation(0);
  drawUiLayout(); // วาด UI พื้นฐาน

  tft.setTextDatum(MC_DATUM);
  tft.drawString("Initializing...", tft.width() / 2, tft.height() / 2);

  // --- เริ่มต้น BNO085 ---
  //if (myIMU.begin() == false) {  // Setup without INT/RST control (Not Recommended)
  if (bno085.begin(BNO08X_ADDR, Wire, -1, -1) == false) {
    Serial.println("BNO08x not detected at default I2C address. Check your jumpers and the hookup guide. Freezing...");
    while (1);
  }

  Serial.println("BNO085 Found!");
  //  bno085.enableRotationVector(50); // เปิดใช้งาน Rotation Vector (สำคัญมาก)

  setReports();

  delay(1000);
  drawUiLayout(); // วาด UI ใหม่อีกครั้งเพื่อลบข้อความ "Initializing"
}

void loop() {
  // --- อ่านค่าจาก BNO085 ---

  if (bno085.wasReset()) {
    Serial.print("sensor was reset ");
    setReports();
  }

  // Has a new event come in on the Sensor Hub Bus?
  if (bno085.getSensorEvent() == true) {

    // is it the correct sensor data we want?
    if (bno085.getSensorEventID() == SENSOR_REPORTID_ROTATION_VECTOR) {

      roll = (bno085.getRoll()) * 180.0 / PI; // Convert roll to degrees
      pitch = (bno085.getPitch()) * 180.0 / PI; // Convert pitch to degrees
      yaw = (bno085.getYaw()) * 180.0 / PI; // Convert yaw / heading to degrees

      Serial.print(roll, 1);
      Serial.print(F(","));
      Serial.print(pitch, 1);
      Serial.print(F(","));
      Serial.print(yaw, 1);
      Serial.println();
    }
    updateDisplay();
  }
  // ไม่ต้องใช้ delay() ที่นี่ เพื่อให้อ่านค่าได้เร็วที่สุด
  // การอัปเดตหน้าจอจะเกิดขึ้นเมื่อมีข้อมูลใหม่จากเซ็นเซอร์เท่านั้น
}

// Here is where you define the sensor outputs you want to receive
void setReports(void) {
  Serial.println("Setting desired reports");
  if (bno085.enableRotationVector() == true) {
    Serial.println(F("Rotation vector enabled"));
    Serial.println(F("Output in form roll, pitch, yaw"));
  } else {
    Serial.println("Could not enable rotation vector");
  }
}

// --- ฟังก์ชันสำหรับอัปเดตค่าที่เปลี่ยนแปลงบนจอ ---
void updateDisplay() {
  char buf[20];
  tft.setTextDatum(TR_DATUM);
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK); // สีพื้นหลัง BLACK ช่วยลบของเก่า

  // --- อัปเดตค่า Roll & Pitch ---
  sprintf(buf, "%.1f deg", roll);
  tft.drawString(buf, 220, 60);
  sprintf(buf, "%.1f deg", pitch);
  tft.drawString(buf, 220, 90);

  // --- อัปเดตค่า Yaw (ตัวเลข) ---
  sprintf(buf, "%.1f deg", yaw);
  tft.setTextDatum(TL_DATUM);
  tft.drawString(buf, 20, 165);

  // --- อัปเดตเข็มทิศ (กราฟิก) ---
  int centerX = 180;
  int centerY = 178;
  int needleLength = 40;

  // คำนวณตำแหน่งปลายเข็มทิศ
  float last_yaw_rad = (last_yaw - 90) * DEG_TO_RAD; // แปลงเป็นเรเดียนและชดเชย
  float yaw_rad = (yaw - 90) * DEG_TO_RAD;

  // ลบเข็มเก่า (วาดทับด้วยสีดำ)
  tft.drawLine(centerX, centerY,
               centerX + needleLength * cos(last_yaw_rad),
               centerY + needleLength * sin(last_yaw_rad),
               TFT_BLACK);

  // วาดเข็มใหม่ (สีแดง)
  tft.drawLine(centerX, centerY,
               centerX + needleLength * cos(yaw_rad),
               centerY + needleLength * sin(yaw_rad),
               TFT_RED);

  last_yaw = yaw; // อัปเดตค่า yaw เก่า
}
				
			

แสดงความคิดเห็น