├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── TVC-Flight-Code ├── BMP.cpp ├── BMP.h ├── Buzzer.cpp ├── Buzzer.h ├── IMU.cpp ├── IMU.h ├── LED.cpp ├── LED.h ├── PID.cpp ├── PID.h ├── Rocket.cpp ├── Rocket.h ├── SDCard.cpp ├── SDCard.h ├── StateMachine.cpp ├── StateMachine.h ├── TVC-Flight-Code.ino ├── VectorDefinition.h ├── VoltageDivider.cpp └── VoltageDivider.h └── flight.gif /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Tom Kuttler 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # TVC-Rocket-Flight-Code 4 | 5 | 6 | arduino 7 | 8 | C 9 | 10 | C++ 11 |
12 |
13 | 14 | This repository contains the source code for a software implementation of thrust vector control (TVC) for model rockets. The software consists of a state machine and PID control loops to adjust the orientation of the rocket during its powered ascent. 15 | 16 | flight video 17 | 18 |
19 | 20 |
21 | Table of Contents 22 | 23 | - [Installation](#installation) 24 | - [Usage](#usage) 25 | - [Features](#features) 26 | - [State Machine](#state-machine) 27 | - [PID Control Loop](#pid-control-loop) 28 | - [Parachute Ejection](#parachute-ejection) 29 | - [Contributing](#contributing) 30 | - [License](#license) 31 |
32 | 33 | ## Installation 34 | 35 | To use this software, you will need a Teensy 4.1 board and the Arduino IDE with the Teensy Loader Application. Follow these steps to install the software: 36 | 37 | 1. Clone the repository to your local machine. 38 | 2. Open the `TVC-Flight-Code.ino` file in the Arduino IDE. 39 | 3. Upload the code to your Teensy. 40 | 41 | ## Usage 42 | 43 | To use the TVC system with this software, you'll need a flight controller and a motor mount which can be rotated with two servos. The parameters in the `Rocket.h` and `StateMachine.h` file need to be adjusted for your specific rocket. After evaluating the correct parameters upload the code to the Teensy. Do not forget to power on the flight controller before launching the rocket. The Software automatically detects the launch and the TVC system will automatically adjust the orientation of the rocket during its powered ascent based on the PID control loop. Note that it's important to follow all safety guidelines when testing or launching your rocket, and to always conduct tests in a safe and legal manner. 44 | 45 | ## Features 46 | 47 | ### State Machine 48 | 49 | The state machine is controlling the model rocket based on the current state of the flight. The state machine has four states: 50 | 51 | - `LAUNCH_PAD_IDLE`: The initialization state where the TVC system is initialized and the rocket is waiting for launch. 52 | - `ASCENT`: The ascent state where the TVC system controls the orientation of the rocket during its powered ascent. 53 | - `DESCENT`: The descent state where the TVC system is deactivated and the rocket is falling down. 54 | - `LANDED`: The landed state where the rocket is waiting for recovery. 55 | 56 | ### PID Control Loop 57 | 58 | The PID control loop is responsible for adjusting the orientation of the motor mount based on the current orientation of the rocket and the desired orientation. The TVC system uses two separate PID control loops, one for the orientation in the x-direction and one for the orientation in the y-direction. 59 | 60 | Each PID control loop has three components: 61 | 62 | - Proportional: The proportional component adjusts the orientation of the rocket based on the difference between the current orientation and the desired orientation. 63 | - Integral: The integral component adjusts the orientation of the rocket based on the cumulative error between the current orientation and the desired orientation. 64 | - Derivative: The derivative component adjusts the orientation of the rocket based on the rate of change of the error between the current orientation and the desired orientation. 65 | 66 | By using two separate PID control loops, the TVC system is able to independently adjust the orientation of the rocket in both the x-direction and the y-direction, by using two servos which gimbal the motor mount. 67 | 68 | ### Parachute Ejection 69 | 70 | While the TVC system controls the orientation of the rocket during ascent, a separate mechanical parachute ejection system is used to deploy the parachute. This system consists of springs and a servo that is triggered by a signal from the state machine when the rocket switches from the ascent state to the descent state. By ejecting the parachute during the switch from ascent to descent, the rocket is able to safely decelerate and return to the ground without sustaining damage. 71 | 72 | ## Contributing 73 | 74 | If you would like to contribute to this project, please follow these steps: 75 | 76 | 1. Fork the repository to your own account. 77 | 2. Create a new branch with your changes. 78 | 3. Submit a pull request with your changes. 79 | 80 | ## License 81 | 82 | This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). You are free to use, modify, and distribute this software for both personal and commercial purposes as long as you give attribution to the original author. For more information about the license, please see the `LICENSE` file. 83 | -------------------------------------------------------------------------------- /TVC-Flight-Code/BMP.cpp: -------------------------------------------------------------------------------- 1 | #include "BMP.h" 2 | 3 | BMP::BMP() { 4 | if (!bmp.begin()) { 5 | Serial.println("ERROR: BMP280 initialisation failed!"); 6 | } 7 | 8 | // Default settings from datasheet https://cdn-shop.adafruit.com/datasheets/BST-BMP280-DS001-11.pdf 9 | bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, /* Operating Mode. */ 10 | Adafruit_BMP280::SAMPLING_X2, /* Temp. oversampling */ 11 | Adafruit_BMP280::SAMPLING_X16, /* Pressure oversampling */ 12 | Adafruit_BMP280::FILTER_X16, /* Filtering. */ 13 | Adafruit_BMP280::STANDBY_MS_500); /* Standby time. */ 14 | } 15 | 16 | float BMP::readTemperature() { 17 | return bmp.readTemperature(); 18 | } 19 | 20 | float BMP::readPressure() { 21 | return bmp.readPressure(); 22 | } 23 | 24 | float BMP::readAltitude(float seaLevelhPa) { 25 | return bmp.readAltitude(seaLevelhPa); 26 | } 27 | -------------------------------------------------------------------------------- /TVC-Flight-Code/BMP.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Barometric Pressure & Altitude Sensor BMP280 3 | * https://www.adafruit.com/product/2651 4 | * 5 | * Outputs the following data: temperature, athmospheric pressure and altitude (which is calculated with the pressure at sea level) 6 | */ 7 | 8 | #include 9 | #include 10 | 11 | class BMP { 12 | public: 13 | BMP(); 14 | 15 | float readTemperature(); 16 | float readPressure(); 17 | float readAltitude(float seaLevelhPa); 18 | 19 | private: 20 | Adafruit_BMP280 bmp; 21 | }; 22 | -------------------------------------------------------------------------------- /TVC-Flight-Code/Buzzer.cpp: -------------------------------------------------------------------------------- 1 | #include "Buzzer.h" 2 | 3 | Buzzer::Buzzer() { 4 | 5 | } 6 | 7 | void Buzzer::noSDCardAlert() { 8 | int slide = 5000; 9 | for (int i = 0; i <= 120; i++) { 10 | tone(BUZZER_PIN, slide); delay(10); noTone(BUZZER_PIN); 11 | slide = slide - 40; 12 | } 13 | noTone(BUZZER_PIN); 14 | } 15 | 16 | void Buzzer::tooLowOnBoardVoltageAlert() { 17 | for(int i = 0; i < 1000; i++) { 18 | tone(BUZZER_PIN, 2000); 19 | delayMicroseconds(i); 20 | noTone(BUZZER_PIN); 21 | delayMicroseconds(i); 22 | } 23 | noTone(BUZZER_PIN); 24 | } 25 | -------------------------------------------------------------------------------- /TVC-Flight-Code/Buzzer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Piezo buzzer 3 | * 4 | * Plays an alarm if no SD-Card is inserted into the Flight Controller or the on-board voltage is too low 5 | */ 6 | 7 | #include 8 | 9 | #define BUZZER_PIN 36 10 | 11 | class Buzzer { 12 | public: 13 | Buzzer(); 14 | 15 | void noSDCardAlert(); 16 | void tooLowOnBoardVoltageAlert(); 17 | }; 18 | -------------------------------------------------------------------------------- /TVC-Flight-Code/IMU.cpp: -------------------------------------------------------------------------------- 1 | #include "IMU.h" 2 | 3 | IMU::IMU() { 4 | Wire.begin(); 5 | Wire.setClock(400000); 6 | if (!bno.begin()) { 7 | Serial.println("ERROR: BNO055 initialisation failed!"); 8 | } 9 | } 10 | 11 | Vector3 IMU::getGyros() { 12 | bno.getEvent(&angVelocityData, Adafruit_BNO055::VECTOR_GYROSCOPE); 13 | return angVelocityData.gyro; 14 | } 15 | 16 | Vector3 IMU::getAcceleration() { 17 | bno.getEvent(&linearAccelerationData, Adafruit_BNO055::VECTOR_LINEARACCEL); 18 | Vector3 Vector(linearAccelerationData.acceleration.x, linearAccelerationData.acceleration.y, linearAccelerationData.acceleration.z); 19 | return Vector; 20 | } 21 | 22 | Vector3 IMU::getOrientation() { 23 | bno.getEvent(&orientationData, Adafruit_BNO055::VECTOR_EULER); 24 | return orientationData.orientation; 25 | } 26 | -------------------------------------------------------------------------------- /TVC-Flight-Code/IMU.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Absolute Orientation Sensor BNO055 3 | * https://www.adafruit.com/product/4646 4 | * 5 | * IMU = inertial measurement unit 6 | * 7 | * Outputs the following data: angular velocity, acceleration and absolute orientation 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "VectorDefinition.h" 15 | 16 | class IMU { 17 | public: 18 | IMU(); 19 | Vector3 getGyros(); 20 | Vector3 getAcceleration(); 21 | Vector3 getOrientation(); 22 | 23 | private: 24 | Adafruit_BNO055 bno = Adafruit_BNO055(-1, 0x28, &Wire1); 25 | sensors_event_t angVelocityData; 26 | sensors_event_t linearAccelerationData; 27 | sensors_event_t orientationData; 28 | }; 29 | -------------------------------------------------------------------------------- /TVC-Flight-Code/LED.cpp: -------------------------------------------------------------------------------- 1 | #include "LED.h" 2 | 3 | LED::LED() { 4 | // Set system led pins as outputs 5 | pinMode(R_LED, OUTPUT); 6 | pinMode(G_LED, OUTPUT); 7 | pinMode(B_LED, OUTPUT); 8 | 9 | // The computer is using a common anode LED -> write LOW to turn it on, and HIGH to turn it off 10 | digitalWrite(R_LED, HIGH); 11 | digitalWrite(G_LED, HIGH); 12 | digitalWrite(B_LED, HIGH); 13 | } 14 | 15 | void LED::red() { 16 | digitalWrite(R_LED, LOW); 17 | digitalWrite(G_LED, HIGH); 18 | digitalWrite(B_LED, HIGH); 19 | } 20 | 21 | void LED::green() { 22 | digitalWrite(R_LED, HIGH); 23 | digitalWrite(G_LED, LOW); 24 | digitalWrite(B_LED, HIGH); 25 | } 26 | 27 | void LED::blue() { 28 | digitalWrite(R_LED, HIGH); 29 | digitalWrite(G_LED, HIGH); 30 | digitalWrite(B_LED, LOW); 31 | } 32 | 33 | void LED::purple() { 34 | digitalWrite(R_LED, LOW); 35 | digitalWrite(G_LED, HIGH); 36 | digitalWrite(B_LED, LOW); 37 | } 38 | 39 | void LED::off() { 40 | digitalWrite(R_LED, HIGH); 41 | digitalWrite(G_LED, HIGH); 42 | digitalWrite(B_LED, HIGH); 43 | } 44 | -------------------------------------------------------------------------------- /TVC-Flight-Code/LED.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Common Anode LED 3 | * 4 | * Changes color according to the state the Flight Controller is currently in 5 | * 6 | * STATE | COLOR 7 | * ----------------|-------- 8 | * LAUNCH_PAD_IDLE | red 9 | * ASCENT | green 10 | * DESCENT | blue 11 | * LANDED | purple 12 | */ 13 | 14 | #include 15 | 16 | #define R_LED 41 17 | #define G_LED 40 18 | #define B_LED 39 19 | 20 | class LED { 21 | public: 22 | LED(); 23 | void red(); 24 | void green(); 25 | void blue(); 26 | void purple(); 27 | void off(); 28 | }; 29 | -------------------------------------------------------------------------------- /TVC-Flight-Code/PID.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * PID Controller, inspired by https://gist.github.com/bradley219/5373998 3 | */ 4 | 5 | #include "PID.h" 6 | 7 | PID::PID(float Kp, float Ki, float Kd, float saturation) { 8 | this->Kp = Kp; 9 | this->Ki = Ki; 10 | this->Kd = Kd; 11 | this->saturation = saturation; 12 | } 13 | 14 | float PID::update(float input) { 15 | // Calculate delata time 16 | currentTime = micros(); 17 | deltaTime = (currentTime - previousTime) / 1000000.0f; 18 | 19 | // Calculate error 20 | error = setpoint - input; 21 | 22 | // Integral term 23 | errorIntegral += error * deltaTime; 24 | 25 | // Derivative term 26 | errorDerivative = (error - errorLastCycle) / deltaTime; 27 | 28 | // Calculate total output 29 | output = Kp * error + Ki * errorIntegral + Kd * errorDerivative; 30 | 31 | // Save current error and time for next cycle 32 | errorLastCycle = error; 33 | previousTime = currentTime; 34 | 35 | // Check if output is in saturation 36 | if (output > saturation) { 37 | output = saturation; 38 | } else if (output < -saturation) { 39 | output = -saturation; 40 | } 41 | 42 | return output; 43 | } 44 | -------------------------------------------------------------------------------- /TVC-Flight-Code/PID.h: -------------------------------------------------------------------------------- 1 | /* 2 | * PID-Controller 3 | * 4 | * The orientation of the motor mount is calculated with two separate PID-Controllers 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | class PID { 11 | public: 12 | PID(float Kp, float Ki, float Kd, float saturation); 13 | float update(float input); 14 | 15 | private: 16 | // Proportional gain 17 | float Kp = 0.0f; 18 | // Integral gain 19 | float Ki = 0.0f; 20 | // Derivative gain 21 | float Kd = 0.0f; 22 | 23 | // Loop interval time 24 | float deltaTime = 0.0f; 25 | float error = 0.0f; 26 | float setpoint = 0.0f; 27 | float errorIntegral = 0.0f; 28 | float errorDerivative = 0.0f; 29 | float errorLastCycle = 0.0f; 30 | 31 | // Maximum value of manipulated variable (absolute value) 32 | float saturation = 0.0f; 33 | float output = 0.0f; 34 | 35 | unsigned long currentTime = 0.0; 36 | unsigned long previousTime = 0.0; 37 | }; 38 | -------------------------------------------------------------------------------- /TVC-Flight-Code/Rocket.cpp: -------------------------------------------------------------------------------- 1 | #include "Rocket.h" 2 | 3 | Rocket::Rocket() { 4 | // Attach and initialize servos 5 | yServo.attach(Y_SERVO_PIN); 6 | zServo.attach(Z_SERVO_PIN); 7 | parachuteServo.attach(PARACHUTE_SERVO_PIN); 8 | 9 | yServo.write(Y_SERVO_MIDDLE); 10 | zServo.write(Z_SERVO_MIDDLE); 11 | parachuteServo.write(PARACHUTE_CLOSED); 12 | 13 | // Check on-board voltage 14 | if(voltageDivider.getBoardVoltage() < MIN_VOLTAGE) { 15 | Serial.println("Error: on-board voltage too low"); 16 | while (true) { 17 | // on-board voltage too low, so do not do anything more - stay stuck here and play alarm 18 | sdCard.buzzer.tooLowOnBoardVoltageAlert(); 19 | 20 | sdCard.led.red(); 21 | delay(500); 22 | sdCard.led.off(); 23 | } 24 | } 25 | } 26 | 27 | void Rocket::padIdle() { 28 | // Update acceleration for lift off check 29 | acceleration = imu.getAcceleration(); 30 | } 31 | 32 | void Rocket::ascent() { 33 | // Calculate flight time in s 34 | flightTime = (millis() - flightStartTime) / 1000.0f; 35 | 36 | // ----- TVC ALGORITHM ----- 37 | // Calculate delta time 38 | currentTime = micros(); 39 | deltaTime = (currentTime - previousTime) / 1000000.0f; 40 | 41 | // Update gyro values 42 | gyros = imu.getGyros(); 43 | 44 | // Integrate angular velocity for relative orientation (roll, pitch and yaw) in rad 45 | relativeOrientation += gyros * deltaTime; 46 | 47 | // Send the yaw and pitch to their respective PID Controller and recieve output 48 | float yPIDoutput = yPID.update(relativeOrientation.y); 49 | float zPIDoutput = zPID.update(relativeOrientation.z); 50 | 51 | // Convert the relative orientation in rad to deg 52 | yPIDoutput = yPIDoutput * RAD2DEG; 53 | zPIDoutput = zPIDoutput * RAD2DEG; 54 | 55 | // Convert the PID output (orientation of motor mount) to servo position (relative orientation of servo horn) 56 | float yServoRelPosition = yPIDoutput * SERVO_RATIO; 57 | float zServoRelPosition = zPIDoutput * SERVO_RATIO; 58 | 59 | // Check if the servo relative positions are greater than the max motor mount rotation 60 | // The motor mount can only be rotated 10 deg => the servos are allowed to rotate 30 deg 61 | if(yServoRelPosition > MAX_SERVO_ROTATION) { 62 | yServoRelPosition = MAX_SERVO_ROTATION; 63 | } 64 | else if(yServoRelPosition < -MAX_SERVO_ROTATION) { 65 | yServoRelPosition = -MAX_SERVO_ROTATION; 66 | } 67 | 68 | if(zServoRelPosition > MAX_SERVO_ROTATION) { 69 | zServoRelPosition = MAX_SERVO_ROTATION; 70 | } 71 | else if(zServoRelPosition < -MAX_SERVO_ROTATION) { 72 | zServoRelPosition = -MAX_SERVO_ROTATION; 73 | } 74 | 75 | // Set the position of the servos 76 | // To get the absolute position of the servo horns, the middle servo horn position is added to the relative orientation 77 | yServo.write(yServoRelPosition + Y_SERVO_MIDDLE); 78 | zServo.write(zServoRelPosition + Z_SERVO_MIDDLE); 79 | 80 | // Save current time for next cycle 81 | previousTime = currentTime; 82 | 83 | // ----- DATA LOGGING ----- 84 | acceleration = imu.getAcceleration(); 85 | orientation = imu.getOrientation(); 86 | 87 | float temperature = bmp.readTemperature(); 88 | float pressure = bmp.readPressure(); 89 | float altitude = bmp.readAltitude(SEA_LEVEL_PRESSURE); 90 | 91 | float voltage = voltageDivider.getBoardVoltage(); 92 | 93 | sdCard.logData(flightTime, gyros.x, gyros.y, gyros.z, acceleration.x, acceleration.y, acceleration.z, orientation.x, orientation.y, orientation.z, temperature, pressure, altitude, yServoRelPosition + Y_SERVO_MIDDLE, zServoRelPosition + Z_SERVO_MIDDLE, voltage, "ASCENT"); 94 | } 95 | 96 | void Rocket::maxApogee() { 97 | // Eject Parachute 98 | parachuteServo.write(PARACHUTE_EJECT); 99 | 100 | // Set tvc servos to middle position 101 | yServo.write(Y_SERVO_MIDDLE); 102 | zServo.write(Z_SERVO_MIDDLE); 103 | } 104 | 105 | void Rocket::descent() { 106 | // Calculate flight time in s 107 | flightTime = (millis() - flightStartTime) / 1000.0f; 108 | 109 | // ----- DATA LOGGING ----- 110 | acceleration = imu.getAcceleration(); 111 | orientation = imu.getOrientation(); 112 | 113 | float temperature = bmp.readTemperature(); 114 | float pressure = bmp.readPressure(); 115 | float altitude = bmp.readAltitude(SEA_LEVEL_PRESSURE); 116 | 117 | float voltage = voltageDivider.getBoardVoltage(); 118 | 119 | sdCard.logData(flightTime, gyros.x, gyros.y, gyros.z, acceleration.x, acceleration.y, acceleration.z, orientation.x, orientation.y, orientation.z, temperature, pressure, altitude, Y_SERVO_MIDDLE, Z_SERVO_MIDDLE, voltage, "DESCENT"); 120 | 121 | } 122 | -------------------------------------------------------------------------------- /TVC-Flight-Code/Rocket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Rocket code file 3 | * 4 | * Contains code that will be run in a specific state 5 | * Functions are called in StateMachine.cpp 6 | * Header file contains all constants that are specific to the model rocket 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | #include "IMU.h" 13 | #include "BMP.h" 14 | #include "PID.h" 15 | #include "SDCard.h" 16 | #include "VoltageDivider.h" 17 | 18 | #define Y_SERVO_PIN 3 // Y Servo (y axis labeled on IMU) = Y Servo (labeled on pcb) 19 | #define Z_SERVO_PIN 2 // Z Servo (z axis labeled on IMU) = X Servo (labeled on pcb) 20 | 21 | #define Y_SERVO_MIDDLE 78 // Y Servo horn vertical position (motor mount is vertical) 22 | #define Z_SERVO_MIDDLE 87 // Z Servo horn vertical position (motor mount is vertical) 23 | 24 | // Servo ratio is calculated by making multiple images of different servo horn angles and comparing the orientation of the horn and the motor mount 25 | #define SERVO_RATIO 3.1 // Servo horn to motor mount ratio (if horn is turned 1 deg, motor mount will turn 3 deg) 26 | #define MAX_SERVO_ROTATION 30 // Motor mount can only be rotated 10 deg => the servos are allowed to rotate 30 deg 27 | 28 | #define PARACHUTE_SERVO_PIN 5 // Parachute Servo = Servo 3 (labeled on pcb) 29 | #define PARACHUTE_CLOSED 0 // Position of the parachute servo before ejection (in deg) 30 | #define PARACHUTE_EJECT 175 // Position of the parachute servo after ejection (in deg) 31 | 32 | #define DEG2RAD 0.01745329251f // Convert degrees to radians by multiplying with this number 33 | #define RAD2DEG 57.2957795131f // Convert radians to degrees by multiplying with this number 34 | 35 | #define MIN_VOLTAGE 4 // Minimum board voltage to complete startup (in V) 36 | 37 | #define SEA_LEVEL_PRESSURE 1013.25 // Standard atmospheric pressure at sea level (in hPa) 38 | 39 | class Rocket { 40 | public: 41 | Rocket(); 42 | 43 | SDCard sdCard; 44 | 45 | // Angular Velocity Vector 46 | // Three axis of rotation speed in radians per second (rad/s) 47 | Vector3 gyros; 48 | 49 | // Acceleration Vector 50 | // Three axis of acceleration (gravity + linear motion) in m/s^2 51 | Vector3 acceleration; 52 | 53 | // Absolute Orientation Vector 54 | // Three axis orientation data based on a 360° sphere (Euler Vector in deg) 55 | Vector3 orientation; 56 | 57 | // Relative Orientation Vector 58 | // Three axis orientation data (roll, pitch and yaw) in rad 59 | Vector3 relativeOrientation; 60 | 61 | float flightTime = 0.0f; // in s 62 | float flightStartTime = 0.0f; // in ms 63 | 64 | void padIdle(); 65 | void ascent(); 66 | void maxApogee(); 67 | void descent(); 68 | void landed(); 69 | 70 | private: 71 | IMU imu; 72 | BMP bmp; 73 | VoltageDivider voltageDivider; 74 | 75 | // PID Values were tuned with a Simulink simulation and verified with AeroVECTOR https://github.com/GuidodiPasquo/AeroVECTOR 76 | // PID-Controller for calculation of motor mount orientation on y-axis 77 | PID yPID = PID(0.6f, 0.05f, 0.125f, 10.0f); 78 | // PID-Controller for calculation of motor mount orientation on z-axis 79 | PID zPID = PID(0.6f, 0.05f, 0.125f, 10.0f); 80 | 81 | Servo yServo; 82 | Servo zServo; 83 | Servo parachuteServo; 84 | 85 | unsigned long currentTime = 0.0; 86 | unsigned long previousTime = 0.0; 87 | float deltaTime = 0.0f; 88 | }; 89 | -------------------------------------------------------------------------------- /TVC-Flight-Code/SDCard.cpp: -------------------------------------------------------------------------------- 1 | #include "SDCard.h" 2 | 3 | SDCard::SDCard() { 4 | if(!SD.begin(chipSelect)) { 5 | Serial.println("Error: The SD card failed to initialize, or is not present!"); 6 | while (true) { 7 | // No SD card, so do not do anything more - stay stuck here and play alarm 8 | buzzer.noSDCardAlert(); 9 | 10 | led.red(); 11 | delay(1000); 12 | led.off(); 13 | } 14 | } 15 | 16 | // Create file (or open it if it already exists) 17 | File dataFile = SD.open("datalog.CSV", FILE_WRITE); 18 | 19 | // Write top description line 20 | dataFile.println("flightTime, gyros.x, gyros.y, gyros.z, acceleration.x, acceleration.y, acceleration.z, orientation.x, orientation.y, orientation.z, temperature, pressure, altitude, yServo orientation, zServo orientation, voltage, state"); 21 | dataFile.close(); 22 | } 23 | 24 | void SDCard::logData(float flightTime, float gyroX, float gyroY, float gyroZ, float accelerationX, float accelerationY, float accelerationZ, float orientationX, float orientationY, float orientationZ, float temperature, float pressure, float altitude, float yServo, float zServo, float voltage, String state) { 25 | // Open the data logging file 26 | File dataFile = SD.open("datalog.CSV", FILE_WRITE); 27 | 28 | // If the file is available, write to it 29 | if (dataFile) { 30 | dataFile.print(flightTime); 31 | dataFile.print(","); 32 | dataFile.print(gyroX); 33 | dataFile.print(","); 34 | dataFile.print(gyroY); 35 | dataFile.print(","); 36 | dataFile.print(gyroZ); 37 | dataFile.print(","); 38 | dataFile.print(accelerationX); 39 | dataFile.print(","); 40 | dataFile.print(accelerationY); 41 | dataFile.print(","); 42 | dataFile.print(accelerationZ); 43 | dataFile.print(","); 44 | dataFile.print(orientationX); 45 | dataFile.print(","); 46 | dataFile.print(orientationY); 47 | dataFile.print(","); 48 | dataFile.print(orientationZ); 49 | dataFile.print(","); 50 | dataFile.print(temperature); 51 | dataFile.print(","); 52 | dataFile.print(pressure); 53 | dataFile.print(","); 54 | dataFile.print(altitude); 55 | dataFile.print(","); 56 | dataFile.print(yServo); 57 | dataFile.print(","); 58 | dataFile.print(zServo); 59 | dataFile.print(","); 60 | dataFile.print(voltage); 61 | dataFile.print(","); 62 | dataFile.println(state); 63 | 64 | dataFile.close(); 65 | } else { 66 | // If the file can not be opened, pop up an error message 67 | Serial.println("Error: Can not open datalog.txt"); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /TVC-Flight-Code/SDCard.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SD-Card 3 | * 4 | * During the ascent and descent states important flight data is saved on the SD-Card 5 | */ 6 | 7 | #include 8 | #include 9 | #include "LED.h" 10 | #include "Buzzer.h" 11 | 12 | class SDCard { 13 | public: 14 | SDCard(); 15 | 16 | LED led; 17 | Buzzer buzzer; 18 | 19 | void logData(float flightTime, float gyroX, float gyroY, float gyroZ, float accelerationX, float accelerationY, float accelerationZ, float orientationX, float orientationY, float orientationZ, float temperature, float pressure, float altitude, float yServo, float zServo, float voltage, String state); 20 | 21 | private: 22 | // Use the built in SD-Card slot of the Teensy 4.1 23 | const int chipSelect = BUILTIN_SDCARD; 24 | }; 25 | -------------------------------------------------------------------------------- /TVC-Flight-Code/StateMachine.cpp: -------------------------------------------------------------------------------- 1 | #include "StateMachine.h" 2 | 3 | StateMachine::StateMachine() { 4 | activeState = LAUNCH_PAD_IDLE; // Default state 5 | 6 | rocket.sdCard.led.red(); 7 | } 8 | 9 | void StateMachine::stateMachineLoop() { 10 | switch (activeState) { 11 | case LAUNCH_PAD_IDLE: 12 | rocket.padIdle(); 13 | // Detect lift off -> Ascent flight 14 | if (liftOffCheck()) { 15 | // Save the time of lift off 16 | rocket.flightStartTime = millis(); 17 | activeState = ASCENT; 18 | rocket.sdCard.led.green(); 19 | } 20 | break; 21 | case ASCENT: 22 | // TVC active 23 | // Log Data 24 | rocket.ascent(); 25 | // Detect max apogee -> Descent flight and eject Parachute 26 | if (maxApogeeCheck()) { 27 | rocket.maxApogee(); 28 | activeState = DESCENT; 29 | rocket.sdCard.led.blue(); 30 | } 31 | break; 32 | case DESCENT: 33 | rocket.descent(); 34 | // Log Data 35 | // Detect landing 36 | if (landedCheck()) { 37 | activeState = LANDED; 38 | rocket.sdCard.led.purple(); 39 | } 40 | break; 41 | case LANDED: 42 | // Do nothing 43 | break; 44 | } 45 | } 46 | 47 | // Check if rocket has launched by looking at the acceleration on vertical axis (x-axis) 48 | bool StateMachine::liftOffCheck() { 49 | if (rocket.acceleration.x < LIFT_THRESHOLD) { 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | // Check if rocket has reached apogee by looking at the time it takes to reach apogee 56 | bool StateMachine::maxApogeeCheck() { 57 | if (rocket.flightTime > TIME_TO_APOGEE) { 58 | return true; 59 | } 60 | return false; 61 | } 62 | 63 | // Check if rocket has landed by looking at the time it takes to land 64 | bool StateMachine::landedCheck() { 65 | if (rocket.flightTime > TIME_TO_LANDING) { 66 | return true; 67 | } 68 | return false; 69 | } 70 | -------------------------------------------------------------------------------- /TVC-Flight-Code/StateMachine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * State Machine 3 | * 4 | * The code is divided into four states: 5 | * 6 | * LAUNCH_PAD_IDLE 7 | * Check the acceleration of the rocket on vertical axis 8 | * ASCENT 9 | * Thrust vector controle is active 10 | * Flight data is logged to SD-Card ‾\ 11 | * DESCENT | Eject parachute 12 | * Flight data is logged to SD-Card <-/ 13 | * LANDED 14 | * No active code (computer waiting to be turned off) 15 | */ 16 | 17 | #include 18 | #include 19 | #include "Rocket.h" 20 | 21 | #define LIFT_THRESHOLD -2 // Acceleration on vertical axis/x-axis (negative value = acceleration upwards) (in m/s^2) 22 | 23 | // Calculated with a flight simulation in AeroVECTOR https://github.com/GuidodiPasquo/AeroVECTOR 24 | #define TIME_TO_APOGEE 4 // Time from start until rocket reaches max apogee and parachute will be ejected (in s) 25 | #define TIME_TO_LANDING 30 // Time from start until rocket lands (in s) 26 | 27 | enum State { 28 | LAUNCH_PAD_IDLE, 29 | ASCENT, 30 | DESCENT, 31 | LANDED 32 | }; 33 | 34 | class StateMachine { 35 | public: 36 | StateMachine(); 37 | void stateMachineLoop(); 38 | bool liftOffCheck(); 39 | bool maxApogeeCheck(); 40 | bool landedCheck(); 41 | 42 | private: 43 | Rocket rocket; 44 | State activeState; 45 | }; 46 | -------------------------------------------------------------------------------- /TVC-Flight-Code/TVC-Flight-Code.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * TVC-Flight-Code 3 | * 4 | * This is the source code for a software implementation of thrust vector control (TVC) for model rockets. 5 | * The software consists of a state machine and PID control loops to adjust the orientation of the rocket during its powered ascent. 6 | * 7 | * This version 1.0 was used for the first test flight of the rocket on Sep 03 2022. 8 | * 9 | * @version 1.0 10 | * @author Tom Kuttler 11 | * @GitHub https://github.com/tomkuttler/TVC-Rocket-Flight-Code 12 | */ 13 | 14 | #include "StateMachine.h" 15 | 16 | StateMachine *stateMachine; 17 | 18 | void setup() { 19 | stateMachine = new StateMachine(); 20 | } 21 | 22 | void loop() { 23 | stateMachine->stateMachineLoop(); 24 | } 25 | -------------------------------------------------------------------------------- /TVC-Flight-Code/VectorDefinition.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Vector definitions 3 | * 4 | * Standard maths definitions 5 | */ 6 | 7 | struct Vector3 { 8 | float x, y, z; 9 | Vector3(float x, float y, float z): x{x}, y{y}, z{z} {} 10 | Vector3(): x(0), y(0), z(0) {} 11 | Vector3(sensors_vec_t vec): x{vec.x}, y{vec.y}, z{vec.z} {}; 12 | 13 | // Multiply vector with float 14 | Vector3 operator * (float d) { 15 | Vector3 P; 16 | P.x = x * d; 17 | P.y = y * d; 18 | P.z = z * d; 19 | return P; 20 | } 21 | 22 | // Integrate vector 23 | Vector3 * operator += (Vector3 Vector) { 24 | x += Vector.x; 25 | y += Vector.y; 26 | z += Vector.z; 27 | return this; 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /TVC-Flight-Code/VoltageDivider.cpp: -------------------------------------------------------------------------------- 1 | #include "VoltageDivider.h" 2 | 3 | VoltageDivider::VoltageDivider() { 4 | 5 | } 6 | 7 | float VoltageDivider::getBoardVoltage() { 8 | int voltageDividerValue = analogRead(VOLTAGEDIVIDER_PIN); 9 | 10 | // R1 + R2 11 | // Vin = --------- * Vout 12 | // R2 13 | 14 | float voltage = ((1000000 + 89000)/89000)*(voltageDividerValue * (5.0 / 1023.0)); 15 | return voltage; 16 | } 17 | -------------------------------------------------------------------------------- /TVC-Flight-Code/VoltageDivider.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Voltage divider 3 | * 4 | * Calculates the on-board voltage by using a voltage divider connected to an analoge input pin of the Teensy 4.1 5 | */ 6 | 7 | #include 8 | 9 | #define VOLTAGEDIVIDER_PIN 38 10 | 11 | class VoltageDivider { 12 | public: 13 | VoltageDivider(); 14 | float getBoardVoltage(); 15 | }; 16 | -------------------------------------------------------------------------------- /flight.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tomkuttler/TVC-Rocket-Flight-Code/199b901097098ee33ccf38209613cc0033cfa55f/flight.gif --------------------------------------------------------------------------------