Thank you so much for your help. I think I figured it out. In my:
inputFile.read((char*) &vect[0], size);
I used &vect[0] which I think when using a pointer led it treat the vect as an array of vectors and attempt to grab the vector at position 0. This is wrong, so I changed it to:
inputFile.read((char*) &(*vect)[0], size);
This defrences the pointer and allows it to point to the correct vector. I’m not sure why this would give me a memory error (perhaps it is the fact that is is trying to point to a vector that doesn’t exist), but this seemed to fix the problem. I am reading the correct data.
Please let me know if you see a problem with this solution.
Here is the entirety of my code if anyone wanted to look through. I am still working on commenting the new parts as it is quite long so be warned:
//Setting up environment
#include "vex.h"
#include <math.h>
#include <fstream>
#include <vector>
#include <sys/stat.h>
#include <functional>
#include <list>
using namespace std;
using namespace vex;
//A global instance of competition
competition Competition;
//Constant variables that allow for changing the bot's behaviour
const float movementTimeDelay = 20; //Milliseconds
const float recordingTimeDelay = 20; //Milliseconds
const float allowedHeadingError = 30; //Degrees
const float disallowHeadingErrorWhenOffset = 5; //Degrees
const float desiredPIDError = 1; //Degrees, the PID correction will run until the error is within this range of the desired value
const uint32_t borderWidth = 5; //Pixels, the border size around each button on the brain screen
const int brainScreenHeight = 240; //Pixels
const int brainScreenWidth = 480; //Pixels
const int brainPrintHeight = 15; //Rows
const int brainPrintWidth = 48; //Cols
//Changeable settings through the brain (coded buttons)
int catapultSpeed = 50; //RPM
const char* slots[5] = {"slot1.dat", "slot2.dat", "slot3.dat", "slot4.dat", "slot5.dat"}; int slotIndex = -1; //Slot/file to read and write the recorded movement
bool PIDActive = true; //Acvtivates the PID correction during autonomous
std::vector<float> settingsVector;
//Holds all the motors
std::vector<vex::motor> motors = {PORT9, PORT8, PORT3, PORT1, PORT10, PORT2, PORT4, PORT6};
//Front_Left, Front_Right, Back_Left, Back_Right, Middle_Left, Middle_Right, Catapult_Right, Catapult_Left
//Global vector for storing recorded movement
std::vector<float> recordedMovementVector;
/*---------------------------------------------------------------------------*/
/* Button Class */
/* */
/* A self made class that allows on screen buttons to be created easily */
/* Takes in a function and will run it whenever the button is pressed */
/* Through optimizing it can usually read a 75,000 byte (1 minute autonomous)*/
/* in roughly 50 milliseconds */
/*---------------------------------------------------------------------------*/
class Button{
int height;
int width;
int xPos;
int yPos;
int textX;
int textY;
const char* label;
vex::color buttonColor;
std::function<void()> func;
public:
Button(int height, int width, int xPos, int yPos, int textX, int textY, std::function<void()> func, vex::color buttonColor, const char* label){
this -> height = height;
this -> width = width;
this -> xPos = xPos;
this -> yPos = yPos;
this -> textX = textX;
this -> textY = textY;
this -> buttonColor = buttonColor;
this -> func = func;
this -> label = label;
}
Button(){
xPos = 0;
yPos = 0;
height = brainScreenHeight;
width = brainScreenWidth;
}
void drawButton(){
Brain.Screen.setFillColor(buttonColor);
Brain.Screen.setPenWidth(borderWidth);
Brain.Screen.setPenColor(black);
Brain.Screen.drawRectangle(xPos, yPos, width, height);
Brain.Screen.setCursor(textY + 2, textX + 2);
Brain.Screen.print(label);
}
void screenPressed(int xPosPressed, int yPosPressed){
if (xPosPressed > xPos && xPosPressed < xPos + width && yPosPressed > yPos && yPosPressed < yPos + height){
func();
}
}
};
/*---------------------------------------------------------------------------*/
/* Button Grid Class */
/* */
/* A self made class that allows on screen buttons to be created easily */
/* Takes in a function and will run it whenever the button is pressed */
/* Through optimizing it can usually read a 75,000 byte (1 minute autonomous)*/
/* in roughly 50 milliseconds */
/*---------------------------------------------------------------------------*/
class ButtonGrid{
int heights;
int widths;
int rows;
int cols;
std::list<Button> buttons;
int numButtons = 0;
public:
ButtonGrid(int rows, int cols){
heights = brainScreenHeight / rows;
widths = brainScreenWidth / cols;
this -> rows = rows;
this -> cols = cols;
}
bool addButton(std::function<void()> func, vex::color buttonColor, const char* label){
if (numButtons >= rows * cols){
return false;
}
Button newButton(heights, widths, fmod(numButtons, cols) * widths, (numButtons / cols) * heights, fmod(numButtons, cols) * (brainPrintWidth/cols), (numButtons / cols) * (brainPrintHeight/rows), func, buttonColor, label);
buttons.insert(buttons.end(), newButton);
numButtons++;
return true;
}
void drawGrid(){
for (Button i : buttons){
i.drawButton();
}
}
void screenPressed(int xPosPressed, int yPosPressed){
for (Button i : buttons){
i.screenPressed(xPosPressed, yPosPressed);
}
}
};
/*---------------------------------------------------------------------------*/
/* Read File Function */
/* */
/* Able to read a binary file of raw floats into the given */
/* vector for use with recording and playing back movements */
/* Through optimizing it can usually read a 75,000 byte (1 minute autonomous)*/
/* in roughly 50 milliseconds */
/*---------------------------------------------------------------------------*/
void readFile(const char* fileName, std::vector<float> *vect) {
//Open the stream for reading in binary
std::ifstream inputFile(fileName, std::ios::in | std::ios::binary);
//Check if the file succesfully opened to ensure no errors
if (inputFile){
// Determine the file length
//Goes to the end of the file
inputFile.seekg(0, std::ios_base::end);
//Gives the current position in the file in terms of bytes
std::size_t size = inputFile.tellg();
if (size % sizeof(float) > 0){
size += (sizeof(float) - (size % sizeof(float)));
}
//Returns back to the beginning
inputFile.seekg(0, std::ios_base::beg);
//For testing purposes, Prints the number of bytes read
Brain.Screen.setCursor(3, 1);
Brain.Screen.print(size);
//Resize vector to store the data with the correct number of spaces
//Must divide by size of float (4 bytes) as that is the raw data type stored in the binary file
vect -> resize(size/sizeof(float));
//Load the data onto the vector array
inputFile.read((char*) &(*vect)[0], size);
//Close the file
inputFile.close();
}else{
//Let the user know about failed file opening
Brain.Screen.setCursor(2, 1);
Brain.Screen.print("Failed to open file");
}
}
/*---------------------------------------------------------------------------*/
/* Write To File Function */
/* */
/* Able to write to a binary file in the form of raw signed floats */
/* This binary file is saved to a sd-card to allow for saving of the movement*/
/* even if the brain is turned off or code unexpectedly terminates */
/* By writing to a binary file instead of a .txt, we can compress the amount */
/* of space needed due to the fact that a text fyle requires 1 byte for each */
/* character including periods (decimals) and negative signs */
/*---------------------------------------------------------------------------*/
void writeToFile(const char* fileName, std::vector<float> outputVector) {
//Make or open a binary file for writing
ofstream output(fileName, std::ios::out | std::ios::binary);
//Write the vector into the binary file using signed raw floats in order to allow for negatives and quick reading
//The amount of bytes stored is equal to the number of floats times the size of each of float in bytes (4 bytes)
output.write((char*)&outputVector[0], outputVector.size() * sizeof(float));
//Close the file
output.close();
}
/*---------------------------------------------------------------------------*/
/* Button Functions */
/* */
/* Records the movement of the bot */
/* and saves it to a binary file on the sd card for playback later */
/*---------------------------------------------------------------------------*/
void selectSlot1(){
Brain.Screen.clearScreen();
Brain.Screen.setCursor(1, 1);
Brain.Screen.print("Now reading and writing from slot: 1");
slotIndex = 0;
wait(3, seconds);
}
void selectSlot2(){
Brain.Screen.clearScreen();
Brain.Screen.setCursor(1, 1);
Brain.Screen.print("Now reading and writing from slot: 2");
slotIndex = 1;
wait(3, seconds);
}
void selectSlot3(){
Brain.Screen.clearScreen();
Brain.Screen.setCursor(1, 1);
Brain.Screen.print("Now reading and writing from slot: 3");
slotIndex = 2;
wait(3, seconds);
}
void selectSlot4(){
Brain.Screen.clearScreen();
Brain.Screen.setCursor(1, 1);
Brain.Screen.print("Now reading and writing from slot: 4");
slotIndex = 3;
wait(3, seconds);
}
void selectSlot5(){
Brain.Screen.clearScreen();
Brain.Screen.setCursor(1, 1);
Brain.Screen.print("Now reading and writing from slot: 5");
slotIndex = 4;
wait(3, seconds);
}
void selectDefaultSlot(){
Brain.Screen.clearScreen();
Brain.Screen.setCursor(1, 1);
Brain.Screen.print("Now using default autonomous");
slotIndex = -1;
wait(3, seconds);
}
void selectSlot(){
while(Brain.Screen.pressing()){
wait(5, msec);
}
Brain.Screen.clearScreen();
ButtonGrid slotButtons(3, 2);
slotButtons.addButton(selectSlot1, white, "Slot 1");
slotButtons.addButton(selectSlot2, white, "Slot 2");
slotButtons.addButton(selectSlot3, white, "Slot 3");
slotButtons.addButton(selectSlot4, white, "Slot 4");
slotButtons.addButton(selectSlot5, white, "Slot 5");
slotButtons.addButton(selectDefaultSlot, white, "Default");
slotButtons.drawGrid();
while(!Brain.Screen.pressing()){
wait(5, msec);
}
slotButtons.screenPressed(Brain.Screen.xPosition(), Brain.Screen.yPosition());
}
void increaseCataRPM(){
catapultSpeed++;
Brain.Screen.setCursor(8, 22);
Brain.Screen.print("Catapult RPM: ");
Brain.Screen.print(catapultSpeed);
}
void decreaseCataRPM(){
catapultSpeed--;
Brain.Screen.setCursor(8, 22);
Brain.Screen.print("Catapult RPM: ");
Brain.Screen.print(catapultSpeed);
}
bool settingCataRPM = false;
void exitCataRPMSetting(){
settingCataRPM = false;
}
void setCatapultRPM(){
Brain.Screen.clearScreen();
ButtonGrid changeCataButtons(2, 2);
changeCataButtons.addButton(decreaseCataRPM, red, "Decrease RPM");
changeCataButtons.addButton(increaseCataRPM, green, "Increase RPM");
changeCataButtons.addButton(exitCataRPMSetting, white, "Exit");
changeCataButtons.drawGrid();
Brain.Screen.setCursor(8, 22);
Brain.Screen.print("Catapult RPM: ");
Brain.Screen.print(catapultSpeed);
settingCataRPM = true;
while(settingCataRPM){
if (Brain.Screen.pressing()){
changeCataButtons.screenPressed(Brain.Screen.xPosition(), Brain.Screen.yPosition());
wait(40, msec);
}
wait(5, msec);
}
}
void togglePID(){
Brain.Screen.clearScreen();
Brain.Screen.setCursor(1, 1);
PIDActive = !PIDActive;
Brain.Screen.print("PID correction: ");
Brain.Screen.print(PIDActive ? "ENABLED" : "DISABLED");
wait(3, seconds);
}
bool settingsMenuActive = false;
void saveSettings(){
Brain.Screen.clearScreen();
vector<float> settings = {static_cast<float>(catapultSpeed), static_cast<float>(PIDActive), static_cast<float>(slotIndex)};
writeToFile("settings.dat", settings);
settingsMenuActive = false;
wait(3, seconds);
}
/*---------------------------------------------------------------------------*/
/* Settings Menu Function */
/* */
/* Records the movement of the bot */
/* and saves it to a binary file on the sd card for playback later */
/*---------------------------------------------------------------------------*/
void settingsMenu(){
while (settingsMenuActive){
while(Brain.Screen.pressing()){
wait(5, msec);
}
Brain.Screen.clearScreen();
ButtonGrid settingsButtons(2, 2);
settingsButtons.addButton(selectSlot, yellow, "Reading/Writing Slots");
settingsButtons.addButton(setCatapultRPM, red, "Catapult RPM");
settingsButtons.addButton(togglePID, blue, "Toggle PID Control");
settingsButtons.addButton(saveSettings, green, "Save Settings");
settingsButtons.drawGrid();
while(!Brain.Screen.pressing()){
wait(5, msec);
}
settingsButtons.screenPressed(Brain.Screen.xPosition(), Brain.Screen.yPosition());
Brain.Screen.clearScreen();
}
}
/*---------------------------------------------------------------------------*/
/* Toggle Settings Menu Function */
/* */
/* Records the movement of the bot */
/* and saves it to a binary file on the sd card for playback later */
/*---------------------------------------------------------------------------*/
void toggleSettingsMenu(){
//If not already recording then start recording
if (!settingsMenuActive){
//Toggle the settings menu, automatically turns off after going through
settingsMenuActive = true;
settingsMenu();
}
}
/*---------------------------------------------------------------------------*/
/* Pre-Autonomous Function */
/* */
/* Actions performed before the competition starts. */
/*---------------------------------------------------------------------------*/
void pre_auton(void) {
//Initializing Robot Configuration. DO NOT REMOVE!
vexcodeInit();
//Calibrating the inertial sensor, threaded task so other pre auton code can still run
//2 second calibration
inertialSensor.calibrate(2);
//Set the stopping types for each motor
Catapult_Left.setStopping(vex::hold);
Catapult_Right.setStopping(vex::hold);
Front_Left.setStopping(vex::coast);
Front_Right.setStopping(vex::coast);
Middle_Left.setStopping(vex::coast);
Middle_Right.setStopping(vex::coast);
Back_Left.setStopping(vex::coast);
Back_Right.setStopping(vex::coast);
//For testing purposes
Brain.Screen.setCursor(2, 1);
Brain.Screen.print("Running pre auton");
//For optimization testing, stores the start time of reading the file for later uses
long loadStartTime = Brain.Timer.time(msec);
//Wait until done calibrating
while (inertialSensor.isCalibrating()){
}
//Set the initial heading to 0 after calibration
inertialSensor.setHeading(0, vex::degrees);
//Vector to store the settings read in from the file
std::vector<float> currSettings;
readFile("settings.dat", &currSettings);
if (currSettings.size() >= 3){
Brain.Screen.setCursor(4, 1);
Brain.Screen.print("hi");
vector<float>::iterator settingsIter = currSettings.begin();
catapultSpeed = static_cast<int>(*settingsIter);
settingsIter++;
PIDActive = static_cast<bool>(*settingsIter);
settingsIter++;
slotIndex = static_cast<int>(*settingsIter);
Brain.Screen.print("Cata RPM: ");
Brain.Screen.print(catapultSpeed);
Brain.Screen.print(", Slot: ");
Brain.Screen.print(slotIndex + 1);
Brain.Screen.print(", PID: ");
Brain.Screen.print(PIDActive);
}
//For optimization testing purposes
Brain.Screen.setCursor(7, 1);
Brain.Screen.print("Finished pre auton, took time(msec): ");
Brain.Screen.print(Brain.Timer.time(msec) - loadStartTime);
}
/*---------------------------------------------------------------------------*/
/* Correct Turning Error Function */
/* */
/* Able to write to a binary file in the form of raw signed floats */
/* This binary file is saved to a sd-card to allow for saving of the movement*/
/* even if the brain is turned off or code unexpectedly terminates */
/* By writing to a binary file instead of a .txt, we can compress the amount */
/* of space needed due to the fact that a text fyle requires 1 byte for each */
/* character including periods (decimals) and negative signs */
/*---------------------------------------------------------------------------*/
void correctTurningError(double desiredValue) {
//Tuning values for the PID algorithm
//Tuning for pontential
float kP = 3;
//Tuning for integral
float kI = 0.0000005;
//Tuning for derivative
float kD = 5;
//Needed instance variables
float error = inertialSensor.heading(vex::degrees) - desiredValue; //Current error (degrees from desired value) - P
float prevError = 0; //Previous calculated error
float derivative; //Uses derivative ideology to slow down the turn if the system (robot) is approaching the desired value too fast - D
float integralError = 0; //Uses inegral ideology to speed up the system (robot) if it is taking too long to reach the desired value - I
//Continue running the PID algorithm until the absolute error is less than the desired error
while (fabs(error) > desiredPIDError){
//P - Potential: Speeds the turn up when far away from the desired value,
//but slows it down as it gets closer
error = inertialSensor.heading(vex::degrees) - desiredValue;
//Finds the minumum angle by wrapping back around if the calculated difference angle is too big
if (error > 180){
error -= 360;
}else if(error < -180){
error += 360;
}
//Gets the difference between the previous and cuurent errors. This ensures that this value will increase as the turn speeds up
//This corrects for overshooting of the Potential value and Integral values
derivative = error - prevError; //Derivative
//Sums up all errors to account for a consistent non-zero error (also known as a steady-state error)
//This corrects for the Potential not spinning fast enough
integralError += error;//Integral
//Sum up each value times their respective tuning constants to get the motor velocities (RPM)
float motorVelocity = error * kP + integralError * kI + derivative * kD;
//Set the drivetrain motor's velocities
Front_Left.setVelocity(fabs(motorVelocity), rpm);
Front_Right.setVelocity(fabs(motorVelocity), rpm);;
Back_Left.setVelocity(fabs(motorVelocity), rpm);;
Back_Right.setVelocity(fabs(motorVelocity), rpm);;
Middle_Left.setVelocity(fabs(motorVelocity), rpm);;
Middle_Right.setVelocity(fabs(motorVelocity), rpm);;
if (motorVelocity < 0){
//Spin each drivetrain motor in the correct direction for turning right
Front_Left.spin(vex::forward);
Front_Right.spin(vex::reverse);
Back_Left.spin(vex::forward);
Back_Right.spin(vex::reverse);
Middle_Left.spin(vex::forward);
Middle_Right.spin(vex::reverse);
}else{
//Spin each drivetrain motor in the correct direction for turning left
Front_Left.spin(vex::reverse);
Front_Right.spin(vex::forward);
Back_Left.spin(vex::reverse);
Back_Right.spin(vex::forward);
Middle_Left.spin(vex::reverse);
Middle_Right.spin(vex::forward);
}
//Setup previous error for the next loop
prevError = error;
//Print information to the screen for troubleshooting
Brain.Screen.setCursor(5, 1);
Brain.Screen.print(error);
Brain.Screen.setCursor(6, 1);
Brain.Screen.print(inertialSensor.heading(vex::degrees));
//Time delay
wait(20, msec);
}
//Stop the motors when the PID is done
Front_Left.setVelocity(0, rpm);
Front_Right.setVelocity(0, rpm);
Back_Left.setVelocity(0, rpm);
Back_Right.setVelocity(0, rpm);
Middle_Left.setVelocity(0, rpm);
Middle_Right.setVelocity(0, rpm);
}
/*---------------------------------------------------------------------------*/
/* Record Movement Function */
/* */
/* Records the movement of the bot */
/* and saves it to a binary file on the sd card for playback later */
/*---------------------------------------------------------------------------*/
//Variable to keep track of recording status
bool recording = false;
int recordMovement(){
//Declare a vector to store velocities during recording
std::vector<float> outputVector = {};
float totalTimeDelay = 0; //The total delay to run each function (minus sleeping the thread), allows for very accurate playback
int loops = 0; //Used in order to calculate the average timeDelay
//Record until button pressed
while (recording){
//Start time of this record loop
float startTime = Brain.Timer.time(msec);
//Loop through each motor in the motors vector
for (vex::motor currMotor : motors){
//Add this motor's velocity to the end of the vector as a float
outputVector.push_back(currMotor.velocity(rpm));
}
//Add the current heading to check for errors during playback
outputVector.push_back(inertialSensor.heading(vex::degrees));
//Add to the total time delay
totalTimeDelay += Brain.Timer.time(msec) - startTime;
//Increment loops
loops++;
//Pause for a time delay to allow for playback and no crashes
this_thread::sleep_for(recordingTimeDelay);
}
//Add the average and root time delay to the front of the vector for playback
outputVector.insert(outputVector.begin(), totalTimeDelay / loops);
outputVector.insert(outputVector.begin(), recordingTimeDelay);
//Write the recorded velocities to the binary file in their raw signed float data to allow for faster reading
//(See Write To File Function to look at details)
writeToFile("output.dat", outputVector);
//Give information about the number of bytes written to the file
Brain.Screen.setCursor(5, 1);
Brain.Screen.print("Size written: ");
Brain.Screen.print(outputVector.size() * sizeof(float));
//Return something to allow for threading
return 0;
}
/*---------------------------------------------------------------------------*/
/* Follow Recorded Movement Function */
/* */
/* Follow the written motor velocities using an iterator through a vector */
/*---------------------------------------------------------------------------*/
void followRecordedMovement(){
//Read in the raw float data from the binary file
//(See Read File Function to look at details)
readFile("output.dat", &recordedMovementVector);
//total time delay and loop for average time delay calculation which allows for accurate playback
float totalTimeDelay = 0;
int loops = 0;
//Instantiate the previous desired heading variable to help with autonomous twicthing
float previousDesiredHeading = 0;
//Instantiate a iterator for the recordedMovementVector vector in order to retrieve recorded data
//See the Read File and Pre-Auton Functions to see how recordedMovementVector was set up
vector<float>::iterator iter = recordedMovementVector.begin();
//Get the iterator data as the root time delay for accurate recording
float rootTimeDelay = *iter;
//Iterate forward
iter++;
//Get the iterator data as the average time delay for accurate recording
float avgTimeDelay = *iter;
//Iterate forward
iter++;
//While the iterator hasn't reached the end of recordedMovementVector
while (iter < recordedMovementVector.end()){
float startTime = Brain.Timer.time(msec);
//Loop through each motor in the vector
for (vex::motor currMotor : motors){
//Set the velocity of this motor to the iterator's data
currMotor.setVelocity(*iter, rpm);
//Stop the motor if it should have 0 velocity
//Helps to hold the catapult in place
if (*iter == 0){
currMotor.stop();
}
//Iterate forward
iter++;
//Spin the motor
currMotor.spin(vex::forward);
}
//Take in the desired heading data from the iterator
float desiredHeading = *iter;
//Iterate
iter++;
if (PIDActive){
//Calculate the heading error based on desired and actual
float headingError = fabs(desiredHeading - inertialSensor.heading(vex::degrees));
//Wrap around the value
if (headingError > 180){
headingError = 360 - headingError;
}
//Calculate the change in desired heading in the file in order the prevent jitters at higher rates of heading changes (During aggressive turns)
float changeInDesired = fabs(desiredHeading - previousDesiredHeading);
//Wrap around value if needed
if (changeInDesired > 180){
changeInDesired = 360 - changeInDesired;
}
//Check if the robot needs to be corrected during the autonomous playback
//If the error is great enough and the changing heading isn't too high
if (headingError > allowedHeadingError && changeInDesired < disallowHeadingErrorWhenOffset){
float correctionStartTime = Brain.Timer.time(msec);;
//Call the correctError method with the desired heading
correctTurningError(desiredHeading);
//Don't include heading error fixes in the average/total time delay
totalTimeDelay -= Brain.Timer.time(msec) - correctionStartTime;
}
//Setup previousDesiredHeading for next loop
previousDesiredHeading = desiredHeading;
}
//Update the total time delay and number of loops in order to keep a track of the average time delay
totalTimeDelay += Brain.Timer.time(msec) - startTime;
loops++;
//Pause for a time delay to allow for playback and no crashes
//Time delay is equal to recording time - average following time, plus 20
//This allows for differences in reading and writing to the vector
wait((avgTimeDelay - (totalTimeDelay / loops)) + rootTimeDelay, msec);
}
//Stop all motors when finished
for (vex::motor currMotor : motors){
currMotor.stop();
}
}