สวัสดีน้องๆ! พี่ชื่อนิโคลัส เป็นนักเรียน Year 10 ที่ออสเตรเลีย
พูดตรงๆ พี่ก็ยังใหม่กับเรื่องโปรแกรมมิ่งและอิเล็กทรอนิกส์อยู่เหมือนกัน ปีนี้ครูเปิดคลาส STEM สำหรับเด็กสายช่าง/วิศวะ (ส่วนใหญ่) ขึ้นมา เรากำลังทำโปรเจคแรกกัน ซึ่งมันคือ:
- พิมพ์รถแข่ง 3D (ให้ตรงตามมาตรฐาน F1 in Schools ของออสเตรเลีย)
- สร้างการปรับแต่ง (modification) เพื่อให้รถปลอดภัยขึ้นสำหรับผู้โดยสารสมมติ พร้อมบันทึกข้อมูลระหว่างการวิ่ง (ลงจากราง) เพื่อพิสูจน์ว่าการเปลี่ยนแปลงนั้นทำให้ปลอดภัยขึ้นจริง
แต่ปัญหาคือ ทั้งห้อง (รวมถึงครู) ประสบการณ์ด้านโปรแกรมมิ่งสำหรับ Arduino น้อยมาก โดยเฉพาะ กำลังต้องการความช่วยเหลืออยู่
ปัญหาหลักอยู่ที่การบันทึกข้อมูลเพื่อเปรียบเทียบ เราติดตั้ง Adafruit Feather 32u4 Adalogger ลงในรถคันจิ๋วเพื่อบันทึกข้อมูลขณะวิ่งลงราง เพื่อคำนวณค่าจีโรสโคป (gyroscope) และความเร่ง (acceleration) ของรถ และดูการเปลี่ยนแปลงเมื่อเราทำการปรับแต่ง
สู้กับแรง G สูง: ความปลอดภัยผ่านการบันทึกข้อมูลระยะไกล (Telemetry)
ในโลกของ F1 in Schools รถสามารถทำความเร็วได้อย่างเหลือเชื่อในระยะทางแค่ 20 เมตร ซึ่งส่งผลให้โครงสร้างรถโมเดลต้องรับแรง G ที่มหาศาล โปรเจคนี้ไม่ได้แค่ทำรถให้สวย แต่ใช้ การปรับแต่งเชิงกล (Mechanical Modulation) เพื่อเพิ่มความปลอดภัยให้ผู้โดยสาร ด้วยการบันทึกข้อมูลความเร็วสูงระหว่างการวิ่ง ทีมงานจะสามารถพิสูจน์ได้อย่างชัดเจนว่าการเปลี่ยนแปลงโครงสร้างส่งผลต่อแรงที่กระทำภายในรถอย่างไร ระหว่างการเร่งและเบรกอย่างรวดเร็ว
Adalogger Stack: บันทึกข้อมูลแบบครบวงจรในตัวเดียว
เพื่อให้รถเบาแต่ยังบันทึกข้อมูลได้ โปรเจคนี้ใช้ Adafruit Feather 32u4 Adalogger:
- ช่องเสียบ SD Card ในตัว: ไม่เหมือน Arduino Uno ทั่วไป Adalogger มีช่องเสียบ microSD card ในตัวที่เชื่อมต่อผ่าน SPI bus ทำให้ไม่ต้องเดินสายซับซ้อน และลด "น้ำหนักบรรทุก" ของรถ เพื่อให้มันยังแข่งบนรางได้อย่างมีประสิทธิภาพ
- จัดการแบตเตอรี่: Feather มีขั้วต่อ JST สำหรับ แบตเตอรี่ LiPo ทำให้ระบบ telemetry ทำงานแบบไร้สายภายในโครงสร้างรถที่พิมพ์จาก 3D printer ได้
การวัดความเฉื่อยด้วย MPU-6050
"คะแนนความปลอดภัย" ของรถคำนวณมาจาก MPU-6050 อุปกรณ์ติดตามการเคลื่อนที่ 6 แกน:
- Accelerometer 3 แกน: วัดแรงเชิงเส้น (หน่วย G) ตามแกน X, Y และ Z สำคัญมากสำหรับการวัด "แรงกระชาก" ขณะที่หัวกระสุน CO2 ถูกยิงออก
- Gyroscope 3 แกน: ตรวจจับการเคลื่อนที่แบบหมุน ถ้ารถเกิดอาการ "โคลงเคลง" ที่ความเร็วสูง Gyroscope จะบันทึกความถี่ของการสั่นสะเทือนนั้น ช่วยให้นักวิศวะปรับแต่งอากาศพลศาสตร์ (aerodynamics) ของตัวรถที่พิมพ์จาก 3D printer ได้
- การเชื่อมต่อ I2C ความเร็วสูง: MPU-6050 ติดต่อกับ Feather ผ่านไลบรารี Wire.h โปรเจคนี้จัดการข้อมูลไบนารีดิบ 14 ไบต์ ทุกๆ 50ms เพื่อให้แน่ใจว่าข้อมูลการวิ่งที่สั้นที่สุดก็ถูกบันทึกด้วยความละเอียดสูง
ตามหาบั๊กในลูปข้อมูล (Troubleshooting the Data Loop)
ปัญหาหลักๆ ที่เจอในโปรเจคนี้ก็คือเรื่อง ความเสถียรของการอ่านเขียนไฟล์ (File I/O Stability) นี่แหละ:
- จัดฟอร์แมต CSV: โค้ดเราตั้งใจให้มันเขียนข้อมูลเป็นฟอร์แมตที่คั่นด้วยจุลภาค (
datalog.txt) ไว้เลย พอแข่งเสร็จ น้องๆ ก็แค่ถอดการ์ด SD ออก แล้วดึงข้อมูลเข้า Excel หรือ Google Sheets ได้ทันที เพื่อไปทำ กราฟเปรียบเทียบความปลอดภัย สวยๆ - ตั้งค่า SD: ต้องระวังให้ดี เพราะบอร์ด Feather มันใช้ขา (Pin) คนละแบบกับ Arduino Uno ทั่วไป (โดยเฉพาะ ขาที่ 4 สำหรับ Chip Select) โปรเจคนี้เลยเป็นบทเรียนสำคัญเรื่องการเช็คฮาร์ดแวร์ให้ชัวร์ และต้องมั่นใจว่าได้ฟอร์แมตการ์ด SD เป็น FAT16/FAT32 ก่อนรันโค้ดทุกครั้ง
ตอนนี้เรากำลังเจอปัญหาว่าโค้ดมันบันทึกข้อมูลลงการ์ด SD ไม่ได้ตามที่คิดไว้ โค้ดที่เรามีตอนนี้หน้าตาเป็นแบบนี้:
// (c) Michael Schoeffler 2017
#include "Wire.h" // This library allows you to communicate with I2C devices.
#include <SPI.h>
#include <SD.h>
const int MPU_ADDR = 0x68; // I2C address of the MPU-6050. If AD0 pin is set to HIGH, the I2C address will be 0x69.
int16_t accelerometer_x, accelerometer_y, accelerometer_z; // variables for accelerometer raw data
int16_t gyro_x, gyro_y, gyro_z; // variables for gyro raw data
int16_t temperature; // variables for temperature data
char tmp_str[7]; // temporary variable used in convert function
char* convert_int16_to_str(int16_t i) { // converts int16 to string. Moreover, resulting strings will have the same length in the debug monitor.
sprintf(tmp_str, "%6d", i);
return tmp_str;
const int chipSelect = 4;
}
void setup() {
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for native USB port only
}
Serial.print("Initializing SD card...");
// see if the card is present and can be initialized:
if (!SD.begin(4)) {
Serial.println("Card failed, or not present");
// don't do anything more:
while (1);
}
Serial.println("card initialized.");
Wire.begin();
Wire.beginTransmission(MPU_ADDR); // Begins a transmission to the I2C slave (GY-521 board)
Wire.write(0x6B); // PWR_MGMT_1 register
Wire.write(0); // set to zero (wakes up the MPU-6050)
Wire.endTransmission(true);
}
void loop() {
// make a string for assembling the data to log:
String dataString = "";
// read three sensors and append to the string:
for (int analogPin = 0; analogPin < 3; analogPin++) {
int sensor = analogRead(analogPin);
dataString += String(sensor);
if (analogPin < 2) {
dataString += ", ";
}
}
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open("datalog.txt", FILE_WRITE);
// if the file is available, write to it:
if (dataFile) {
dataFile.println(dataString);
dataFile.close();
// print to the serial port too:
Serial.println(dataString);
}
// if the file isn't open, pop up an error:
else {
Serial.println("error opening datalog.txt");
}
Wire.beginTransmission(MPU_ADDR);
Wire.write(0x3B); // starting with register 0x3B (ACCEL_XOUT_H) [MPU-6000 and MPU-6050 Register Map and Descriptions Revision 4.2, p.40]
Wire.endTransmission(false); // the parameter indicates that the Arduino will send a restart. As a result, the connection is kept active.
Wire.requestFrom(MPU_ADDR, 7*2, true); // request a total of 7*2=14 registers
// "Wire.read()<<8 | Wire.read();" means two registers are read and stored in the same variable
accelerometer_x = Wire.read()<<8 | Wire.read(); // reading registers: 0x3B (ACCEL_XOUT_H) and 0x3C (ACCEL_XOUT_L)
accelerometer_y = Wire.read()<<8 | Wire.read(); // reading registers: 0x3D (ACCEL_YOUT_H) and 0x3E (ACCEL_YOUT_L)
accelerometer_z = Wire.read()<<8 | Wire.read(); // reading registers: 0x3F (ACCEL_ZOUT_H) and 0x40 (ACCEL_ZOUT_L)
temperature = Wire.read()<<8 | Wire.read(); // reading registers: 0x41 (TEMP_OUT_H) and 0x42 (TEMP_OUT_L)
gyro_x = Wire.read()<<8 | Wire.read(); // reading registers: 0x43 (GYRO_XOUT_H) and 0x44 (GYRO_XOUT_L)
gyro_y = Wire.read()<<8 | Wire.read(); // อ่านค่าจากรีจิสเตอร์: 0x45 (GYRO_YOUT_H) และ 0x46 (GYRO_YOUT_L)
gyro_z = Wire.read()<<8 | Wire.read(); // อ่านค่าจากรีจิสเตอร์: 0x47 (GYRO_ZOUT_H) และ 0x48 (GYRO_ZOUT_H)
// พิมพ์ค่าออกมาให้ดูหน่อย
Serial.print("aX = "); Serial.print(convert_int16_to_str(accelerometer_x));
Serial.print(" | aY = "); Serial.print(convert_int16_to_str(accelerometer_y));
Serial.print(" | aZ = "); Serial.print(convert_int16_to_str(accelerometer_z));
// สูตรนี้เอามาจากคู่มือนะจ๊ะ [MPU-6000/MPU-6050 Register Map and Description, p.30]
Serial.print(" | tmp = "); Serial.print(temperature/340.00+36.53);
Serial.print(" | gX = "); Serial.print(convert_int16_to_str(gyro_x));
Serial.print(" | gY = "); Serial.print(convert_int16_to_str(gyro_y));
Serial.print(" | gZ = "); Serial.print(convert_int16_to_str(gyro_z));
Serial.println();
// รอหน่อย เดี๋ยวเซนเซอร์ช็อต
delay(50);
}
ถ้าใครรู้ว่าผิดพลาดตรงไหน ช่วยบอกพี่หน่อยนะน้อง ขอบคุณล่วงหน้าจ้า!
โปรเจคนี้แหละตัวดี เป็นตัวอย่างของ STEM Methodology แบบจัดเต็ม: ใช้ 3D printing ในการสร้างชิ้นงาน ใช้ความรู้ด้านอิเล็กทรอนิกส์ในการเก็บข้อมูล และใช้ซอฟต์แวร์ในการวิเคราะห์เปรียบเทียบ เพื่อแก้ปัญหาด้านวิศวกรรมความปลอดภัยในชีวิตจริง สู้งานนะน้อง!