Hi, I'm Nicholas, a Year 10 student in Australia.
I'm relatively new to programming and such in general. Our teachers created a STEM class this year for gifted students in the engineering (predominantly) field. We're working on our first project and it is as follows:
- 3D print a car (suitable for Australian F1 in Schools standards)
- Create a modification for the car to make the car safer for possible passengers inside, documenting the changes between the cars runs (down the track), proving the change in safety
However, our entire class' (including the teacher) has a lack of experience with the programming side of things for Arduino specifically and could really do with a hand.
The problem lies within the documentation of the change. We are hooking up an Adafruit Feather 32u4 Adalogger to the small cars as we race them down the track to calculate the gyroscope and acceleration of the car and thus, the change when we need our modulation.
Combatting High-G Impacts: Safety Through Telemetry
In the world of F1 in Schools, cars reach incredible speeds over a short 20-meter track, subjecting the miniature chassis to intense G-forces. This project aims to move beyond aesthetics by using Mechanical Modulation to improve passenger safety. By recording high-speed data during a run, the team can prove exactly how structural changes affect the forces felt inside the vehicle during rapid acceleration and deceleration.
The Adalogger Stack: Integrated Recording
To keep the car lightweight while capturing data, the project utilizes the Adafruit Feather 32u4 Adalogger:
- Internal SD Slot: Unlike the standard Arduino Uno, the Adalogger features a built-in microSD card slot connected via the SPI bus. This eliminates complex wiring and reduces the "Payload Weight" of the car, ensuring it stays competitive on the track.
- Battery Management: The Feather includes a JST connector for LiPo batteries, allowing the telemetry system to run wirelessly inside the 3D-printed chassis.
Inertial Measurement with the MPU-6050
The car’s "Safety Score" is calculated using the MPU-6050, a 6-axis Motion Tracking device:
- Triple-Axis Accelerometer: Measures the linear force (in Gs) along the X, Y, and Z axes. This is critical for measuring the "Snatch" during the initial CO2 canister puncture.
- Triple-Axis Gyroscope: Detects rotational motion. If the car experiences "Wobble" at high speeds, the gyroscope will record the frequency of the vibration, helping engineers refine the aerodynamics of the 3D-printed body.
- I2C High-Speed Link: The MPU-6050 communicates with the Feather via the Wire.h library. The project handles 14 bytes of raw binary data every 50ms, ensuring that even the shortest runs are captured with high resolution.
Troubleshooting the Data Loop
The primary engineering challenge in this project involves File I/O Stability:
- CSV Formatting: The code is designed to write data in a comma-separated format (
datalog.txt). This allows the students to pull the SD card after a race and instantly import the results into Excel or Google Sheets for Safety Comparison Graphs. - SD Initialization: Since the Feather uses different pin assignments than a standard Uno (specifically Pin 4 for Chip Select), the project serves as an important lesson in hardware-specific definitions and ensuring the SD card is properly formatted to FAT16/FAT32 before a run.
We are having troubles logging the data from the code into the SD card properly. The code we have so far is as follows:
// (c) Michael Schoeffler 2017, http://www.mschoeffler.de
#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(); // reading registers: 0x45 (GYRO_YOUT_H) and 0x46 (GYRO_YOUT_L)
gyro_z = Wire.read()<<8 | Wire.read(); // reading registers: 0x47 (GYRO_ZOUT_H) and 0x48 (GYRO_ZOUT_L)
// print out data
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));
// the following equation was taken from the documentation [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
delay(50);
}
If anyone could tell me where we are going wrong, it would be greatly appreciated. Thanks in advance!
This modulation project is a perfect example of the STEM Methodology: using 3D printing for fabrication, electronics for data collection, and software for comparative analysis to solve a real-world safety engineering problem.