All mbed code for control over dive planes, pump motor, valve motor, BCUs, UART interface, etc.

Dependencies:   mbed ESC mbed MODDMA

Files at this revision

API Documentation at this revision

Comitter:
juansal12
Date:
Tue Jan 14 19:17:05 2020 +0000
Commit message:
Sofi7 mbed code;

Changed in this revision

robotic_fish_6/.gitignore Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/.hgignore Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/AcousticControl/AcousticController.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/AcousticControl/AcousticController.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/AcousticControl/ToneDetector.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/AcousticControl/ToneDetector.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/BNO055/BNO055.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/BNO055/BNO055.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/BrushlessMotor.lib Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/BuoyancyControlUnit/BuoyancyControlUnit.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/BuoyancyControlUnit/BuoyancyControlUnit.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/ButtonBoard.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/ButtonBoard.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/FishController.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/FishController.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/MODDMA.lib Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/MS5837/MS5837.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/MS5837/MS5837.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/PumpWithValve/PumpWithValve.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/PumpWithValve/PumpWithValve.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/QEI/QEI.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/QEI/QEI.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/ROSControl/ROSController.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/ROSControl/ROSController.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/SerialControl/SerialController.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/SerialControl/SerialController.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/Servo/.hgignore Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/Servo/Servo.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/Servo/Servo.h Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/data_stream_format.txt Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/main.cpp Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/makefile Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/mbed.bld Show annotated file Show diff for this revision Revisions of this file
robotic_fish_6/mbed.lib Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/.gitignore	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,1 @@
+/.settings/
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/.hgignore	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,22 @@
+syntax: regexp
+\.hgignore$
+\.git$
+\.svn$
+\.orig$
+\.msub$
+\.meta$
+\.ctags
+\.uvproj$
+\.uvopt$
+\.project$
+\.cproject$
+\.launch$
+\.project$
+\.cproject$
+\.launch$
+Makefile$
+\.ewp$
+\.eww$
+\.htm$
+Debug$
+.settings$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/AcousticControl/AcousticController.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,905 @@
+/*
+ * Author: Joseph DelPreto
+ */
+
+#include "AcousticController.h"
+
+#ifdef acousticControl
+
+// The static instance
+AcousticController acousticController;
+
+// Map received state to fish values
+const float pitchLookupAcoustic[] = {0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8}; // [0.2 - 0.8]
+const float yawLookupAcoustic[] = {-1, -0.7, -0.5, 0, 0.5, 0.7, 1}; // [-1, 1]
+const float thrustLookupAcoustic[] = {0, 0.25, 0.50, 0.75};
+const float frequencyLookupAcoustic[] = {0.0000009, 0.0000012, 0.0000014, 0.0000016}; // cycles/us // NOTE also update periodHalfLookup if you update these values
+const float periodHalfLookupAcoustic[] = {555555, 416666, 357142, 312500}; // 1/(2*frequencyLookup) -> us
+
+// AGC definition
+const uint8_t agcGains[] = {1, 1, 2, 5, 10, 20, 50, 100};  // the possible gains of the AGC
+const uint8_t agcGainsLength = sizeof(agcGains)/sizeof(agcGains[0]);
+const uint16_t signalLevelBufferLength = (agcUpdateWindow)/(sampleWindow*sampleInterval);         // first constant is window length for updating agc in microseconds
+const int32_t signalLevelSumDesired = ((uint32_t)500)*((uint32_t)signalLevelBufferLength); // desire about 2 VPP, so signal minus offset should have 1V amplitude, so say mean a bit smaller, about 0.4V ~= 500
+
+// Fish timeout
+const uint16_t fishTimeoutAcousticBuffers = fishTimeoutAcousticWindow/(sampleWindow*sampleInterval); // first constant is timeout in microseconds
+
+// Apparently it's hard to get a proper function pointer to a member function
+// so this dummy method will be set as the toneDetector callback function
+void toneDetectorCallback(int32_t* newTonePowers, uint32_t signalLevel)
+{
+	acousticController.processTonePowers(newTonePowers, signalLevel);
+}
+
+// Constructor
+AcousticController::AcousticController(Serial* usbSerialObject /* = NULL */) :
+    // Initialize variables
+    bufferCount(0),
+	lastDataWord(0),
+	timeSinceGoodWord(0),
+	streamCurFishStateAcoustic(0),
+	streamCurFishStateEventAcoustic(0)
+{
+	// AGC Pins
+	gain0 = new DigitalOut(agcPin0);
+	gain1 = new DigitalOut(agcPin1);
+	gain2 = new DigitalOut(agcPin2);
+	agc[0] = gain0;
+	agc[1] = gain1;
+	agc[2] = gain2;
+	#ifdef AGCLeds
+	agcLED0 = new DigitalOut(LED1);
+	agcLED1 = new DigitalOut(LED2);
+	agcLED2 = new DigitalOut(LED3);
+	agcLEDs[0] = agcLED0;
+	agcLEDs[1] = agcLED1;
+	agcLEDs[2] = agcLED2;
+	#endif
+
+	// Low battery
+	lowBatteryVoltageInput = new DigitalIn(lowBatteryVoltagePin);
+
+	// Misc
+	#ifdef artificialPowers
+	FILE* finPowers; = fopen("/local/powers.wp", "r");
+	#endif
+
+	// Complete initialization
+	init(usbSerialObject);
+}
+
+// Initialization
+void AcousticController::init(Serial* usbSerialObject /* = NULL */)
+{
+	// Create usb serial object or use provided one
+	if(usbSerialObject == NULL)
+	{
+		usbSerialObject = new Serial(USBTX, USBRX);
+		usbSerialObject->baud(serialDefaultBaudUSB);
+	}
+	usbSerial = usbSerialObject;
+	// Miscellaneous
+	bufferCount = 0;
+	lastDataWord = 0;
+	timeSinceGoodWord = 0;
+
+	// Will check if battery is low in every buffer callback
+	lowBatteryVoltageInput->mode(PullUp);
+	//lowBatteryInterrupt.fall(&lowBatteryCallback);
+
+	// TODO remove this?
+	wait(1.5);
+
+	// Configure the tone detector
+	toneDetector.setCallback(&toneDetectorCallback);
+	toneDetector.init();
+
+	// Clear detection arrays
+	#if defined(threshold2)
+	for(int t = 0; t < numTones; t++)
+	{
+		detectSums[t] = 0;
+		for(uint8_t p = 0; p < detectWindow; p++)
+			tonesPresent[p][t] = 0;
+	}
+	detectWindowIndex = 0;
+	readyToThreshold = false;
+	powerHistoryIndex = 0;
+	powerHistoryDetectIndex = (powerHistoryLength - 1) - (powerHistoryDetectWindow-1) + 1; // assumes powerHistoryLength >= detectWindow > 1
+	for(uint8_t t = 0; t < numTones; t++)
+	{
+		powerHistorySumDetect[t] = 0;
+		powerHistorySumNoDetect[t] = 0;
+		powerHistoryMaxDetect[t] = 0;
+		powerHistoryMaxNoDetect[t] = 0;
+		for(uint16_t p = 0; p < powerHistoryLength; p++)
+		{
+			powerHistory[p][t] = 0;
+		}
+	}
+	#elif defined(threshold1)
+	for(int t = 0; t < numTones; t++)
+	{
+		detectSums[t] = 0;
+	}
+	powerHistoryIndex = 0;
+	powerHistoryDetectIndex = (powerHistoryLength - 1) - (detectWindow-1) + 1; // assumes powerHistoryLength >= detectWindow > 1
+	for(uint8_t t = 0; t < numTones; t++)
+	{
+		powerHistorySum[t] = 0;
+		powerHistoryMax[t] = 0;
+		for(uint16_t p = 0; p < powerHistoryLength; p++)
+		{
+			powerHistory[p][t] = 0;
+		}
+	}
+	readyToThreshold = false;
+	#endif
+	#ifdef singleDataStream
+		#ifdef saveData
+		dataIndex = 0;
+		#endif
+	#else
+		#ifdef saveData
+		dataWordIndex = 0;
+		#endif
+		dataBitIndex = 0;
+		interWord = false;
+	#endif
+	waitingForEnd = false;
+	periodIndex = 0;
+	fskIndex = 0;
+
+	// Initialize adjustable gain control
+	signalLevelBufferIndex = 0;
+	signalLevelSum = 0;
+	currentGainIndex = 4;
+	for(uint8_t i = 0; i < sizeof(agc)/sizeof(agc[0]); i++)
+	{
+		agc[i]->write(currentGainIndex & (1 << i));
+		#ifdef AGCLeds
+		agcLEDs[i].write(currentGainIndex & (1 << i));
+		#endif
+	}
+}
+
+
+// Called by toneDetector when new tone powers are computed (once per buffer)
+void AcousticController::processTonePowers(int32_t* newTonePowers, uint32_t signalLevel)
+{
+	#ifdef streamAcousticControlLog
+	acousticControlLogToStream[4] = -10;
+	#endif
+    // Check for low battery and if so terminate tone detector
+    if(lowBatteryVoltageInput == 0)
+    {
+        lowBatteryCallback();
+        return;
+    }
+    // See if we're done and if so terminate the tone detector
+    if(bufferCount == loopCount)
+    {
+        #ifndef infiniteLoopAcoustic
+        toneDetector.stop();
+        return;
+        #else
+        //bufferCount = 0;
+        #endif
+    }
+    bufferCount++;
+    // See if we're in autonomous mode and if so just let it be
+    if(fishController.autoMode)
+    	return;
+
+    periodIndex++;
+    timeSinceGoodWord++;
+
+    #if defined(threshold2)
+    // Update threshold window state for each tone
+    for(uint8_t t = 0; t < numTones; t++)
+    {
+        // Update our history of powers for this tone
+        powerHistory[powerHistoryIndex][t] = (newTonePowers[t] >> 5);
+        // Compute max/sum of the current window (only for frequencies we currently anticipate)
+        if((t == fskIndex*2 || t == fskIndex*2+1) && !waitingForEnd)
+        {
+            powerHistoryMaxDetect[t] = 0;
+            powerHistoryMaxNoDetect[t] = 0;
+            powerHistorySumDetect[t] = 0;
+            powerHistorySumNoDetect[t] = 0;
+            // Look at non-detection zone
+            for(uint16_t p = ((powerHistoryIndex+1)%powerHistoryLength); p != powerHistoryDetectIndex; p=(p+1)%powerHistoryLength)
+            {
+                powerHistorySumNoDetect[t] += powerHistory[p][t];
+                if(powerHistory[p][t] > powerHistoryMaxNoDetect[t])
+                    powerHistoryMaxNoDetect[t] = powerHistory[p][t];
+            }
+            // Look at detection zone
+            for(uint16_t p = powerHistoryDetectIndex; p != ((powerHistoryIndex+1)%powerHistoryLength); p=((p+1)%powerHistoryLength))
+            {
+                powerHistorySumDetect[t] += powerHistory[p][t];
+                if(powerHistory[p][t] > powerHistoryMaxDetect[t])
+                    powerHistoryMaxDetect[t] = powerHistory[p][t];
+            }
+        }
+    }
+    // Advance our power history index (circular buffer)
+    powerHistoryIndex = (powerHistoryIndex+1) % powerHistoryLength;
+    powerHistoryDetectIndex = (powerHistoryDetectIndex+1) % powerHistoryLength;
+    readyToThreshold = readyToThreshold || (powerHistoryIndex == 0);
+    // If not waiting out silence until next pulse is expected, see if a tone is present
+    if(!waitingForEnd && readyToThreshold)
+    {
+        // Based on new max/mean, see how many powers indicate a tone present in the last detectWindow readings
+        for(uint8_t t = fskIndex*2; t <= fskIndex*2+1; t++)
+        {
+            detectSums[t] -= tonesPresent[detectWindowIndex][t];
+            if(
+                   ((powerHistorySumDetect[t] << 2) > powerHistorySumNoDetect[t])
+                && (powerHistoryMaxDetect[t] > powerHistoryMaxNoDetect[t])
+                && ((powerHistorySumDetect[t] - (powerHistorySumDetect[t] >> 3)) > powerHistorySumDetect[fskIndex*2+((t+1)%2)])
+                && ((powerHistorySumDetect[t] << 2) > powerHistorySumNoDetect[fskIndex*2+((t+1)%2)])
+                && ((powerHistoryMaxDetect[t] << 4) > powerHistorySumNoDetect[t])
+                && (powerHistoryMaxDetect[t] > 100000)
+                )
+                tonesPresent[detectWindowIndex][t] = 1;
+            else
+                tonesPresent[detectWindowIndex][t] = 0;
+            detectSums[t] += tonesPresent[detectWindowIndex][t];
+        }
+        detectWindowIndex = (detectWindowIndex+1) % detectWindow;
+        // If both are considered present (should be very rare?), choose the one with a higher mean
+        if(detectSums[fskIndex*2] > detectThresh && detectSums[fskIndex*2+1] > detectThresh)
+        {
+            if(powerHistorySumDetect[fskIndex*2] > powerHistorySumDetect[fskIndex*2+1])
+                detectSums[fskIndex*2+1] = 0;
+            else
+                detectSums[fskIndex*2] = 0;
+        }
+        // See if a tone is present
+        int tonePresent = -1;
+        if(detectSums[fskIndex*2] > detectThresh)
+            tonePresent = fskIndex*2;
+        else if(detectSums[fskIndex*2+1] > detectThresh)
+            tonePresent = fskIndex*2+1;
+        // Record data and update state
+        if(tonePresent > -1)
+        {
+            #ifdef singleDataStream // if we just want a stream of bits instead of segmenting into words
+                #ifdef saveData
+                data[dataIndex++] = tonePresent%2;
+                #endif
+                #ifdef streamData
+                usbSerial->printf("%ld\t%d\n", bufferCount, (bool)(tonePresent%2));
+                #endif
+                periodIndex = detectSums[tonePresent];
+                fskIndex = (fskIndex+1) % numFSKGroups;
+            #else
+            // See if it has been a long time since last pulse
+            if(periodIndex >= interWordWait)
+            {
+                // If we currently think that we're between words, then that's good so process the previous data word
+                // If we don't think we're between words, then we missed a bit so discard whatever we've collected in the word so far
+                // Either way, this is the start of a new word so reset dataBitIndex
+                if(interWord && dataBitIndex == dataWordLength)
+                {
+                	timeSinceGoodWord = 0;
+                	streamCurFishStateEventAcoustic = 5;
+                    // Decode last word and then send it out
+                    #ifdef saveData
+                	decodeAcousticWord(data[dataWordIndex]);
+                    dataWordIndex++;
+                    #else
+                    decodeAcousticWord(dataWord);
+                    #endif
+                    dataBitIndex = 0;
+                    interWord = false;
+                }
+                else if(!interWord) // missed a bit
+                {
+                    #ifdef streamData
+                	usbSerial->printf("%ld\t0\t-1\n", bufferCount);
+                    #endif
+					#ifdef saveData
+                    for(int dbi = 0; dbi < dataWordLength; dbi++)
+                    	data[dataWordIndex][dbi] = 0;
+                    data[dataWordIndex][dataWordLength-1] = 1;
+                    dataWordIndex++;
+					#endif
+					#ifdef streamAcousticControlLog
+                    acousticControlLogToStream[4] = -1;
+                    streamCurFishStateEventAcoustic = 1;
+					#endif
+                    lastDataWord = 0;
+                }
+                // TODO this is a debug check - if transmitter not putting space between words, set interWordWait to 1
+                if(interWordWait > 1)
+                {
+                    dataBitIndex = 0;
+                    interWord = false;
+                }
+            }
+            else if(interWord)
+            {
+                // It has not been a long time since the last pulse, yet we thought it should be
+                // Seems like we erroneously detected a bit that shouldn't have existed
+                // Discard current word as garbage and start again
+                dataBitIndex = 0;
+                #ifdef streamData
+                usbSerial->printf("%ld\t0\t-2\n", bufferCount);
+                #endif
+				#ifdef saveData
+				for(int dbi = 0; dbi < dataWordLength; dbi++)
+					data[dataWordIndex][dbi] = 0;
+				data[dataWordIndex][dataWordLength-2] = 1;
+				dataWordIndex++;
+				#endif
+				#ifdef streamAcousticControlLog
+				acousticControlLogToStream[4] = -2;
+				streamCurFishStateEventAcoustic = 2;
+				#endif
+                lastDataWord = 0;
+            }
+            // If we're not between words (either normally or because we were just reset above), store the new bit
+            if(!interWord)
+            {
+                #ifdef saveData
+                data[dataWordIndex][dataBitIndex++] = tonePresent%2;
+                #else
+                dataWord[dataBitIndex++] = tonePresent%2;
+                #endif
+                // Rotate through which FSK frequency we expect for the next pulse
+                fskIndex = (fskIndex+1) % numFSKGroups;
+                // If we've finished a word, say we're waiting between words
+                // Word won't be processed until next word begins though in case we accidentally detected a nonexistent bit (see logic above)
+                if(dataBitIndex == dataWordLength)
+                {
+                    interWord = true;
+                    fskIndex = 0;
+                }
+            }
+            periodIndex = detectSums[tonePresent];  // Use number of detected points rather than 0 to get it closer to actual rising edge
+            #endif
+            // Wait out reflections until next pulse
+            waitingForEnd = true;
+        }
+    }
+    else if(periodIndex > period) // done waiting for next bit (waiting out reflections)
+    {
+        waitingForEnd = false;
+        // Reset the sums and indicators
+        for(uint8_t t = 0; t < numTones; t++)
+        {
+            detectSums[t] = 0;
+            for(uint8_t p = 0; p < detectWindow; p++)
+                tonesPresent[p][t] = 0;
+        }
+    }
+    #elif defined(threshold1)
+    // Update threshold window state for each tone
+    for(uint8_t t = 0; t < numTones; t++)
+    {
+        // Update our history of powers for this tone
+        powerHistorySum[t] -= powerHistory[powerHistoryIndex][t];
+        powerHistory[powerHistoryIndex][t] = newTonePowers[t];
+        powerHistorySum[t] += newTonePowers[t];
+        // Compute max of the current window (only for frequencies we currently anticipate)
+        if((t == fskIndex*2 || t == fskIndex*2+1) && !waitingForEnd)
+        {
+            powerHistoryMax[t] = 0;
+            for(uint16_t p = 0; p < powerHistoryLength; p++)
+            {
+                if(powerHistory[p][t] > powerHistoryMax[t])
+                    powerHistoryMax[t] = powerHistory[p][t];
+            }
+        }
+    }
+    // Advance our power history index (circular buffer)
+    powerHistoryIndex = (powerHistoryIndex+1) % powerHistoryLength;
+    readyToThreshold = readyToThreshold || (powerHistoryIndex == 0);
+    // If not waiting until next pulse is expected, see if a tone is present
+    if(!waitingForEnd && readyToThreshold)
+    {
+        // Based on new max/mean, see how many powers indicate a tone present in the last detectWindow readings
+        for(uint8_t t = fskIndex*2; t <= fskIndex*2+1; t++)
+        {
+            detectSums[t] = 0;
+            for(uint16_t p = powerHistoryDetectIndex; p != powerHistoryIndex; p = (p+1) % powerHistoryLength)
+            {
+                if((powerHistory[p][t] > (powerHistorySum[fskIndex*2+((t+1)%2)] >> 2)) // power greater than 12.5 times mean of other channel
+                    && (powerHistory[p][t] > (powerHistoryMax[t] >> 2))                // power greater than 1/4 of the max on this channel
+                    && (powerHistory[p][t] > 1000)                                     // power greater than a fixed threshold
+                    && powerHistoryMax[t] > (powerHistorySum[t] >> 3))                 // max on this channel is 6.25 times greater than the mean on this channel (in case this channel noise is just must higher than other channel)
+                    detectSums[t]++;
+            }
+        }
+        // If both are considered present (should be very rare?), choose the one with a higher mean
+        if(detectSums[fskIndex*2] > detectThresh && detectSums[fskIndex*2+1] > detectThresh)
+        {
+            if(powerHistorySum[fskIndex*2] > powerHistorySum[fskIndex*2+1])
+                detectSums[fskIndex*2+1] = 0;
+            else
+                detectSums[fskIndex*2] = 0;
+        }
+        // See if a tone is present
+        int tonePresent = -1;
+        if(detectSums[fskIndex*2] > detectThresh)
+            tonePresent = fskIndex*2;
+        else if(detectSums[fskIndex*2+1] > detectThresh)
+            tonePresent = fskIndex*2+1;
+        // Record data and update state
+        if(tonePresent > -1)
+        {
+            #ifdef singleDataStream
+                #ifdef saveData
+                data[dataIndex++] = tonePresent%2;
+                #endif
+                #ifdef streamData
+                usbSerial->printf("%d", (bool)(tonePresent%2));
+                #endif
+                periodIndex = detectSums[tonePresent];
+                fskIndex = (fskIndex+1) % numFSKGroups;
+            #else
+            // See if it has been a long time since last pulse
+            if(periodIndex >= interWordWait)
+            {
+                // If we currently think that we're between words, then that's good so process the previous data word
+                // If we don't think we're between words, then we missed a bit so discard whatever we've collected in the word so far
+                // Either way, this is the start of a new word so reset dataBitIndex
+                if(interWord && dataBitIndex == dataWordLength)
+                {
+                    // Decode last word and then send it out
+                    #ifdef saveData
+                	decodeAcousticWord(data[dataWordIndex]);
+                    dataWordIndex++;
+                    #else
+                    decodeAcousticWord(dataWord);
+                    #endif
+                    dataBitIndex = 0;
+                    interWord = false;
+
+                    timeSinceGoodWord = 0;
+                }
+                else if(interWord)
+                {
+                    #ifdef streamData
+                	usbSerial->printf("0\t-1\n");
+                    #endif
+                    lastDataWord = 0;
+                }
+                // TODO this is a debug check - if transmitter not putting space between words, set interWordWait to 1
+                if(interWordWait > 1)
+                {
+                    dataBitIndex = 0;
+                    interWord = false;
+                }
+            }
+            else if(interWord)
+            {
+                // It has not been a long time since the last pulse, yet we thought it should be
+                // Seems like we erroneously detected a bit that shouldn't have existed
+                // Discard current word as garbage and start again
+                dataBitIndex = 0;
+                #ifdef streamData
+                usbSerial->printf("0\t-2\n");
+                #endif
+                lastDataWord = 0;
+            }
+            // If we're not between words (either normally or because we were just reset above), store the new bit
+            if(!interWord)
+            {
+                #ifdef saveData
+                data[dataWordIndex][dataBitIndex++] = tonePresent%2;
+                #else
+                dataWord[dataBitIndex++] = tonePresent%2;
+                #endif
+                fskIndex = (fskIndex+1) % numFSKGroups;
+                periodIndex = detectSums[tonePresent];  // Use number of detected points rather than 0 to get it closer to actual rising edge
+                // If we've finished a word, say we're waiting between words
+                // Word won't be processed until next word begins though in case we accidentally detected a nonexistent bit (see logic above)
+                if(dataBitIndex == dataWordLength)
+                {
+                    interWord = true;
+                    fskIndex = 0;
+                }
+            }
+            #endif
+            // Wait out reflections until next pulse
+            waitingForEnd = true;
+        }
+    }
+    else if(periodIndex > period) // done waiting for next bit (waiting out reflections)?
+    {
+        waitingForEnd = false;
+    }
+    // Advance our power history start detect index (circular buffer) to stay detectWindow elements behind main index
+    powerHistoryDetectIndex = (powerHistoryDetectIndex+1) % powerHistoryLength;
+    #endif // end switch between threshold1 or threshold2
+
+    // Update signal level history
+    signalLevelSum += signalLevel;
+    signalLevelBufferIndex = (signalLevelBufferIndex+1) % signalLevelBufferLength;
+    #ifdef streamSignalLevel
+    //usbSerial->printf("%ld\t%d\t%d\n", signalLevel, currentGainIndex, lastDataWord);
+    usbSerial->printf("%ld\t%d\n", signalLevel, currentGainIndex);
+    #endif
+	#ifdef streamAcousticControlLog
+    acousticControlLogToStream[3] = currentGainIndex;
+	#endif
+    // If have seen enough readings, choose a new gain
+    if(signalLevelBufferIndex == 0)
+    {
+        // Calculate ideal gain for signalLevelSum to become signalLevelSumDesired
+        int32_t newGain = ((int64_t)signalLevelSumDesired * (int64_t)agcGains[currentGainIndex]) / (int64_t)signalLevelSum;
+        // See which available gain is closest
+        uint8_t bestIndex = currentGainIndex;
+        int32_t minDiff = 10000;
+        for(uint8_t g = 0; g < agcGainsLength; g++)
+        {
+            int32_t diff = (int32_t)agcGains[g] - newGain;
+            if(diff > 0 && diff < minDiff)
+            {
+                minDiff = diff;
+                bestIndex = g;
+            }
+            else if(diff < 0 && -1*diff < minDiff)
+            {
+                minDiff = -1*diff;
+                bestIndex = g;
+            }
+        }
+        // Set the gain
+        currentGainIndex = bestIndex;
+        for(uint8_t i = 0; i < 3; i++)
+        {
+            #ifdef useAGC
+            agc[i]->write(currentGainIndex & (1 << i));
+            #endif
+            #ifdef AGCLeds
+            agcLEDs[i].write(currentGainIndex & (1 << i));
+            #endif
+        }
+        // Reset sum
+        signalLevelSum = 0;
+    }
+
+    #ifdef artificialPowers
+    usbSerial->printf("%ld \t%ld \t%ld \t%d \t%ld \t%ld \t%ld \t%ld \t%d \t%d \t%d \n", newTonePowers[0], newTonePowers[1], detectSums[0], detectSums[1], powerHistorySum[0], powerHistorySum[1], powerHistoryMax[0], powerHistoryMax[1], fskIndex, waitingForEnd, periodIndex);
+    #endif
+
+    // Check for timeout
+	#ifdef fishTimeoutAcoustic
+    if(timeSinceGoodWord >= fishTimeoutAcousticBuffers)
+    {
+        #ifndef singleDataStream
+        if(timeSinceGoodWord == fishTimeoutAcousticBuffers)
+        {
+            bool neutralWord[] = {0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0}; // Decodes to state 54, which is neutral state
+            decodeAcousticWord(neutralWord);
+            streamCurFishStateEventAcoustic = 4;
+        }
+        #endif
+        timeSinceGoodWord = fishTimeoutAcousticBuffers+1; // make sure we won't constantly send a neutral command (only timeout once)
+    }
+	#endif
+
+    // Stream data
+	#ifdef streamAcousticControlLog
+    //usbSerial->printf("%ld %ld %ld %ld %ld %ld\n", bufferCount-1, acousticControlLogToStream[0], acousticControlLogToStream[1], acousticControlLogToStream[2], acousticControlLogToStream[3], acousticControlLogToStream[4]);
+    //usbSerial->printf("%ld %ld %ld\n", acousticControlLogToStream[0], acousticControlLogToStream[1], acousticControlLogToStream[2]);
+    //usbSerial->printf("%ld %ld %ld %ld %ld\n", acousticControlLogToStream[0], acousticControlLogToStream[1], acousticControlLogToStream[2], acousticControlLogToStream[3], acousticControlLogToStream[4]);
+    //FunctionPointer fp = &tempMethod;
+    //acousticControlLogToStream[0] = 6100;
+    //acousticControlLogToStream[1] = 62;
+    //acousticControlLogToStream[2] = 63;
+    //acousticControlLogToStream[3] = 64;
+    //acousticControlLogToStream[4] = -10;
+    uint32_t streamGoertzel1 = acousticControlLogToStream[0];// >> 8; // second Fiji day included >> 8 to reduce bits
+    uint32_t streamGoertzel2 = acousticControlLogToStream[1];// >> 8; // second Fiji day included >> 8 to reduce bits
+    uint16_t streamSignalLevel = ((acousticControlLogToStream[2] >> 2) & ((uint16_t)2047)) + (((uint16_t)21) << 11); // first Fiji day was just acousticControlLogToStream[2], second day included code word
+    uint8_t streamGain = acousticControlLogToStream[3] + 168; // only used in first day
+    //int16_t streamWord = acousticControlLogToStream[4];
+    // Decide the event to transmit, giving priority to acoustic events over button board events
+    uint16_t streamFishStateEvent = fishController.streamFishStateEventController;
+    if(streamCurFishStateEventAcoustic > 0)
+    	streamFishStateEvent = streamCurFishStateEventAcoustic;
+    uint16_t streamWord = (uint16_t)streamCurFishStateAcoustic | ((uint16_t)streamFishStateEvent << 11); // same for both Fiji days
+
+    uint8_t* bufferOut = (uint8_t*) (&streamGoertzel1);
+    usbSerial->putc(bufferOut[0]);
+    usbSerial->putc(bufferOut[1]);
+    usbSerial->putc(bufferOut[2]);
+    usbSerial->putc(bufferOut[3]); // commented out for second day
+
+    bufferOut = (uint8_t*) (&streamGoertzel2);
+    usbSerial->putc(bufferOut[0]);
+    usbSerial->putc(bufferOut[1]);
+    usbSerial->putc(bufferOut[2]);
+    usbSerial->putc(bufferOut[3]); // commented out for second day
+
+	bufferOut = (uint8_t*) (&streamSignalLevel);
+	usbSerial->putc(bufferOut[0]);
+	usbSerial->putc(bufferOut[1]);
+
+	bufferOut = (uint8_t*) (&streamGain);
+	usbSerial->putc(bufferOut[0]); // commented out for second day
+
+    bufferOut = (uint8_t*) (&streamWord);
+    usbSerial->putc(bufferOut[0]);
+    usbSerial->putc(bufferOut[1]);
+    //usbSerial->printf("%d %d\n", streamCurFishState, streamWord);
+
+	/*uint8_t* bufferOut = (uint8_t*) acousticControlLogToStream;
+    for(uint8_t i = 0; i < 20; i++)
+    	usbSerial->putc(bufferOut[i]);*/
+
+    // Reset the events so we don't constantly print them
+    // TODO remove this so we do constantly print them, and only look for changes in the column in case some are dropped?
+    streamCurFishStateEventAcoustic = 0;
+    fishController.streamFishStateEventController = 0;
+	#endif
+}
+
+
+#ifndef singleDataStream
+// Use Hamming code to decode the data word and check for an error
+// TODO make constants/defines for magic numbers below that indicate length of hamming code
+void AcousticController::decodeAcousticWord(volatile bool* data)
+{
+	// Note that the below code is written such that "5" (number of parity bits) can be a variable / #define, but it's just not yet
+    // Check whether each parity bit is correct
+    bool paritySums[5] = {0};
+    for(uint8_t b = 0; b < dataWordLength-1; b++)
+    {
+        for(uint8_t p = 0; p < 5; p++)
+        {
+            if(((b+1) & (1 << p)) > 0 || p == 5-1) // check if bit belongs to parity set (overall parity at end includes everything)
+                paritySums[p] ^= data[b];
+        }
+    }
+    paritySums[5-1] ^= data[dataWordLength-1]; // overall parity bit
+    // See if we have an error to correct
+    uint8_t errorBit = 0;
+    uint8_t numParityErrors = 0;
+    for(uint8_t p = 0; p < 5-1; p++)
+    {
+        if(paritySums[p])
+        {
+            errorBit += (1 << p);
+            numParityErrors++;
+        }
+    }
+    // Flip the erroneous bit if applicable
+    if(numParityErrors > 1)
+        data[errorBit-1] = !data[errorBit-1];
+    // If using final parity bit, check that we don't detect an uncorrectable error
+    if(!(numParityErrors > 0 && !paritySums[5-1]))
+    {
+        uint16_t word = 0;
+        uint8_t i = 0;
+        uint8_t nextParity = 1;
+        for(uint8_t b = 0; b < dataWordLength; b++)
+        {
+            if(b == nextParity-1)
+                nextParity <<= 1;
+            else
+                word |=  data[b] << i++;
+        }
+        // Update the fish
+        processAcousticWord(word);
+        lastDataWord = word;
+        #ifdef streamData
+        if(timeSinceGoodWord >= fishTimeoutAcousticBuffers)
+        	usbSerial->printf("%ld\t%d\t-4\n", bufferCount, (int)word);
+        else
+        	usbSerial->printf("%ld\t%d\t1\n", bufferCount, (int)word);
+        #endif
+		#ifdef streamAcousticControlLog
+        if(timeSinceGoodWord >= fishTimeoutAcousticBuffers) // check if we're timed out, since we may have reached this method when the timeout checker called us to reset the fish
+        	acousticControlLogToStream[4] = -4;
+        else
+        	acousticControlLogToStream[4] = word;
+		#endif
+    }
+    else // there was an uncorrectable error
+    {
+        #ifdef streamData
+    	uint16_t word = 0;
+    	for(int i = 0; i < 16; i++)
+    		word += data[i] << i;
+    	usbSerial->printf("%ld\t%d\t-3\n", bufferCount, word);
+        #endif
+		#ifdef streamAcousticControlLog
+        acousticControlLogToStream[4] = -3;
+        streamCurFishStateEventAcoustic = 3;
+		#endif
+        lastDataWord = 0;
+    }
+}
+
+void AcousticController::processAcousticWord(uint16_t word)
+{
+    // Extract state from word
+    newSelectButtonIndex = getSelectIndexAcoustic(word);
+    newPitchIndex = getPitchIndexAcoustic(word);
+    newYawIndex = getYawIndexAcoustic(word);
+    newThrustIndex = getThrustIndexAcoustic(word);
+    newFrequencyIndex = getFrequencyIndexAcoustic(word);
+    // Log it
+    streamCurFishStateAcoustic = getCurStateWordAcoustic;
+    // Set the states
+	#ifdef acousticControllerControlFish
+    fishController.setSelectButton(newSelectButtonIndex > 0);
+    fishController.setPitch(pitchLookupAcoustic[newPitchIndex]);
+    fishController.setYaw(yawLookupAcoustic[newYawIndex]);
+    fishController.setThrust(thrustLookupAcoustic[newThrustIndex]);
+    fishController.setFrequency(frequencyLookupAcoustic[newFrequencyIndex], periodHalfLookupAcoustic[newFrequencyIndex]);
+	#endif
+}
+#endif
+
+// Stop the AcousticController
+// Will stop the tone detector and will stop the fishController
+// Note that the acoustic controller's run() method is blocking, so if you
+//  want to use this stop method you should launch run() in a separate thread
+void AcousticController::stop()
+{
+	// Stop the tone detector
+	// This will end the toneDetector's run() method at the next buffer
+	// which will cause our run() method to advance and finish up
+	toneDetector.stop();
+}
+
+// Main loop
+// Is blocking, so won't return until loopCount is reached or another thread calls stop()
+void AcousticController::run()
+{
+	// Create file for logging data words
+	#ifdef saveData
+	int fileNum = -1;
+	char filename[25];
+	foutDataWords = NULL;
+	do
+	{
+		fileNum++;
+		fclose(foutDataWords);
+		sprintf(filename, "/local/%d.txt", fileNum);
+		foutDataWords = fopen(filename, "r");
+		usbSerial->printf("%d\n", fileNum);
+	} while(foutDataWords != NULL);
+	foutDataWords = fopen(filename, "w");
+	#endif
+
+    #ifdef acousticControllerControlFish
+    // Start the fish controller
+    fishController.start();
+    #endif
+
+    #ifndef artificialPowers
+    // Start listening for tones
+    programTimer.start();
+    toneDetector.run();
+    programTimer.stop();
+    toneDetector.finish(); // we won't include the time this takes to write files in the elapsed time
+    #else
+    // Read powers from file
+    usbSerial->printf("newPower[0] \tnewPower[1] \tdetectSums[0] \tdetectSums[1] \tsum[0] \tsum[1] \tmax[0] \tmax[1] \tfskIndex \twaiting \tperiodIndex \n");
+    int32_t maxSignalValTemp = 0;
+    int res = fscanf(finPowers, "%ld\t%ld\n", &nextPowers[0], &nextPowers[1]);
+    while(res > 0)
+    {
+        processTonePowers(nextPowers, maxSignalValTemp);
+        res = fscanf(finPowers, "%ld\t%ld\n", &nextPowers[0], &nextPowers[1]);
+    }
+    fclose(finPowers);
+    #endif
+
+    #ifdef acousticControllerControlFish
+    // Stop the fish controller
+    fishController.stop();
+    // If battery died, wait a bit for pi to clean up and shutdown and whatnot
+    if(lowBatteryVoltageInput == 0)
+    {
+		wait(90); // Give the Pi time to shutdown
+		fishController.setLEDs(255, false);
+    }
+    #endif
+
+    // Print results
+	#ifdef printBufferSummary
+    int elapsed = programTimer.read_us();
+    usbSerial->printf("\n");
+    usbSerial->printf("Buffers processed: %ld\n", bufferCount);
+    usbSerial->printf("Elapsed time  : %d us\n", elapsed);
+    usbSerial->printf("Per-sample time : %f us\n", (double)elapsed/(double)bufferCount/(double)sampleWindow);
+    usbSerial->printf("  Sample frequency: %f kHz\n", (double)bufferCount*(double)sampleWindow/(double)elapsed*1000.0);
+    usbSerial->printf("Per-buffer time : %f us\n", (double)elapsed/(double)bufferCount);
+    usbSerial->printf("  Buffer-processing frequency: %f kHz\n", (double)bufferCount/(double)elapsed*1000.0);
+
+    usbSerial->printf("\nComputed powers from last buffer: \n");
+    int32_t* lastTonePowers = toneDetector.getTonePowers();
+    for(int i = 0; i < numTones; i++)
+    	usbSerial->printf("  Tone %d: %f Hz -> %f\n", i, targetTones[i], toFloat(lastTonePowers[i]));
+	#endif
+    #if defined(singleDataStream) && defined(saveData)
+    usbSerial->printf("\nData received (%d bits):\n", dataIndex);
+    fprintf(foutDataWords, "\nData received (%d bits):\n", dataIndex);
+    long errors = 0;
+    for(int d = 5; d < dataIndex; d++)
+    {
+    	usbSerial->printf("%d", data[d]);
+        fprintf(foutDataWords, "%d", data[d]);
+        if(d > 0 && data[d] == data[d-1])
+            errors++;
+    }
+    usbSerial->printf("\n");
+    usbSerial->printf("errors: %ld\n", errors);
+    fprintf(foutDataWords, "\n");
+	fprintf(foutDataWords, "errors: %ld\n", errors);
+	fclose(foutDataWords);
+    #ifdef debugLEDs
+    if(errors > 0)
+        led1 = 1;
+    #endif
+    #elif defined(saveData)
+    usbSerial->printf("\nData received (%d words):\n", dataWordIndex);
+    fprintf(foutDataWords, "\nData received (%d words):\n", dataWordIndex);
+    long errors = 0;
+    long badWords = 0;
+    for(int w = 0; w < dataWordIndex; w++)
+    {
+        errors = 0;
+        usbSerial->printf("  ");
+        fprintf(foutDataWords, "  ");
+        for(int b = 0; b < dataWordLength; b++)
+        {
+        	usbSerial->printf("%d", data[w][b]);
+            fprintf(foutDataWords, "%d", data[w][b]);
+            if(b > 0 && data[w][b-1] == data[w][b])
+                errors++;
+        }
+        if(errors > 0)
+        {
+        	usbSerial->printf(" X");
+            fprintf(foutDataWords, " X");
+            badWords++;
+        }
+        usbSerial->printf("\n");
+        fprintf(foutDataWords, "\n");
+    }
+    usbSerial->printf("\nbad words: %d\n", badWords);
+    fprintf(foutDataWords, "\nbad words: %d\n", badWords);
+    fclose(foutDataWords);
+    #ifdef debugLEDs
+    if(badWords > 0)
+        led1 = 1;
+    #endif
+    #endif
+
+    // TODO remove these waits?
+    wait(1);
+	#ifdef printBufferSummary
+    usbSerial->printf("\nAcousticController Done!");
+	#endif
+    wait(3);
+	#ifdef printBufferSummary
+    usbSerial->printf("\n");
+	#endif
+}
+
+
+void AcousticController::lowBatteryCallback()
+{
+    // Stop the tone detector
+    // This will end the main call to start, causing main to terminate
+    // Main will also stop the fish controller once this method ends
+    toneDetector.stop();
+    // Also force the pin low to signal the Pi
+    // (should have already been done, but just in case)
+    // TODO check that this really forces it low after this method ends and the pin object may be deleted
+    DigitalOut simBatteryLow(lowBatteryVoltagePin);
+    simBatteryLow = 0;
+}
+
+#endif // #ifdef acousticControl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/AcousticControl/AcousticController.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,256 @@
+/*
+ * AcousticController.h
+ * Author: Joseph DelPreto
+ */
+
+//#define acousticControl
+
+#ifdef acousticControl
+
+#ifndef ACOUSTICCONTROL_ACOUSTICCONTROLLER_H_
+#define ACOUSTICCONTROL_ACOUSTICCONTROLLER_H_
+
+#include "ToneDetector.h"
+#include "FishController.h"
+#include "mbed.h"
+
+// ===== INPUT/OUTPUT =====
+// NOTE: To be silent (to not use usbSerial object at all), undefine printBufferSummary, streamAcousticControlLog, streamData, and streamSignalLevel
+//       To stream the log info used in Fiji, just define streamAcousticControlLog - these are not human-readable streams
+//       To view the data as it's being received/decoded, define streamData (or streamSignalLevel for signal level) - these are human-readable streams
+//       To view a data summary (ex. number of buffers processed, words received, etc.) define printBufferSummary
+#define serialDefaultBaudUSB 921600 // IMPORTANT: must be 921600 when using streaAcousticControlLog (to be fast enough to keep up with data stream)
+#define printBufferSummary    // print a summary of how many buffers were processed, the last computed tone powers, etc. once the controller is stopped
+/* SEE ALSO: streamAcousticControlLog, defined in ToneDetector.h
+   If defined, will update the following (see end of processTonePowers for how they're packaged):
+
+ acousticControlLogToStream:
+ [0]: goertzel powers f1
+ [1]: goerztel powers f2
+ [2]: signal level
+ [3]: AGC gain
+ [4]: fish state/events over Serial
+        -1: erroneously missed a bit
+		-2: erroneously added a bit
+		-3: detected an uncorrectable error in the Hamming decoding
+		-4: fish timeout (reset to neutral)
+		>0: the data word that was successfully decoded and processed
+		-10: no event
+
+UPDATE:
+
+  acousticControlLogToStream[4] has been deprecated and replaced with streamCurFishStateEventAcoustic and streamCurFishStateAcoustic
+  streamCurFishStateEventAcoustic can have the following values:
+	  1: erroneously missed a bit
+	  2: erroneously added a bit
+	  3: detected an uncorrectable error in the Hamming decoding
+	  4: fish timeout (reset to neutral)
+	  5: successfully received a word (note streamCurFishState indicates the current fish state)
+	  6: button board yaw left
+	  7: button board yaw right
+	  8: button board faster
+	  9: button board slower
+	  10: button board pitch up
+	  11: button board pitch down
+	  12: button board shutdown pi
+	  13: button board reset mbed
+	  14: button board auto mode
+	  15: button board unknown
+*/
+
+//#define artificialPowers // will use tone powers saved in a file rather than actually sampling (expects two tab-separated columns of powers in /local/powers.wp)
+//#define singleDataStream // just read a single continuous stream of bits (as opposed to segmenting into words/decoding)
+
+//#define saveData   // save data such as received words to an array, then save it to a file at the end - faster than printing continuously but worry about overflow
+//#define streamData // stream data to the screen as it's received (controlled independently of saveData)
+//#define streamSignalLevel // stream current signal level to the screen
+
+// ===== EXECUTION =====
+#define infiniteLoopAcoustic  // if undefined, will stop after loopCount buffers are processed
+#define loopCount 20000       // number of buffers to process before terminating the program (if infiniteLoopAcoustic is not defined)
+#define fishTimeoutAcoustic   // whether to reset the fish to neutral if an acoustic word hasn't been received in a while.  Not used with singleDataStream.
+#define fishTimeoutAcousticWindow 5000000 // time between good words that cause a timeout (in microseconds)
+
+#define acousticControllerControlFish // whether to start fishController to control the servos and motor
+
+#define useAGC        // if undefined, will only set agc (adjustable gain control of the acoustic input board) once at startup
+//#define AGCLeds     // if defined, will light the mBed LEDs to indicate the chosen agc gain index (whether or not useAGC is on)
+#define agcUpdateWindow 10000000 // how often to update the AGC gain (in microseconds)
+
+#define dataRate 20 // data rate in bps.  Can be 20, 50, 77, or 100.  20 was used in Fiji. Above 50 may be less reliable.
+
+//#define threshold1 // older algorithm for processing Goertzel buffers (not sure if it still works)
+#define threshold2
+
+// ===== PINS =====
+#define agcPin0 p16
+#define agcPin1 p17
+#define agcPin2 p18
+// note lowBatteryVoltagePin is defined in FishController
+
+// ===== Sampling / Thresholding configuration =====
+// 20 bps (5/45 ms tone/silence)
+#if dataRate == 20
+#define detectWindow 10    // number of buffers to look at when deciding if a tone is present
+#define detectThresh 6     // number of 1s that must be present in a detectWindow group of buffer outputs to declare a tone present
+#define period 80          // the number of buffers from a rising edge to when we start looking for the next rising edge
+#define bitPeriod 100      // only used with saveData: how many buffers it should actually be between rising edges (only used to size the data array)
+#define interWordWait 300  // how many buffers of silence to expect between words
+#elif dataRate == 50
+// 50 bps (5/15 ms tone/silence)
+#define detectWindow 10    // number of buffers to look at when deciding if a tone is present
+#define detectThresh 6     // number of 1s that must be present in a detectWindow group of buffer outputs to declare a tone present
+#define period 20          // the number of buffers from a rising edge to when we start looking for the next rising edge
+#define bitPeriod 40       // only used with saveData: how many buffers it should actually be between rising edges (only used to size the data array)
+#define interWordWait 300  // how many buffers of silence to expect between words
+#elif dataRate == 77
+// 77 bps (5/8 ms tone/silence)
+#define detectWindow 10    // number of buffers to look at when deciding if a tone is present
+#define detectThresh 6     // number of 1s that must be present in a detectWindow group of buffer outputs to declare a tone present
+#define period 10          // the number of buffers from a rising edge to when we start looking for the next rising edge
+#define bitPeriod 26       // only used with saveData: how many buffers it should actually be between rising edges (only used to size the data array)
+#define interWordWait 300  // how many buffers of silence to expect between words
+#elif dataRate == 100
+// 100 bps (5/5 ms tone/silence)
+#define detectWindow 10    // number of buffers to look at when deciding if a tone is present
+#define detectThresh 6     // number of 1s that must be present in a detectWindow group of buffer outputs to declare a tone present
+#define period 6           // the number of buffers from a rising edge to when we start looking for the next rising edge
+#define bitPeriod 20       // only used with saveData: how many buffers it should actually be between rising edges (only used to size the data array)
+#define interWordWait 300  // how many buffers of silence to expect between words
+#endif
+
+// ===== Macros for extracting state from data words =====
+#define getSelectIndexAcoustic(d)     ((d & (1 << 0)) >> 0)
+#define getPitchIndexAcoustic(d)      ((d & (7 << 1)) >> 1)
+#define getYawIndexAcoustic(d)        ((d & (7 << 4)) >> 4)
+#define getThrustIndexAcoustic(d)     ((d & (3 << 7)) >> 7)
+#define getFrequencyIndexAcoustic(d)  ((d & (3 << 9)) >> 9)
+
+#define getCurStateWordAcoustic (uint16_t)((uint16_t)newSelectButtonIndex + ((uint16_t)newPitchIndex << 1) + ((uint16_t)newYawIndex << 4) + ((uint16_t)newThrustIndex << 7) + ((uint16_t)newFrequencyIndex << 9));
+
+class AcousticController
+{
+	public:
+		// Initialization
+		AcousticController(Serial* usbSerialObject = NULL); // if serial object is null, one will be created
+		void init(Serial* usbSerialObject = NULL); // if serial object is null, one will be created
+		// Execution control
+		void run();
+		void stop();
+		// Called by toneDetector when new tone powers are computed
+		// Only public since we needed a dummy non-member function to call it from the static instance to pass a pointer to tonedetector (see comments on toneDetectorCallback)
+		void processTonePowers(int32_t* newTonePowers, uint32_t signalLevel);
+	private:
+		// Control
+		volatile uint32_t bufferCount;
+		Timer programTimer;
+		Serial* usbSerial;
+
+		#ifndef singleDataStream
+		void decodeAcousticWord(volatile bool* data);  // Use Hamming code to decode the data word and check for an error
+		void processAcousticWord(uint16_t word);       // Parse the decoded word into the various fish states
+		#endif
+
+		// TODO check what actually needs to be volatile
+
+		// Data detection
+		volatile bool waitingForEnd;     // got data, now waiting until next bit transmission is expected (waiting until "period" buffers have elapsed since edge)
+		volatile uint16_t periodIndex;   // how many buffers it's been since the last clock edge
+		volatile uint8_t fskIndex;       // which set of 0/1 frequencies are next expected
+
+		#if defined(threshold2)
+		#define powerHistoryLength 50       // number of buffer results to consider when deciding if a tone is present.  should be half of a bit-width
+		#define powerHistoryDetectWindow 20 // portion of powerHistory to consider as the detection zone (where a peak is expected)
+		volatile int32_t powerHistory[powerHistoryLength][numTones];  // History of Goertzel results for the last powerHistoryLength buffers
+		volatile int16_t powerHistoryIndex;                   // Index into powerHistory (circular buffer)
+		volatile int16_t powerHistoryDetectIndex;             // Marks where in powerHistory to start window for detection; will always be detectWindow elements behind powerHistoryIndex in the circular buffer
+		volatile int32_t powerHistorySumDetect[numTones];     // Sum of powers stored in powerHistory in the detect window (stand-in for mean)
+		volatile int32_t powerHistorySumNoDetect[numTones];   // Sum of powers stored in powerHistory outside the detect window (stand-in for mean)
+		volatile int32_t powerHistoryMaxDetect[numTones];     // Max of powers stored in powerHistory in the detect window
+		volatile int32_t powerHistoryMaxNoDetect[numTones];   // Max of powers stored in powerHistory outside the detect window
+		volatile bool readyToThreshold;                       // Whether powerHistory has been filled at least once
+		volatile bool tonesPresent[detectWindow][numTones];   // Circular buffer of whether tones were detected in last detectWindow powers
+		volatile uint8_t detectSums[numTones];                // Rolling sum over last detectWindow tonesPresent results (when > detectWindow/2, declares data bit)
+		volatile uint8_t detectWindowIndex;
+		#elif defined(threshold1)
+		#define powerHistoryLength 50                       // number of results to consider when deciding if a tone is present.  should be half of a bit-width
+		volatile uint8_t detectSums[numTones];              // Rolling sum over last detectWindow buffer results (when > detectWindow/2, declares clock/data)
+		int32_t powerHistory[powerHistoryLength][numTones]; // History of Goertzel results for the last powerHistoryLength buffers
+		volatile int16_t powerHistoryIndex;                 // Index into powerHistory (circular buffer)
+		volatile int16_t powerHistoryDetectIndex;           // Marks where in powerHistory to start window for detection; will always be detectWindow elements behind powerHistoryIndex in the circular buffer
+		int32_t powerHistorySum[numTones];                  // Sum of powers stored in powerHistory (stand-in for mean)
+		int32_t powerHistoryMax[numTones];                  // Max of powers stored in powerHistory
+		volatile bool readyToThreshold;                     // Whether powerHistory has been filled at least once
+		#endif
+
+		// Segmenting of data into bits/words
+		#if defined(singleDataStream) && defined(saveData)
+			volatile bool data[loopCount/(bitPeriod) * (numTones-1) + 50]; // Received data (each clock pulse is associated with numTones-1 bits) // TODO update for multi-hop FSK, but this is fine for one FSK group
+			volatile uint16_t dataIndex;
+		#else
+			#define dataWordLength 16          // bits per word.  note that changing this also requires adjusting the decodeAcousticWord algorithm, since it currently assumes Hamming encoding
+			volatile uint8_t dataBitIndex;     // current bit of that word
+			volatile bool interWord;           // whether we are currently in between words (waiting out the long inter-word pause)
+			#ifdef saveData
+			volatile bool data[loopCount/(bitPeriod*dataWordLength)][dataWordLength]; // Each element is a word as an array of bits
+			volatile uint16_t dataWordIndex;   // current data word
+			#else
+			volatile bool dataWord[dataWordLength]; // an array of bits making up the current word
+			#endif
+		#endif
+		volatile uint16_t lastDataWord; // not used anymore, but still updated
+
+		// Adjustable gain control (AGC)
+		volatile uint8_t currentGainIndex; 		  // the current index into agcGains
+		volatile int32_t signalLevelSum; 		  // the running sum of signal level readings
+		volatile uint16_t signalLevelBufferIndex; // index into signal level buffer // note we don't have a buffer anymore, but use this to know when to reset the sum
+
+		DigitalOut* gain0;
+		DigitalOut* gain1;
+		DigitalOut* gain2;
+		DigitalOut* agc[3]; // facilitates loops and whatnot
+		#ifdef AGCLeds
+			#undef debugLEDs
+			DigitalOut* agcLED0;
+			DigitalOut* agcLED1;
+			DigitalOut* agcLED2;
+			DigitalOut* agcLEDs[3];
+		#endif
+
+		// Fish reset timeout
+		volatile uint16_t timeSinceGoodWord; // measured in buffers
+
+		// Low battery monitor
+		DigitalIn* lowBatteryVoltageInput;
+		void lowBatteryCallback();
+
+		// Testing/debugging
+		#ifdef saveData
+		LocalFileSystem local("local");
+		FILE* foutDataWords;
+		#endif
+		#ifdef artificialPowers
+		    #if (!defined recordStreaming || (!defined recordOutput && !defined recordSamples)) && !defined saveData
+		    LocalFileSystem local("local");
+		    #endif
+		    FILE* finPowers;
+		    int32_t nextPowers[numTones];
+		#endif
+		volatile uint16_t streamCurFishStateAcoustic; // the current state of the fish
+		volatile uint16_t streamCurFishStateEventAcoustic; // events such as decoding words, errors, etc.
+
+		// Fish state extracted from acoustic data word (index into lookup tables defined above)
+		volatile uint8_t newSelectButtonIndex;
+		volatile uint8_t newPitchIndex;
+		volatile uint8_t newYawIndex;
+		volatile uint8_t newThrustIndex;
+		volatile uint8_t newFrequencyIndex;
+};
+
+
+// Create a static instance of AcousticController to be used by anyone doing detection
+extern AcousticController acousticController;
+
+#endif
+
+#endif // #ifdef acousticControl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/AcousticControl/ToneDetector.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,626 @@
+/*
+ * Author: Joseph DelPreto
+ */
+
+#include "ToneDetector.h"
+
+#ifdef acousticControl
+
+// The static instance
+ToneDetector toneDetector;
+#ifdef streamAcousticControlLog
+int32_t acousticControlLogToStream[5] = {0,0,0,0,0};
+#endif
+
+//============================================
+// Initialization
+//============================================
+
+// Constructor
+ToneDetector::ToneDetector() :
+    terminated(0),
+    fillingBuffer0(false),
+    transferComplete(false),
+    readyToBegin(false),
+    callbackFunction(0)
+    #ifdef artificialSamplesMode
+    , numTestSamples(0),
+    testSampleIndex(0),
+    sampleIndex(0)
+    #endif
+{
+}
+
+// Set a callback function that should be called after each buffer is processed
+void ToneDetector::setCallback(void (*myFunction)(int32_t* tonePowers, uint32_t signalLevel))
+{
+    callbackFunction = myFunction;
+}
+
+// Initialize the Goertzel variables, arrays, etc.
+// sampleWindow, sampleInterval, and tones must have been previously set
+//   if not, this method will return without doing anything
+void ToneDetector::init()
+{
+    // Make sure sampleWindow, sampleInterval, and desired tones have been set
+    if(sampleWindow == 0 || sampleInterval == 0 || numTones == 0)
+        return;
+
+    // Initialize sample buffers
+    //printf("Sampling interval: %d us\n", sampleInterval);
+    //printf("Buffer size: %d samples\n", sampleWindow);
+    for(uint16_t i = 0; i < sampleWindow; i++) // not using memset since sizeof seems to not work with uint16_t?
+    {
+        sampleBuffer0[i] = 0;
+        sampleBuffer1[i] = 0;
+    }
+    #if defined(recordSamples) && !defined(recordStreaming)
+    savedSamplesIndex = 0;
+    for(uint16_t i = 0; i < numSavedSamples; i++) // not using memset since sizeof seems to not work with uint16_t?
+        savedSamples[i] = 0;
+    #endif
+
+    // Initialize goertzel arrays
+    tonePowersWindowIndex = 0;
+    for(int i = 0; i < numTones; i++)
+    {
+        goertzelCoefficients[i] = 0;
+        for(int w = 0; w < tonePowersWindow; w++)
+            tonePowers[i][w] = 0;
+        tonePowersSum[i] = 0;
+    }
+    readyToThreshold = false;
+
+    // Some convenience variables as doubles for precision (will cast to fixed point later)
+    double sampleFrequency = 1000.0/((double)sampleInterval/1000.0); // Hz
+    double N = (double) sampleWindow;
+
+    // Calculate the coefficient for each tone
+    //printf("Initializing Goertzel algorithm\n");
+    //printf("  Bin size: %f\n", sampleFrequency/N);
+    for(int i = 0; i < numTones; i++)
+    {
+        // Determine K and then f for desired tone (f = desiredF/Fs)
+        // tone/fs = f = K/N --> K = (int)(tone*N/fs)
+        double tone = targetTones[i];
+        int k = (int) (0.5 + ((N * tone) / sampleFrequency));
+        float f = ((float)k) / N;
+        //printf("  Desired tone %f -> %f", tone, f*sampleFrequency);
+        float cosine = cos(2.0 * PI * f);
+        // Store the result as a fixed-point number
+        goertzelCoefficients[i] = toFixedPoint(2.0*cosine);
+        //printf("\t  (coefficient: %f)\n", toFloat(goertzelCoefficients[i]));
+    }
+
+    #if defined(recordOutput) && !defined(recordStreaming)
+    savedTonePowersIndex = 0;
+    for(uint16_t i = 0; i < numSavedTonePowers; i++)    // not using memset since sizeof seems to not work with uint16_t?
+    {
+        for(uint8_t t = 0; t < numTones; t++)
+            savedTonePowers[i][t] = 0;
+    }
+    #endif
+
+    // We're ready to process some tunes!
+    readyToBegin = true;
+}
+
+#ifndef artificialSamplesMode
+// Configure and start the DMA channels for ADC sample gathering
+// Will have a linked list of two DMA operations, one for each buffer
+void ToneDetector::startDMA()
+{
+    // Create the Linked List Items
+    lli[0] = new MODDMA_LLI;
+    lli[1] = new MODDMA_LLI;
+
+    // Prepare DMA configuration, which will be chained (one operation for each buffer)
+    dmaConf = new MODDMA_Config;
+    dmaConf
+     ->channelNum    ( MODDMA::Channel_0 )
+     ->srcMemAddr    ( 0 )
+     ->dstMemAddr    ( (uint32_t)sampleBuffer0 )
+     ->transferSize  ( sampleWindow )
+     ->transferType  ( MODDMA::p2m )
+     ->transferWidth ( MODDMA::word )
+     ->srcConn       ( MODDMA::ADC )
+     ->dstConn       ( 0 )
+     ->dmaLLI        ( (uint32_t)lli[1] ) // Looks like it does the above setup and then calls this LLI - thus we have this setup mimic lli[0] and then the chain actually starts by calling lli[1]
+     ->attach_tc     ( &TC0_callback )
+     ->attach_err    ( &ERR0_callback )
+    ;
+    // Create LLI to transfer from ADC to adcBuffer0 (and then launch lli[1])
+    lli[0]->SrcAddr = (uint32_t)dma.LUTPerAddr(dmaConf->srcConn());
+    lli[0]->DstAddr = (uint32_t)sampleBuffer0;
+    lli[0]->NextLLI = (uint32_t) lli[1];
+    lli[0]->Control = dma.CxControl_TransferSize(dmaConf->transferSize())
+                | dma.CxControl_SBSize((uint32_t)dma.LUTPerBurst(dmaConf->srcConn()))
+                | dma.CxControl_DBSize((uint32_t)dma.LUTPerBurst(dmaConf->srcConn()))
+                | dma.CxControl_SWidth((uint32_t)dma.LUTPerWid(dmaConf->srcConn()))
+                | dma.CxControl_DWidth((uint32_t)dma.LUTPerWid(dmaConf->srcConn()))
+                | dma.CxControl_DI()
+                | dma.CxControl_I();
+    // Create LLI to transfer from ADC to adcBuffer1 (and then launch lli[0] to repeat)
+    lli[1]->SrcAddr = (uint32_t)dma.LUTPerAddr(dmaConf->srcConn());
+    lli[1]->DstAddr = (uint32_t)sampleBuffer1;
+    lli[1]->NextLLI = (uint32_t) lli[0];
+    lli[1]->Control = dma.CxControl_TransferSize(dmaConf->transferSize())
+                | dma.CxControl_SBSize((uint32_t)dma.LUTPerBurst(dmaConf->srcConn()))
+                | dma.CxControl_DBSize((uint32_t)dma.LUTPerBurst(dmaConf->srcConn()))
+                | dma.CxControl_SWidth((uint32_t)dma.LUTPerWid(dmaConf->srcConn()))
+                | dma.CxControl_DWidth((uint32_t)dma.LUTPerWid(dmaConf->srcConn()))
+                | dma.CxControl_DI()
+                | dma.CxControl_I();
+
+    // Start the DMA chain
+    fillingBuffer0 = true;
+    transferComplete = false;
+    if (!dma.Prepare(dmaConf)) {
+        error("Doh! Error preparing initial dma configuration");
+    }
+}
+
+// Configure the ADC to trigger on Timer1
+// Start the timer with the desired sampling interval
+void ToneDetector::startADC()
+{
+    // We use the ADC irq to trigger DMA and the manual says
+    // that in this case the NVIC for ADC must be disabled.
+    NVIC_DisableIRQ(ADC_IRQn);
+
+    // Power up the ADC and set PCLK
+    LPC_SC->PCONP    |=  (1UL << 12); // enable power
+    LPC_SC->PCLKSEL0 &= ~(3UL << 24); // Clear divider to use CCLK/8 = 12MHz directly (see page 57) // original example code comment: PCLK = CCLK/4 96M/4 = 24MHz
+
+    // Enable the ADC, 12MHz,  ADC0.0
+    LPC_ADC->ADCR  = (1UL << 21);
+    LPC_ADC->ADCR &= ~(255 << 8); // No clock divider (use the 12MHz directly)
+
+    // Set the pin functions to ADC (use pin p15)
+    LPC_PINCON->PINSEL1 &= ~(3UL << 14);  /* P0.23, Mbed p15. */
+    LPC_PINCON->PINSEL1 |=  (1UL << 14);
+
+    // Enable ADC irq flag (to DMA).
+    // Note, don't set the individual flags,
+    // just set the global flag.
+    LPC_ADC->ADINTEN = 0x100;
+
+    // (see page 586 of http://www.nxp.com/documents/user_manual/UM10360.pdf)
+    // Disable burst mode
+    LPC_ADC->ADCR &= ~(1 << 16);
+    // Have the ADC convert based on timer 1
+    LPC_ADC->ADCR |= (6 << 24); // Trigger on MAT1.0
+    LPC_ADC->ADCR |= (1 << 27); // Falling edge
+
+    // Set up timer 1
+    LPC_SC->PCONP    |= (1UL << 2);          // Power on Timer1
+    LPC_SC->PCLKSEL0 &= ~(3 << 4);           // No clock divider (use 12MHz directly) (see page 57 of datasheet)
+    LPC_TIM1->PR      = 11;                  // TC clocks at 1MHz since we selected 12MHz above (see page 507 of datasheet)
+    LPC_TIM1->MR0     = sampleInterval-1;    // sampling interval in us
+    LPC_TIM1->MCR     = 3;                   // Reset TCR to zero on match
+    LPC_TIM1->EMR     = (3UL<<4)|1;          // Make MAT1.0 toggle.
+    //NVIC_EnableIRQ(TIMER1_IRQn);           // Enable timer1 interrupt NOTE: enabling the interrupt when MCR is 3 will make everything stop working.  enabling the interrupt when MCR is 2 will work but the interrupt isn't actually called.
+
+    // Start the timer (which thus starts the ADC)
+    LPC_TIM1->TCR=0;
+    LPC_TIM1->TCR=1;
+}
+#endif // end if(not artificial samples mode)
+
+//============================================
+// Execution Control
+//============================================
+
+// Start acquiring and processing samples
+// Will run forever or until stop() is called
+void ToneDetector::run()
+{
+    if(!readyToBegin)
+        return;
+    terminated = false;
+    //printf("\nTone detector starting...\n");
+    #ifdef recordStreaming
+        #ifdef recordSamples
+        printf("\tSample (V)");
+        printf("\n");
+        #endif
+        #ifdef recordOutput
+        for(uint8_t t = 0; t < numTones; t++)
+            printf("\t%f Hz", targetTones[t]);
+        printf("\n");
+        #endif
+    #endif
+
+    // Set up initial buffer configuration
+    samplesWriting = sampleBuffer0;
+    samplesProcessing = sampleBuffer1;
+    fillingBuffer0 = true;
+    transferComplete = false;
+    // Start periodically sampling
+    #ifdef artificialSamplesMode
+    initTestModeSamples(); // artificially create samples
+    sampleTicker.attach_us(&toneDetector, &ToneDetector::tickerCallback, sampleInterval);  // "sample" artificial samples at desired rate
+    #else
+    startDMA();
+    if(!terminated) // If DMA got an error, terminated will be set true
+        startADC();
+    #endif
+
+    #ifdef debugLEDs
+    led1 = 1; // Indicate start of tone detection
+    #endif
+    #ifdef debugPins // after LEDs to be more accurate
+    debugPin1 = 1;   // Indicate start of tone detection
+    #endif
+
+    // Main loop
+    // Wait for buffers to fill and then process them
+    while(!terminated)
+    {
+        // Check if a buffer of samples is gathered and if so process it
+        if(transferComplete)
+        {
+            transferComplete = false;
+            processSamples();
+        }
+    }
+
+    #ifdef debugPins // before LEDs to be more accurate
+    debugPin1 = 0; // Indicate cessation of tone detection
+    debugPin2 = 0; // Turn off indicator that at least two buffers were processed
+    debugPin4 = 1; // Indicate completion
+    #endif
+    #ifdef debugLEDs
+    led1 = 0; // Indicate cessation of tone detection
+    led2 = 0; // Turn off indicator that at least one buffer was processed
+    led4 = 1; // Indicate completion
+    #endif
+}
+
+// Finish up (write results to file and whatnot)
+// This is separate method so that main program can time the running itself without this extra overhead
+void ToneDetector::finish()
+{
+    //printf("Tone detector finished\n");
+
+    #if defined(recordSamples) && !defined(recordStreaming)
+        // Write saved samples to file
+        LocalFileSystem local("local");
+        FILE *foutSamples = fopen("/local/samples.wp", "w");  // Open "samples.wp" on the local file system for writing
+        fprintf(foutSamples, "Sample (V)\n");
+        uint16_t savedSamplesN = savedSamplesIndex;
+        do
+        {
+            fprintf(foutSamples, "%f\n", toFloat(savedSamples[savedSamplesN])/4.0*3.3);
+            savedSamplesN++;
+            savedSamplesN %= numSavedSamples;
+        } while(savedSamplesN != savedSamplesIndex);
+        fclose(foutSamples);
+    #endif // recordSamples && !recordStreaming
+    #if defined(recordOutput) && !defined(recordStreaming)
+        // Write saved outputs to file
+        #ifndef recordSamples
+        LocalFileSystem local("local");
+        #endif // not recordSamples
+        FILE *foutOutput = fopen("/local/out.wp", "w");  // Open "out.wp" on the local file system for writing
+        for(uint8_t t = 0; t < numTones; t++)
+            fprintf(foutOutput, "%f Hz\t", tones[t]);
+        uint16_t savedTonePowersN = savedTonePowersIndex;
+        do
+        {
+            for(uint8_t t = 0; t < numTones; t++)
+                fprintf(foutOutput, "%ld  \t", savedTonePowers[savedTonePowersN][t]);
+            fprintf(foutOutput, "\n");
+            savedTonePowersN++;
+            savedTonePowersN %= numSavedTonePowers;
+        } while(savedTonePowersN != savedTonePowersIndex);
+        fclose(foutOutput);
+    #endif // recordOutput
+}
+
+// Terminate the tone detector
+// Note: Will actually terminate after next time buffer1 is filled, so won't be instantaneous
+void ToneDetector::stop()
+{
+    // Stop sampling
+    #ifdef artificialSamplesMode
+    sampleTicker.detach();
+    #else
+    lli[1]->Control = 0;                        // Make the DMA stop after next time buffer1 is filled
+    while(!(!fillingBuffer0 && transferComplete)); // Wait for buffer1 to be filled
+    LPC_TIM1->TCR=0;              // Stop the timer (and thus the ADC)
+    LPC_SC->PCONP &= ~(1UL << 2); // Power off the timer
+    #endif
+
+    // Stop the main loop
+    terminated = true;
+}
+
+//============================================
+// Sampling / Processing
+//============================================
+
+// Acquire a new sample
+#ifdef artificialSamplesMode
+// If a buffer has been filled, swap buffers and signal the main thread to process it
+void ToneDetector::tickerCallback()
+{
+    // Get a sample
+    samplesWriting[sampleIndex] = testSamples[testSampleIndex];
+    testSampleIndex++;
+    testSampleIndex %= numTestSamples;
+
+    // Increment sample index
+    sampleIndex++;
+    sampleIndex %= sampleWindow;
+
+    // See if we just finished a buffer
+    if(sampleIndex == 0)
+    {
+        // Swap writing and processing buffers
+        // Let the main tone detector thread know that processing should take place
+        if(fillingBuffer0)
+        {
+            samplesProcessing = sampleBuffer0;
+            samplesWriting = sampleBuffer1;
+        }
+        else
+        {
+            samplesProcessing = sampleBuffer1;
+            samplesWriting = sampleBuffer0;
+        }
+        transferComplete = true;
+        fillingBuffer0 = !fillingBuffer0;
+    }
+}
+#else // not artificial mode - we want real samples!
+// Callback for DMA channel 0
+void TC0_callback(void)  // static method
+{
+    // Swap writing and processing buffers used by main loop
+    if(toneDetector.fillingBuffer0)
+    {
+        toneDetector.samplesProcessing = toneDetector.sampleBuffer0;
+        toneDetector.samplesWriting = toneDetector.sampleBuffer1;
+    }
+    else
+    {
+        toneDetector.samplesProcessing = toneDetector.sampleBuffer1;
+        toneDetector.samplesWriting = toneDetector.sampleBuffer0;
+    }
+    // Tell main() loop that this buffer is ready for processing
+    toneDetector.fillingBuffer0 = !toneDetector.fillingBuffer0;
+    toneDetector.transferComplete = true;
+
+    // Clear DMA IRQ flags.
+    if(toneDetector.dma.irqType() == MODDMA::TcIrq) toneDetector.dma.clearTcIrq();
+    if(toneDetector.dma.irqType() == MODDMA::ErrIrq) toneDetector.dma.clearErrIrq();
+}
+
+// Configuration callback on Error for channel 0
+void ERR0_callback(void)  // static method
+{
+    // Stop sampling
+    LPC_TIM1->TCR = 0;            // Stop the timer (and thus the ADC)
+    LPC_SC->PCONP &= ~(1UL << 2); // Power off the timer
+
+    // Stop the main loop (don't call stop() since that would wait for next buffer to fill)
+    toneDetector.terminated = true;
+
+    error("Oh no! My Mbed EXPLODED! :( Only kidding, go find the problem (DMA chan 0)");
+}
+#endif
+
+// Goertzelize the process buffer
+void ToneDetector::processSamples()
+{
+    #ifdef debugLEDs
+    if(fillingBuffer0)
+        led2 = 1; // Indicate that at least two buffers have been recorded
+    led3 = 1;     // Indicate start of processing
+    #endif
+    #ifdef debugPins // after LEDs and timer to be more accurate
+    if(fillingBuffer0)
+        debugPin2 = 1; // Indicate that at least two buffers have been recorded
+    debugPin3 = 1;     // Indicate start of processing
+    #endif
+
+    // Create variables for storing the Goertzel series
+    int32_t s0, s1, s2;
+    volatile int32_t newTonePower;
+    // Create variables for getting max input value
+    int32_t sample = 0;
+    uint32_t signalLevel = 0;
+    // For each desired tone, compute power and then reset the Goertzel
+    for(uint8_t i = 0; i < numTones; i++)
+    {
+        // Reset
+        s0 = 0;
+        s1 = 0;
+        s2 = 0;
+        // Compute the Goertzel series
+        for(uint16_t n = 0; n < sampleWindow; n++)
+        {
+            // Note: bottom 4 bits from ADC indicate channel, top 12 are the actual data
+            // Note: Assuming Q10 format, we are automatically scaling the ADC value to [0, 4] range
+            sample = ((int32_t)((samplesProcessing[n] >> 4) & 0xFFF));
+            // Get signal level indicator
+            if(i == 0)
+            {
+                if(sample-2048 >= 0)
+                    signalLevel += (sample-2048);
+                else
+                    signalLevel += (2048-sample);
+            }
+            // TODO check the effect of this subtraction?
+            //sample -= 2048;
+            s0 = sample + fixedPointMultiply(goertzelCoefficients[i], s1) - s2;
+            s2 = s1;
+            s1 = s0;
+        }
+        // Compute the power
+        newTonePower = fixedPointMultiply(s2,s2) + fixedPointMultiply(s1,s1) - fixedPointMultiply(fixedPointMultiply(goertzelCoefficients[i],s1),s2);
+        // Update the running sum
+        tonePowersSum[i] -= tonePowers[i][tonePowersWindowIndex];
+        tonePowersSum[i] += newTonePower;
+        // Update the history of powers
+        tonePowers[i][tonePowersWindowIndex] = newTonePower;
+    }
+    // See if first circular buffer has been filled
+    readyToThreshold = readyToThreshold || (tonePowersWindowIndex == tonePowersWindow-1);
+    // Deliver results if a callback function was provided
+    if(callbackFunction != 0 && readyToThreshold)
+        callbackFunction(tonePowersSum, signalLevel >> 7); // divide signal level by 128 is basically making it an average (125 samples/buffer)
+	#ifdef streamAcousticControlLog
+	acousticControlLogToStream[0] = tonePowers[0][tonePowersWindowIndex];
+	acousticControlLogToStream[1] = tonePowers[1][tonePowersWindowIndex];
+	acousticControlLogToStream[2] = signalLevel >> 7;
+	#endif
+    #ifdef debugLEDs
+    led3 = 0;        // Indicate completion of processing
+    #endif
+    #ifdef recordSamples
+        #ifdef recordStreaming
+        for(uint16_t n = 0; n < sampleWindow; n++)
+            printf("%ld\n", (samplesProcessing[n] >> 4) & 0xFFF);
+        #else
+        for(uint16_t n = 0; n < sampleWindow; n++)
+        {
+            savedSamples[savedSamplesIndex++] = (samplesProcessing[n] >> 4) & 0xFFF;
+            savedSamplesIndex %= numSavedSamples;
+        }
+        #endif
+    #endif
+    #ifdef recordOutput
+        #ifdef recordStreaming
+        for(uint8_t t = 0; t < numTones; t++)
+            printf("%ld\t", tonePowers[t][tonePowersWindowIndex] >> 10); // used to shift 10
+        printf("\n");
+        #else
+        for(uint8_t t = 0; t < numTones; t++)
+        {
+            savedTonePowers[savedTonePowersIndex][t] = tonePowers[t][tonePowersWindowIndex];
+        }
+        savedTonePowersIndex++;
+        savedTonePowersIndex %= numSavedTonePowers;
+        #endif
+    #endif
+    // Increment window index (circularly)
+    tonePowersWindowIndex = (tonePowersWindowIndex+1) % tonePowersWindow;
+
+    #ifdef debugPins // before LEDs, timer, and recordSamples to be more accurate
+    debugPin3 = 0;   // Indicate completion of processing
+    #endif
+}
+
+int32_t* ToneDetector::getTonePowers()
+{
+    return tonePowersSum;
+}
+
+//============================================
+// Testing / Debugging
+//============================================
+
+#ifdef artificialSamplesMode
+// Use artificial samples, either from a file or from summing cosine waves of given frequencies
+// Samples in a file will be interpreted as volts, so should be in range [0, 3.3]
+void ToneDetector::initTestModeSamples()
+{
+    #ifdef sampleFilename
+    LocalFileSystem local("local");
+    printf("Using samples from file: "); printf(sampleFilename); printf("\n");
+    FILE *fin = fopen(sampleFilename, "r");  // Open "setup.txt" on the local file system for read
+    if(fin == 0)
+    {
+        printf("  *** Cannot open file! ***\n");
+        wait_ms(3000);
+        numTestSamples = 1;
+        testSamples = new uint32_t[numTestSamples];
+        testSamples[0] = 0;
+        testSampleIndex = 0;
+        return;
+    }
+    // See how long the file is
+    int numTestSamples = 0;
+    float val;
+    float maxVal = 0;
+    float minVal = 0;
+    float avgVal = 0;
+    int res = fscanf(fin, "%d\n", &val);
+    while(res > 0)
+    {
+        numTestSamples++;
+        res = fscanf(fin, "%f\n", &val);
+        if(val > maxVal)
+            maxVal = val;
+        if(val < minVal)
+            minVal = val;
+        avgVal = numTestSamples > 1 ? (avgVal + val)/2.0 : val;
+    }
+    printf("  Found %d samples in the file, max %f, min %f, avg %f\n", numTestSamples, maxVal, minVal, avgVal);
+    if(minVal < 0)
+        printf("  WARNING: File supposed to represent voltage input to ADC, so negative numbers will be interpreted as 0\n");
+    if(maxVal > 3.3)
+        printf("  WARNING: File supposed to represent voltage input to ADC, so numbers greater than 3.3 will be interpreted as 3.3\n");
+    fclose(fin);
+    // Read the samples
+    testSamples = new uint32_t[numTestSamples];
+    fin = fopen(sampleFilename, "r");  // Open "setup.txt" on the local file system for read
+    for(int i = 0; i < numTestSamples; i++)
+    {
+        // Read the voltage
+        fscanf(fin, "%f\n", &val);
+        // Clip it like the ADC would
+        val = val > 3.3 ? 3.3 : val;
+        val = val < 0 ? 0 : val;
+        // Convert voltage to 12-bit ADC reading
+        testSamples[i] = val/3.3*4096.0;
+        // Shift it by 4 to mimic what the DMA would write for a reading (lower 4 would indicate ADC channel)
+        testSamples[i] = testSamples[i] << 4;
+    }
+    fclose(fin);
+    testSampleIndex = 0;
+    sampleIndex = 0;
+
+    #else // not using file for samples, will create a sum of cosine waves instead
+
+    numTestSamples = 1000;
+    testSamples = new uint32_t[numTestSamples];
+    testSampleIndex = 0;
+
+    // Adjust overall amplitude and offset - make sure it's nonnegative
+    float amplitude = 1;  // volts
+    float baseline = 1.5; // volts
+    // Print out the frequencies being used
+    float frequencies[] = sumSampleFrequencies; // Test signal will be a summation of cosines at these frequencies (in Hz)
+    printf("Using samples from cosines with the following frequencies:\n");
+    for(int f = 0; f < sizeof(frequencies)/sizeof(float); f++)
+        printf("  %f\n", frequencies[f]);
+    printf("\n");
+
+    // Create the samples
+    float sampleFrequency = 1000.0/((double)sampleInterval/1000.0);
+    for(uint16_t n = 0; n < numTestSamples; n++)
+    {
+        float nextSample = 0;
+        // Sum the frequencies
+        for(int f = 0; f < sizeof(frequencies)/sizeof(float); f++)
+            nextSample += cos(2.0*PI*frequencies[f]*(float)n/sampleFrequency);
+        // Normalize
+        nextSample /= (float)(sizeof(frequencies)/sizeof(float));
+        // Amplify
+        nextSample *= amplitude;
+        // Positivify
+        nextSample += baseline;
+        // Convert to 12-bit ADC reading
+        testSamples[n] = ((uint32_t)(nextSample/3.3*4095.0));
+        // Shift it by 4 to mimic what the DMA would write for a reading (lower 4 would indicate ADC channel)
+        testSamples[n] = testSamples[n] << 4;
+    }
+    sampleIndex = 0;
+    #endif // which sample mode
+}
+#endif // artificialSamplesMode
+
+#endif // #ifdef acousticControl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/AcousticControl/ToneDetector.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,182 @@
+/**
+ * Author: Joseph DelPreto
+ * A class for sampling a signal and detecting whether certain frequencies are present
+ * Implements the Goertzel algorithm for tone detection
+ *   Uses fixed-point arithmetic to a > 10x speedup over a floating-point implementation
+ *   Uses Q15 number format
+ *
+ * Can easily configure the sampling frequency, the tones to detect, and the length of the buffer window
+ * Can set a callback function to be called at the end of each buffer processing to see the relative powers of each target tone
+ *   Make sure the callback function execution time, plus the time to process a buffer of samples, takes less time than it takes to acquire the buffer!
+ *   You can check how long it takes to process a buffer of samples by enabling "timeProcess" below then using initProcessTimer() and getProcessTimes()
+ *
+ * Debugging options such as LEDs to indicate status are also available
+ * Can also choose to time each processing stage to ensure it is not taking longer the time needed to acquire the buffer of samples
+ * Can also record samples and write them to a file (max 1000 will be recorded)
+ * LED pattern if enabled:
+ *   LED1 indicates tone detection in progress
+ *   LED2 indicates at least two buffers have been filled
+ *   LED3 turns on when processing starts and off when it finishes (so lower duty cycle is better)
+ *   LED4 turns on when tone detection is stopped (and then other LEDs turn off)
+ * Debug pins will go high/low in same pattern as described above for LEDs, but using pins p5-p8
+ *
+ * Test mode can be enabled, in which case samples can be artificially generated instead of using the ADC
+ *
+ * Currently only set up for one instance of ToneDetector to be in operation
+ *   Use the object toneDetector, declared in this file, for all of your tone detecting needs!
+ *
+ */
+
+#define acousticControl
+
+#ifdef acousticControl
+
+#ifndef TONE_DETECTOR_H
+#define TONE_DETECTOR_H
+
+//#define streamAcousticControlLog // for every buffer, streams goertzel powers f1, goerztel powers f2, signal level, gain, fish state/events over Serial
+
+// Configuration
+#define sampleInterval 4  // us between samples
+#define sampleWindow 125  // number of samples in a Goertzel buffer
+#define numTones 2
+#define numFSKGroups 1
+static const double targetTones[numTones] = {36000, 30000};  // Tones to detect (in Hz): first tone is a 0 bit second is a 1 bit
+
+// Test / Debugging options
+//#define artificialSamplesMode // if this is defined, should define either sampleFilename OR sumSampleFrequencies
+//#define sampleFilename "/local/2tone11.txt"
+//#define sumSampleFrequencies {10000,30000} // will be used to initialize float[] array.  Test signal will be summation of cosines with these frequencies (in Hz)
+
+#define debugLEDs
+#define debugPins
+#define recordStreaming // print to COM each sample/output (save everything), as opposed to only write last few hundred to a file (but faster since don't write to file while processing)
+						// note that either recordOutput or recordSamples must be undefined to actually record anything
+//#define recordOutput  // save tone powers - will either stream to COM or save the last numSavedTonePowers powers to a file (based on recordStreaming)
+//#define recordSamples // save samples - will either stream to COM or save the last numSavedSamples samples to a file (based on recordStreaming)
+
+
+#if defined(recordSamples) && !defined(recordStreaming)
+#define numSavedSamples 1000
+#endif
+#if defined(recordOutput) && !defined(recordStreaming)
+#define numSavedTonePowers 250
+#endif
+
+// Will use fixed-point arithmetic for speed
+//   numbers will be int32_t with 10 bits as fractional portion
+//   note: this means the 12 bits from the ADC can be used directly, implicitly scaling them to be floats in range [0,4]
+//     (so if you want to change the Q format, make sure to shift the samples accordingly in the processSamples() method)
+#define toFixedPoint(floatNum) ((int32_t)((float)(floatNum) * 1024.0f))
+#define toFloat(fixedPointNum) ((float)(fixedPointNum)/1024.0f)
+#define fixedPointMultiply(a,b) ((int32_t)(((int64_t)a * (int64_t)b) >> 10))
+
+// Constants
+#define tonePowersWindow 32 // change to 5 for threshold1
+
+// Include files
+#include <stdint.h>   // for types like int32_t
+#include <math.h>     // for cos // TODO remove this once samples are real samples
+#define PI 3.1415926  // not included with math.h for some reason
+#include "mbed.h"     // for the ticker
+#ifndef artificialSamplesMode
+#include "MODDMA.h"   // DMA for reading the ADC
+#endif
+
+// Debugging
+#ifdef debugLEDs
+static DigitalOut led1(LED1);
+static DigitalOut led2(LED2);
+static DigitalOut led3(LED3);
+static DigitalOut led4(LED4);
+#endif
+#ifdef debugPins
+static DigitalOut debugPin1(p5);
+static DigitalOut debugPin2(p6);
+static DigitalOut debugPin3(p7);
+static DigitalOut debugPin4(p8);
+#endif
+
+// Define the ToneDetector!
+class ToneDetector
+{
+    public:
+        // Initialization
+        ToneDetector();
+        void init();
+        void setCallback(void (*myFunction)(int32_t* tonePowers, uint32_t signalLevel));
+        // Execution Control
+        virtual void run();      // main loop, runs forever or until stop() is called
+        virtual void stop();      // stop the main loop
+        void finish();            // write final output to files and whatnot
+        volatile bool terminated; // indicates main loop should stop (may be set by error callback or by stop())
+        // Sampling / Processing (these are public to allow DMA callbacks to access them)
+        volatile bool fillingBuffer0;    // Whether sampling is currently writing to buffer 0 or buffer 1
+        volatile bool transferComplete;  // Signals that samplesProcessing is ready to be processed
+        uint32_t sampleBuffer0[sampleWindow];         // Buffer of samples used by one DMA operation
+        uint32_t sampleBuffer1[sampleWindow];         // Buffer of samples used by second DMA operation
+        volatile uint32_t* samplesWriting;    // Pointer to the buffer to which we should write (alternates between buffer0 and buffer1)
+        volatile uint32_t* samplesProcessing; // Pointer to the buffer which we should process (alternates between buffer0 and buffer1)
+        int32_t* getTonePowers();        // Get the most recent tone powers
+        #ifndef artificialSamplesMode
+        MODDMA dma;  // DMA controller
+        #endif
+
+    private:
+        // Initialization
+        bool readyToBegin;              // Whether sample window, sample interval, and tones have all been specified
+        #ifndef artificialSamplesMode
+        MODDMA_Config *dmaConf;         // Overall DMA configuration
+        MODDMA_LLI *lli[2];             // Linked List Items for chaining DMA operations (will have one for each ADC buffer)
+        void startDMA();
+        void startADC();
+        #endif
+        // Sampling
+        // Goertzel Processing (arrays will have an element per desired tone)
+        //   Tone detecting
+        int32_t goertzelCoefficients[numTones];  // Pre-computed coefficients based on target tones
+        int32_t tonePowersSum[numTones];         // Result of Goertzel algorithm (summed over tonePowersWindow to smooth results)
+        int32_t tonePowers[numTones][tonePowersWindow];           // For each tone, store the most recent tonePowersWindow powers (in circular buffers)
+        uint8_t tonePowersWindowIndex;  // Index into the circular buffers of tonePowers
+        bool readyToThreshold;          // Whether thresholding is ready, or first buffer is still being filled
+        void (*callbackFunction)(int32_t* tonePowers, uint32_t signalLevel);  // Called when new powers are computed
+        void processSamples();          // Process a buffer of samples to detect tones
+
+        // Testing / Debugging
+        #ifdef artificialSamplesMode
+        Ticker sampleTicker;            // Triggers a sample to be read from the pre-filled array
+        void initTestModeSamples();     // Creates the samples, either from a file or by summing tones
+        void tickerCallback();          // Called each time a sample is read
+        uint32_t* testSamples;          // Array of pre-stored sample values to use
+        uint16_t numTestSamples;        // Length of the testSamples array (need not be same as buffer length)
+        volatile uint16_t testSampleIndex;  // Current index into testSamples
+        volatile uint16_t sampleIndex;  // Index into current sample buffer
+        #endif
+        #ifndef recordStreaming
+            #ifdef recordSamples
+            uint16_t savedSamplesIndex;
+            uint32_t savedSamples[numSavedSamples];
+            #endif
+            #ifdef recordOutput
+            uint16_t savedTonePowersIndex;
+            int32_t savedTonePowers[numSavedTonePowers][numTones];
+            #endif
+        #endif
+};
+
+// DMA IRQ callback methods
+void TC0_callback();    // Callback for when DMA channel 0 has filled a buffer
+void ERR0_callback();   // Error on dma channel 0
+
+// Create a static instance of ToneDetector to be used by anyone doing detection
+//   This allows the ticker callback to be a member function
+extern ToneDetector toneDetector;
+#ifdef streamAcousticControlLog
+extern int32_t acousticControlLogToStream[5]; // goertzel powers f1, goerztel powers f2, signal level, gain, events (events is deprecated)
+#endif
+
+#endif // ifndef TONE_DETECTOR_H
+
+#endif // #ifdef acousticControl
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/BNO055/BNO055.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,280 @@
+#include "BNO055.h"
+#include "mbed.h"
+
+BNO055::BNO055(PinName SDA, PinName SCL) : _i2c(SDA,SCL){
+    //Set I2C fast and bring reset line high
+    _i2c.frequency(400000);
+    address = BNOAddress;
+    accel_scale = 0.001f;
+    rate_scale = 1.0f/16.0f;
+    angle_scale = 1.0f/16.0f;
+    temp_scale = 1;
+    }
+    
+void BNO055::reset(){
+//Perform a power-on-reset
+    readchar(BNO055_SYS_TRIGGER_ADDR);
+    rx = rx | 0x20;
+    writechar(BNO055_SYS_TRIGGER_ADDR,rx);
+//Wait for the system to come back up again (datasheet says 650ms)
+    wait_ms(675);
+}
+    
+bool BNO055::check(){
+//Check we have communication link with the chip
+    readchar(BNO055_CHIP_ID_ADDR);
+    if (rx != 0xA0) return false;
+//Grab the chip ID and software versions
+    tx[0] = BNO055_CHIP_ID_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,rawdata,7,false); 
+    ID.id = rawdata[0];
+    ID.accel = rawdata[1];
+    ID.mag = rawdata[2];
+    ID.gyro = rawdata[3];
+    ID.sw[0] = rawdata[4];
+    ID.sw[1] = rawdata[5];
+    ID.bootload = rawdata[6];
+    setpage(1);
+    tx[0] = BNO055_UNIQUE_ID_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,ID.serial,16,false); 
+    setpage(0);
+    return true;
+    }
+    
+void BNO055::SetExternalCrystal(bool yn){
+// Read the current status from the device
+    readchar(BNO055_SYS_TRIGGER_ADDR); 
+    if (yn) rx = rx | 0x80;
+    else rx = rx & 0x7F;
+    writechar(BNO055_SYS_TRIGGER_ADDR,rx); 
+}
+
+void BNO055::set_accel_units(char units){
+    readchar(BNO055_UNIT_SEL_ADDR);
+    if(units == MPERSPERS){
+        rx = rx & 0xFE;
+        accel_scale = 0.01f;
+        }
+    else {
+        rx = rx | units;
+        accel_scale = 0.001f;
+        }
+    writechar(BNO055_UNIT_SEL_ADDR,rx);
+}
+
+void BNO055::set_anglerate_units(char units){
+    readchar(BNO055_UNIT_SEL_ADDR);
+    if (units == DEG_PER_SEC){
+        rx = rx & 0xFD;
+        rate_scale = 1.0f/16.0f;
+        }
+    else {
+        rx = rx | units;
+        rate_scale = 1.0f/900.0f;
+        }
+    writechar(BNO055_UNIT_SEL_ADDR,rx);
+}    
+
+void BNO055::set_angle_units(char units){
+    readchar(BNO055_UNIT_SEL_ADDR);
+    if (units == DEGREES){
+        rx = rx & 0xFB;
+        angle_scale = 1.0f/16.0f;
+        }
+    else {
+        rx = rx | units;
+        rate_scale = 1.0f/900.0f;
+        }
+    writechar(BNO055_UNIT_SEL_ADDR,rx);
+}    
+
+void BNO055::set_temp_units(char units){
+    readchar(BNO055_UNIT_SEL_ADDR);
+    if (units == CENTIGRADE){
+        rx = rx & 0xEF;
+        temp_scale = 1;
+        }
+    else {
+        rx = rx | units;
+        temp_scale = 2;
+        }
+    writechar(BNO055_UNIT_SEL_ADDR,rx);
+}    
+
+void BNO055::set_orientation(char units){
+    readchar(BNO055_UNIT_SEL_ADDR);
+    if (units == WINDOWS) rx = rx &0x7F;
+    else rx = rx | units;
+    writechar(BNO055_UNIT_SEL_ADDR,rx);
+}        
+
+void BNO055::setmode(char omode){
+    writechar(BNO055_OPR_MODE_ADDR,omode);
+    op_mode = omode;
+}
+
+void BNO055::setpowermode(char pmode){
+    writechar(BNO055_PWR_MODE_ADDR,pmode);
+    pwr_mode = pmode;
+}
+
+void BNO055::get_accel(void){
+    tx[0] = BNO055_ACCEL_DATA_X_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,rawdata,6,0); 
+    accel.rawx = (rawdata[1] << 8 | rawdata[0]);
+    accel.rawy = (rawdata[3] << 8 | rawdata[2]);
+    accel.rawz = (rawdata[5] << 8 | rawdata[4]);
+    accel.x = float(accel.rawx)*accel_scale;
+    accel.y = float(accel.rawy)*accel_scale;
+    accel.z = float(accel.rawz)*accel_scale;
+}
+    
+void BNO055::get_gyro(void){
+    tx[0] = BNO055_GYRO_DATA_X_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,rawdata,6,0); 
+    gyro.rawx = (rawdata[1] << 8 | rawdata[0]);
+    gyro.rawy = (rawdata[3] << 8 | rawdata[2]);
+    gyro.rawz = (rawdata[5] << 8 | rawdata[4]);
+    gyro.x = float(gyro.rawx)*rate_scale;
+    gyro.y = float(gyro.rawy)*rate_scale;
+    gyro.z = float(gyro.rawz)*rate_scale;
+}
+
+void BNO055::get_mag(void){
+    tx[0] = BNO055_MAG_DATA_X_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,rawdata,6,0); 
+    mag.rawx = (rawdata[1] << 8 | rawdata[0]);
+    mag.rawy = (rawdata[3] << 8 | rawdata[2]);
+    mag.rawz = (rawdata[5] << 8 | rawdata[4]);
+    mag.x = float(mag.rawx);
+    mag.y = float(mag.rawy);
+    mag.z = float(mag.rawz);
+}
+
+void BNO055::get_lia(void){
+    tx[0] = BNO055_LINEAR_ACCEL_DATA_X_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,rawdata,6,0); 
+    lia.rawx = (rawdata[1] << 8 | rawdata[0]);
+    lia.rawy = (rawdata[3] << 8 | rawdata[2]);
+    lia.rawz = (rawdata[5] << 8 | rawdata[4]);
+    lia.x = float(lia.rawx)*accel_scale;
+    lia.y = float(lia.rawy)*accel_scale;
+    lia.z = float(lia.rawz)*accel_scale;
+}
+
+void BNO055::get_grv(void){
+    tx[0] = BNO055_GRAVITY_DATA_X_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,rawdata,6,0); 
+    gravity.rawx = (rawdata[1] << 8 | rawdata[0]);
+    gravity.rawy = (rawdata[3] << 8 | rawdata[2]);
+    gravity.rawz = (rawdata[5] << 8 | rawdata[4]);
+    gravity.x = float(gravity.rawx)*accel_scale;
+    gravity.y = float(gravity.rawy)*accel_scale;
+    gravity.z = float(gravity.rawz)*accel_scale;
+}
+
+void BNO055::get_quat(void){
+    tx[0] = BNO055_QUATERNION_DATA_W_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,rawdata,8,0); 
+    quat.raww = (rawdata[1] << 8 | rawdata[0]);
+    quat.rawx = (rawdata[3] << 8 | rawdata[2]);
+    quat.rawy = (rawdata[5] << 8 | rawdata[4]);
+    quat.rawz = (rawdata[7] << 8 | rawdata[6]);
+    quat.w = float(quat.raww)/16384.0f;
+    quat.x = float(quat.rawx)/16384.0f;
+    quat.y = float(quat.rawy)/16384.0f;
+    quat.z = float(quat.rawz)/16384.0f;
+}
+
+void BNO055::get_angles(void){
+    tx[0] = BNO055_EULER_H_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address+1,rawdata,6,0); 
+    euler.rawyaw = (rawdata[1] << 8 | rawdata[0]);
+    euler.rawroll = (rawdata[3] << 8 | rawdata[2]);
+    euler.rawpitch = (rawdata[5] << 8 | rawdata[4]);
+    euler.yaw = float(euler.rawyaw)*angle_scale;
+    euler.roll = float(euler.rawroll)*angle_scale;
+    euler.pitch = float(euler.rawpitch)*angle_scale;
+}
+
+
+void BNO055::get_temp(void){
+    readchar(BNO055_TEMP_ADDR);
+    temperature = rx / temp_scale;
+}
+
+void BNO055::get_calib(void){
+     readchar(BNO055_CALIB_STAT_ADDR);
+     calib = rx;
+}
+
+void BNO055::read_calibration_data(void){
+    char tempmode = op_mode;
+    setmode(OPERATION_MODE_CONFIG);
+    wait_ms(20);
+    tx[0] = ACCEL_OFFSET_X_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.read(address,calibration,22,false); 
+    setmode(tempmode);
+    wait_ms(10);
+}
+
+void BNO055::write_calibration_data(void){
+    char tempmode = op_mode;
+    setmode(OPERATION_MODE_CONFIG);
+    wait_ms(20);
+    tx[0] = ACCEL_OFFSET_X_LSB_ADDR;
+    _i2c.write(address,tx,1,true);  
+    _i2c.write(address,calibration,22,false); 
+    setmode(tempmode);
+    wait_ms(10);
+}
+
+void BNO055::set_mapping(char orient){
+    switch (orient){
+        case 0: 
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x21);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x04);
+            break;
+        case 1:
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x24);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x00);
+            break;
+        case 2:
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x24);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x00);
+            break;
+        case 3:
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x21);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x02);
+            break;
+        case 4:
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x24);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x03);
+            break;
+        case 5:
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x21);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x01);
+            break;
+        case 6:
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x21);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x07);
+            break;
+        case 7:
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x24);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x05);
+            break;
+        default:
+            writechar(BNO055_AXIS_MAP_CONFIG_ADDR,0x24);
+            writechar(BNO055_AXIS_MAP_SIGN_ADDR,0x00);
+        }
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/BNO055/BNO055.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,280 @@
+#ifndef BNO055_H
+#define BNO055_H
+
+#include "mbed.h"
+//
+#define BNOAddress (0x28 << 1)
+//Register definitions
+/* Page id register definition */ 
+#define BNO055_PAGE_ID_ADDR          0x07
+/* PAGE0 REGISTER DEFINITION START*/ 
+#define BNO055_CHIP_ID_ADDR          0x00 
+#define BNO055_ACCEL_REV_ID_ADDR     0x01
+#define BNO055_MAG_REV_ID_ADDR       0x02
+#define BNO055_GYRO_REV_ID_ADDR      0x03
+#define BNO055_SW_REV_ID_LSB_ADDR    0x04
+#define BNO055_SW_REV_ID_MSB_ADDR    0x05
+#define BNO055_BL_REV_ID_ADDR        0x06
+/* Accel data register */ 
+#define BNO055_ACCEL_DATA_X_LSB_ADDR 0x08
+#define BNO055_ACCEL_DATA_X_MSB_ADDR 0x09
+#define BNO055_ACCEL_DATA_Y_LSB_ADDR 0x0A
+#define BNO055_ACCEL_DATA_Y_MSB_ADDR 0x0B
+#define BNO055_ACCEL_DATA_Z_LSB_ADDR 0x0C 
+#define BNO055_ACCEL_DATA_Z_MSB_ADDR 0x0D
+/* Mag data register */ 
+#define BNO055_MAG_DATA_X_LSB_ADDR   0x0E
+#define BNO055_MAG_DATA_X_MSB_ADDR   0x0F
+#define BNO055_MAG_DATA_Y_LSB_ADDR   0x10
+#define BNO055_MAG_DATA_Y_MSB_ADDR   0x11
+#define BNO055_MAG_DATA_Z_LSB_ADDR   0x12
+#define BNO055_MAG_DATA_Z_MSB_ADDR   0x13
+/* Gyro data registers */ 
+#define BNO055_GYRO_DATA_X_LSB_ADDR  0x14
+#define BNO055_GYRO_DATA_X_MSB_ADDR  0x15
+#define BNO055_GYRO_DATA_Y_LSB_ADDR  0x16
+#define BNO055_GYRO_DATA_Y_MSB_ADDR  0x17
+#define BNO055_GYRO_DATA_Z_LSB_ADDR  0x18
+#define BNO055_GYRO_DATA_Z_MSB_ADDR  0x19
+/* Euler data registers */ 
+#define BNO055_EULER_H_LSB_ADDR      0x1A
+#define BNO055_EULER_H_MSB_ADDR      0x1B
+#define BNO055_EULER_R_LSB_ADDR      0x1C
+#define BNO055_EULER_R_MSB_ADDR      0x1D
+#define BNO055_EULER_P_LSB_ADDR      0x1E
+#define BNO055_EULER_P_MSB_ADDR      0x1F
+/* Quaternion data registers */ 
+#define BNO055_QUATERNION_DATA_W_LSB_ADDR  0x20
+#define BNO055_QUATERNION_DATA_W_MSB_ADDR  0x21
+#define BNO055_QUATERNION_DATA_X_LSB_ADDR  0x22
+#define BNO055_QUATERNION_DATA_X_MSB_ADDR  0x23
+#define BNO055_QUATERNION_DATA_Y_LSB_ADDR  0x24
+#define BNO055_QUATERNION_DATA_Y_MSB_ADDR  0x25
+#define BNO055_QUATERNION_DATA_Z_LSB_ADDR  0x26
+#define BNO055_QUATERNION_DATA_Z_MSB_ADDR  0x27
+/* Linear acceleration data registers */ 
+#define BNO055_LINEAR_ACCEL_DATA_X_LSB_ADDR 0x28
+#define BNO055_LINEAR_ACCEL_DATA_X_MSB_ADDR 0x29
+#define BNO055_LINEAR_ACCEL_DATA_Y_LSB_ADDR 0x2A
+#define BNO055_LINEAR_ACCEL_DATA_Y_MSB_ADDR 0x2B
+#define BNO055_LINEAR_ACCEL_DATA_Z_LSB_ADDR 0x2C
+#define BNO055_LINEAR_ACCEL_DATA_Z_MSB_ADDR 0x2D
+/* Gravity data registers */ 
+#define BNO055_GRAVITY_DATA_X_LSB_ADDR      0x2E
+#define BNO055_GRAVITY_DATA_X_MSB_ADDR      0x2F
+#define BNO055_GRAVITY_DATA_Y_LSB_ADDR      0x30
+#define BNO055_GRAVITY_DATA_Y_MSB_ADDR      0x31
+#define BNO055_GRAVITY_DATA_Z_LSB_ADDR      0x32
+#define BNO055_GRAVITY_DATA_Z_MSB_ADDR      0x33
+/* Temperature data register */ 
+#define BNO055_TEMP_ADDR                    0x34 
+/* Status registers */ 
+#define BNO055_CALIB_STAT_ADDR              0x35 
+#define BNO055_SELFTEST_RESULT_ADDR         0x36 
+#define BNO055_INTR_STAT_ADDR               0x37 
+#define BNO055_SYS_CLK_STAT_ADDR            0x38 
+#define BNO055_SYS_STAT_ADDR                0x39 
+#define BNO055_SYS_ERR_ADDR                 0x3A 
+/* Unit selection register */ 
+#define BNO055_UNIT_SEL_ADDR                0x3B 
+#define BNO055_DATA_SELECT_ADDR             0x3C 
+/* Mode registers */ 
+#define BNO055_OPR_MODE_ADDR                0x3D 
+#define BNO055_PWR_MODE_ADDR                0x3E 
+#define BNO055_SYS_TRIGGER_ADDR             0x3F 
+#define BNO055_TEMP_SOURCE_ADDR             0x40 
+/* Axis remap registers */ 
+#define BNO055_AXIS_MAP_CONFIG_ADDR         0x41 
+#define BNO055_AXIS_MAP_SIGN_ADDR           0x42 
+/* Accelerometer Offset registers */ 
+#define ACCEL_OFFSET_X_LSB_ADDR             0x55 
+#define ACCEL_OFFSET_X_MSB_ADDR             0x56 
+#define ACCEL_OFFSET_Y_LSB_ADDR             0x57
+#define ACCEL_OFFSET_Y_MSB_ADDR             0x58 
+#define ACCEL_OFFSET_Z_LSB_ADDR             0x59 
+#define ACCEL_OFFSET_Z_MSB_ADDR             0x5A 
+/* Magnetometer Offset registers */ 
+#define MAG_OFFSET_X_LSB_ADDR               0x5B
+#define MAG_OFFSET_X_MSB_ADDR               0x5C
+#define MAG_OFFSET_Y_LSB_ADDR               0x5D
+#define MAG_OFFSET_Y_MSB_ADDR               0x5E
+#define MAG_OFFSET_Z_LSB_ADDR               0x5F
+#define MAG_OFFSET_Z_MSB_ADDR               0x60
+/* Gyroscope Offset registers*/ 
+#define GYRO_OFFSET_X_LSB_ADDR              0x61
+#define GYRO_OFFSET_X_MSB_ADDR              0x62
+#define GYRO_OFFSET_Y_LSB_ADDR              0x63
+#define GYRO_OFFSET_Y_MSB_ADDR              0x64
+#define GYRO_OFFSET_Z_LSB_ADDR              0x65
+#define GYRO_OFFSET_Z_MSB_ADDR              0x66
+/* Radius registers */ 
+#define ACCEL_RADIUS_LSB_ADDR               0x67 
+#define ACCEL_RADIUS_MSB_ADDR               0x68 
+#define MAG_RADIUS_LSB_ADDR                 0x69 
+#define MAG_RADIUS_MSB_ADDR                 0x6A 
+
+/* Page 1 registers */
+#define BNO055_UNIQUE_ID_ADDR               0x50
+
+//Definitions for unit selection
+#define MPERSPERS   0x00
+#define MILLIG      0x01
+#define DEG_PER_SEC 0x00
+#define RAD_PER_SEC 0x02
+#define DEGREES     0x00
+#define RADIANS     0x04
+#define CENTIGRADE  0x00
+#define FAHRENHEIT  0x10
+#define WINDOWS     0x00
+#define ANDROID     0x80
+
+//Definitions for power mode
+#define POWER_MODE_NORMAL   0x00 
+#define POWER_MODE_LOWPOWER 0x01 
+#define POWER_MODE_SUSPEND  0x02 
+
+//Definitions for operating mode
+#define OPERATION_MODE_CONFIG        0x00 
+#define OPERATION_MODE_ACCONLY       0x01 
+#define OPERATION_MODE_MAGONLY       0x02 
+#define OPERATION_MODE_GYRONLY       0x03 
+#define OPERATION_MODE_ACCMAG        0x04 
+#define OPERATION_MODE_ACCGYRO       0x05 
+#define OPERATION_MODE_MAGGYRO       0x06 
+#define OPERATION_MODE_AMG           0x07 
+#define OPERATION_MODE_IMUPLUS       0x08 
+#define OPERATION_MODE_COMPASS       0x09 
+#define OPERATION_MODE_M4G           0x0A 
+#define OPERATION_MODE_NDOF_FMC_OFF  0x0B 
+#define OPERATION_MODE_NDOF          0x0C 
+
+typedef struct values{
+    int16_t rawx,rawy,rawz;
+    float x,y,z;
+    }values;
+    
+typedef struct angles{
+    int16_t rawroll,rawpitch,rawyaw;
+    float roll, pitch, yaw;
+    } angles;
+    
+typedef struct quaternion{
+    int16_t raww,rawx,rawy,rawz;
+    float w,x,y,z;
+    }quaternion;
+
+typedef struct chip{
+    char id;
+    char accel;
+    char gyro;
+    char mag;
+    char sw[2];
+    char bootload;
+    char serial[16];
+    }chip;
+
+/** Class for operating Bosch BNO055 sensor over I2C **/
+class BNO055 
+{ 
+public: 
+
+/** Create BNO055 instance **/
+    BNO055(PinName SDA, PinName SCL); 
+    
+/** Perform a power-on reset of the BNO055 **/
+    void reset();
+/** Check that the BNO055 is connected and download the software details 
+and serial number of chip and store in ID structure **/
+    bool check();
+/** Turn the external timing crystal on/off **/
+    void SetExternalCrystal(bool yn);
+/** Set the operation mode of the sensor **/    
+    void setmode(char mode);
+/** Set the power mode of the sensor **/
+    void setpowermode(char mode);
+    
+/** Set the output units from the accelerometer, either MPERSPERS or MILLIG **/
+    void set_accel_units(char units);
+/** Set the output units from the gyroscope, either DEG_PER_SEC or RAD_PER_SEC **/
+    void set_anglerate_units(char units);
+/** Set the output units from the IMU, either DEGREES or RADIANS **/
+    void set_angle_units(char units);
+/** Set the output units from the temperature sensor, either CENTIGRADE or FAHRENHEIT **/
+    void set_temp_units(char units);
+/** Set the data output format to either WINDOWS or ANDROID **/    
+    void set_orientation(char units);
+/** Set the mapping of the exes/directions as per page 25 of datasheet
+    range 0-7, any value outside this will set the orientation to P1 (default at power up) **/
+    void set_mapping(char orient);
+    
+/** Get the current values from the accelerometer **/
+    void get_accel(void);
+/** Get the current values from the gyroscope **/
+    void get_gyro(void);
+/** Get the current values from the magnetometer **/
+    void get_mag(void);
+/** Get the corrected linear acceleration **/
+    void get_lia(void);
+/** Get the current gravity vector **/
+    void get_grv(void);
+/** Get the output quaternion **/
+    void get_quat(void);
+/** Get the current Euler angles **/
+    void get_angles(void);
+/** Get the current temperature **/
+    void get_temp(void);
+
+/** Read the calibration status register and store the result in the calib variable **/
+    void get_calib(void);
+/** Read the offset and radius values into the calibration array**/
+    void read_calibration_data(void);
+/** Write the contents of the calibration array into the registers **/    
+    void write_calibration_data(void);
+    
+/** Structures containing 3-axis data for acceleration, rate of turn and magnetic field.
+    x,y,z are the scale floating point values and
+    rawx, rawy, rawz are the int16_t values read from the sensors **/
+    values accel,gyro,mag,lia,gravity;
+/** Stucture containing the Euler angles as yaw, pitch, roll as scaled floating point
+    and rawyaw, rawroll & rollpitch as the int16_t values loaded from the registers **/
+    angles euler;
+/** Quaternion values as w,x,y,z (scaled floating point) and raww etc... as int16_t loaded from the
+    registers **/    
+    quaternion quat;
+
+/** Current contents of calibration status register **/
+    char calib;
+/** Contents of the 22 registers containing offset and radius values used as calibration by the sensor **/
+    char calibration[22];
+/** Structure containing sensor numbers, software version and chip UID **/
+    chip ID;
+/** Current temperature **/
+    int temperature;
+    
+    private:
+    
+        I2C _i2c;
+        char rx,tx[2],address;  //I2C variables
+        char rawdata[22]; //Temporary array for input data values
+        char op_mode;
+        char pwr_mode;
+        float accel_scale,rate_scale,angle_scale;
+        int temp_scale;
+        
+void readchar(char location){
+    tx[0] = location;
+    _i2c.write(address,tx,1,true);
+    _i2c.read(address,&rx,1,false);
+}
+
+void writechar(char location, char value){
+    tx[0] = location;
+    tx[1] = value;
+    _i2c.write(address,tx,2);
+}
+
+void setpage(char value){
+    writechar(BNO055_PAGE_ID_ADDR,value);
+}
+    };
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/BrushlessMotor.lib	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,1 @@
+http://developer.mbed.org/teams/jetfishteam/code/ESC/#39e83e74b8a1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/BuoyancyControlUnit/BuoyancyControlUnit.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,239 @@
+#include <BuoyancyControlUnit/BuoyancyControlUnit.h>
+
+// The static instance
+BuoyancyControlUnit buoyancyControlUnit;
+
+//============================================
+// Initialization
+//============================================
+
+// Constructor
+BuoyancyControlUnit::BuoyancyControlUnit() :
+    				// Initialize variables
+    				//bcuPWM(bcuPwmPin),
+					bcuDirA(bcuDirAPin),
+					bcuDirB(bcuDirBPin),
+					//bcuCurrent(bcuCurrentPin),
+					wiper(wiperPin),
+					pressureSensor(PIN_IMU_TX,PIN_IMU_RX)
+{
+	setDepth = 0; // input command
+	curDepth = 0; // actual depth based on sensor
+
+	depthLastTime = 0.0;
+	prevDepthErr = 0.0;
+	depthInt = 0.0;
+
+	posLastTime = 0.0;
+	prevPosErr = 0.0;
+	posInt = 0.0;
+	VRef = 0;
+
+	resetFlag = 0;
+	inDepthLoop = 0;
+	inPosLoop = 0;
+}
+
+
+void BuoyancyControlUnit::start()
+{
+	bcuDirA.write(0.0); // apply nothing to start
+	bcuDirB.write(0.0); // apply nothing to start
+	//bcuEncoder.reset(); // gives it a reference to return to
+	
+	pressureSensor.MS5837Init();
+	timer.start();
+	
+	// pressure sensor takes just over 0.3 seconds to read
+	depthControl.attach(&buoyancyControlUnit, &BuoyancyControlUnit::setDepthFuncVoid, 0.4);
+	posControl.attach(&buoyancyControlUnit, &BuoyancyControlUnit::setEncoderPosVoid, 0.1);
+}
+
+void BuoyancyControlUnit::stop()
+{
+	//bcuPWM.write(0.0);
+	// return bcu back to start position
+	// have to rethink how to call this because we don't want the while loop stopping everything else
+//	while(curPos > 15){
+//		this->setEncoderPosition(0);
+//	}
+
+	depthControl.detach();
+	posControl.detach();
+
+	bcuDirA = 0.0;
+	bcuDirB = 0.0;
+}
+
+void BuoyancyControlUnit::returnToZero()
+{
+	depthControl.detach();
+	resetFlag = 1;
+}
+
+//============================================
+// Processing
+//============================================
+void BuoyancyControlUnit::set(float depthDesIn)
+{
+	setDepth = depthDesIn;
+}
+
+void BuoyancyControlUnit::setDepthFuncVoid(){
+	while(inPosLoop);
+	inDepthLoop = 1;
+	this->setDepthFunc(setDepth);
+	inDepthLoop = 0;
+}
+void BuoyancyControlUnit::setEncoderPosVoid(){
+	while(inDepthLoop);
+	inPosLoop = 1;
+
+	if(resetFlag){
+		posRef = 0;
+	} else {
+		depthErr = setDepth - curDepth;
+		if(depthErr < 5 && depthErr > -5){
+			posRef = curPos;
+		}
+	}
+
+	this->setEncoderPosition(posRef);
+	inPosLoop = 0;
+}
+
+void BuoyancyControlUnit::setDepthFunc(float depthDesIn){
+	setDepth = depthDesIn;
+//	pressureSensor.Barometer_MS5837();
+//	curDepth = pressureSensor.MS5837_Pressure();
+/*
+TODO: Map measured depth to 0 - 30 using h = P/rg. 
+#define MIN_DEPTH 1019.0 //mbar
+curDepth = curDepth * (fishMaxPitch - fishMinPitch)/(MAX_DEPTH - MIN_DEPTH)
+*/
+	curDepth = (curPos * 1.25); // fake depth readings
+
+	depthErr = setDepth - curDepth;
+
+/*	float depthdt = timer.read() - depthLastTime;
+
+	depthInt += depthErr * depthdt;
+	depthDer = (depthErr - prevDepthErr)/depthdt;
+
+	posRef += KpDepth*depthErr + KiDepth*depthInt + KdDepth*depthDer;
+*/
+	posRef += KpDepth * depthErr;
+	
+	if (posRef > 30){
+		posRef = 30;
+	}
+	else if (posRef < 0){
+		posRef = 0;
+	}
+
+//	if(posRef < 0){
+//		posRef = 0; // limit position to only values greater than 0
+//		depthInt = 0; // reset these values so they don't build while the bcu can't reach this depth value
+//		depthDer = 0;
+//	}
+
+//	depthLastTime = timer.read();
+//	prevDepthErr = depthErr;
+}
+
+
+void BuoyancyControlUnit::setEncoderPosition(float setPosIn) {
+
+	setPos = setPosIn;
+	if(setPos < 0){ setPos = 0; }
+	curPos = wiper * (30.0); //map ADC reading to system level voltage, try adjusting pitch max and min values (0 - 30)
+	posErr = setPos - curPos;
+	float posdt = timer.read() - posLastTime;
+
+	posInt += posErr * posdt;
+	posDer = (posErr - prevPosErr)/posdt;
+
+	VRef = KpEnc * posErr + KiEnc * posInt + KdEnc * posDer;
+//	VRef = KpEnc * posErr;
+
+	/* Add constrain + map here */
+	if (VRef > 6.0){
+		VRef = 6.0;
+	}
+	else if (VRef < -6.0){
+		VRef = -6.0;
+	}
+	
+	// Map Vref to 0.7 - 1.0 duty cycle
+	if (VRef < 0){
+		VRef = VRef * (0.3/(-6.0)) + 0.7;
+		bcuDir = 1;
+	}
+	else if (VRef > 0){
+		VRef = VRef * (0.3/(6.0)) + 0.7;
+		bcuDir = 0;
+	}
+	
+	posLastTime = timer.read();
+	prevPosErr = posErr;
+
+	if(resetFlag){
+		if(posErr > 10 || posErr < -10){
+			this->setV(VRef);
+		} else {
+			resetFlag = 0;
+			this->stop();
+		}
+	} else {
+		if(posErr > 5 || posErr < -5) { // TODO: determine appropriate tolerance (final error from target pos?)
+			this->setV(VRef);
+			#ifdef debugBCU
+			DigitalOut test(LED1);
+			test = 0;
+			#endif
+		} else {
+			bcuDirA = 0.0;
+			bcuDirB = 0;
+			#ifdef debugBCU
+			//Testing whether fishController is running
+			DigitalOut test(LED1);
+			test = 1;
+			#endif 
+		}
+	}
+}
+
+void BuoyancyControlUnit::setV(float Vin){
+	float setV;
+	
+	if(bcuDir == 1){
+		bcuDirA.write(setV);
+		bcuDirB.write(0.0);
+	} else if(bcuDir == 0){
+		bcuDirA.write(0.0);
+		bcuDirB.write(setV);
+	}
+	
+}
+
+void BuoyancyControlUnit::runBackwards() {
+	this->stop();
+	this->setV(-0.2);
+}
+
+void BuoyancyControlUnit::runForwards() {
+	this->stop();
+	this->setV(0.2);
+}
+
+bool BuoyancyControlUnit::getBCUdir() { return bcuDir; }
+float BuoyancyControlUnit::getVset() { return VRef; }
+float BuoyancyControlUnit::getSetDepth() { return setDepth; }
+float BuoyancyControlUnit::getCurDepth() { return curDepth; }
+float BuoyancyControlUnit::getSetPos() { return setPos; }
+float BuoyancyControlUnit::getCurPos() { return curPos; }
+float BuoyancyControlUnit::readPressure()
+{
+	pressureSensor.Barometer_MS5837();
+	return pressureSensor.MS5837_Pressure();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/BuoyancyControlUnit/BuoyancyControlUnit.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,111 @@
+
+/*
+ * BuoyancyControlUnit.h
+ *
+ * Author: Cyndia Cao, Robert Katzschmann
+ */
+
+#ifndef BUOYANCYCONTROLUNIT_H_
+#define BUOYANCYCONTROLUNIT_H_
+
+#include "mbed.h"
+#include "MS5837/MS5837.h"
+#include "QEI/QEI.h"
+
+
+//#define bcuPwmPin p25
+#define bcuDirAPin p24 //now serves as bcuPWMAPin
+#define bcuDirBPin p25 //now serves as bcuPWMBPin
+#define wiperPin p17   // BCU potentiometer wiper 
+//#define bcuCurrentPin p20
+
+#define encoderPinA p16
+#define encoderPinB p17
+#define count2rev 12 //https://www.pololu.com/product/3081/blog
+#define gearRatio 75 // need to check
+
+#define PIN_IMU_TX p28
+#define PIN_IMU_RX p27
+
+//#define maxBCUCurrent 0.5 // need to measure
+//#define minBCUCurrent 0.1 // need to measure
+
+#define KpDepth 5 // random dummy val
+#define KdDepth 0.0 // ignore for now; start with P control
+#define KiDepth 0.0
+
+#define KpEnc 0.0005
+#define KdEnc 0.00003
+#define KiEnc 0.000000000
+
+
+class BuoyancyControlUnit
+{
+public:
+	// Initialization
+	BuoyancyControlUnit();
+	void start();
+	void stop();
+	void set(float depthDesIn);
+	void setDepthFunc(float depthDesIn);
+	void setEncoderPosition(float setPosIn);
+	void setV(float Vin);
+
+	void setDepthFuncVoid();
+	void setEncoderPosVoid();
+
+	void returnToZero();
+	void runBackwards();
+	void runForwards();
+
+	bool getBCUdir();
+	float getVset();
+	float getSetPos();
+	float getCurPos();
+	float getCurDepth();
+	float getSetDepth();
+	float readPressure();
+
+private:
+	volatile bool resetFlag;
+	volatile bool inDepthLoop;
+	volatile bool inPosLoop;
+
+	volatile float setDepth;
+	volatile float curDepth;
+	volatile bool bcuDir;
+
+	volatile float posRef;
+	volatile float depthErr;
+	volatile float prevDepthErr;
+	volatile float depthDer;
+	volatile float depthInt;
+	volatile float depthLastTime;
+
+	volatile float setPos;
+	volatile float curPos;
+
+	volatile float VRef;
+	volatile float posErr;
+	volatile float prevPosErr;
+	volatile float posDer;
+	volatile float posInt;
+	volatile float posLastTime;
+
+	Timer timer;
+	//PwmOut bcuPWM;
+	PwmOut bcuDirA;
+	PwmOut bcuDirB;
+	AnalogIn wiper;
+//	AnalogIn bcuCurrent; // actually is a voltage value proportional to current
+//	QEI bcuEncoder;
+	MS5837 pressureSensor;
+
+	Ticker depthControl;
+	Ticker posControl;
+};
+
+// Create a static instance of BuoyancyControlUnit to be used by anyone controlling the Buoyancy Control Unit
+extern BuoyancyControlUnit buoyancyControlUnit;
+
+#endif /* BUOYANCYCONTROLUNIT_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/ButtonBoard.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,321 @@
+// buttons.cpp
+
+#include "ButtonBoard.h"
+
+extern "C" void mbed_reset();
+
+ButtonBoard::ButtonBoard(PinName sda, PinName scl, PinName int1, PinName int2) :
+    _i2c(sda, scl),
+    _int1(int1),
+    _int2(int2),
+    _callbackFunction(NULL),
+    _button_state(0)
+{
+    // Initialize callback table
+    for (int i=0; i < BTTN_COUNT; i++)
+    {
+        _callback_table_valid[i] = false;
+    }
+    
+    // Set I2C frequency to fast-mode 400KHz
+    _i2c.frequency(400000L);
+    
+    ///// Configuration
+    bool bd1_failure = false;
+    bool bd2_failure = false;
+    
+    // Configure input latching
+    out_buf[0] = 0x44;  // Input latch register
+    out_buf[1] = 0x00;  // Don't need latching on output
+    out_buf[1] = 0x00;  // Latch them all
+    bd1_failure |= _i2c.write(ADDR_BOARD_1, out_buf, 3);
+    bd2_failure |= _i2c.write(ADDR_BOARD_2, out_buf, 3);
+    
+    // Disable pull-ups
+    out_buf[0] = 0x46;  // Pull-up/down enable register
+    out_buf[1] = 0x00;  // Don't need on outputs
+    out_buf[2] = 0x00;  // Don't need
+    out_buf[3] = 0xFF;  // Select pull-ups
+    out_buf[4] = 0xFF;
+    bd1_failure |= _i2c.write(ADDR_BOARD_1, out_buf, 5);
+    bd2_failure |= _i2c.write(ADDR_BOARD_2, out_buf, 5);
+    
+    // Configure outputs as open-drain
+    out_buf[0] = 0x4F;  // Output port config register
+    out_buf[1] = 0x02;  // Port 1 to open drain
+    bd1_failure |= _i2c.write(ADDR_BOARD_1, out_buf, 2);
+    bd2_failure |= _i2c.write(ADDR_BOARD_2, out_buf, 2);
+    
+    // Reset output register high
+    _led_ports = 0xFFFF; // All LED's off
+    out_buf[0] = 0x02; // Output register
+    out_buf[1] = _led_ports & 0xFF; 
+    bd1_failure |= _i2c.write(ADDR_BOARD_1, out_buf, 2);
+    out_buf[2] = _led_ports>>8;
+    bd2_failure |= _i2c.write(ADDR_BOARD_2, out_buf, 2);
+    
+    // Configure ports as input or outputs
+    out_buf[0] = 0x06;  // Configuration registers
+    out_buf[1] = 0x00;  // Port 1 -> Output
+    out_buf[2] = 0xFF;  // Port 2 <- Input 
+    bd1_failure |= _i2c.write(ADDR_BOARD_1, out_buf, 3); 
+    bd2_failure |= _i2c.write(ADDR_BOARD_2, out_buf, 3); 
+    
+    // Read input registers to clear interrupts
+    out_buf[0] = 0x00; // Input registers
+    bd1_failure |= _i2c.write(ADDR_BOARD_1, out_buf, 1, true); 
+    bd1_failure |= _i2c.read(ADDR_BOARD_1, out_buf, 2);  // Read registers
+    bd2_failure |= _i2c.write(ADDR_BOARD_2, out_buf, 1, true); 
+    bd2_failure |= _i2c.read(ADDR_BOARD_2, out_buf, 2);  // Read registers
+    
+    // Disable interrupt masking on inputs
+    out_buf[0] = 0x4A;  // Interrupt mask register
+    out_buf[1] = 0xFF;  // Mask outputs
+    out_buf[2] = 0xFF;  // Don't mask inputs
+    bd1_failure |= _i2c.write(ADDR_BOARD_1, out_buf, 3);
+    bd2_failure |= _i2c.write(ADDR_BOARD_2, out_buf, 3);
+    
+    // Disable interrupt masking on inputs
+    out_buf[0] = 0x4A;  // Interrupt mask register
+    out_buf[1] = 0xFF;  // Mask outputs
+    out_buf[2] = 0x00;  // Don't mask inputs
+    bd1_failure |= _i2c.write(ADDR_BOARD_1, out_buf, 3);
+    bd2_failure |= _i2c.write(ADDR_BOARD_2, out_buf, 3);
+    
+    if (bd1_failure)
+    {
+        // serial.printf("Nack recieved while configuring button board 1\n");
+        bd1_failure = false;
+    }
+    if (bd2_failure)
+    {
+        // serial.printf("Nack recieved while configuring button board 2\n");
+        bd2_failure = false;
+    }
+    
+    // Enable mbed interrupt lines
+    _int1.fall(this, &ButtonBoard::_int1_handler);
+    _int2.fall(this, &ButtonBoard::_int2_handler);
+    _int1.mode(PullUp);
+    _int2.mode(PullUp);
+    
+    // Register callbacks
+    //registerCallback(BTTN_RESET, &mbed_reset);
+    
+}
+
+ButtonBoard::~ButtonBoard()
+{
+}
+
+void ButtonBoard::_int1_handler()
+{
+    _fall_handler(ADDR_BOARD_1);
+}
+
+void ButtonBoard::_int2_handler()
+{
+    _fall_handler(ADDR_BOARD_2);
+}
+
+void ButtonBoard::_fall_handler(char board)
+{    
+    char int_status;
+    char input_port;
+    char masked_port;
+    
+    int board_offset;
+    if (board == ADDR_BOARD_1)
+    {
+        board_offset = 0;
+    }
+    else
+    {
+        board_offset = BTTN_COUNT_BOARD_1;
+    }
+    
+    // Poll board for interrupt source byte and input reg byte
+    out_buf[0] = 0x4d; // Port 1 Interrupt status
+    _i2c.write(board, out_buf, 1);
+    _i2c.read(board, &int_status, 1);
+    
+    out_buf[0] = 0x01; // Port 1 input register
+    _i2c.write(board, out_buf, 1);
+    _i2c.read(board, &input_port, 1);
+    
+    // Use int status to mask input port (input goes low when button depressed)
+    // int status will always indicate which button changed
+    // masked_port will also indicate button if button is pressed, but will be 0 if button is released
+    masked_port = int_status & (~input_port);
+    if(masked_port)
+        _button_state |= int_status;
+    else
+        _button_state &= ~int_status;
+    
+    for (int i=0; i<8; i++)
+    {
+        // For every high bit in masked port
+        if (masked_port & (1<<i))
+        {
+            // Call corresponding callback
+            int cb_i = i+board_offset;
+            if (_callback_table_valid[cb_i] == true)
+            {
+                _callback_table[cb_i].call();
+            }
+        }
+    }
+    // Call master callback
+    if(_callbackFunction != NULL)
+        _callbackFunction(int_status, masked_port, _button_state);
+}
+
+
+void ButtonBoard::registerCallback(uint32_t button, FunctionPointer p)
+{
+    if (button < BTTN_COUNT)
+    { 
+        _callback_table[button] = p;
+        _callback_table_valid[button] = true;
+    }
+}
+
+void ButtonBoard::registerCallback(void (*p)(char buttonMask, bool pressed, char curState))
+{
+    _callbackFunction = p;
+}
+
+//void ButtonBoard::setLED(uint32_t led, bool val)
+//{
+//    if (led > BTTN_COUNT)
+//    {
+//        // invalid, skip
+//        return;
+//    }
+//    
+//    char board;
+//    
+//    if (led < BTTN_COUNT_BOARD_1)
+//    {
+//        // Address first board
+//        board = ADDR_BOARD_1;
+//    }
+//    else
+//    {
+//        // Address second board
+//        board = ADDR_BOARD_2;
+//        led = led - BTTN_COUNT_BOARD_1;
+//    }
+//    
+//    bool fail = false;
+//    
+//    // Read port state
+//    char port_state;
+//    out_buf[0] = 0x02; // Port 0 output register
+//    
+//    fail |= _i2c.write(board, out_buf, 1, true);
+//    fail |= _i2c.read(board, &port_state, 1);
+//    
+//    if (val == true)
+//    {
+//        // Turn LED on by clearing bit
+//        port_state &= ~(1<<led);
+//    }
+//    else
+//    {
+//        // Turn LED off by raising bit
+//        port_state |= (1<<led);
+//    }
+//    
+//    // Now write to board
+//    out_buf[0] = 0x02; // Port 0 output register
+//    out_buf[1] = port_state;
+//    
+//    fail |= _i2c.write(board, out_buf, 2);
+//    
+//    if (fail)
+//    {
+//        // serial.printf("Nack recieved when writing LED %d on Board %d", led, board);
+//    }
+//    else
+//    {
+//        _led_ports = port_state<<(8*board) + _led_ports & (~(0xFF<<(8*board)));
+//    }
+//}
+
+void ButtonBoard::setLEDs(char mask, bool turnOn, char board /* = ADDR_BOARD_1 */)
+{
+    bool fail = false;
+    
+    // Read port state
+    char port_state;
+    out_buf[0] = 0x02; // Port 0 output register
+    
+    fail |= _i2c.write(board, out_buf, 1, true);
+    fail |= _i2c.read(board, &port_state, 1);
+    
+    if(turnOn == true)
+    {
+        // Turn LEDs on by clearing bits
+        port_state &= ~(mask);
+    }
+    else
+    {
+        // Turn LEDs off by raising bits
+        port_state |= (mask);
+    }
+    
+    // Now write to board
+    out_buf[0] = 0x02; // Port 0 output register
+    out_buf[1] = port_state;
+    
+    fail |= _i2c.write(board, out_buf, 2);
+    
+    if(fail)
+    {
+        // serial.printf("Nack recieved when writing LED %d on Board %d", led, board);
+        //printf("button board write failed\n");
+    }
+    else
+    {
+        //_led_ports = port_state<<(8*board) + _led_ports & (~(0xFF<<(8*board)));
+        _led_ports = port_state;
+    }
+}
+
+char ButtonBoard::getLEDs(char ledMask, char board /* = ADDR_BOARD_1 */)
+{
+    return ~_led_ports & ledMask;
+//    bool fail = false;
+//    
+//    // Read port state
+//    char port_state;
+//    out_buf[0] = 0x02; // Port 0 output register
+//    
+//    fail |= _i2c.write(board, out_buf, 1, true);
+//    fail |= _i2c.read(board, &port_state, 1);
+//    
+//    return ~port_state & ledMask;
+}
+
+char ButtonBoard::getButtons(char buttonMask, char board /* = ADDR_BOARD_1 */)
+{
+    return _button_state & buttonMask;
+}
+
+//uint16_t ButtonBoard::readInputs()
+//{
+//    char b1_p1;
+//    char b2_p1;
+//    
+//    out_buf[0] = 0x01;
+//    _i2c.write(ADDR_BOARD_1, out_buf, 1, true);
+//    _i2c.read(ADDR_BOARD_1, &b1_p1, 1);
+//    
+//    _i2c.write(ADDR_BOARD_2, out_buf, 1, true);
+//    _i2c.read(ADDR_BOARD_2, &b2_p1, 1);
+//    
+//    uint16_t out = (b2_p1<<8) | b1_p1;
+//    
+//    return out;
+//}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/ButtonBoard.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,53 @@
+// buttons.h
+
+#ifndef BUTTONS_H
+#define BUTTONS_H
+
+#include "mbed.h"
+
+#define ADDR_BOARD_1        0x40
+#define ADDR_BOARD_2        0x40
+
+#define BTTN_COUNT_BOARD_1  6
+#define BTTN_COUNT_BOARD_2  6
+#define BTTN_COUNT          (BTTN_COUNT_BOARD_1 + BTTN_COUNT_BOARD_2)
+
+class ButtonBoard
+{
+private:
+    I2C _i2c;
+    InterruptIn _int1;
+    InterruptIn _int2;
+
+    char out_buf[8];
+    
+    // Callback functions
+    FunctionPointer _callback_table[BTTN_COUNT];
+    bool _callback_table_valid[BTTN_COUNT];
+    void (*_callbackFunction)(char buttonMask, bool pressed, char curState);
+    
+    // Interrupt handlers
+    void _int1_handler();
+    void _int2_handler();
+    void _fall_handler(char board);
+    
+    // status
+    volatile uint16_t _led_ports;
+    volatile uint8_t _button_state;
+    
+public:
+    ButtonBoard(PinName sda, PinName scl, PinName int1, PinName int2);
+    ~ButtonBoard();
+    
+    void registerCallback(uint32_t button, FunctionPointer p);
+    void registerCallback(void (*p)(char buttonMask, bool pressed, char curState));
+    //void setLED(uint32_t led, bool val);
+    void setLEDs(char mask, bool turnOn, char board = ADDR_BOARD_1);
+    char getLEDs(char ledMask, char board = ADDR_BOARD_1);
+    char getButtons(char buttonMask, char board = ADDR_BOARD_1);
+
+
+    //uint16_t readInputs();
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/FishController.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,488 @@
+
+#include "FishController.h"
+
+// The static instance
+FishController fishController;
+
+// Function to reset mbed
+extern "C" void mbed_reset();
+
+// Auto mode
+float autoModeCommands[][4] = {FISH_STRAIGHT, FISH_UP, FISH_STRAIGHT, FISH_DOWN};
+uint32_t autoModeDurations[] = {4000, 2000, 2000, 2000}; // durations in milliseconds (is it really? -> check if statement that compares autoModeDurations elements to 10*indexcount
+const uint8_t autoModeLength = sizeof(autoModeDurations)/sizeof(autoModeDurations[0]);
+
+//============================================
+// Initialization
+//============================================
+
+// Constructor
+FishController::FishController():
+    // Initialize variables
+	autoMode(false),
+	ignoreExternalCommands(false),
+    tickerInterval(fishControllerTickerInterval),
+	inTickerCallback(false),
+	servoLeft(servoLeftPin),
+    servoRight(servoRightPin),
+	#ifdef FISH4
+    curTime(0),
+    fullCycle(true),
+    raiser(3.5),
+    // Outputs for motor and servos
+    //motorPWM(motorPWMPin),
+    //motorOutA(motorOutAPin),
+    //motorOutB(motorOutBPin),
+    servoLeft(servoLeftPin),
+    servoRight(servoRightPin),
+    //brushlessMotor(p25),
+    brushlessOffTime(30000),
+	#endif
+/*	#ifdef FISH6 // these are declared in BCU class
+	pressureSensor(pressureSensorPinSDA, pressureSensorPinSCL, ms5837_addr_no_CS),
+	imuSensor(imuSensorPinSDA, imuSensorPinSCL)
+	#endif*/
+    // Button board
+	buttonBoard(buttonBoardSDAPin, buttonBoardSCLPin, buttonBoardInt1Pin, buttonBoardInt2Pin) // sda, scl, int1, int2
+
+{
+	streamFishStateEventController = 0;
+
+    newSelectButton = resetSelectButtonValue;
+    newPitch = resetPitchValue;
+    newYaw = resetYawValue;
+    newThrust = resetThrustValue;
+    newFrequency = resetFrequencyValue;
+    newPeriodHalf = resetPeriodHalfValue;
+
+    selectButton = newSelectButton;
+    pitch = newPitch;
+    yaw = newYaw;
+    thrust = newThrust;
+    frequency = newFrequency;
+#ifdef FISH4
+    periodHalf = newPeriodHalf;
+    thrustCommand = 0;
+    dutyCycle = 0;
+    brushlessOff = false;
+#endif
+
+    buttonBoard.registerCallback(&FishController::buttonCallback);
+    buttonBoard.setLEDs(255, false);
+
+    autoModeIndex = 0;
+    autoModeCount = 0;
+
+}
+
+// Set the desired state
+// They will take affect at the next appropriate time in the control cycle
+void FishController::setSelectButton(bool newSelectButtonValue, bool master /* = false*/)
+{
+	if(!ignoreExternalCommands || master)
+		newSelectButton = newSelectButtonValue;
+}
+void FishController::setPitch(float newPitchValue, bool master /* = false*/)
+{
+	if(!ignoreExternalCommands || master)
+	{
+		newPitch = newPitchValue;
+		setLEDs(BTTN_PITCH_UP,   (newPitch-fishMinPitch) > (fishMaxPitch - newPitch));
+		setLEDs(BTTN_PITCH_DOWN, (newPitch-fishMinPitch) < (fishMaxPitch - newPitch));
+	}
+}
+void FishController::setYaw(float newYawValue, bool master /* = false*/)
+{
+	if(!ignoreExternalCommands || master)
+	{
+		newYaw = newYawValue;
+		setLEDs(BTTN_YAW_LEFT,  (newYaw-fishMinYaw) < (fishMaxYaw - newYaw));
+		setLEDs(BTTN_YAW_RIGHT, (newYaw-fishMinYaw) > (fishMaxYaw - newYaw));
+	}
+}
+void FishController::setThrust(float newThrustValue, bool master /* = false*/)
+{
+	if(!ignoreExternalCommands || master)
+	{
+		newThrust = newThrustValue;
+		setLEDs(BTTN_FASTER, newThrust>fishMinThrust);
+		// If we're in button-control mode, keep the no-thrust light on as an indicator
+		if(!ignoreExternalCommands)
+			setLEDs(BTTN_SLOWER, newThrust==fishMinThrust);
+		else
+			setLEDs(BTTN_SLOWER, true);
+	}
+}
+void FishController::setFrequency(float newFrequencyValue, float newPeriodHalfValue /* = -1 */, bool master /* = false*/)
+{
+	if(!ignoreExternalCommands || master)
+	{
+		newFrequency = newFrequencyValue;
+		newPeriodHalf = newPeriodHalfValue > -1 ? newPeriodHalfValue : (1.0/(2.0*newFrequency));
+	}
+}
+// Get the (possible pending) state
+bool FishController::getSelectButton() {return newSelectButton;}
+float FishController::getPitch() {return newPitch;}
+float FishController::getYaw() {return newYaw;}
+float FishController::getThrust() {return newThrust;}
+float FishController::getFrequency() {return newFrequency;}
+float FishController::getPeriodHalf() {return newPeriodHalf;}
+
+void FishController::start()
+{
+
+    // Blink button board LEDs to indicate startup
+    for(uint8_t i = 0; i < 3; i++)
+    {
+        buttonBoard.setLEDs(255, true);
+        wait_ms(500);
+        buttonBoard.setLEDs(255, false);
+        wait_ms(500);
+    }
+    
+#ifdef FISH6
+    buoyancyControlUnit.start();
+    pumpWithValve.start();
+#endif
+
+    // Start control ticker callback
+    ticker.attach_us(&fishController, &FishController::tickerCallback, tickerInterval);
+#ifdef debugFishState
+    printf("Starting...\n");
+#endif
+
+
+}
+
+void FishController::stop()
+{
+    // Stop updating the fish
+    while(inTickerCallback); // wait for commands to settle
+    ticker.detach(); // stop updating commands
+    wait_ms(5);      // wait a bit to make sure it stops
+
+    // Reset fish state to neutral
+    newSelectButton = resetSelectButtonValue;
+    newPitch = resetPitchValue;
+    newYaw = resetYawValue;
+    newThrust = resetThrustValue;
+    newFrequency = resetFrequencyValue;
+    newPeriodHalf = resetPeriodHalfValue;
+    // Send commands to fish (multiple times to make sure we get in the right part of the cycle to actually update it)
+    for(int i = 0; i < 200; i++)
+    {
+    	tickerCallback();
+    	wait_ms(10);
+    }
+    // Make sure commands are sent to motors and applied
+    wait(1);
+
+	#ifdef FISH4
+    // Put dive planes in a weird position to indicate stopped
+    servoLeft = 0.3;
+    servoRight = 0.3;
+	#endif
+
+	#ifdef FISH6
+	pumpWithValve.stop();
+	buoyancyControlUnit.stop();
+	#endif // FISH6
+
+
+    // Light the LEDs to indicate termination
+    buttonBoard.setLEDs(255, true);
+}
+
+//============================================
+// Processing
+//============================================
+#ifdef FISH4
+void FishController::tickerCallback()
+{
+    inTickerCallback = true; // so we don't asynchronously stop the controller in a bad point of the cycle
+
+    // get the current elapsed time since last reset (us)
+    curTime += tickerInterval;
+
+    // see if brushless should be shut down
+    brushlessOff = curTime > (periodHalf-brushlessOffTime);
+
+    // update every half cycle
+    if(curTime > periodHalf)
+    {
+        // read new yaw value every half cycle
+        yaw = newYaw; // a value from -1 to 1
+
+        // Read frequency only every full cycle
+        if(fullCycle)
+        {
+            // Read other new inputs
+            thrust = newThrust; // a value from 0 to 1
+            frequency = newFrequency;
+            periodHalf = newPeriodHalf;
+            // Adjust thrust if needed
+            if(yaw < 0.0)
+                thrustCommand = (1.0 + 0.75*yaw)*thrust; // 0.7 can be adjusted to a power of 2 if needed
+            else
+            	thrustCommand = thrust;
+            fullCycle = false;
+        }
+        else
+        {
+            // Reverse for the downward slope
+            if(yaw > 0.0)
+            	thrustCommand = -(1.0 - 0.75*yaw)*thrust;
+            else
+            	thrustCommand = -thrust;
+            fullCycle = true;
+        }
+
+        // Reset time
+        curTime = 0;
+    }
+
+    // Update the servos
+    
+    pitch = newPitch;
+    servoLeft = pitch - 0.05; // The 0.03 calibrates the angles of the servo
+    servoRight = (1.0 - pitch) < 0.03 ? 0.03 : (1.0 - pitch);
+
+	//	Testing whether fishController is running
+	//	DigitalOut test(LED1);
+    //	test = 1;
+
+    // Update the duty cycle
+    /*dutyCycle = raiser * sin(PI2 * frequency * curTime); // add factor 4.0 to get a cut off sinus
+    if(dutyCycle > 1)
+        dutyCycle = 1;
+    if(dutyCycle < -1)
+        dutyCycle = -1;
+    dutyCycle *= thrustCommand;
+    if(dutyCycle >= 0 && dutyCycle < 0.01)
+        dutyCycle = 0;
+    if(dutyCycle < 0 && dutyCycle > -0.01)
+        dutyCycle = 0;
+    // Update the brushed motor
+    if(dutyCycle >= 0)
+    {
+        motorOutA.write(0);
+        motorOutB.write(1);
+        motorPWM.write(dutyCycle);
+    }
+    else
+    {
+        motorOutA.write(1);
+        motorOutB.write(0);
+        motorPWM.write(-1 * dutyCycle);
+    }*/
+    // Update the brushless motor
+    //brushlessMotor = dutyCycle * !brushlessOff;
+    //brushlessMotor.pulsewidth_us(dutyCycle*500+1500);
+    //brushlessMotor();
+
+
+#ifdef debugFishState
+    printDebugState();
+#endif
+    //printf("%f\n", dutyCycle);
+    //printf("%f %f\r\n", pitch, servoLeft.read());
+    inTickerCallback = false;
+}
+#endif
+
+#ifdef FISH6
+void FishController::tickerCallback()
+{
+    inTickerCallback = true; // so we don't asynchronously stop the controller in a bad point of the cycle
+
+    //set current state to newly commanded value
+    frequency = newFrequency;
+    yaw = newYaw;
+    thrust = newThrust;
+    pitch = newPitch;
+    
+    // Update dive planes
+    servoLeft = pitch - 0.05; // The 0.03 calibrates the angles of the servo
+    servoRight = (1.0 - pitch) < 0.03 ? 0.03 : (1.0 - pitch);
+    
+    pumpWithValve.set(frequency, yaw, thrust);
+    
+    /* TURNING OFF BCU FOR FIRST OPEN WORLD TEST - AUGUST 21, 2019*/
+    //buoyancyControlUnit.set(pitch); //1100 - 1180 seems to follow well
+    
+    //Testing whether fishController.tickerCallback() is running
+	//DigitalOut test(LED1);
+    //test = 1;
+    
+
+#ifdef debugFishState
+    printDebugState();
+#endif
+
+    inTickerCallback = false;
+}
+#endif
+
+
+// button will be mask indicating which button triggered this interrupt
+// pressed will indicate whether that button was pressed or released
+// buttonState will be a mask that indicates which buttons are currently pressed
+void FishController::buttonCallback(char button, bool pressed, char state) // static
+{
+    //printf("button %d\t pressed: %d\t state: %d\n", button, pressed, state);
+    //fishController.buttonBoard.setLEDs(button, !fishController.buttonBoard.getLEDs(button));
+    // Only act on button presses (not releases)
+    if(!pressed)
+        return;
+
+    DigitalOut* simBatteryLow;
+    float newYaw, newThrust, newPitch;
+    switch(state)
+    {
+        case BTTN_YAW_LEFT:
+        	newYaw = fishController.newYaw;
+        	newYaw -= (fishMaxYaw - fishMinYaw)/4.0;
+        	newYaw = newYaw < fishMinYaw ? fishMinYaw : newYaw;
+        	fishController.setYaw(newYaw, true);
+            fishController.streamFishStateEventController = 6;
+            break;
+        case BTTN_YAW_RIGHT:
+        	newYaw = fishController.newYaw;
+			newYaw += (fishMaxYaw - fishMinYaw)/4.0;
+			newYaw = newYaw > fishMaxYaw ? fishMaxYaw : newYaw;
+			fishController.setYaw(newYaw, true);
+            fishController.streamFishStateEventController = 7;
+            break;
+        case BTTN_FASTER:
+        	newThrust = fishController.newThrust;
+        	newThrust += (fishMaxThrust - fishMinThrust)/4.0;
+        	newThrust = newThrust > fishMaxThrust ? fishMaxThrust : newThrust;
+        	fishController.setThrust(newThrust, true);
+            fishController.streamFishStateEventController = 8;
+            break;
+        case BTTN_SLOWER:
+        	newThrust = fishController.newThrust;
+			newThrust -= (fishMaxThrust - fishMinThrust)/4.0;
+			newThrust = newThrust < fishMinThrust ? fishMinThrust : newThrust;
+			fishController.setThrust(newThrust, true);
+            fishController.streamFishStateEventController = 9;
+            break;
+        case BTTN_PITCH_UP:
+        	newPitch = fishController.newPitch;
+        	newPitch += (fishMaxPitch - fishMinPitch)/4.0;
+        	newPitch = newPitch > fishMaxPitch ? fishMaxPitch : newPitch;
+        	fishController.setPitch(newPitch, true);
+            fishController.streamFishStateEventController = 10;
+            break;
+        case BTTN_PITCH_DOWN:
+        	newPitch = fishController.newPitch;
+			newPitch -= (fishMaxPitch - fishMinPitch)/4.0;
+			newPitch = newPitch < fishMinPitch ? fishMinPitch : newPitch;
+			fishController.setPitch(newPitch, true);
+            fishController.streamFishStateEventController = 11;
+            break;
+        case BTTN_SHUTDOWN_PI: // signal a low battery signal to trigger the pi to shutdown
+        	fishController.streamFishStateEventController = 12;
+            simBatteryLow = new DigitalOut(lowBatteryVoltagePin);
+            simBatteryLow->write(0);
+            break;
+        case BTTN_RESET_MBED:
+        	fishController.streamFishStateEventController = 13; // ... if you see this, it didn't happen :)
+            mbed_reset();
+            break;
+        case BTTN_AUTO_MODE:
+        	fishController.streamFishStateEventController = 14;
+        	if(fishController.autoMode)
+        		fishController.stopAutoMode();
+        	else
+        		fishController.startAutoMode();
+        	break;
+        case BTTN_BTTN_MODE:
+        	fishController.setIgnoreExternalCommands(!fishController.getIgnoreExternalCommands());
+        	break;
+        default:
+        	fishController.streamFishStateEventController = 15;
+        	break;
+    }
+}
+
+void FishController::setIgnoreExternalCommands(bool ignore)
+{
+	ignoreExternalCommands = ignore;
+}
+
+bool FishController::getIgnoreExternalCommands()
+{
+	return ignoreExternalCommands;
+}
+
+void FishController::startAutoMode()
+{
+	// Start ignoring external commands so as not to interfere with auto mode
+	// But remember what the previous setting was so we can restore it after auto mode
+	ignoreExternalCommandsPreAutoMode = ignoreExternalCommands;
+	setIgnoreExternalCommands(true);
+	// Reset state
+	autoModeCount = 0;
+	autoModeIndex = 0;
+	// Start executing the auto loop
+	autoMode = true;
+	autoModeTicker.attach_us(&fishController, &FishController::autoModeCallback, 10000);
+}
+
+void FishController::stopAutoMode()
+{
+	autoModeTicker.detach();
+	// Auto mode was terminated - put fish into a neutral position
+	setSelectButton(resetSelectButtonValue, true);
+	setPitch(resetPitchValue, true);
+	setYaw(resetYawValue, true);
+	setThrust(resetThrustValue, true);
+	setFrequency(resetFrequencyValue, resetPeriodHalfValue, true);
+	// Restore external mode to what is was previously
+	setIgnoreExternalCommands(ignoreExternalCommandsPreAutoMode);
+	autoMode = false;
+}
+
+void FishController::autoModeCallback()
+{
+	// Assign the current state (stored as pitch, yaw, thrust, frequency)
+	setPitch(autoModeCommands[autoModeIndex][0], true);
+	setYaw(autoModeCommands[autoModeIndex][1], true);
+	setThrust(autoModeCommands[autoModeIndex][2], true);
+	setFrequency(autoModeCommands[autoModeIndex][3], 1.0/(2.0*autoModeCommands[autoModeIndex][3]), true);
+	// See if we advance to the next command
+	autoModeCount++;
+	if(autoModeCount*10 > autoModeDurations[autoModeIndex])
+	{
+		autoModeCount = 0;
+		autoModeIndex = (autoModeIndex+1) % autoModeLength; // loop continuously through commands
+	}
+}
+
+#ifdef debugFishState
+void FishController::printDebugState()
+{
+    printf("pitch: %f yaw: %f thrust: %f frequency: %.8f\r\n",
+            pitch, yaw, thrust, frequency);
+}
+
+#endif
+
+void FishController::setLEDs(char mask, bool turnOn)
+{
+    buttonBoard.setLEDs(mask, turnOn);
+}
+
+#ifdef debugBCU
+/* BCU + Pressure Sensor Helper Functions */
+
+float FishController::getBCUVset(){ return buoyancyControlUnit.getVset(); }
+float FishController::getBCUSetDepth(){	return buoyancyControlUnit.getSetDepth(); }
+float FishController::getBCUCurDepth(){	return buoyancyControlUnit.getCurDepth(); }
+float FishController::getBCUSetPos(){ return buoyancyControlUnit.getSetPos(); }
+float FishController::getBCUCurPos(){ return buoyancyControlUnit.getCurPos(); }
+float FishController::getreadPressure(){ return buoyancyControlUnit.readPressure(); }
+
+#endif
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/FishController.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,237 @@
+/*
+ * AcousticController.h
+ * Author: Joseph DelPreto
+ */
+
+#ifndef FISH_CONTROLLER_H
+#define FISH_CONTROLLER_H
+
+// comment out if no debug wanted
+#define debugFishState
+
+// Fish version (only define one of them)
+#define FISH6
+//#define FISH4
+
+#include "mbed.h"
+#include "string"
+#include "ButtonBoard.h"
+#ifdef FISH4
+#include "Servo.h"
+#include "esc.h" // brushless motor controller
+#endif
+#ifdef FISH6
+#include "Servo.h"
+#include "PumpWithValve/PumpWithValve.h"
+#include "BuoyancyControlUnit/BuoyancyControlUnit.h"
+#endif
+
+
+// Control
+#define fishControllerTickerInterval 1000 // how often to call the control ticker, in microseconds
+
+// Constants
+#define PI2 6.2831853  // PI is not included with math.h for some reason
+// Values to use for resetting the fish to neutral
+#define resetSelectButtonValue 0
+#define resetPitchValue        0.5
+#define resetYawValue          0
+#define resetThrustValue       0
+#define resetFrequencyValue    0.0000012 // cycles/us
+#define resetPeriodHalfValue   416666    // 1/(2*frequency) -> us
+
+// Value ranges
+#ifdef FISH4
+#define fishMinPitch     ((float)(0.2)) // will want to redefine for fish 6 based on depth instead
+#define fishMaxPitch     ((float)(0.8))
+#endif
+
+#ifdef FISH6
+#define fishMinPitch     ((float)(0.2)) // will want to redefine for fish 6 based on depth instead
+#define fishMaxPitch     ((float)(0.8))
+//#define fishMinPitch     ((float)(0.0)) // seems appropriate for BCUs
+//#define fishMaxPitch     ((float)(30.0))
+#endif
+
+#define fishMinYaw       ((float)(-1.0))
+#define fishMaxYaw       ((float)(1.0))
+
+#define fishMinThrust    ((float)(0.0))
+#ifdef FISH4
+#define fishMaxThrust    ((float)(0.75))
+#endif
+#ifdef FISH6
+#define fishMaxThrust    ((float)(1.0))
+#endif
+
+#define fishMinFrequency ((float)(0.0000009))
+#define fishMaxFrequency ((float)(0.0000016))
+
+// Preset states for auto mode definition
+// Each one is pitch, yaw, thrust, frequency
+#define FISH_STRAIGHT 	{resetPitchValue, resetYawValue	, (fishMaxThrust + fishMinThrust)/2.0	, (fishMaxFrequency + fishMinFrequency)/2.0}
+#define FISH_UP 		{fishMaxPitch	, resetYawValue	, (fishMaxThrust + fishMinThrust)/2.0	, (fishMaxFrequency + fishMinFrequency)/2.0}
+#define FISH_DOWN 		{fishMinPitch	, resetYawValue	, (fishMaxThrust + fishMinThrust)/2.0	, (fishMaxFrequency + fishMinFrequency)/2.0}
+#define FISH_LEFT 		{resetPitchValue, fishMaxYaw	, (fishMaxThrust + fishMinThrust)/2.0	, (fishMaxFrequency + fishMinFrequency)/2.0}
+#define FISH_RIGHT 		{resetPitchValue, fishMinYaw	, (fishMaxThrust + fishMinThrust)/2.0	, (fishMaxFrequency + fishMinFrequency)/2.0}
+#define FISH_STOP 		{resetPitchValue, resetYawValue	, resetThrustValue						, resetFrequencyValue}
+
+// Pins
+#define lowBatteryVoltagePin p16
+
+#ifdef FISH4
+#define motorPWMPin   p23
+#define motorOutAPin  p11
+#define motorOutBPin  p12
+#define servoLeftPin  p21
+#define servoRightPin p26 //p24
+#endif
+
+#ifdef FISH6
+// NOTE: FISH6 pins are defined in BCU and Valve classes
+#define pressureSensorPinSDA p28
+#define pressureSensorPinSCL p27
+#define imuSensorPinSDA p28
+#define imuSensorPinSCL p27
+#define servoLeftPin  p21
+#define servoRightPin p26
+#endif
+
+
+#define buttonBoardSDAPin  p9
+#define buttonBoardSCLPin  p10
+#define buttonBoardInt1Pin p29
+#define buttonBoardInt2Pin p30
+
+/* Button board commands
+  Commented indexes go from top left (0) to bottom right (5) as follows:
+                 /=========================|
+  	  	  	   /   ______________________  |
+  	  	  	 /	  |	 (0:8) (1:16) (2:32) | |
+  fish nose |     |(3:1) (4:2) (5:4)     | |  fish tail
+  	  	  	 \     ----------------------| |
+  	  	  	   \                           |
+  	  	  	     \=========================|
+  The numbers after the colons are the values to use for that button
+*/
+#define BTTN_FASTER      1  // 3
+#define BTTN_SLOWER      2  // 4
+#define BTTN_YAW_LEFT    4  // 5
+#define BTTN_YAW_RIGHT   8  // 0
+#define BTTN_PITCH_UP    16 // 1 // swims down
+#define BTTN_PITCH_DOWN  32 // 2 // swims up
+#define BTTN_RESET_MBED  36 // 2 and 5
+#define BTTN_SHUTDOWN_PI 9  // 0 and 3
+#define BTTN_AUTO_MODE   33 // 2 and 3
+#define BTTN_BTTN_MODE   12 // 0 and 5
+
+
+class FishController
+{
+    public:
+        // Initialization
+        FishController();
+        void start();
+        void stop();
+        // Processing
+        void tickerCallback();
+        // Debug / Logging
+        volatile uint8_t streamFishStateEventController; // will indicate the last button board event - up to the caller to reset it if desired
+        #ifdef debugFishState
+        void printDebugState();
+        #endif
+        // LEDs
+        void setLEDs(char mask, bool turnOn);
+        // Set New State (which will take affect at next appropriate point in control cycle)
+		void setSelectButton(bool newSelectButtonValue, bool master= false);
+		void setPitch(float newPitchValue, bool master = false);
+		void setYaw(float newYawValue, bool master = false);
+		void setThrust(float newThrustValue, bool master = false);
+		void setFrequency(float newFrequencyValue, float newPeriodHalfValue = -1, bool master = false);
+		// Get (possible pending) State
+		bool getSelectButton();
+		float getPitch();
+		float getYaw();
+		float getThrust();
+		float getFrequency();
+		float getPeriodHalf();
+		// Auto mode
+		volatile bool autoMode;
+		void startAutoMode();
+		void stopAutoMode();
+		void autoModeCallback();
+
+		void setIgnoreExternalCommands(bool ignore);
+		bool getIgnoreExternalCommands();
+		
+		// BCU Helper Functions
+        float getBCUVset();
+        float getBCUSetDepth();
+        float getBCUCurDepth();
+        float getBCUSetPos();
+        float getBCUCurPos();
+        float getreadPressure(); 
+        
+    private:
+		// Misc State
+		volatile bool ignoreExternalCommands;
+		// Ticker for controlling tail
+		Ticker ticker;
+		const uint16_t tickerInterval;
+		volatile bool inTickerCallback;
+
+		// State which will be applied at the next appropriate time in the control cycle
+		volatile bool newSelectButton;
+		volatile float newPitch;
+		volatile float newYaw;
+		volatile float newThrust;
+		volatile float newFrequency;
+		volatile float newPeriodHalf;
+
+        // State currently executing on fish
+        volatile bool selectButton;
+        volatile float pitch;
+        volatile float yaw;
+        volatile float thrust;
+        volatile float frequency;
+        
+        // Servos (Fish 6)
+        Servo servoLeft;
+        Servo servoRight;
+        
+
+#ifdef FISH4
+        volatile float thrustCommand;
+        volatile float periodHalf;
+        volatile float dutyCycle;
+        volatile bool brushlessOff;
+        volatile uint32_t curTime;
+        volatile bool fullCycle;
+        const float raiser;
+        // Outputs for motor and servos
+        //PwmOut motorPWM;
+        //DigitalOut motorOutA;
+        //DigitalOut motorOutB;
+        Servo servoLeft;
+        Servo servoRight;
+        //PwmOut brushlessMotor;
+        const uint32_t brushlessOffTime;
+#endif
+
+        // Button control
+        ButtonBoard buttonBoard;
+        static void buttonCallback(char button, bool pressed, char state);
+
+        // Auto mode
+        Ticker autoModeTicker;
+        uint32_t autoModeCount;
+        uint16_t autoModeIndex;
+        bool ignoreExternalCommandsPreAutoMode;
+};
+
+// Create a static instance of FishController to be used by anyone doing detection
+extern FishController fishController;
+extern volatile uint8_t streamFishStateEvent;
+extern volatile uint16_t streamCurFishState;
+
+#endif // ifndef FISH_CONTROLLER_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/MODDMA.lib	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,1 @@
+https://developer.mbed.org/teams/jetfishteam/code/MODDMA/#aa486e03742a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/MS5837/MS5837.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,100 @@
+#include <stdlib.h>
+#include "MS5837.h"
+
+
+/*
+ * Sensor operating function according data sheet
+ */
+
+void MS5837::MS5837Init(void)
+{
+    MS5837Reset();
+    MS5837ReadProm();
+    return;
+}
+
+/* Send soft reset to the sensor */
+void MS5837::MS5837Reset(void)
+{
+    /* transmit out 1 byte reset command */
+    ms5837_tx_data[0] = ms5837_reset;
+    if ( i2c.write( device_address,  ms5837_tx_data, 1 ) );
+    //printf("send soft reset");
+    wait_ms(20);
+}
+
+/* read the sensor calibration data from rom */
+void MS5837::MS5837ReadProm(void)
+{
+    uint8_t i,j;
+    for (i=0; i<8; i++) {
+        j = i;
+        ms5837_tx_data[0] = ms5837_PROMread + (j<<1);
+        if ( i2c.write( device_address,  ms5837_tx_data, 1 ) );
+        if ( i2c.read( device_address,  ms5837_rx_data, 2 ) );
+        C[i]   = ms5837_rx_data[1] + (ms5837_rx_data[0]<<8);
+    }
+}
+
+/* Start the sensor pressure conversion */
+void MS5837::MS5837ConvertD1(void)
+{
+    ms5837_tx_data[0] = ms5837_convD1;
+    if ( i2c.write( device_address,  ms5837_tx_data, 1 ) );
+}
+
+/* Start the sensor temperature conversion */
+void MS5837:: MS5837ConvertD2(void)
+{
+    ms5837_tx_data[0] = ms5837_convD2;
+    if ( i2c.write( device_address,  ms5837_tx_data, 1 ) );
+}
+
+/* Read the previous started conversion results */
+int32_t MS5837::MS5837ReadADC(void)
+{
+    int32_t adc;
+    wait_ms(150);
+    ms5837_tx_data[0] = ms5837_ADCread;
+    if ( i2c.write( device_address,  ms5837_tx_data, 1 ) );
+    if ( i2c.read( device_address,  ms5837_rx_data, 3 ) );
+    adc = ms5837_rx_data[2] + (ms5837_rx_data[1]<<8) + (ms5837_rx_data[0]<<16);
+    return (adc);
+}
+
+/* return the results */
+float MS5837::MS5837_Pressure (void)
+{
+    return P_MS5837;
+}
+float MS5837::MS5837_Temperature (void)
+{
+    return T_MS5837;
+}
+
+/* Sensor reading and calculation procedure */
+void MS5837::Barometer_MS5837(void)
+{
+    int32_t dT, temp;
+    int64_t OFF, SENS, press;
+
+    //no need to do this everytime!
+    //MS5837Reset();                 // reset the sensor
+    //MS5837ReadProm();             // read the calibration values
+    
+    
+    MS5837ConvertD1();             // start pressure conversion
+    D1 = MS5837ReadADC();        // read the pressure value
+    MS5837ConvertD2();             // start temperature conversion
+    D2 = MS5837ReadADC();         // read the temperature value
+
+    /* calculation according MS5837-01BA data sheet DA5837-01BA_006 */
+    dT       = D2 - (C[5]* 256);
+    OFF      = (int64_t)C[2] * (1<<16) + ((int64_t)dT * (int64_t)C[4]) / (1<<7);
+    SENS     = (int64_t)C[1] * (1<<15) + ((int64_t)dT * (int64_t)C[3]) / (1<<8);
+
+    temp     = 2000 + (dT * C[6]) / (1<<23);
+    T_MS5837 = (float) temp / 100.0f;                 // result of temperature in deg C in this var
+    press    = (((int64_t)D1 * SENS) / (1<<21) - OFF) / (1<<13);
+    P_MS5837 = (float) press / 10.0f;                 // result of pressure in mBar in this var
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/MS5837/MS5837.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,64 @@
+#include "mbed.h"
+
+#ifndef MS5837_H
+#define MS5837_H
+
+#define MS5837_RX_DEPTH 3 //
+#define MS5837_TX_DEPTH 2 //
+
+// choose your connection here
+#define ms5837_addr_no_CS  0x76 //0b1110110
+
+#define ms5837_reset       0x1E // Sensor Reset
+
+#define ms5837_convD1_256  0x40 // Convert D1 OSR 256
+#define ms5837_convD1_512  0x42 // Convert D1 OSR 512
+#define ms5837_convD1_1024 0x44 // Convert D1 OSR 1024
+#define ms5837_convD1_2048 0x46 // Convert D1 OSR 2048
+#define ms5837_convD1_4096 0x48 // Convert D1 OSR 4096
+#define ms5837_convD1_8192 0x4A // Convert D1 OSR 8192
+
+#define ms5837_convD1 ms5837_convD1_4096 // choose your sampling rate here
+
+#define ms5837_convD2_256  0x50 // Convert D2 OSR  256
+#define ms5837_convD2_512  0x52 // Convert D2 OSR  512
+#define ms5837_convD2_1024 0x54 // Convert D2 OSR 1024
+#define ms5837_convD2_2048 0x56 // Convert D2 OSR 2048
+#define ms5837_convD2_4096 0x58 // Convert D2 OSR 4096
+#define ms5837_convD2_8192 0x5A // Convert D2 OSR 8192
+
+#define ms5837_convD2 ms5837_convD2_4096 // choose your sampling rate here
+
+#define ms5837_ADCread     0x00 // read ADC command
+#define ms5837_PROMread    0xA0 // read PROM command base address
+
+class MS5837{
+private:
+    int D1, D2, Temp, C[8];
+    float T_MS5837, P_MS5837;
+    /* Data buffers */
+    char ms5837_rx_data[MS5837_RX_DEPTH];
+    char ms5837_tx_data[MS5837_TX_DEPTH];
+
+public:
+    MS5837 (PinName sda, PinName scl,
+            char ms5837_addr = ms5837_addr_no_CS  )
+            : i2c( sda, scl ), device_address( ms5837_addr << 1 ) {
+    }
+    void MS5837Init(void);
+    void MS5837Reset(void);
+    void MS5837ReadProm(void);
+    void MS5837ConvertD1(void);
+    void MS5837ConvertD2(void);
+    int32_t MS5837ReadADC(void);
+    float MS5837_Pressure (void);
+    float MS5837_Temperature (void);
+    void Barometer_MS5837(void);
+
+
+private:
+    I2C     i2c;
+    char    device_address;
+
+};
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/PumpWithValve/PumpWithValve.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,166 @@
+#include <PumpWithValve/PumpWithValve.h>
+
+// The static instance
+PumpWithValve pumpWithValve;
+
+void flipFlowUpStatic()
+{
+	pumpWithValve.flipFlowUp();
+}
+
+void flipFlowDownStatic()
+{
+	pumpWithValve.flipFlowDown();
+}
+
+
+//============================================
+// Initialization
+//============================================
+
+// Constructor
+PumpWithValve::PumpWithValve() :
+    		// Initialize variables
+    		pumpPWM(pumpPwmPin),
+			valvePWM(valvePwmPin),
+			//valveEncoder(encoderPinA, encoderPinB, NC, count2rev), // use X2 encoding by default
+			//valveCurrent(valveCurrentPin),
+			hallSignal(hallInterruptPin),
+			valveLED(LED4)
+{
+	hallSignal.rise(&flipFlowUpStatic);
+	hallSignal.fall(&flipFlowDownStatic);
+
+	frequency = 0;
+	thrust = 0;
+	yaw = 0;
+
+	valveSide = true;
+	valveV1 = 0;
+	valveV2 = 0;
+	Vfreq = 0;
+	VfreqAdjusted = 0;
+	Vset = 0;
+	dVFreq = 0;
+	freqErr = 0;
+	prevFreqErr = 0;
+
+	timer.start();
+}
+
+
+void PumpWithValve::start()
+{
+	valvePWM.write(0.0); // apply nothing to start
+	pumpPWM.write(0.0);
+	periodSide1 = 0.0;
+	periodSide2 = 0.0;
+
+	timer.reset();
+	valveControl.attach(&pumpWithValve, &PumpWithValve::setVoid, 0.08);
+}
+
+void PumpWithValve::stop()
+{
+	valveControl.detach();
+	valvePWM.write(0.0);
+	pumpPWM.write(0.0);
+}
+
+void PumpWithValve::flipFlowUp()
+{
+	// when the hall sensor sees a rising edge, we have rotated 180 degrees
+	// --> want to adjust the applied voltage based on the side we are on
+	valveSide = true; //change boolean state, keeps track of which half of the rotation we are on
+	valveLED = 1;
+	periodSide1 = timer.read_us();
+	timer.reset();
+	freqAct = 1/(periodSide1 + periodSide2);
+}
+
+void PumpWithValve::flipFlowDown()
+{
+	valveSide = false;
+	valveLED = 0;
+	periodSide2 = timer.read_us();
+	timer.reset();
+	freqAct = 1/(periodSide1 + periodSide2);
+}
+
+//============================================
+// Processing
+//============================================
+void PumpWithValve::set(float freq_in, float yaw_in, float thrust_in){
+	thrust = thrust_in;
+	yaw = yaw_in;
+	frequency = freq_in;
+}
+
+void PumpWithValve::setVoid() {
+	//Centrifugal Pump
+	pumpPWM.write(thrust);
+
+	// set speed of the valve motor through the frequency value
+	if (periodSide1 == 0 || periodSide2 == 0) {
+		Vfreq = frequency * 400000; //just setting directly the voltage, scaled up; need to tune this value
+		this->calculateYawMethod1();
+	}
+	else { // don't be fooled by initialization values
+		   // Failure mode - if it has been a full (desired) period since a hall sensor has been read
+		if (timer.read_us() > 1.0 / frequency) {
+			pumpWithValve.stop(); // may have to add a condition that allows for sudden input changes
+		}
+		else {
+			freqErr = frequency - freqAct;
+			dVFreq = KpFreq * freqErr + KdFreq * (freqErr - prevFreqErr);
+			prevFreqErr = freqErr; //reset previous frequency error
+			Vfreq += dVFreq;
+			this->calculateYawMethod1();
+		}
+	}
+}
+
+
+void PumpWithValve::calculateYawMethod1()
+{
+	// split tail frequency voltage into voltage on either side of the valve
+	// TODO figure out ideal relationship between yaw and offf between V1 and V2
+	// is it additive or multiplicative? start with super simple math
+
+	valveV1 = (1.0 + valveOffsetGain * yaw) * Vfreq;
+	valveV2 = (1.0 - valveOffsetGain * yaw) * Vfreq;
+
+	// TODO need to decide whether to give up frequency or yaw when we are at input limits
+	if (valveV1 > 1.0) {valveV1 = 1.0;}
+	else if (valveV1 < 0.0) {valveV1 = 0.05;}
+	if (valveV2 > 1.0) {valveV2 = 1.0;}
+	else if (valveV2 < 0.0) {valveV2 = 0.05;}
+
+	// write valve voltage based on which side the hall sensor says we are on
+	if (valveSide) { Vset = valveV1; }
+	else { Vset = valveV2; }
+
+	valvePWM.write(Vset);
+}
+
+void PumpWithValve::calculateYawMethod2()
+{
+
+	if (yaw < 0.0 && !valveSide) {
+		Vset = (1.0 + valveOffsetGain*yaw)*Vfreq; // 0.7 can be adjusted to a power of 2 if needed
+		if (Vset > 1.0) { VfreqAdjusted = 1.0; }
+	} 
+	else if (yaw > 0.0 && valveSide) {
+		Vset = (1.0 - valveOffsetGain*yaw)*Vfreq; // 0.7 can be adjusted to a power of 2 if needed
+		if (Vset < 0.0) { VfreqAdjusted = 0.05; } // needs to keep turning
+	}
+	else {
+		Vset = Vfreq;
+		VfreqAdjusted = Vfreq;
+	}
+	valvePWM.write(VfreqAdjusted);
+
+}
+
+float PumpWithValve::getVset() { return Vset;}
+bool PumpWithValve::getVside() { return valveSide; }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/PumpWithValve/PumpWithValve.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,80 @@
+
+/*
+ * PumpWithValve.h
+ *
+ * Author: Cyndia Cao, Robert Katzschmann
+ */
+
+#ifndef PUMPWITHVALVE_H_
+#define PUMPWITHVALVE_H_
+
+#include "mbed.h"
+
+#define valvePwmPin 		p22
+#define pumpPwmPin 			p23
+#define hallInterruptPin 	p12
+//#define encoderPinA 		p25
+//#define encoderPinB 		p24
+//#define valveCurrentPin 	p19
+
+#define count2rev 			12
+#define valveMotorGearRatio 297.92
+#define KpFreq	 			10000.0 // frequency on the order of 10^-7
+#define KdFreq	 			0.00
+#define valveOffsetGain 	0.5
+
+class PumpWithValve
+{
+public:
+	// Initialization
+	PumpWithValve();
+	void start();
+	void stop();
+
+	void flipFlowUp();
+	void flipFlowDown();
+	void set(float freq_in, float yaw_in, float thrust_in);
+	void setVoid();
+
+	float getVset();
+	bool getVside();
+
+protected:
+	void calculateYawMethod1();
+	void calculateYawMethod2();
+
+private:
+
+	volatile float frequency;
+	volatile float yaw;
+	volatile float thrust;
+	volatile float periodSide1;
+	volatile float periodSide2;
+
+	volatile bool 	valveSide;
+	volatile float 	valveV1;
+	volatile float 	valveV2;
+	volatile float 	Vfreq;
+	volatile float 	VfreqAdjusted;
+	volatile float  Vset;
+
+	volatile float freqAct;
+	volatile float freqErr;
+	volatile float prevFreqErr;
+	volatile float dVFreq;
+
+	PwmOut pumpPWM;
+	PwmOut valvePWM;
+	//QEI valveEncoder;
+	//AnalogIn valveCurrent; // actually is a voltage value proportional to current
+	InterruptIn hallSignal;
+	Timer timer;
+	DigitalOut valveLED;
+
+	Ticker valveControl;
+};
+
+// Create a static instance of PumpWithValve to be used by anyone controlling the pump with valve
+extern PumpWithValve pumpWithValve;
+
+#endif /* PUMPWITHVALVE_H_ */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/QEI/QEI.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,289 @@
+/**
+ * @author Aaron Berk
+ *
+ * @section LICENSE
+ *
+ * Copyright (c) 2010 ARM Limited
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @section DESCRIPTION
+ *
+ * Quadrature Encoder Interface.
+ *
+ * A quadrature encoder consists of two code tracks on a disc which are 90
+ * degrees out of phase. It can be used to determine how far a wheel has
+ * rotated, relative to a known starting position.
+ *
+ * Only one code track changes at a time leading to a more robust system than
+ * a single track, because any jitter around any edge won't cause a state
+ * change as the other track will remain constant.
+ *
+ * Encoders can be a homebrew affair, consisting of infrared emitters/receivers
+ * and paper code tracks consisting of alternating black and white sections;
+ * alternatively, complete disk and PCB emitter/receiver encoder systems can
+ * be bought, but the interface, regardless of implementation is the same.
+ *
+ *               +-----+     +-----+     +-----+
+ * Channel A     |  ^  |     |     |     |     |
+ *            ---+  ^  +-----+     +-----+     +-----
+ *               ^  ^
+ *               ^  +-----+     +-----+     +-----+
+ * Channel B     ^  |     |     |     |     |     |
+ *            ------+     +-----+     +-----+     +-----
+ *               ^  ^
+ *               ^  ^
+ *               90deg
+ *
+ * The interface uses X2 encoding by default which calculates the pulse count
+ * based on reading the current state after each rising and falling edge of
+ * channel A.
+ *
+ *               +-----+     +-----+     +-----+
+ * Channel A     |     |     |     |     |     |
+ *            ---+     +-----+     +-----+     +-----
+ *               ^     ^     ^     ^     ^
+ *               ^  +-----+  ^  +-----+  ^  +-----+
+ * Channel B     ^  |  ^  |  ^  |  ^  |  ^  |     |
+ *            ------+  ^  +-----+  ^  +-----+     +--
+ *               ^     ^     ^     ^     ^
+ *               ^     ^     ^     ^     ^
+ * Pulse count 0 1     2     3     4     5  ...
+ *
+ * This interface can also use X4 encoding which calculates the pulse count
+ * based on reading the current state after each rising and falling edge of
+ * either channel.
+ *
+ *               +-----+     +-----+     +-----+
+ * Channel A     |     |     |     |     |     |
+ *            ---+     +-----+     +-----+     +-----
+ *               ^     ^     ^     ^     ^
+ *               ^  +-----+  ^  +-----+  ^  +-----+
+ * Channel B     ^  |  ^  |  ^  |  ^  |  ^  |     |
+ *            ------+  ^  +-----+  ^  +-----+     +--
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ * Pulse count 0 1  2  3  4  5  6  7  8  9  ...
+ *
+ * It defaults
+ *
+ * An optional index channel can be used which determines when a full
+ * revolution has occured.
+ *
+ * If a 4 pules per revolution encoder was used, with X4 encoding,
+ * the following would be observed.
+ *
+ *               +-----+     +-----+     +-----+
+ * Channel A     |     |     |     |     |     |
+ *            ---+     +-----+     +-----+     +-----
+ *               ^     ^     ^     ^     ^
+ *               ^  +-----+  ^  +-----+  ^  +-----+
+ * Channel B     ^  |  ^  |  ^  |  ^  |  ^  |     |
+ *            ------+  ^  +-----+  ^  +-----+     +--
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ *               ^  ^  ^  +--+  ^  ^  +--+  ^
+ *               ^  ^  ^  |  |  ^  ^  |  |  ^
+ * Index      ------------+  +--------+  +-----------
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ * Pulse count 0 1  2  3  4  5  6  7  8  9  ...
+ * Rev.  count 0          1           2
+ *
+ * Rotational position in degrees can be calculated by:
+ *
+ * (pulse count / X * N) * 360
+ *
+ * Where X is the encoding type [e.g. X4 encoding => X=4], and N is the number
+ * of pulses per revolution.
+ *
+ * Linear position can be calculated by:
+ *
+ * (pulse count / X * N) * (1 / PPI)
+ *
+ * Where X is encoding type [e.g. X4 encoding => X=44], N is the number of
+ * pulses per revolution, and PPI is pulses per inch, or the equivalent for
+ * any other unit of displacement. PPI can be calculated by taking the
+ * circumference of the wheel or encoder disk and dividing it by the number
+ * of pulses per revolution.
+ */
+
+/**
+ * Includes
+ */
+#include "QEI.h"
+
+QEI::QEI(PinName channelA,
+         PinName channelB,
+         PinName index,
+         int pulsesPerRev,
+         Encoding encoding) : channelA_(channelA), channelB_(channelB),
+        index_(index) {
+
+    pulses_       = 0;
+    revolutions_  = 0;
+    pulsesPerRev_ = pulsesPerRev;
+    encoding_     = encoding;
+
+    //Workout what the current state is.
+    int chanA = channelA_.read();
+    int chanB = channelB_.read();
+
+    //2-bit state.
+    currState_ = (chanA << 1) | (chanB);
+    prevState_ = currState_;
+
+    //X2 encoding uses interrupts on only channel A.
+    //X4 encoding uses interrupts on      channel A,
+    //and on channel B.
+    channelA_.rise(this, &QEI::encode);
+    channelA_.fall(this, &QEI::encode);
+
+    //If we're using X4 encoding, then attach interrupts to channel B too.
+    if (encoding == X4_ENCODING) {
+        channelB_.rise(this, &QEI::encode);
+        channelB_.fall(this, &QEI::encode);
+    }
+    //Index is optional.
+    if (index !=  NC) {
+        index_.rise(this, &QEI::index);
+    }
+
+}
+
+void QEI::reset(void) {
+
+    pulses_      = 0;
+    revolutions_ = 0;
+
+}
+
+int QEI::getCurrentState(void) {
+
+    return currState_;
+
+}
+
+int QEI::getPulses(void) {
+
+    return pulses_;
+
+}
+
+int QEI::getRevolutions(void) {
+
+    return revolutions_;
+
+}
+
+// +-------------+
+// | X2 Encoding |
+// +-------------+
+//
+// When observing states two patterns will appear:
+//
+// Counter clockwise rotation:
+//
+// 10 -> 01 -> 10 -> 01 -> ...
+//
+// Clockwise rotation:
+//
+// 11 -> 00 -> 11 -> 00 -> ...
+//
+// We consider counter clockwise rotation to be "forward" and
+// counter clockwise to be "backward". Therefore pulse count will increase
+// during counter clockwise rotation and decrease during clockwise rotation.
+//
+// +-------------+
+// | X4 Encoding |
+// +-------------+
+//
+// There are four possible states for a quadrature encoder which correspond to
+// 2-bit gray code.
+//
+// A state change is only valid if of only one bit has changed.
+// A state change is invalid if both bits have changed.
+//
+// Clockwise Rotation ->
+//
+//    00 01 11 10 00
+//
+// <- Counter Clockwise Rotation
+//
+// If we observe any valid state changes going from left to right, we have
+// moved one pulse clockwise [we will consider this "backward" or "negative"].
+//
+// If we observe any valid state changes going from right to left we have
+// moved one pulse counter clockwise [we will consider this "forward" or
+// "positive"].
+//
+// We might enter an invalid state for a number of reasons which are hard to
+// predict - if this is the case, it is generally safe to ignore it, update
+// the state and carry on, with the error correcting itself shortly after.
+void QEI::encode(void) {
+
+    int change = 0;
+    int chanA  = channelA_.read();
+    int chanB  = channelB_.read();
+
+    //2-bit state.
+    currState_ = (chanA << 1) | (chanB);
+
+    if (encoding_ == X2_ENCODING) {
+
+        //11->00->11->00 is counter clockwise rotation or "forward".
+        if ((prevState_ == 0x3 && currState_ == 0x0) ||
+                (prevState_ == 0x0 && currState_ == 0x3)) {
+
+            pulses_++;
+
+        }
+        //10->01->10->01 is clockwise rotation or "backward".
+        else if ((prevState_ == 0x2 && currState_ == 0x1) ||
+                 (prevState_ == 0x1 && currState_ == 0x2)) {
+
+            pulses_--;
+
+        }
+
+    } else if (encoding_ == X4_ENCODING) {
+
+        //Entered a new valid state.
+        if (((currState_ ^ prevState_) != INVALID) && (currState_ != prevState_)) {
+            //2 bit state. Right hand bit of prev XOR left hand bit of current
+            //gives 0 if clockwise rotation and 1 if counter clockwise rotation.
+            change = (prevState_ & PREV_MASK) ^ ((currState_ & CURR_MASK) >> 1);
+
+            if (change == 0) {
+                change = -1;
+            }
+
+            pulses_ -= change;
+        }
+
+    }
+
+    prevState_ = currState_;
+
+}
+
+void QEI::index(void) {
+
+    revolutions_++;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/QEI/QEI.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,244 @@
+/**
+ * @author Aaron Berk
+ *
+ * @section LICENSE
+ *
+ * Copyright (c) 2010 ARM Limited
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @section DESCRIPTION
+ *
+ * Quadrature Encoder Interface.
+ *
+ * A quadrature encoder consists of two code tracks on a disc which are 90
+ * degrees out of phase. It can be used to determine how far a wheel has
+ * rotated, relative to a known starting position.
+ *
+ * Only one code track changes at a time leading to a more robust system than
+ * a single track, because any jitter around any edge won't cause a state
+ * change as the other track will remain constant.
+ *
+ * Encoders can be a homebrew affair, consisting of infrared emitters/receivers
+ * and paper code tracks consisting of alternating black and white sections;
+ * alternatively, complete disk and PCB emitter/receiver encoder systems can
+ * be bought, but the interface, regardless of implementation is the same.
+ *
+ *               +-----+     +-----+     +-----+
+ * Channel A     |  ^  |     |     |     |     |
+ *            ---+  ^  +-----+     +-----+     +-----
+ *               ^  ^
+ *               ^  +-----+     +-----+     +-----+
+ * Channel B     ^  |     |     |     |     |     |
+ *            ------+     +-----+     +-----+     +-----
+ *               ^  ^
+ *               ^  ^
+ *               90deg
+ *
+ * The interface uses X2 encoding by default which calculates the pulse count
+ * based on reading the current state after each rising and falling edge of
+ * channel A.
+ *
+ *               +-----+     +-----+     +-----+
+ * Channel A     |     |     |     |     |     |
+ *            ---+     +-----+     +-----+     +-----
+ *               ^     ^     ^     ^     ^
+ *               ^  +-----+  ^  +-----+  ^  +-----+
+ * Channel B     ^  |  ^  |  ^  |  ^  |  ^  |     |
+ *            ------+  ^  +-----+  ^  +-----+     +--
+ *               ^     ^     ^     ^     ^
+ *               ^     ^     ^     ^     ^
+ * Pulse count 0 1     2     3     4     5  ...
+ *
+ * This interface can also use X4 encoding which calculates the pulse count
+ * based on reading the current state after each rising and falling edge of
+ * either channel.
+ *
+ *               +-----+     +-----+     +-----+
+ * Channel A     |     |     |     |     |     |
+ *            ---+     +-----+     +-----+     +-----
+ *               ^     ^     ^     ^     ^
+ *               ^  +-----+  ^  +-----+  ^  +-----+
+ * Channel B     ^  |  ^  |  ^  |  ^  |  ^  |     |
+ *            ------+  ^  +-----+  ^  +-----+     +--
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ * Pulse count 0 1  2  3  4  5  6  7  8  9  ...
+ *
+ * It defaults
+ *
+ * An optional index channel can be used which determines when a full
+ * revolution has occured.
+ *
+ * If a 4 pules per revolution encoder was used, with X4 encoding,
+ * the following would be observed.
+ *
+ *               +-----+     +-----+     +-----+
+ * Channel A     |     |     |     |     |     |
+ *            ---+     +-----+     +-----+     +-----
+ *               ^     ^     ^     ^     ^
+ *               ^  +-----+  ^  +-----+  ^  +-----+
+ * Channel B     ^  |  ^  |  ^  |  ^  |  ^  |     |
+ *            ------+  ^  +-----+  ^  +-----+     +--
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ *               ^  ^  ^  +--+  ^  ^  +--+  ^
+ *               ^  ^  ^  |  |  ^  ^  |  |  ^
+ * Index      ------------+  +--------+  +-----------
+ *               ^  ^  ^  ^  ^  ^  ^  ^  ^  ^
+ * Pulse count 0 1  2  3  4  5  6  7  8  9  ...
+ * Rev.  count 0          1           2
+ *
+ * Rotational position in degrees can be calculated by:
+ *
+ * (pulse count / X * N) * 360
+ *
+ * Where X is the encoding type [e.g. X4 encoding => X=4], and N is the number
+ * of pulses per revolution.
+ *
+ * Linear position can be calculated by:
+ *
+ * (pulse count / X * N) * (1 / PPI)
+ *
+ * Where X is encoding type [e.g. X4 encoding => X=44], N is the number of
+ * pulses per revolution, and PPI is pulses per inch, or the equivalent for
+ * any other unit of displacement. PPI can be calculated by taking the
+ * circumference of the wheel or encoder disk and dividing it by the number
+ * of pulses per revolution.
+ */
+
+#ifndef QEI_H
+#define QEI_H
+
+/**
+ * Includes
+ */
+#include "mbed.h"
+
+/**
+ * Defines
+ */
+#define PREV_MASK 0x1 //Mask for the previous state in determining direction
+//of rotation.
+#define CURR_MASK 0x2 //Mask for the current state in determining direction
+//of rotation.
+#define INVALID   0x3 //XORing two states where both bits have changed.
+
+/**
+ * Quadrature Encoder Interface.
+ */
+class QEI {
+
+public:
+
+    typedef enum Encoding {
+
+        X2_ENCODING,
+        X4_ENCODING
+
+    } Encoding;
+
+    /**
+     * Constructor.
+     *
+     * Reads the current values on channel A and channel B to determine the
+     * initial state.
+     *
+     * Attaches the encode function to the rise/fall interrupt edges of
+     * channels A and B to perform X4 encoding.
+     *
+     * Attaches the index function to the rise interrupt edge of channel index
+     * (if it is used) to count revolutions.
+     *
+     * @param channelA mbed pin for channel A input.
+     * @param channelB mbed pin for channel B input.
+     * @param index    mbed pin for optional index channel input,
+     *                 (pass NC if not needed).
+     * @param pulsesPerRev Number of pulses in one revolution.
+     * @param encoding The encoding to use. Uses X2 encoding by default. X2
+     *                 encoding uses interrupts on the rising and falling edges
+     *                 of only channel A where as X4 uses them on both
+     *                 channels.
+     */
+    QEI(PinName channelA, PinName channelB, PinName index, int pulsesPerRev, Encoding encoding = X2_ENCODING);
+
+    /**
+     * Reset the encoder.
+     *
+     * Sets the pulses and revolutions count to zero.
+     */
+    void reset(void);
+
+    /**
+     * Read the state of the encoder.
+     *
+     * @return The current state of the encoder as a 2-bit number, where:
+     *         bit 1 = The reading from channel B
+     *         bit 2 = The reading from channel A
+     */
+    int getCurrentState(void);
+
+    /**
+     * Read the number of pulses recorded by the encoder.
+     *
+     * @return Number of pulses which have occured.
+     */
+    int getPulses(void);
+
+    /**
+     * Read the number of revolutions recorded by the encoder on the index channel.
+     *
+     * @return Number of revolutions which have occured on the index channel.
+     */
+    int getRevolutions(void);
+
+private:
+
+    /**
+     * Update the pulse count.
+     *
+     * Called on every rising/falling edge of channels A/B.
+     *
+     * Reads the state of the channels and determines whether a pulse forward
+     * or backward has occured, updating the count appropriately.
+     */
+    void encode(void);
+
+    /**
+     * Called on every rising edge of channel index to update revolution
+     * count by one.
+     */
+    void index(void);
+
+    Encoding encoding_;
+
+    InterruptIn channelA_;
+    InterruptIn channelB_;
+    InterruptIn index_;
+
+    int          pulsesPerRev_;
+    int          prevState_;
+    int          currState_;
+
+    volatile int pulses_;
+    volatile int revolutions_;
+
+};
+
+#endif /* QEI_H */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/ROSControl/ROSController.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,197 @@
+/*
+ * Author: Joseph DelPreto
+ */
+
+#include "ROSController.h"
+
+#ifdef rosControl
+
+// The static instance
+ROSController rosController;
+
+// Define global methods that call methods of the singleton, to make callbacks easier
+// TODO find out how to get a function pointer to a member function of a specific object (so we won't need these methods)
+void processROSMessageStatic(const fish_msgs::JoystickState& msg)
+{
+	rosController.processROSMessage(msg);
+}
+void lowBatteryCallbackROSStatic()
+{
+	rosController.lowBatteryCallback();
+}
+
+// Initialization
+ROSController::ROSController(Serial* serialObject /* = NULL */, Serial* usbSerialObject /* = NULL */):
+		subscriber(rosTopicName, &processROSMessageStatic),
+		terminated(false)
+{
+	#ifdef debugLEDsROS
+	rosLEDs[0] = new DigitalOut(LED1);
+	rosLEDs[1] = new DigitalOut(LED2);
+	rosLEDs[2] = new DigitalOut(LED3);
+	rosLEDs[3] = new DigitalOut(LED4);
+	#endif
+	init(serialObject, usbSerialObject);
+}
+
+void ROSController::init(Serial* serialObject /* = NULL */, Serial* usbSerialObject /* = NULL */)
+{
+	// Create serial object or use provided one
+	if(serialObject == NULL)
+	{
+		serialObject = new Serial(rosDefaultTX, rosDefaultRX);
+		serialObject->baud(rosDefaultBaud);
+	}
+	serial = serialObject;
+	// Create usb serial object or use provided one
+	if(usbSerialObject == NULL)
+	{
+		usbSerialObject = new Serial(USBTX, USBRX);
+		usbSerialObject->baud(rosDefaultBaudUSB);
+	}
+	usbSerial = usbSerialObject;
+
+	// Will check for low battery at startup and using an interrupt
+	lowBatteryVoltageInput = new DigitalIn(lowBatteryVoltagePin);
+	lowBatteryVoltageInput->mode(PullUp);
+	detectedLowBattery = false;
+	lowBatteryTicker.attach(&lowBatteryCallbackROSStatic, 5);
+
+	// ROS setup
+	nodeHandle.initNode();
+	nodeHandle.subscribe(subscriber);
+
+	// Debug
+	#ifdef debugLEDsROS
+	rosLEDs[0]->write(1);
+	rosLEDs[1]->write(0);
+	rosLEDs[2]->write(0);
+	rosLEDs[3]->write(0);
+	#endif
+}
+
+// Process a received ROS message
+void ROSController::processROSMessage(const fish_msgs::JoystickState& msg)
+{
+	#ifdef debugLEDsROS
+	rosLEDs[2]->write(1);
+	#endif
+
+	// Extract the desired fish state from msg
+	// TODO check that this matches your message format
+	bool selectButton = 0;
+	float pitch = ((msg.depth_ctrl+128) * (rosMaxPitch - rosMinPitch) / 255.0) + rosMinPitch;
+	float yaw = ((msg.yaw_ctrl+127) * (rosMaxYaw - rosMinYaw) / 254.0) + rosMinYaw; // 0 MUST map to 0
+	float thrust = ((msg.speed_ctrl+128) * (rosMaxThrust - rosMinThrust) / 255.0) + rosMinThrust;
+	float frequency = ((msg.freq_ctrl+128) * (rosMaxFrequency- rosMinFrequency) / 255.0) + rosMinFrequency;
+
+	// Apply the new state to the fish
+	fishController.setSelectButton(selectButton);
+	fishController.setPitch(pitch);
+	fishController.setYaw(yaw);
+	fishController.setThrust(thrust);
+	fishController.setFrequency(frequency);
+
+	#ifdef printStatusROSController
+	usbSerial->printf("Start %d\t Pitch %f\t Yaw %f\t Thrust %f\t Freq %.8f\r\n", selectButton, pitch, yaw, thrust, frequency);
+	#endif
+	#ifdef debugLEDsROS
+	rosLEDs[2]->write(0);
+	#endif
+}
+
+// Stop the controller (will also stop the fish controller)
+//
+void ROSController::stop()
+{
+	terminated = true;
+}
+
+// Main loop
+// This is blocking - will not return until terminated by timeout or by calling stop() in another thread
+void ROSController::run()
+{
+
+	#ifdef rosControllerControlFish
+    // Start the fish controller
+    fishController.start();
+    #endif
+
+	#ifdef printStatusROSController
+	usbSerial->printf("\r\nStarting to listen for ROS commands\r\n");
+	#endif
+
+	#ifdef debugLEDsROS
+	rosLEDs[0]->write(1);
+	rosLEDs[1]->write(1);
+	rosLEDs[2]->write(0);
+	rosLEDs[3]->write(0);
+	#endif
+
+	// Process any incoming ROS messages
+	programTimer.reset();
+	programTimer.start();
+	while(!terminated)
+	{
+		// Handle any messages that have been received
+		nodeHandle.spinOnce();
+		// See if we've run for the desired amount of time
+		#ifndef infiniteLoopROS
+		if(programTimer.read_ms() > runTimeROS)
+			stop();
+		#endif
+	}
+	programTimer.stop();
+	#ifdef debugLEDsROS
+	rosLEDs[0]->write(0);
+	rosLEDs[1]->write(0);
+	rosLEDs[2]->write(0);
+	rosLEDs[3]->write(0);
+	#endif
+
+	// TODO stop the ROS node handle / unsubscribe
+	// note this is for when we call stop() on the mbed, independent of when the main ROS program terminates on the Pi
+
+	// Stop the fish controller
+	#ifdef rosControllerControlFish
+    fishController.stop();
+    // If battery died, wait a bit for pi to clean up and shutdown and whatnot
+    if(lowBatteryVoltageInput == 0)
+    {
+		wait(90); // Give the Pi time to shutdown
+		fishController.setLEDs(255, false);
+    }
+    #endif
+
+	#ifdef printStatusROSController
+	usbSerial->printf("\r\nROS controller done!\r\n");
+	#endif
+}
+
+
+void ROSController::lowBatteryCallback()
+{
+	if(lowBatteryVoltageInput == 0 && detectedLowBattery)
+	{
+		// Stop the ROS controller
+		// This will end the main loop, causing main to terminate
+		// Main will also stop the fish controller once this method ends
+		stop();
+		// Also force the pin low to signal the Pi
+		// (should have already been done, but just in case)
+		// TODO check that this really forces it low after this method ends and the pin object may be deleted
+		DigitalOut simBatteryLow(lowBatteryVoltagePin);
+		simBatteryLow = 0;
+		#ifdef printStatusROSController
+		usbSerial->printf("\r\nLow battery! Shutting down.\r\n");
+		wait(0.5); // wait for the message to actually flush
+		#endif
+	}
+	else if(lowBatteryVoltageInput == 0)
+	{
+		detectedLowBattery = true;
+	}
+}
+
+#endif // #ifdef rosControl
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/ROSControl/ROSController.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,84 @@
+/*
+ * ROSController.h
+ *
+ * Author: Joseph DelPreto
+ */
+
+//#define rosControl
+
+#ifdef rosControl
+
+#ifndef ROSCONTROL_ROSCONTROLLER_H_
+#define ROSCONTROL_ROSCONTROLLER_H_
+
+#include "FishController.h"
+#include "mbed.h"
+#include <ros.h>
+#include <std_msgs/Empty.h>
+#include <fish_msgs/JoystickState.h>
+
+// ROS setup
+// TODO replace this with your topic name
+#define rosTopicName "to_serial"
+
+// Pins and comm
+#define rosDefaultBaudUSB 115200
+#define rosDefaultBaud 115200
+#define rosDefaultTX p13
+#define rosDefaultRX p14
+// note lowBatteryVoltagePin is defined in FishController
+
+//#define printStatusROSController // whether to print what's going on (i.e. when it gets commands, etc.)
+#define debugLEDsROS      // LED1: initialized LED2: running LED3: receiving a message LED4: done (others turn off)
+#define runTimeROS 10000  // how long to run for (in milliseconds) if inifiniteLoopROS is undefined
+#define infiniteLoopROS   // if defined, will run forever (or until stop() is called from another thread)
+
+#define rosControllerControlFish // whether to start fishController to control the servos and motor
+
+// Map bytes sent over ROS serial to ranges for each fish property
+#define rosMinPitch     fishMinPitch
+#define rosMaxPitch     fishMaxPitch
+#define rosMinYaw       fishMinYaw
+#define rosMaxYaw       fishMaxYaw
+#define rosMinThrust    fishMinThrust
+#define rosMaxThrust    fishMaxThrust
+#define rosMinFrequency fishMinFrequency
+#define rosMaxFrequency fishMaxFrequency
+
+class ROSController
+{
+	public:
+		// Initialization
+		ROSController(Serial* serialObject = NULL, Serial* usbSerialObject = NULL); // if objects are null, ones will be created
+		void init(Serial* serialObject = NULL, Serial* usbSerialObject = NULL); // if serial objects are null, ones will be created
+		// Execution control
+		void run();
+		void stop();
+		void lowBatteryCallback();
+		// ROS
+		ros::NodeHandle nodeHandle;
+		ros::Subscriber<fish_msgs::JoystickState> subscriber;
+		void processROSMessage(const fish_msgs::JoystickState& msg);
+	private:
+		Timer programTimer;
+		bool terminated;
+		Serial* usbSerial;
+		Serial* serial;
+
+		// Low battery monitor
+		DigitalIn* lowBatteryVoltageInput;
+		Ticker lowBatteryTicker;
+		bool detectedLowBattery;
+
+		// Debug LEDs
+		#ifdef debugLEDsROS
+		DigitalOut* rosLEDs[4];
+		#endif
+};
+
+// Create a static instance of ROSController to be used by anyone doing ROS control
+extern ROSController rosController;
+
+#endif /* ROSCONTROL_ROSCONTROLLER_H_ */
+
+#endif // #ifdef rosControl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/SerialControl/SerialController.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,249 @@
+/*
+ * Author: Joseph DelPreto
+ */
+
+#include "SerialController.h"
+
+#ifdef serialControl
+
+// The static instance
+SerialController serialController;
+
+void lowBatteryCallbackSerialStatic()
+{
+	serialController.lowBatteryCallback();
+}
+
+// Initialization
+SerialController::SerialController(Serial* serialObject /* = NULL */, Serial* usbSerialObject /* = NULL */):
+		terminated(false)
+{
+	#ifdef debugLEDsSerial
+	serialLEDs[0] = new DigitalOut(LED1);
+	serialLEDs[1] = new DigitalOut(LED2);
+	serialLEDs[2] = new DigitalOut(LED3);
+	serialLEDs[3] = new DigitalOut(LED4);
+	#endif
+	init(serialObject, usbSerialObject);
+}
+
+void SerialController::init(Serial* serialObject /* = NULL */, Serial* usbSerialObject /* = NULL */)
+{
+	// Create serial object or use provided one
+	if(serialObject == NULL)
+	{
+		serialObject = new Serial(serialDefaultTX, serialDefaultRX);
+		serialObject->baud(serialDefaultBaud);
+	}
+	serial = serialObject;
+	// Create usb serial object or use provided one
+	if(usbSerialObject == NULL)
+	{
+		usbSerialObject = new Serial(USBTX, USBRX);
+		usbSerialObject->baud(serialDefaultBaudUSB);
+	}
+	usbSerial = usbSerialObject;
+
+	// Will check for low battery at startup and using an interrupt
+	lowBatteryVoltageInput = new DigitalIn(lowBatteryVoltagePin);
+	lowBatteryVoltageInput->mode(PullUp);
+	// NOTE Switching to polling instead of interrupt
+	// since it seems like the low battery detector chip may have spurious falls
+	// that cause the interrupt to trigger when the battery isn't actually low
+	//lowBatteryInterrupt = new InterruptIn(lowBatteryVoltagePin);
+	//lowBatteryInterrupt->fall(lowBatteryCallbackSerialStatic);
+	detectedLowBattery = false;
+	lowBatteryTicker.attach(&lowBatteryCallbackSerialStatic, 5);
+
+	#ifdef debugLEDsSerial
+	serialLEDs[0]->write(1);
+	serialLEDs[1]->write(0);
+	serialLEDs[2]->write(0);
+	serialLEDs[3]->write(0);
+	#endif
+}
+
+// Parse the received word into the desired fish state
+// FORMAT: 5 successive bytes indicating selectButton, Pitch, Yaw, Thrust, Frequency
+//         a null termination character (0) ends the word
+//         each one maps 1-255 to the range specified by the min and max values for that property
+void SerialController::processSerialWord(uint8_t* word)
+{
+	// Scale the bytes into the desired ranges for each property
+	bool selectButton = (word[0] > 127);
+	float pitch = word[1];
+	pitch = ((pitch) * (serialMaxPitch - serialMinPitch) / 6.0) + serialMinPitch;
+
+	float yaw = word[2];
+	yaw = ((yaw) * (serialMaxYaw - serialMinYaw) / 6.0) + serialMinYaw;
+
+	float thrust = word[3];
+	thrust = ((thrust) * (serialMaxThrust - serialMinThrust) / 3.0) + serialMinThrust;
+
+	float frequency = word[4];
+	frequency = ((frequency) * (serialMaxFrequency- serialMinFrequency) / 3.0) + serialMinFrequency;
+
+	// Apply the new state to the fish
+	fishController.setSelectButton(selectButton);
+	fishController.setPitch(pitch);
+	fishController.setYaw(yaw);
+	fishController.setThrust(thrust);
+	fishController.setFrequency(frequency, 1.0/(2.0*frequency));
+
+	#ifdef printStatusSerialController
+	//usbSerial->printf("%s", word);
+	usbSerial->printf("Start %d\t Pitch %f\t Yaw %f\t Thrust %f\t Freq %.8f\r\n", selectButton, pitch, yaw, thrust, frequency);
+	#endif
+}
+
+// Stop the controller (will also stop the fish controller)
+//
+void SerialController::stop()
+{
+	terminated = true;
+}
+
+// Main loop
+// This is blocking - will not return until terminated by timeout or by calling stop() in another thread
+void SerialController::run()
+{
+	
+	#ifdef serialControllerControlFish
+    // Start the fish controller
+    fishController.start();
+    #endif
+    
+    #ifdef enableAutoMode
+    fishController.startAutoMode();
+    #endif
+
+    // Moved to ticker instead of interrupt (see comments in init), so don't need this check
+    // Check for low battery voltage (also have the interrupt, but check that we're not starting with it low)
+	//if(lowBatteryVoltageInput == 0)
+	//	lowBatteryCallback();
+
+	#ifdef printStatusSerialController
+	usbSerial->printf("\r\nStarting to listen for serial commands 2\r\n");
+	#endif
+
+	#ifdef debugLEDsSerial
+	serialLEDs[0]->write(1);
+	serialLEDs[1]->write(1);
+	serialLEDs[2]->write(0);
+	serialLEDs[3]->write(0);
+	#endif
+
+	// Process any incoming serial commands
+	uint8_t serialBuffer[20];
+	uint32_t serialBufferIndex = 0;
+	programTimer.reset();
+	programTimer.start();
+	while(!terminated)
+	{
+		if(serial->readable())
+		{
+			#ifdef debugLEDsSerial
+			serialLEDs[2]->write(1);
+			
+			#endif
+	
+			//int nextByte = serial->putc('t');
+			//usbSerial->printf("Processed <%s>: ", nextByte);
+			uint8_t nextByte = serial->getc();
+			serialBuffer[serialBufferIndex++] = nextByte;
+			//usbSerial->printf("%c", serialBufferIndex);
+			// If we've received a complete command, process it now
+			if(nextByte == 8)
+			{
+				//usbSerial->printf("Got zero!\n");
+				//usbSerial->printf((char*) (serialBuffer));
+				processSerialWord(serialBuffer);
+				
+				serialBufferIndex = 0;
+			}
+		}
+		else
+		{
+			#ifdef debugLEDsSerial
+			serialLEDs[2]->write(0);
+			#endif
+		}
+		#ifndef infiniteLoopSerial
+		if(programTimer.read_ms() > runTimeSerial)
+			stop(); //terminated is made true, break out of while loop
+		#endif
+		
+		#ifdef print2Pi
+		if(programTimer.read_ms() - printTime > dataPeriod){
+			serial->printf("Start %d\t Pitch %f\t Yaw %f\t Thrust %f\t Freq %.8f\r\n", fishController.getSelectButton(), fishController.getPitch(), fishController.getYaw(), fishController.getThrust(), fishController.getFrequency());
+		    printTime = programTimer.read_ms();
+		}
+		//serial->printf("test");
+		#endif
+		
+		#ifdef debugBCUControl 
+		usbSerial->printf("V %f\t SDepth %f\t CDepth %f\t sPos %f\t CPos %f\r\n", fishController.getBCUVset(), fishController.getBCUSetDepth(), fishController.getBCUCurDepth(), fishController.getBCUSetPos(), fishController.getBCUCurPos());
+		wait_ms(250);
+		#endif 
+		
+		#ifdef debugSensor
+		usbSerial->printf("Pressure: %f\r\n", fishController.getreadPressure());
+		wait_ms(250);
+		#endif 
+	}
+	programTimer.stop();
+	#ifdef debugLEDsSerial
+	serialLEDs[0]->write(0);
+	serialLEDs[1]->write(0);
+	serialLEDs[2]->write(0);
+	serialLEDs[3]->write(0);
+	#endif
+
+	// Stop the fish controller
+	#ifdef serialControllerControlFish
+    fishController.stop();
+    
+    #ifdef enableAutoMode
+    fishController.stopAutoMode();
+    #endif
+    
+    // If battery died, wait a bit for pi to clean up and shutdown and whatnot
+    if(lowBatteryVoltageInput == 0)
+    {
+		wait(90); // Give the Pi time to shutdown
+		fishController.setLEDs(255, false);
+    }
+    #endif
+
+	#ifdef printStatusSerialController
+	usbSerial->printf("\r\nSerial controller done!\r\n");
+	#endif
+}
+
+
+void SerialController::lowBatteryCallback()
+{
+	if(lowBatteryVoltageInput == 0 && detectedLowBattery)
+	{
+		// Stop the serial controller
+		// This will end the main loop, causing main to terminate
+		// Main will also stop the fish controller once this method ends
+		stop();
+		// Also force the pin low to signal the Pi
+		// (should have already been done, but just in case)
+		// TODO check that this really forces it low after this method ends and the pin object may be deleted
+		DigitalOut simBatteryLow(lowBatteryVoltagePin);
+		simBatteryLow = 0;
+		#ifdef printStatusSerialController
+		usbSerial->printf("\r\nLow battery! Shutting down.\r\n");
+		wait(0.5); // wait for the message to actually flush
+		#endif
+	}
+	else if(lowBatteryVoltageInput == 0)
+	{
+		detectedLowBattery = true;
+	}
+}
+
+#endif // #ifdef serialControl
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/SerialControl/SerialController.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,83 @@
+/*
+ * JoystickController.h
+ *
+ * Author: Joseph DelPreto
+ */
+
+#define serialControl
+
+#ifdef serialControl
+
+#ifndef SERIALCONTROL_SERIALCONTROLLER_H_
+#define SERIALCONTROL_SERIALCONTROLLER_H_
+
+#include "FishController.h"
+#include "mbed.h"
+#include "string"
+#include "SerialBase.h"
+
+#define serialDefaultBaudUSB 115200
+#define serialDefaultBaud 115200
+#define serialDefaultTX p13
+#define serialDefaultRX p14
+// note lowBatteryVoltagePin is defined in FishController
+
+//#define debugBCUControl			// whether to print BCU control values (setDepth, curDepth, Vset, etc.) 
+//#define debugSensor 				// whether to print sensor values being read
+//#define print2Pi					// whether to print data to Pi serial monitor
+#define printStatusSerialController // whether to print what's going on (i.e. when it gets commands, etc.)
+//#define debugLEDsSerial    // LED1: initialized LED2: running LED3: receiving a character LED4: done (others turn off)
+//#define runTimeSerial 10000 	   // how long to run for (in milliseconds) if infiniteLoopSerial is undefined
+#define infiniteLoopSerial // if defined, will run forever (or until stop() is called from another thread)
+
+#define serialControllerControlFish // whether to start fishController to control the servos and motor
+#define enableAutoMode				// whether to start automode (ignores commands, follows commands defined in FishController.cpp
+
+// Map bytes sent over serial (1-255) to ranges for each fish property
+#define serialMinPitch     fishMinPitch
+#define serialMaxPitch     fishMaxPitch
+#define serialMinYaw       fishMinYaw
+#define serialMaxYaw       fishMaxYaw
+#define serialMinThrust    fishMinThrust
+#define serialMaxThrust    fishMaxThrust
+#define serialMinFrequency fishMinFrequency
+#define serialMaxFrequency fishMaxFrequency
+#define dataPeriod 1000
+
+class SerialController
+{
+public:
+	// Initialization
+	SerialController(Serial* serialObject = NULL, Serial* usbSerialObject = NULL); // if objects are null, ones will be created
+	void init(Serial* serialObject = NULL, Serial* usbSerialObject = NULL); // if serial objects are null, ones will be created
+	// Execution control
+	void run();
+	void stop();
+	void lowBatteryCallback();
+	float printTime;
+private:
+	Timer programTimer;
+	bool terminated;
+	Serial* usbSerial;
+	Serial* serial;
+
+	void processSerialWord(uint8_t* word);
+
+	// Low battery monitor
+	DigitalIn* lowBatteryVoltageInput;
+	//InterruptIn* lowBatteryInterrupt;
+	Ticker lowBatteryTicker;
+	bool detectedLowBattery;
+
+	// Debug LEDs
+	#ifdef debugLEDsSerial
+	DigitalOut* serialLEDs[4];
+	#endif
+};
+
+// Create a static instance of SerialController to be used by anyone doing serial control
+extern SerialController serialController;
+
+#endif /* SERIALCONTROL_SERIALCONTROLLER_H_ */
+
+#endif // #ifdef serialControl
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/Servo/.hgignore	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,19 @@
+syntax: regexp
+\.hgignore$
+\.git$
+\.svn$
+\.orig$
+\.msub$
+\.meta$
+\.ctags
+\.uvproj$
+\.uvopt$
+\.project$
+\.cproject$
+\.launch$
+\.project$
+\.cproject$
+\.launch$
+Makefile$
+\.ewp$
+\.eww$
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/Servo/Servo.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,74 @@
+/* mbed R/C Servo Library
+ *  
+ * Copyright (c) 2007-2010 sford, cstyles
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+ 
+#include "Servo.h"
+#include "mbed.h"
+
+static float clamp(float value, float min, float max) {
+    if(value < min) {
+        return min;
+    } else if(value > max) {
+        return max;
+    } else {
+        return value;
+    }
+}
+
+Servo::Servo(PinName pin) : _pwm(pin) {
+    calibrate();
+    write(0.5);
+}
+
+void Servo::write(float percent) {
+    float offset = _range * 2.0 * (percent - 0.5);
+    _pwm.pulsewidth(0.0015 + clamp(offset, -_range, _range));
+    _p = clamp(percent, 0.0, 1.0);
+}
+
+void Servo::position(float degrees) {
+    float offset = _range * (degrees / _degrees);
+    _pwm.pulsewidth(0.0015 + clamp(offset, -_range, _range));
+}
+
+void Servo::calibrate(float range, float degrees) {
+    _range = range;
+    _degrees = degrees;
+}
+
+float Servo::read() {
+    return _p;
+}
+
+Servo& Servo::operator= (float percent) { 
+    write(percent);
+    return *this;
+}
+
+Servo& Servo::operator= (Servo& rhs) {
+    write(rhs.read());
+    return *this;
+}
+
+Servo::operator float() {
+    return read();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/Servo/Servo.h	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,98 @@
+/* mbed R/C Servo Library
+ * Copyright (c) 2007-2010 sford, cstyles
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+  
+#ifndef MBED_SERVO_H
+#define MBED_SERVO_H
+
+#include "mbed.h"
+
+/** Servo control class, based on a PwmOut
+ *
+ * Example:
+ * @code
+ * // Continuously sweep the servo through it's full range
+ * #include "mbed.h"
+ * #include "Servo.h"
+ * 
+ * Servo myservo(p21);
+ * 
+ * int main() {
+ *     while(1) {
+ *         for(int i=0; i<100; i++) {
+ *             myservo = i/100.0;
+ *             wait(0.01);
+ *         }
+ *         for(int i=100; i>0; i--) {
+ *             myservo = i/100.0;
+ *             wait(0.01);
+ *         }
+ *     }
+ * }
+ * @endcode
+ */
+class Servo {
+
+public:
+    /** Create a servo object connected to the specified PwmOut pin
+     *
+     * @param pin PwmOut pin to connect to 
+     */
+    Servo(PinName pin);
+    
+    /** Set the servo position, normalised to it's full range
+     *
+     * @param percent A normalised number 0.0-1.0 to represent the full range.
+     */
+    void write(float percent);
+    
+    /**  Read the servo motors current position
+     *
+     * @param returns A normalised number 0.0-1.0  representing the full range.
+     */
+    float read();
+    
+    /** Set the servo position
+     *
+     * @param degrees Servo position in degrees
+     */
+    void position(float degrees);
+    
+    /**  Allows calibration of the range and angles for a particular servo
+     *
+     * @param range Pulsewidth range from center (1.5ms) to maximum/minimum position in seconds
+     * @param degrees Angle from centre to maximum/minimum position in degrees
+     */
+    void calibrate(float range = 0.0005, float degrees = 45.0); 
+        
+    /**  Shorthand for the write and read functions */
+    Servo& operator= (float percent);
+    Servo& operator= (Servo& rhs);
+    operator float();
+
+protected:
+    PwmOut _pwm;
+    float _range;
+    float _degrees;
+    float _p;
+};
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/data_stream_format.txt	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,31 @@
+Day 1
+uint32: goertzel 36k (0)
+uint32: goertzel 30k (1)
+uint16: signal level (average over last buffer) 
+uint8 : codeword (21) in most significant 5 bits, gain index in least significant 3 bits
+uint16: fish state event in upper 5 bits, fish state in lower 11 bits
+
+Day 2
+3 bytes: goertzel 36k (0) >> 8
+3 bytes: goertzel 30k (1) >> 8
+2 bytes: (signal level >> 2) in lower 11 bits, codeword (21) in upper 5 bits
+2 bytes: fish state event in upper 5 bits, fish state in lower 11 bits
+
+
+fish state events:
+  0: no event
+  1: bad word timing
+  2: bad word timing
+  3: bad word hamming
+  4: timeout
+  5: good acoustic word
+  6: button: yaw left
+  7: button: yaw right
+  8: button: faster
+  9: button: slower
+ 10: button: pitch up
+ 11: button: pitch down
+ 12: button: shutdown pi
+ 13: button: reset mbed
+ 14: button: auto mode (toggle)
+ 15: button: invalid combination
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/main.cpp	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,51 @@
+
+// NOTE look at the below h files to define whether that control mode is enabled
+
+#include "mbed.h"
+//#include "AcousticControl/AcousticController.h" // also need to define acousticControl in ToneDetector.h
+#include "SerialControl/SerialController.h"
+//#include "ROSControl/ROSController.h"
+
+Serial pc(USBTX, USBRX);
+
+int main()
+{
+	/* ACOUSTIC CONTROL */
+	//#ifdef acousticControl
+//	pc.baud(115200);
+//	// Initialize the acoustic controller
+//	acousticController.init(&pc); // if no serial object is provided, it will create one on the USB pins
+//	// Start the controller
+//	// NOTE this is a blocking method, and if infiniteLoopAcoustic is defined it will run forever (or until low battery callback or button board reset command)
+//	// It can be stopped by the acousticController.stop() method, but you have to
+//	//  control threading here to actually be able to call that
+//	//  The acoustic controller hasn't been tested with multi-threading though
+//	acousticController.run();
+//	#endif
+
+	/* SERIAL CONTROL */
+	#ifdef serialControl
+
+	pc.baud(115200);
+	pc.printf("Beginning serial control. \n");
+	// Initialize the serial controller
+	serialController.init(NULL, &pc);
+	// Start the controller
+	// NOTE this is a blocking method, and if infiniteLoopSerial is defined it will run forever (or until low battery callback or button board reset command)
+	// It can be stopped by the serialController.stop() method, but you have to
+	//  control threading here to actually be able to call that
+	serialController.run();
+	#endif
+
+	/* ROS CONTROL */
+	//#ifdef rosControl
+//	pc.baud(115200);
+//	// Initialize the ROS controller
+//	rosController.init(NULL, &pc);
+//	// Start the controller
+//	// NOTE this is a blocking method, and if infiniteLoopSerial is defined it will run forever (or until low battery callback or button board reset command)
+//	// It can be stopped by the rosController.stop() method, but you have to
+//	//  control threading here to actually be able to call that
+//	rosController.run();
+//	#endif
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/makefile	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,8 @@
+PROJECT         := fish_6_0_with_ROS
+DEVICES         := LPC1768
+GCC4MBED_DIR    := $(GCC4MBED_DIR)
+USER_LIBS       := $(ROS_LIB_DIR)
+NO_FLOAT_SCANF  := 1
+NO_FLOAT_PRINTF := 1
+
+include $(GCC4MBED_DIR)/build/gcc4mbed.mk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/mbed.bld	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,1 @@
+http://mbed.org/users/mbed_official/code/mbed/builds/7cff1c4259d7
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/robotic_fish_6/mbed.lib	Tue Jan 14 19:17:05 2020 +0000
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/juansal12/code/mbed/#44931ff4a3ed