Appendix - Catapult
Table 1: Bill of materials for final build
The code does the following:
- Sets a desired RPM and maintains this using a PID
- Outputs the current value of acceleration being collected by the IMU
- Runs the mechanism for one full throwing cycle and records the acceleration data
- Pauses the code after running one cycle
- Prints recorded acceleration data during this time
- Wipes data from memory to improve speed
- Continues to power the motor and output accel data after data has been collected to throw more balls
#include <Wire.h>
#include <Adafruit_MPU6050.h>
#include <Adafruit_Sensor.h>
#include <PID_v1_bc.h>
#include <Encoder.h>
// MPU-6050 setup
Adafruit_MPU6050 mpu;
// Motor and encoder setup
const uint8_t motorPinA = 8;
const uint8_t motorPinB = 9;
const uint8_t encoderPinA = 3;
const uint8_t encoderPinB = 2;
const uint8_t motorPWMPin = 5;
Encoder encoder(encoderPinA, encoderPinB);
// PID setup for constant motor speed
const int desiredRPM = 400;
const int countsPerRevolution = 950;
double Setpoint, Input, Output;
double Kp = 0.5, Ki = 5, Kd = 0.01;
PID myPID(&Input, &Output, &Setpoint, Kp, Ki, Kd, DIRECT);
unsigned long previousMillis = 0;
long previousEncoderCount = 0;
uint16_t revolutions = 0;
const int maxDataPoints = 50;
const uint8_t maxRevolutions = 20;
const uint8_t samplesPerRev = maxDataPoints/maxRevolutions;
const int samplesPerStep = countsPerRevolution / samplesPerRev;
int revolutionCounter = 0;
long datapreviousEncoderCount =0;
int dataIndex = 0;
bool recording = true;
unsigned long dataTimes[maxDataPoints];
double accelDataX[maxDataPoints];
double accelDataY[maxDataPoints];
double accelDataZ[maxDataPoints];
void setup() {
Serial.begin(19200);
Wire.begin();
// Initialize MPU-6050
if (!mpu.begin()) {
Serial.println("Failed to find MPU6050 chip");
while (1) {
delay(10);
}
}
mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
mpu.setGyroRange(MPU6050_RANGE_250_DEG);
mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
// Configure PID
Setpoint = desiredRPM;
Serial.print(Setpoint);
myPID.SetMode(AUTOMATIC);
myPID.SetSampleTime(100);
myPID.SetOutputLimits(0, 255);
// Motor pins
pinMode(motorPinA, OUTPUT);
pinMode(motorPinB, OUTPUT);
pinMode(motorPWMPin, OUTPUT);
}
void loop() {
Serial.println();
unsigned long currentMillis = millis();
unsigned long deltaTime = currentMillis - previousMillis;
// Read accelerometer and gyroscope data
sensors_event_t a_event, g_event, temp_event;
mpu.getEvent(&a_event, &g_event, &temp_event);
// Convert raw values to Gs
float accel_x = a_event.acceleration.x / 9.81;
float accel_y = a_event.acceleration.y / 9.81;
float accel_z = a_event.acceleration.z / 9.81;
// Print accelerometer values in Gs
Serial.print("Accel X: ");
Serial.print(accel_x);
Serial.print(" Accel Y: ");
Serial.print(accel_y);
Serial.print(" Accel Z: ");
Serial.println(accel_z);
// Calculate and print the magnitude of acceleration
float magnitude = sqrt(pow(accel_x, 2) + pow(accel_y, 2) + pow(accel_z, 2));
Serial.print("Magnitude: ");
Serial.println(magnitude);
if (deltaTime >= 100) {
// Read encoder value and calculate counts per second
long currentEncoderCount = encoder.read();
long deltaEncoderCount = currentEncoderCount - previousEncoderCount;
previousEncoderCount = currentEncoderCount;
double countsPerSecond = (deltaEncoderCount / (double)deltaTime) * 1000;
// Calculate current RPM
Input = (countsPerSecond / countsPerRevolution) * 60;
Serial.print("Input: ");
Serial.println(Input);
// Update revolution counter
revolutions = currentEncoderCount / countsPerRevolution;
Serial.print("Revolutions: ");
Serial.println(revolutions);
// Record acceleration data
if (recording && dataIndex < maxDataPoints) {
int deltaEncoderCount = currentEncoderCount - datapreviousEncoderCount;
if (deltaEncoderCount >= samplesPerStep) {
datapreviousEncoderCount = currentEncoderCount;
dataTimes[dataIndex] = millis();
accelDataX[dataIndex] = accel_x;
accelDataY[dataIndex] = accel_y;
accelDataZ[dataIndex] = accel_z;
dataIndex++;
}
} else if (recording && revolutions >= maxRevolutions) {
recording = false;
endRecording();
}
// Compute PID
myPID.Compute();
// Control motor
controlMotor(Output);
Serial.print("PID OUTPUT (PWM): ");
Serial.println(Output);
// Print current encoder speed, encoder value, and RPM
Serial.print("Encoder Speed (counts/s): ");
Serial.print(countsPerSecond);
Serial.print(" | Encoder Value: ");
Serial.print(currentEncoderCount);
Serial.print(" | RPM: ");
Serial.println(Input);
// Update revolution counter
revolutions = currentEncoderCount / countsPerRevolution;
Serial.print("Revolutions: ");
Serial.println(revolutions);
previousMillis = currentMillis;
}
}
void controlMotor(int speed) {
if (speed > 0) {
digitalWrite(motorPinA, HIGH);
digitalWrite(motorPinB, LOW);
analogWrite(motorPWMPin, speed);
} else {
digitalWrite(motorPinA, LOW);
digitalWrite(motorPinB, LOW);
analogWrite(motorPWMPin, 0);
}
}
void endRecording() {
// Print the recorded data
controlMotor(0);
Serial.println("Recorded Data:");
for (int i = 0; i < maxDataPoints; i++) {
//Serial.print(" Time: ");
Serial.print(" ");
Serial.print(dataTimes[i]);
}
Serial.println(" ");
for (int i = 0; i < maxDataPoints; i++) {
//Serial.print(" Accel X: ");
Serial.print(" ");
Serial.print(accelDataX[i]);
}
Serial.println(" ");
for (int i = 0; i < maxDataPoints; i++) {
//Serial.print(" Accel Y: ");
Serial.print(" ");
Serial.print(accelDataY[i]);
}
Serial.println(" ");
for (int i = 0; i < maxDataPoints; i++) {
//Serial.print(" Accel Y: ");
Serial.print(" ");
Serial.print(accelDataZ[i]);
}
Serial.println(" ");
// Reset the data arrays and counters
memset(dataTimes, 0, sizeof(dataTimes));
memset(accelDataX, 0, sizeof(accelDataX));
memset(accelDataY, 0, sizeof(accelDataY));
}