/*Change Record Old Version# Change 1.6 first official; used in fall of 2018 1.7 for "D" command, if battery <5V meaning battery disconnected, changed text. When "A" command invoked, data is output to USB while in flight state. At power up, this function is disabled. Search for "CR1.7" to see changes. 1.8 debug verson 1.9 Changed all "goto"s to while().Changed time stamp from int which can be negative to unsigned int. Search for "CR1.9" 2.0 divided CalibrateClockQ() down into smaller parts; replace port0 with A8 etc because compiler was changing the wrong port. Changed menu item related to locked memory to say locked manually or due to full memory. If EEPROM write and subsequent read do not match, write error code 10. Text when H entered now says "Memory full and locked." when next block is 131060. Found a few variables that were defined but not given initial values. This caused an intermittent failue. After set to pre-flight and just powered up, I was running the clock before clearing the justpoweredup flag. This let time() run before init was done and that caused the first two blocks not to be written. Previously stored garbage would display. "CR2.0" 3.0 changed size of data block from 10 to 15 bytes, eliminated time as the first two bytes, save 2 bytes for each port from 0 to 6. Port 7 is to monitor battery and will leave it one byte. If this last byte = 0, it flags that a power hit has occured so restart the time. Since I record one record every second, no need to put timer in those first two bytes. Turn LED on right after power up for 200 ms to show software is running. Then cadence comes 5 seconds later. Changed control block to 16 bytes which gives me 131072/16 = 8192 blocks. First data block starts at address 16 and the last data block starts at 131056 or 2^17-1-16. This gives a max record time of 136 minutes 31 seconds. OneSecondMillisecondsUnsignedLong was initialized to 0 which could cause a divide by 0. Changed it to 1000L. 3.1 Added command "T" which generates a test pattern in the data file. 3.2 change sampling rate to every SamplingRateByte seconds. Output reflects this sampling rate. Added % full to "H" response. Added NumberOfTestBlocksLong to set size of test pattern. Output changed from 2 to 3 place accuracy. */ //#define SoftwireActive 1 //comment out if ports 0 and 1 are to be used; port 0 becomes SDA; port 1 becomes SCL #include //I2C interface library #ifdef SoftwireActive //used by Softwire #include #include #endif #define EEPROM_ADR_LOW_BLOCK 0x50 #define EEPROM_ADR_HIGH_BLOCK 0x54 #define DataBlockSize 16 //size of one block of sensor data CR3.0 /****************************************************** E E P R O M T O A R D U I N O H A R D W A R E C O N N E C T I O N S ******************************************* 24LC1025 Arduino Physical Logical description Physical 1 A0 ground 3,4,23 2 A1 ground 3,4,23 3 A2 5V 21 4 VSS ground 3,4,23 5 SDA SDA* 5 6 SCL SCL* 6 7 WP ground 3,4,23 8 VCC 5v 21 *these are hard wire I2C and not Softwire **********************************************************/ /****************************************************** A R D U I N O T O P O R T C O N N E C T I O N S ********************************************************* Arduino Physical logical port description 11 A8 0 or SDA** analog input 9 A7 1 or SCL** analog input 13 A10 2 analog input 17 A0 3 analog input 18 A1 4 analog input 19 A2 5 analog input 20 A3 6 analog input 12 A9 7 battery monitor **these are option Software I2C connections **********************************************************/ /****************************************************** A R D U I N O O U T P U T C O N N E C T I O N S ********************************************************* Arduino Physical logical description 8 D5 external LED **********************************************************/ const int ExternalLED = 5; /****************************************************** C O N T R O L B L O C K IN E E P R O M ********************************************************* In the EEPROM the first 20 bytes are the control block. byte description ==== =========== 0 ReadyForLaunch flag 1 InFlight flag 2 DisruptedPower flag 3 StartOfNextMemoryBlockBaseAddressByte (base address so byte 0) 4 address byte 1 5 address byte 2 6 address byte 3 7 MemoryLock 8 OneSecondMillisecondsBaseAddressByte (base address so byte 0) 9 address byte 1 10 hardware and software error codes 11-15 spare CR3.0 **********************************************************/ const byte ReadyForLaunchFlagAddressByte = 0; const byte InFlightFlagAddressByte = 1; const byte DisruptedPowerFlagAddressByte = 2; const byte StartOfNextMemoryBlockPointerBaseAddressByte = 3; const byte MemoryLockFlagAddressByte = 7; const byte OneSecondMillisecondsBaseAddressByte = 8; const byte ErrorCodeAddressByte = 10; /********************************************************* R E F E R E N C E I N F O R M A T I O N ********************************************************** to interface with laptop: www.arduino.cc\en\Reference\Serial.html https://learn.sparkfun.com/tutorials/reading-and-writing-serial-eeproms Based on: https://playground.arduino.cc/Code/I2CEEPROM **********************************************************/ /******************************************************************************** C O N S T A N T S *********************************************************************************/ const byte LogicalAnalogPinByte[8]={8,7,10,0,1,2,3,9}; //maps port number to analog logical pin number String VersionNumber = "1.0"; const byte ExternalLED_OnByte = 1; const byte ExternalLED_OffByte = 0; const long StartOfDataMemoryLong = 16L; const byte SamplingRateByte = 2; //rate data is collected and output /******************************************************************************** H A R D W A R E A N D S O F T W A R E E R R O R C O D E S *********************************************************************************/ const byte SystemNormalFlagByte = 0; const byte OutOfRangeReadFlagByte = 1; const byte OutOfRangeWriteFlagByte = 2; const byte ControlTheFlightParametersReadyForLaunchReadBackFailureFlagByte = 3; const byte ControlTheFlightParametersInFlightFlagReadBackFailureFlagByte = 4; const byte JustWaitReadBackFailureFlagByte = 5; const byte PrepareForLaunchReadBackFailureFlagByte = 6; const byte WriteEEPROM_AttemptMadeToWriteToLockedMemoryFlagByte = 7; //rfv const byte AttemptMadeToWriteToOneSecondMillisecondsConstant = 8; const byte SerialAvailableReturnCodeOutOfRange = 9; //CR1.9 const byte ReadBackFromEEPROM_Mismatch = 10; //CR2.0 /******************************************************************************** V A R I A B L E S *********************************************************************************/ int OneSecondMillisecondsInt = 0; //use to calibrate hardware clock to give 1 second; data comes from EEPROM bytes 8 and 9 was replaced with UL unsigned long OneSecondMillisecondsUnsignedLong = 1000L; //CR3.0 long StartOfNextMemoryBlockLong = 0; //this is the pointer to the next available memory location; it is initialized at power up unsigned long StartTimeMS_UnsignedLong = 0; //will be set to millis() when I start timing CR3.0 int TimeNowSecondsInt = 0; //used to set seconds flag int Last_TimeNow_SecondsInt = 0; //used to set seconds flag boolean TimeToTakeSample = false; //is set true at the start of each second and cleared by last user CR3.2 char Command = 'N'; //stores single character sent from laptop; initilized to "N" = null. boolean ReadyForLaunchFlag = false; boolean InFlightFlag = false; unsigned long StartOfPreFlightCadenceMS_UnsignedLong = millis(); unsigned long StartOfInFlightCadenceMS_UnsignedLong = millis(); unsigned long StartOfDisruptedPowerCadenceMS_UnsignedLong = millis(); unsigned long StartOfMemoryLockedLED_CadenceMS_UnsignedLong = millis(); unsigned long StartOfFaultLED_CadenceMS_UnsignedLong = millis(); boolean PreFlightLED_CadenceFlag = false; boolean InFlightLED_CadenceFlag = false; boolean DisruptedPowerLED_CadenceFlag = false; boolean MemoryLockedLED_CadenceFlag = false; boolean FaultLED_CadenceFlag = false; boolean JustPoweredUpFlag = true; //flag used by ControlTheFlightParameters(). At every power up, this flag is set true. boolean JustPoweredUpForTimeStampFlag = true; //flag used by time stamp function. At every power up, this flag is set true. CR3.0 int InFlightCadenceIntervalMS_Int = 0; int PreFlightCadenceIntervalMS_Int= 0; int MemoryLockedCadenceIntervalMS_Int= 0; int DisruptedPowerCadenceIntervalMS_Int= 0; int FaultCadenceIntervalMS_Int= 0; long eeAddressLong= 0;//local variable used for all addressing of EEPROM boolean ContinuousDisplayDataFlag = false; //flag set by ContinuousDisplayDataQ() and cleared at power cycle CR1.7 long DurationMS_UnsignedLong= 0 ; long IdealCalibrationTimeSeconds_UnsignedLong = 10800UL; //3 hours=180 minutes=10800 seconds (10800UL) so get number of internal ms counts per second unsigned long StartOfCalibrationTimeMS_UnsignedLong = 0; long CalibrationTimeMS_Long = 0; float OneSecondMillisecondsFloat = 0; //CR2.0 float ErrorPercentageFloat = 0; boolean FoundCommandQ = false; boolean DeepReturn = false;//passes a return from a subroutine up to the calling subroutines float LimitTestFloat = 0; int SecondsInt = 0; const byte CalledBySetupByte = 1; //user IDs const byte CalledByIncrementEEPROM_PointerByte = 2; const byte CalledByDiagnoticOutputControlBlockByte = 4; const byte CalledByRecordDataByte = 6; const byte CalledByOutputDataToFileByte = 7; /******************************************************************************** S O F T W I R E S E T U P (O P T I O N A L) *********************************************************************************/ #ifdef SoftwireActive byte sdaPin = 8; //alternate definition for port 0 byte sclPin = 7; // alternate definition for port 1 byte cmdAmbient = 6; byte cmdObject1 = 7; byte cmdObject2 = 8; byte cmdFlags = 0xf0; byte cmdSleep = 0xff; SoftWire i2c(sdaPin, sclPin); //comment this out when using ports 5 and 6 #endif /******************************************************************************** S E T U P *********************************************************************************/ void setup(){ pinMode(ExternalLED, OUTPUT); Wire.begin(); //set up communications path between Arduino and EEPROM Wire.setClock(400000); //set clock rate for EEPROM Serial.begin(19200); //set up communications path between Arduino and USB; specify data rate //set all analog ports to inputs #ifndef SoftwireActive //if Softwire not enabled pinMode(A8, INPUT); //must use hard coded values to define these pins because the "A" is essential. A8 is different than 8. pinMode(A7, INPUT); #endif pinMode(A10, INPUT); pinMode(A0, INPUT); pinMode(A1, INPUT); pinMode(A2, INPUT); pinMode(A3, INPUT); pinMode(A9, INPUT); /* port reference Port0 is A8 Port1 is A7 Port2 is A10 Port3 is A0 Port4 is A1 Port5 is A2 Port6 is A3 Port7 is A9 */ #ifndef SoftwireActive //if Softwire not enabled pinMode(A8, INPUT_PULLUP);//pull up all analog ports so when not terminated I see near 5V. pinMode(A7, INPUT_PULLUP); #endif pinMode(A10, INPUT_PULLUP); pinMode(A0, INPUT_PULLUP); pinMode(A1, INPUT_PULLUP); pinMode(A2, INPUT_PULLUP); pinMode(A3, INPUT_PULLUP); //check if control block has any out of range values. If so, initialize and tell user to power cycle. ReadEEPROM_Pointer(CalledBySetupByte); //populates StartOfNextMemoryBlockLong if ((ControlBlockReadEEPROM(ReadyForLaunchFlagAddressByte) > 1) || (ControlBlockReadEEPROM(InFlightFlagAddressByte) > 1) || (ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte) > 1) || (StartOfNextMemoryBlockLong (131072L - DataBlockSize))&&(ControlBlockReadEEPROM(MemoryLockFlagAddressByte) == false)) || (ControlBlockReadEEPROM(MemoryLockFlagAddressByte) > 1 )){//at least one value in the control block is out of range so assume it must be initialized. CR3.0 ControlBlockWriteEEPROM(ReadyForLaunchFlagAddressByte,false); ControlBlockWriteEEPROM(InFlightFlagAddressByte,false); ControlBlockWriteEEPROM(DisruptedPowerFlagAddressByte,false); ControlBlockWriteEEPROM(MemoryLockFlagAddressByte,false); StartOfNextMemoryBlockLong = 20L; WriteEEPROM_Pointer(); while(true){ //output this message until we get power cycle CR1.9 Serial.println(); Serial.println(); Serial.println(F("EEPROM was initialized. Power Cycle")); Serial.println(F("to complete task. If you get this")); Serial.println(F("message again after the power cycle,")); Serial.println(F("you have a hardware fault.")); delay(5000); } } digitalWrite(ExternalLED, ExternalLED_OnByte);//wink LED to show software is running after power up CR3.0 delay(200); digitalWrite(ExternalLED, ExternalLED_OffByte); } /******************************************************************************** L O O P *********************************************************************************/ void loop(){ Time(); //applies on the ground and in the air OnTheGround(); //tasks mostly done on the ground InTheAir(); //tasks mostly done in the air } /******************************************************************************** L E V E L 1 S U B R O U T I N E S *********************************************************************************/ void Time(){ LapseTimerSeconds(); //only advance time if ControlTheFlightParameters() has run due to power up. Otherwise, time will advance before variables that depend on being initialized have been set. CR2.0 RefreshTimeCalibrationConstantQ(); LED_Cadences(); } void OnTheGround(){ ControlTheFlightParameters(); ScanForCommand(); OutputMenuQ(); //M command SingleDisplayDataQ(); //D command PrepareForLaunchQ(); //P command ContinuousDisplayDataQ(); //A command CR1.7 OutputDataFileQ(); //O command StopCollectingDataQ(); //S command CalibrateClockQ(); //C command DiagnoticOutputControlBlockQ(); //H command GenerateTestPatternQ(); //T command FlashLED(); } void InTheAir(){ if (JustPoweredUpFlag == true)return; //if we just powered up, data recording parameters have not yet been initialized. This is done in ControlTheFlightParameters () which is within OnTheGround() CR2.0 DataIn(); //record, process, and save time and sensor data to EEPROM } //The 24LC1025 is 1024kbit or 128k bytes. //If the eeaddress is less than the 64k byte threshold we use I2C address 0x50 //If the address is above 65535 then we use 0x54 address //Based on: https://playground.arduino.cc/Code/I2CEEPROM /******************************************************************************** L E V E L 2 S U B R O U T I N E S *********************************************************************************/ void RefreshTimeCalibrationConstantQ(){ if ((ControlBlockReadEEPROM(InFlightFlagAddressByte)== true)||(ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte)== true)){ //then worth refreshing the calibration constant in case it was corrupted OneSecondMillisecondsUnsignedLong = ControlBlockReadEEPROM(OneSecondMillisecondsBaseAddressByte) + ( ControlBlockReadEEPROM(OneSecondMillisecondsBaseAddressByte + 1)*256); } } void ControlTheFlightParameters(){ if (JustPoweredUpFlag == true){ //executes only when power first comes up JustPoweredUpFlag = false; StartTimeMS_UnsignedLong = millis();// application of power restarts t=0. if (ControlBlockReadEEPROM(ReadyForLaunchFlagAddressByte) == true){ //if ReadyForLaunch true, clear it. Then set InFlightFlag and clear DisruptedPowerFlag. Initialize flight time. ControlBlockWriteEEPROM(ReadyForLaunchFlagAddressByte,false); ControlBlockWriteEEPROM(InFlightFlagAddressByte,true); ControlBlockWriteEEPROM(DisruptedPowerFlagAddressByte,false); //verify writes worked if ((ControlBlockReadEEPROM(ReadyForLaunchFlagAddressByte) != false)||(ReadEEPROM(InFlightFlagAddressByte)!= true)||(ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte)!= false)){ ControlBlockWriteEEPROM(ErrorCodeAddressByte, ControlTheFlightParametersReadyForLaunchReadBackFailureFlagByte); } return; } if (ControlBlockReadEEPROM(InFlightFlagAddressByte) == true){ //if InFlightFlag true, clear ReadyForLaunchFlag and InFlightFlag. Then set DisruptedPowerFlag true. ControlBlockWriteEEPROM(ReadyForLaunchFlagAddressByte, false); ControlBlockWriteEEPROM(InFlightFlagAddressByte, false); ControlBlockWriteEEPROM(DisruptedPowerFlagAddressByte, true); //verify writes worked if ((ControlBlockReadEEPROM(ReadyForLaunchFlagAddressByte) != false)||(ControlBlockReadEEPROM(InFlightFlagAddressByte)!= false)||(ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte)!= true)){ ControlBlockWriteEEPROM(ErrorCodeAddressByte, ControlTheFlightParametersInFlightFlagReadBackFailureFlagByte); } return; } } } void LapseTimerSeconds(){ if (JustPoweredUpFlag == true)return; //do not advance time if we just powered up because OnTheGround()//ControlTheFlightParameters() has not run yet to initialize related parameters TimeNowSecondsInt = int((millis() - StartTimeMS_UnsignedLong)/OneSecondMillisecondsUnsignedLong); //millis() must always be larger than StartTimeMS_UnsignedLong which was set to millis() previously so I should never have the case where two unsigned longs produce a negative number if ((TimeNowSecondsInt - Last_TimeNow_SecondsInt) > (SamplingRateByte - 1)){ //we have to advance SamplingRateByte second in order to say we have gone SamplingRateByte seconds since last reading of ports. CR3.2 TimeToTakeSample = true; //set seconds flag if we just started new second //TimeToTakeSample set false just before ports scanned Last_TimeNow_SecondsInt = TimeNowSecondsInt; } //if TimeNowSeconds is equal to LastTimeNowSeconds, there has not been an advanced to the next second so do nothing } void LED_Cadences(){ InFlightCadence(); //1 flash DisruptedPowerCadence(); //2 flashes MemoryLockedCadence(); //3 flashes PreFlightCadence(); //4 flashes FaultCadence(); //5 flashes } void ScanForCommand(){ //detect if laptop connected. If so, check for incoming command. If there is one, set flags for requested tasks if (Serial.available() > 0) { //if there is at least one character in the buffer, process first one Command = Serial.read(); if ((Command != 'D') && (Command != 'd')&&(Command != 'A') && (Command != 'a')&&(Command != 'C') && (Command != 'c')&&(Command != 'S') && (Command != 's')&& (Command != 'P') && (Command != 'p')&&(Command != 'O')&&(Command != 'o')&&(Command != 'H')&&(Command != 'h')&&(Command != 'T')&&(Command != 't'))Command = 'M'; //map any character except D, A, C, S, P, O, H, and T to "M" for display of Menu. Set to "N" when done processing command. CR1.7, CR3.1 if (Command == '\n')Command = 'M'; } } void CalibrateClockQ(){ //CR2.0 DeepReturn = false; //when set true by a lower level subroutine, we will return from CalibrateClockQ() C_CommandQ(); if (DeepReturn == true)return; while (Serial.available()==0)FlickerLEDforCalibration(); // waiting for Abort or G command; replace goto CR1.9 if (Serial.available() > 0) Command = Serial.read(); //we have a character so save it to the variable Command SerialAvailableResponseErrorCheck(); //if Serial.available() returns a negative number, it is a fault //first command was a C. Now have a second command which will be either A,G, or neither FirstAbortCommandQ(); if (DeepReturn == true)return; //to get here, command cannot be A G_CommandQ(); if (DeepReturn == true)return; //command was not G or g //to get here, command must be G or g Command = 'N'; //clear command back to null PrintCalibrationStartedText(); StartOfCalibrationTimeMS_UnsignedLong = millis(); //record start in ms. //calibration interval just started. Watch for end or abort command EndOfCalibration(); //responds to commands I, A, E. } void C_CommandQ(){ if ((Command == 'C') || (Command == 'c')){ Command = 'N'; //clear Command back to null PrintCalibrationIntroText(); }else{ DeepReturn = true; //causes return from CalibrateClockQ() return; } //if command is "C", wait for user response } void FirstAbortCommandQ(){ if ((Command == 'A') || (Command == 'a')){ Command = 'N'; //clear Command back to null A_CommandText(); DeepReturn = true; //causes return from CalibrateClockQ() return; } } void G_CommandQ(){ if ((Command != 'G')&&(Command != 'g')){//command is not G so tell user and abort Command = 'N'; //clear Command back to null InvalidCommandText(); DeepReturn = true; //causes return from CalibrateClockQ() return; //false means not G or g } } void PrintCalibrationIntroText(){ Serial.println(F("C")); Serial.println(); Serial.println(F("***Clock Calibration Function***")); Serial.println(); Serial.println(F("WARNING: This task takes 3 hours to run. Do not proceed unless")); Serial.println(F("you are willing to complete the job.")); Serial.println(); Serial.println(F("Type 'A' now to Abort.")); Serial.println(); Serial.println(F("Use https://time.is/ for the best timing source.")); Serial.println(); Serial.println(F("When you are ready to Go, type 'G'.")); Serial.println(); } void FlickerLEDforCalibration(){ digitalWrite(ExternalLED, ExternalLED_OnByte); //turn external LED on delay(50); digitalWrite(ExternalLED, ExternalLED_OffByte);//turn external LED off delay(50); } void SerialAvailableResponseErrorCheck(){ if (Serial.available() < 0) { ControlBlockWriteEEPROM(ErrorCodeAddressByte,SerialAvailableReturnCodeOutOfRange); //added error check and generate error code if return value invalid Serial.print(F(" USB interface problem detected.")); } } void PrintCalibrationStartedText(){ Serial.println(F("G")); Serial.println(); Serial.println(F("***Time calibration has just started.***")); Serial.println(F("Type 'E' after exactly 3 hours to End calibration interval. Type 'A' to Abort.")); Serial.println(F("Type 'I' to initialize the timer constant to 1000.")); } void CalibrationUserStatus(){ while(Serial.available() == 0){ //while no characters… DurationMS_UnsignedLong = millis() - StartOfCalibrationTimeMS_UnsignedLong; if ((DurationMS_UnsignedLong % 1000) == 0)CalibrationLEDFlicker();//at every second flicker LED once if ((DurationMS_UnsignedLong != 0)&&(DurationMS_UnsignedLong % 60000) == 0){//every minute after start Serial.print(F(".")); if ((DurationMS_UnsignedLong != 0)&&(DurationMS_UnsignedLong % 3600000 == 0)){//every hour after start Serial.println(); Serial.println(F("About one hour has passed.")); } } } } void CalibrationLEDFlicker(){ digitalWrite(ExternalLED, ExternalLED_OnByte); //turn external LED on delay(100); digitalWrite(ExternalLED, ExternalLED_OffByte); //turn external LED off } void I_CommandText(){ Command = 'N'; //clear command back to null Serial.println(F("I")); Serial.println(); Serial.println(F("This will initialize the timer correction constant.")); Serial.println(F("Do this only if the Pro Micro or EEPROM are changed on the board.")); Serial.println(F("Proceed? Y/N.")); } void A_CommandText(){ Serial.println(F("A")); Serial.println(); Serial.println(F("Clock calibration aborted.")); } void InvalidCommandText(){ Serial.println(F("Neither A (abort) or G (go) received.")); Serial.println(); Serial.println(F("Clock calibration aborted.")); } void StopCollectingDataQ(){ if ((Command == 'S')||(Command == 's')){ Serial.println(); Serial.println(); Serial.println(F("Data collection has been stopped. The EEPROM is now locked.")); Serial.println(); ControlBlockWriteEEPROM(MemoryLockFlagAddressByte,true); //sets lock EEPROM flag to true Command = 'N'; //clear Command } } void EndOfCalibration(){ FoundCommandQ = false; while (FoundCommandQ == false){ //CR1.9 while(Serial.available() == 0)CalibrationUserStatus(); //while no characters coming in from USB, display progress with LED and on screen //We fell out of the above while loop so have a character SerialAvailableResponseErrorCheck(); //if Serial.available() returns a negative number, it is a fault if (Serial.available() > 0) Command = Serial.read(); //have Command while in calibration interval //see if valid EndOfCalibrationCommandValidityCheck(); //have new command and it is A, E, or I. }//if no valid command found, keep looking; otherwise, new command found so exit while() IandY_CommandQ(); //abort 3 hour timer calibration and set constant to 1000 if Yes is input; otherwise keep timing. //evaluate the received Command if (DeepReturn == true)return; //I and Y seen so abort calibration A_CommandQ(); //abort timer calibration if (DeepReturn == true)return; E_Command(); //marks end of 3 hour timer interval so save resulting calibration constant. No "Q" because it has to be E. } void EndOfCalibrationCommandValidityCheck(){ if ((Command != 'A')&&(Command != 'E') && (Command != 'a')&&(Command != 'e') && (Command != 'I')&&(Command != 'i')){ //then it is not valid Serial.print(Command); Command = 'N'; //clear command back to null Serial.println(F(" is not a valid command. Please enter E, A, or I to initialize constant.")); //command not valid so leave FoundCommandQ at false and try again }else{ FoundCommandQ = true;//command is valid so stop looking } } void IandY_CommandQ(){ if ((Command == 'I')||(Command == 'i')){ Command = 'N'; //clear command back to null I_CommandText(); FoundCommandQ = false; //now looking for yesb or anything else which will be read as no while (FoundCommandQ == false){ //CR1.9 SerialAvailableResponseErrorCheck(); //if Serial.available() returns a negative number, it is a fault if (Serial.available() > 0) { //read a response Command = Serial.read(); FoundCommandQ = true; } } Y_CommandQ(); //if yes, calibration constant is set to the nominal value. if no, we continue to look for Abort or End if (DeepReturn == true)return; //if calibration constant was set to the nominal value, stop measuring timing interval } } void Y_CommandQ(){ if ((Command == 'Y')||(Command == 'y')){ Serial.println(F("Time correction constant has been set to the nominal value.")); SetNominalTimerValue(); DeepReturn = true; //do return from calling subroutine because there is no need to continue timing }else{ //if not Y or y, take it as a no and abort the setting of the time correction value to 1000 plus keep timing Serial.println(F("Time correction constant initialization has been aborted. 3 hour interval still proceeding.")); } } void SetNominalTimerValue(){ OneSecondMillisecondsUnsignedLong = 1000L;//nominal value TimerConstantWriteEEPROM(OneSecondMillisecondsBaseAddressByte, lowByte(OneSecondMillisecondsUnsignedLong)); //save to EEPROM TimerConstantWriteEEPROM(OneSecondMillisecondsBaseAddressByte+1, highByte(OneSecondMillisecondsUnsignedLong)); //save to EEPROM with write subrotine that alows write to timer constant. all other write subrotines do not. } void A_CommandQ(){ if ((Command == 'A')||(Command == 'a')){ Serial.println(F("A")); Serial.println(); Command = 'N'; //clear command back to null Serial.println(F("Clock calibration aborted.")); DeepReturn = true; //do return from calling subroutine return; } } void E_Command(){ //command must be E so no need to check for it Serial.println(F("E")); Serial.println(); Command = 'N'; //clear command back to null Serial.println(F("Time Calibration interval has ended.")); CalculateAndFormatCalibrationValue(); //range test calibration value and tell user LimitTestFloat = float(OneSecondMillisecondsUnsignedLong)/1000L; if ((LimitTestFloat < 0.9) || (LimitTestFloat > 1.1)){ Serial.println(); Serial.println(F("Calibration value outside expected limits so has been rejected.")); return; //don't need to do DeepReturn because E_CommandQ() is the last line of the calling subroutine } //to get here, calibration must be within 10% of nominal SaveTimerCalibrationValueToEEPROM(); TellUserAboutTimerCalibrationValue(); } void CalculateAndFormatCalibrationValue(){ DurationMS_UnsignedLong = millis() - StartOfCalibrationTimeMS_UnsignedLong; //now have count of internal milliseconds per second OneSecondMillisecondsUnsignedLong = DurationMS_UnsignedLong/IdealCalibrationTimeSeconds_UnsignedLong; //calculate calibration value } void SaveTimerCalibrationValueToEEPROM(){ TimerConstantWriteEEPROM(OneSecondMillisecondsBaseAddressByte, lowByte(OneSecondMillisecondsUnsignedLong)); //save to EEPROM TimerConstantWriteEEPROM(OneSecondMillisecondsBaseAddressByte+1, highByte(OneSecondMillisecondsUnsignedLong)); //save to EEPROM with write subrotine that alows write to timer constant. all other write subrotines do not. } void TellUserAboutTimerCalibrationValue(){ Serial.println(F("Timer calibration done.")); Serial.print(F("Calibration showed internal clock is off by ")); OneSecondMillisecondsFloat = float(OneSecondMillisecondsUnsignedLong); //convert long to float; not clear why I did this since it is not used in this subrotine ErrorPercentageFloat = 100*(LimitTestFloat -1); Serial.print(ErrorPercentageFloat, 4);//display to 4 places past decimal Serial.println(F("%.")); Serial.println(); } void OutputDataFileQ(){ if ((Command == 'O')|| (Command == 'o')){ //Output data to file on laptop OutputDataToFile(); Command = 'N'; //clear Command } } void DiagnoticOutputControlBlockQ(){ if ((Command == 'H')|| (Command == 'h')){ //Output control block DiagnoticOutputControlBlock(); Command = 'N'; //clear Command } } void SingleDisplayDataQ(){ if ((Command == 'D')|| (Command == 'd') ){//Display battery voltage and all port voltages OutputSingleLiveScan(); Command = 'N'; //clear Command } } void ContinuousDisplayDataQ(){ //Sets flag to display ports 0 through 6 voltages as they are read CR1.7 if ((Command == 'A')|| (Command == 'a') ){ ContinuousDisplayDataFlag = true; //flag used by DataIn; flag cleared by power cycle OutputContinuousDisplayDataStateChange(); //tell user they will see port readings as they are collected Command = 'N'; //clear Command } } void OutputMenuQ(){ if ((Command == 'M')|| (Command == 'm') ){ OutputMenuOfCommandsAndLED_Cadences(); Command = 'N'; //clear Command } } void FlashLED(){ if (ControlBlockReadEEPROM(ErrorCodeAddressByte) != 0){ if (FaultLED_CadenceFlag == true){ digitalWrite(ExternalLED, ExternalLED_OnByte); //turn external LED on }else{ digitalWrite(ExternalLED, ExternalLED_OffByte);//turn external LED off return; //fault cadence supersedes all others } } if((ControlBlockReadEEPROM(ReadyForLaunchFlagAddressByte) == false) && (ControlBlockReadEEPROM(InFlightFlagAddressByte) == false) && (ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte) == false)){ //in pre-flight state if (PreFlightLED_CadenceFlag == true){ digitalWrite(ExternalLED, ExternalLED_OnByte); //turn external LED on }else{ digitalWrite(ExternalLED, ExternalLED_OffByte);//turn external LED off } return; } if (ControlBlockReadEEPROM(MemoryLockFlagAddressByte) == true){//Memory locked has a higher priority than In Flight and disrupted power; memory locked or full so data collection stopped if (MemoryLockedLED_CadenceFlag == true){ digitalWrite(ExternalLED, ExternalLED_OnByte); //turn external LED on }else{ digitalWrite(ExternalLED, ExternalLED_OffByte);//turn external LED off } return; } if (ControlBlockReadEEPROM(InFlightFlagAddressByte) == true){ if (InFlightLED_CadenceFlag == true){ digitalWrite(ExternalLED, ExternalLED_OnByte); //turn external LED on }else{ digitalWrite(ExternalLED, ExternalLED_OffByte);//turn external LED off } return; } if (ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte) == true){ if (DisruptedPowerLED_CadenceFlag == true){ digitalWrite(ExternalLED, ExternalLED_OnByte); //turn external LED on }else{ digitalWrite(ExternalLED, ExternalLED_OffByte);//turn external LED off } return; } } void PrepareForLaunchQ(){ if ((Command == 'P') || (Command == 'p')){ SendPrepareForLaunchWarning(); ActOnResponseToPrepareForLaunch(); Command = 'N'; //clear Command } } void GenerateTestPatternQ(){//CR3.1 if ((Command == 'T') || (Command == 't')){ GenerateTestPatternWarning(); ActOnResponseToGenerateTestPattern(); Command = 'N'; //clear Command } } void DataIn(){ if (ControlBlockReadEEPROM(MemoryLockFlagAddressByte)== true)return; if ((ControlBlockReadEEPROM(InFlightFlagAddressByte)== true) || (ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte) == true)){ if (TimeToTakeSample == true){ RecordData(); } } } /******************************************************************************** L E V E L 3 S U B R O U T I N E S *********************************************************************************/ void RecordData(){ TimeToTakeSample = false; ReadEEPROM_Pointer(CalledByRecordDataByte); RecordPortReadings(); IncrementEEPROM_Pointer(); WriteEEPROM_Pointer(); } void PreFlightCadence(){ //4 flasher per cycle PreFlightCadenceIntervalMS_Int = int(millis() - StartOfPreFlightCadenceMS_UnsignedLong); if ((PreFlightCadenceIntervalMS_Int > 0) && (PreFlightCadenceIntervalMS_Int < 500)){ PreFlightLED_CadenceFlag = true; return; } if ((PreFlightCadenceIntervalMS_Int > 500) && (PreFlightCadenceIntervalMS_Int < 1000)){ PreFlightLED_CadenceFlag = false; return; } if ((PreFlightCadenceIntervalMS_Int > 1000) && (PreFlightCadenceIntervalMS_Int < 1500)){ PreFlightLED_CadenceFlag = true; return; } if ((PreFlightCadenceIntervalMS_Int > 1500) && (PreFlightCadenceIntervalMS_Int < 2000)){ PreFlightLED_CadenceFlag = false; return; } if ((PreFlightCadenceIntervalMS_Int > 2000) && (PreFlightCadenceIntervalMS_Int < 2500)){ PreFlightLED_CadenceFlag = true; return; } if ((PreFlightCadenceIntervalMS_Int > 2500) && (PreFlightCadenceIntervalMS_Int < 3000)){ PreFlightLED_CadenceFlag = false; return; } if ((PreFlightCadenceIntervalMS_Int > 3000) && (PreFlightCadenceIntervalMS_Int < 3500)){ PreFlightLED_CadenceFlag = true; return; } if ((PreFlightCadenceIntervalMS_Int > 3500) && (PreFlightCadenceIntervalMS_Int < 4000)){ PreFlightLED_CadenceFlag = false; return; } if (PreFlightCadenceIntervalMS_Int > 9000) { StartOfPreFlightCadenceMS_UnsignedLong = millis(); //restart timer return; } } void InFlightCadence(){ //1 pulse per cycle InFlightCadenceIntervalMS_Int = int(millis() - StartOfInFlightCadenceMS_UnsignedLong); if ((InFlightCadenceIntervalMS_Int > 0) && (InFlightCadenceIntervalMS_Int < 500)){ InFlightLED_CadenceFlag = true; return; } if ((InFlightCadenceIntervalMS_Int > 500) && (InFlightCadenceIntervalMS_Int < 1000)){ InFlightLED_CadenceFlag = false; return; } if (InFlightCadenceIntervalMS_Int > 5500) { StartOfInFlightCadenceMS_UnsignedLong = millis(); //restart timer return; } } void DisruptedPowerCadence(){ //2 pulses per cycle DisruptedPowerCadenceIntervalMS_Int = int(millis() - StartOfDisruptedPowerCadenceMS_UnsignedLong); if ((DisruptedPowerCadenceIntervalMS_Int > 0) && (DisruptedPowerCadenceIntervalMS_Int < 500)){ DisruptedPowerLED_CadenceFlag = true; return; } if ((DisruptedPowerCadenceIntervalMS_Int > 500) && (DisruptedPowerCadenceIntervalMS_Int < 1000)){ DisruptedPowerLED_CadenceFlag = false; return; } if ((DisruptedPowerCadenceIntervalMS_Int > 1000) && (DisruptedPowerCadenceIntervalMS_Int < 1500)){ DisruptedPowerLED_CadenceFlag = true; return; } if ((DisruptedPowerCadenceIntervalMS_Int > 1500) && (DisruptedPowerCadenceIntervalMS_Int < 2000)){ DisruptedPowerLED_CadenceFlag = false; return; } if (DisruptedPowerCadenceIntervalMS_Int > 7000) { StartOfDisruptedPowerCadenceMS_UnsignedLong = millis(); //restart timer return; } } void MemoryLockedCadence(){ //3 pulses per cycle MemoryLockedCadenceIntervalMS_Int = int(millis() - StartOfMemoryLockedLED_CadenceMS_UnsignedLong); //added int() to convert from unsignedlong CR2.0 if ((MemoryLockedCadenceIntervalMS_Int > 0) && (MemoryLockedCadenceIntervalMS_Int < 500)){ MemoryLockedLED_CadenceFlag = true; return; } if ((MemoryLockedCadenceIntervalMS_Int > 500) && (MemoryLockedCadenceIntervalMS_Int < 1000)){ MemoryLockedLED_CadenceFlag = false; return; } if ((MemoryLockedCadenceIntervalMS_Int > 1000) && (MemoryLockedCadenceIntervalMS_Int < 1500)){ MemoryLockedLED_CadenceFlag = true; return; } if ((MemoryLockedCadenceIntervalMS_Int > 1500) && (MemoryLockedCadenceIntervalMS_Int < 2000)){ MemoryLockedLED_CadenceFlag = false; return; } if ((MemoryLockedCadenceIntervalMS_Int > 2000) && (MemoryLockedCadenceIntervalMS_Int < 2500)){ MemoryLockedLED_CadenceFlag = true; return; } if ((MemoryLockedCadenceIntervalMS_Int > 2500) && (MemoryLockedCadenceIntervalMS_Int < 3000)){ MemoryLockedLED_CadenceFlag = false; return; } if (MemoryLockedCadenceIntervalMS_Int > 7500) { StartOfMemoryLockedLED_CadenceMS_UnsignedLong = millis(); //restart timer return; } } void FaultCadence(){ //5 pulses in set and 5 seconds between FaultCadenceIntervalMS_Int = int(millis() - StartOfFaultLED_CadenceMS_UnsignedLong); if ((FaultCadenceIntervalMS_Int > 0) && (FaultCadenceIntervalMS_Int < 500)){ FaultLED_CadenceFlag = true; return; } if ((FaultCadenceIntervalMS_Int > 500) && (FaultCadenceIntervalMS_Int < 1000)){ FaultLED_CadenceFlag = false; return; } if ((FaultCadenceIntervalMS_Int > 1000) && (FaultCadenceIntervalMS_Int < 1500)){ FaultLED_CadenceFlag = true; return; } if ((FaultCadenceIntervalMS_Int > 1500) && (FaultCadenceIntervalMS_Int < 2000)){ FaultLED_CadenceFlag = false; return; } if ((FaultCadenceIntervalMS_Int > 2000) && (FaultCadenceIntervalMS_Int < 2500)){ FaultLED_CadenceFlag = true; return; } if ((FaultCadenceIntervalMS_Int > 2500) && (FaultCadenceIntervalMS_Int < 3000)){ FaultLED_CadenceFlag = false; return; } if ((FaultCadenceIntervalMS_Int > 3000) && (FaultCadenceIntervalMS_Int < 3500)){ FaultLED_CadenceFlag = true; return; } if ((FaultCadenceIntervalMS_Int > 3500) && (FaultCadenceIntervalMS_Int < 4000)){ FaultLED_CadenceFlag = false; return; } if ((FaultCadenceIntervalMS_Int > 4000) && (FaultCadenceIntervalMS_Int < 4500)){ FaultLED_CadenceFlag = true; return; } if ((FaultCadenceIntervalMS_Int > 4500) && (FaultCadenceIntervalMS_Int < 5000)){ FaultLED_CadenceFlag = false; return; } if (FaultCadenceIntervalMS_Int > 10000) { StartOfFaultLED_CadenceMS_UnsignedLong = millis(); //restart timer return; } } void OutputMenuOfCommandsAndLED_Cadences(){ Serial.println(); Serial.println(F(" Flight Data Recorder")); Serial.println(F(" by Rick Sparber")); Serial.println(); Serial.print(F("Version ")); Serial.println(VersionNumber); Serial.println(); Serial.println(F("M This menu.")); Serial.println(F("D Display all port voltages and set the pre-flight state.")); Serial.println(F("P Prepare for launch!")); Serial.println(F("A Use only while in flight: Display all port voltages.")); //CR1.7 Serial.println(F("O Output data file.")); Serial.println(F("S Stop data collection.")); Serial.println(F("C Calibrate internal hardware clock.")); Serial.println(F("H Diagnostic: Output control block.")); Serial.println(F("T Diagnostic: Generate test pattern in data memory.")); Serial.println(); Serial.println(F("LED semaphore")); Serial.println(F("1 flash per cycle - normal data collection")); Serial.println(F("2 flashes per cycle - normal data collection after power disruption")); Serial.println(F("3 flashes per cycle - memory locked either manually or because it is full"));///CR2.0 Serial.println(F("4 flashes per cycle - pre-flight")); Serial.println(F("5 flashes per cycle - hardware or software fault detected")); } void OutputSingleLiveScan(){ byte PortNumberByte = 0; //CR2.0 float PortVoltageFloat = 0; //CR2.0 float BateryVoltageFloat = 0; //CR2.0 const byte Port7 = 7; BateryVoltageFloat = LivePortVoltageReadingFloat(Port7)*2; //port 7 reads half due to voltage divider so multiply by 2 before displaying. //if a fault is present, explain it now DisplayErrorStateQ(); //if any error detected, tell user Serial.println(); Serial.println(); Serial.println(F("***Display of all Port Voltages and Pre-flight State***")); Serial.println(); Serial.print(F("Battery: ")); //CR1.7 if (BateryVoltageFloat < 5){ //when battery is off, we see less than 5V and sensors do not get 5V Serial.println(F(" disconnected")); Serial.println(F("Sensors do not have 5V.")); }else{ Serial.print(BateryVoltageFloat,2); //output format x.xx Serial.println(F(" volts")); } Serial.println(); Serial.println(F("Port Voltage")); Serial.println(F("==== =======")); for (PortNumberByte = 0; PortNumberByte < 7; PortNumberByte++){ Serial.print(F(" ")); Serial.print(PortNumberByte); Serial.print(F(" ")); PortVoltageFloat = LivePortVoltageReadingFloat(PortNumberByte); Serial.println(PortVoltageFloat,2); //output format x.xx } Serial.println(); Serial.print(F("Time calibration is now showing ")); Serial.print(int(ControlBlockReadEEPROM(OneSecondMillisecondsBaseAddressByte)) + int (256* ControlBlockReadEEPROM(OneSecondMillisecondsBaseAddressByte +1))); Serial.println(F(" internal")); Serial.println(F("millisecond counts per actual second.")); Serial.println(); Serial.println(F("If not between 900 and 1100, run time calibration.")); Serial.println(); Serial.println(); JustWait(); //when port voltages are displayed, we automatically go into the just wait state. } void JustWait(){ ControlBlockWriteEEPROM(ReadyForLaunchFlagAddressByte,false); ControlBlockWriteEEPROM(InFlightFlagAddressByte, false); ControlBlockWriteEEPROM(DisruptedPowerFlagAddressByte,false); ControlBlockWriteEEPROM(MemoryLockFlagAddressByte,true); ControlBlockWriteEEPROM(ErrorCodeAddressByte,SystemNormalFlagByte); //verify writes worked if ((ControlBlockReadEEPROM(ReadyForLaunchFlagAddressByte) != false)||(ControlBlockReadEEPROM(InFlightFlagAddressByte)!= false)||(ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte)!= false) || (ControlBlockReadEEPROM(MemoryLockFlagAddressByte)!= true)){ ControlBlockWriteEEPROM(ErrorCodeAddressByte, JustWaitReadBackFailureFlagByte); } } void OutputContinuousDisplayDataStateChange(){ //CR1.7 if (ControlBlockReadEEPROM(MemoryLockFlagAddressByte)== true){ Serial.println(); Serial.println(F("Memory is locked so there is no new data to display.")); return; } if ((ControlBlockReadEEPROM(InFlightFlagAddressByte)== true) || (ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte) == true)){ Serial.println(); Serial.println(); Serial.println(F("Port readings will be displayed as they are collected.")); Serial.println(); Serial.println(F("Port0 Port1 Port2 || Port3 Port4 Port5 || Port6 battery")); } } void OutputDataToFile(){ //data was collected every 2 seconds so is output the same way CR3.0 //output line format is //Minutes,Seconds,Port0,Port1,Port2,Port3,Port4,Port5,Port6,Battery, int MinutesInt = 0; //CR2.0 float VoltageFloat = 0; //CR2.0 int TimeStampInt = 0;// CR2.0 ControlBlockWriteEEPROM(MemoryLockFlagAddressByte,true); //set MemoryLock to true so no data can be overwritten Serial.println(F("Minutes,Seconds,Port0,Port1,Port2,Port3,Port4,Port5,Port6,Battery,"));//output titles of the columns. ReadEEPROM_Pointer(CalledByOutputDataToFileByte); //get pointer to end of data which is called. The 7 is the calling subroutine's ID StartOfNextMemoryBlockLong for (long EEPROM_PointerLong = StartOfDataMemoryLong; EEPROM_PointerLong < StartOfNextMemoryBlockLong;EEPROM_PointerLong = EEPROM_PointerLong + DataBlockSize){//move to start of each block of data and sequence through all of the blocks //first two output fields are the time stamp unless we had power hit CR3.0 if (ReadEEPROM(EEPROM_PointerLong + 14L) + ReadEEPROM(EEPROM_PointerLong + 15L) == 0){ //0 means power hit recorded Serial.println(); Serial.println(F("Power has been disrupted during flight.")); TimeStampInt = 0; //when battery voltage reads 0, it means power hit recorded so restart time stamp count }else{ TimeStampInt = TimeStampInt + SamplingRateByte; //blocks are built every SamplingRateByte seconds so block count is time; first block gets time stamp of SamplingRateByte. MinutesInt = TimeStampInt/60; SecondsInt = TimeStampInt % 60; //show remainder seconds in each minute Serial.print(MinutesInt); Serial.print(","); //building a Comma Separated Volume (CSV) output Serial.print(SecondsInt); Serial.print(","); for (byte port = 0; port<8;port++){ //ports 0-7 VoltageFloat = (float(256L*(ReadEEPROM(EEPROM_PointerLong + (2L*long(port)))) + ReadEEPROM(EEPROM_PointerLong + 1L + (2L*long(port))))*0.004888); //read high byte and low byte, convert to float, then convert to voltage with 5V for Vref divided by 1023 = 0.004888 CR3.0 if (port == 7)VoltageFloat = 2*VoltageFloat;//battery reading, multiply by 2 because voltage divider cut battery voltage in half Serial.print(VoltageFloat,3);//3 places past decimal point CR3.2 Serial.print(","); } } Serial.println();//end of block of data so add line feed } } void DiagnoticOutputControlBlock(){ //english output version //output Control Block plus file name float TotalRunTimeFloat = 0; int PercentFullInt = 0; Serial.println(); Serial.println(); Serial.println(F("Control Block")); Serial.println(F("======================")); if((ReadEEPROM(ReadyForLaunchFlagAddressByte) == true) && (ReadEEPROM(MemoryLockFlagAddressByte) == false))Serial.println(F("In Pre-flight.")); if((ReadEEPROM(InFlightFlagAddressByte) == true) && (ReadEEPROM(MemoryLockFlagAddressByte) == false))Serial.println(F("In flight.")); if((ReadEEPROM(DisruptedPowerFlagAddressByte) == true) && (ReadEEPROM(MemoryLockFlagAddressByte) == false))Serial.println(F("In flight with power hit.")); if(ReadEEPROM(MemoryLockFlagAddressByte) == true)Serial.println(F("Memory is locked. In Data Readout.")); if(ReadEEPROM(ErrorCodeAddressByte) == 0){ Serial.println(F("There are no detected faults.")); }else{ Serial.print(F("Fault number ")); Serial.print(ReadEEPROM(ErrorCodeAddressByte)); Serial.println(F(" has been detected.")); Serial.println(); Serial.println(F("Clear fault flag with Pre-flight command.")); } Serial.print(F("Start of Next Memory Block = "));//CR3.0 ReadEEPROM_Pointer(CalledByDiagnoticOutputControlBlockByte); Serial.print(StartOfNextMemoryBlockLong); Serial.print(F(" (")); PercentFullInt = int(0.5+((StartOfNextMemoryBlockLong-StartOfDataMemoryLong)*100L)/(131072L-StartOfDataMemoryLong));//round to the nearest percent CR3.2 Serial.print(PercentFullInt); Serial.println(F("% full)")); Serial.print(F("Time calibration value = ")); OneSecondMillisecondsUnsignedLong = ControlBlockReadEEPROM(OneSecondMillisecondsBaseAddressByte) + ( ControlBlockReadEEPROM(OneSecondMillisecondsBaseAddressByte + 1)*256); Serial.println(OneSecondMillisecondsUnsignedLong); Serial.println(); Serial.print(F("Data will be recorded every ")); if(SamplingRateByte == 1) { Serial.println(F("second for a total")); Serial.print(F("run time of ")); }else{ Serial.print(SamplingRateByte); Serial.println(F(" seconds for a total")); Serial.print(F("run time of ")); } float SampleRateFloat = SamplingRateByte; TotalRunTimeFloat = 8191*SampleRateFloat/3600; //maximum run time in decimal hours Serial.print(TotalRunTimeFloat); Serial.println(F(" hours.")); Serial.println(); Serial.print(F("Compilation date is ")); Serial.println(__DATE__); Serial.println(); Serial.println(__FILE__); Serial.println(); } void SendPrepareForLaunchWarning(){ Serial.println(); Serial.println(F("You asked to prepare to collect data.")); Serial.println(F("This will erase all data. Are you sure? Y/N"));//verify user wants to do this } void GenerateTestPatternWarning(){ //CR3.1 Serial.println(); Serial.println(F("You asked to generate a test pattern in the data.")); Serial.println(F("This will erase all existing data. Are you sure? Y/N"));//verify user wants to do this } void ActOnResponseToPrepareForLaunch(){ while (true){//wait until a character comes in CR1.9 if (Serial.available() > 0){ Command = Serial.read(); if ((Command == 'Y')|| (Command == 'y')){ PrepareForLaunch(); Serial.println (F("Ready to launch. Turn off power now.")); Serial.println(F("No further commands will be accepted until after power cycle.")); NonFaultStopProgram(); }else{ Serial.println (F("Prepare for launch request aborted.")); return; } } } } void ActOnResponseToGenerateTestPattern(){ while (true){//wait until a character comes in CR3.1 if (Serial.available() > 0){ Command = Serial.read(); if ((Command == 'Y')|| (Command == 'y')){ Serial.println(); Serial.println (F("Test Pattern generation about to start.")); GenerateTestPattern(); Serial.println(); Serial.println (F("Done.")); return; }else{ Serial.println (F("Test pattern request aborted.")); return; } } } } void GenerateTestPattern(){//CR3.1 /* I need to be able to generate a test pattern in the data. LSB is about 0.005V but I display down to 0.01V. This means I should increment starting at bit 2 which means increment in steps of 2. I don’t want any data to be the same as adjacent data so can start first port at 0*2 so will show 0, the second port at 1*2 and should shown 0.01v, third 2*3 and should show 0.03, etc. On next block, the first port will be at 2+16 = 18. */ const long NumberOfTestBlocksLong = 10L; //CR3.2 long TopOfMemoryLong = 0; int StartingValueInt = 0; int PortStepSizeInt = 1; //since bit 2, this value is multiplied by 2 int BlockStepSizeInt = 2;//leave room for oversampled value that fits between blocks (feature not used yet) byte TestValueHighByte = 0; byte TestValueLowByte = 0; unsigned long AddressUnsignedLong = 0; int TestValueInt = StartingValueInt; int TestValueBatteryInt = 0; ControlBlockWriteEEPROM(MemoryLockFlagAddressByte, false); //set MemoryLock to false for (TopOfMemoryLong = StartOfDataMemoryLong; TopOfMemoryLong < ((NumberOfTestBlocksLong*DataBlockSize)+StartOfDataMemoryLong);TopOfMemoryLong = TopOfMemoryLong+DataBlockSize){//move across the blocks of data to fill all of memory for (byte PortPointerByte = 0; PortPointerByte < 7; PortPointerByte=PortPointerByte+1){//move along current block of data but stop before battery value AddressUnsignedLong = TopOfMemoryLong + (2*PortPointerByte);//generate address of next byte to be written TestValueHighByte = highByte(TestValueInt); TestValueLowByte = lowByte(TestValueInt);//parce test value into its bytes WriteEEPROM(AddressUnsignedLong,TestValueHighByte); //write test bytes to EEPROM WriteEEPROM(AddressUnsignedLong+1L,TestValueLowByte); TestValueInt = TestValueInt + PortStepSizeInt*2;//advance test value } TestValueBatteryInt = (TestValueInt/2) + 512;//battery reading which must be >5 to avoid battery off warning. 512 is equiv to 2.5V which is multiplied by 2 in output code AddressUnsignedLong = TopOfMemoryLong + (2*7); //battery's address TestValueHighByte = highByte(TestValueBatteryInt);//output multiplies this value by 2 so we will stay above 5V TestValueLowByte = lowByte(TestValueBatteryInt); WriteEEPROM(AddressUnsignedLong,TestValueHighByte); //write test bytes to EEPROM WriteEEPROM(AddressUnsignedLong+1L,TestValueLowByte); TestValueInt = TestValueInt + PortStepSizeInt*2;//so average of adjacent blocks for the same port is not truncated Serial.print(F("."));//status to user - one block written }//end of block so move to next block StartOfNextMemoryBlockLong = (NumberOfTestBlocksLong*DataBlockSize)+StartOfDataMemoryLong; //update memory pointer so output subroutine knows when to stop WriteEEPROM_Pointer(); ControlBlockWriteEEPROM(MemoryLockFlagAddressByte, true); //done generating test data so lock memory } void ReadEEPROM_Pointer(byte ID){ //ID used to figure out who called this subroutine since it is called by 4 subroutines StartOfNextMemoryBlockLong = (1L * long(ControlBlockReadEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte))) + (256L * (long(ControlBlockReadEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte + 1)))) + (65536L * (long(ControlBlockReadEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte + 2)))) + (16777216L * long(ControlBlockReadEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte + 3))); } //a long is 4 bytes. Each read is a byte that I first convert to a long. Then I multiply each converted byte by an integer defined as a long and add them up. The sum is a long. In this way we don't mix data types. void WriteEEPROM_Pointer(){ WriteEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte,byte(StartOfNextMemoryBlockLong)); //this should take the lowest byte of the float. WriteEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+1,byte(StartOfNextMemoryBlockLong>>8)); //this should take the second byte WriteEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+2,byte(StartOfNextMemoryBlockLong>>16)); //this should take the third byte WriteEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+3,byte(StartOfNextMemoryBlockLong>>24)); //this should take the forth byte } void RecordPortReadings(){ //records one block of data #ifdef SoftwireActive //if Softwire enabled, this subroutine must change to read digital sensors,store the results in EEPROM, and conditionally output data to display; put new code here. #else int PortReadingInInt = 0;//CR2.0 byte PortReadingInHighByte = 0;//CR3.0 byte PortReadingInLowByte = 0;//CR3.0 byte PortReadingOutByte = 0; //CR2.0 long PortDataAddressLong = StartOfNextMemoryBlockLong; //port 0 CR3.0 float VoltageFloat = 0; //used when sending port voltages to display if (ContinuousDisplayDataFlag == true)Serial.println(); //used on the ground to test system; force new line before outputting data; CR1.7 for (byte PortCountByte = 0; PortCountByte < 8; PortCountByte++){ //read, write and optionally display all 7 data ports CR3.0 if((PortCountByte == 7) && (JustPoweredUpForTimeStampFlag == true) && (ControlBlockReadEEPROM(DisruptedPowerFlagAddressByte) == true)){ //this is the battery data. If we just recovered from a power hit, set this value to 0 to indicate power hit CR3.0 PortReadingInInt = 0; JustPoweredUpForTimeStampFlag = false; //is set at power up and this is the only use so can be cleared here } else { //did not have a power hit to record all 8 ports the same PortReadingInInt = analogRead(LogicalAnalogPinByte[PortCountByte]); //10 bits of data } PortReadingInHighByte = highByte(PortReadingInInt);//CR3.0 PortReadingInLowByte = lowByte(PortReadingInInt); //CR3.0 WriteEEPROM(PortDataAddressLong,PortReadingInHighByte); //CR3.0 WriteEEPROM(PortDataAddressLong+1L,PortReadingInLowByte); //CR3.0 if (ContinuousDisplayDataFlag == true){ //CR1.7 if (PortCountByte < 7){ //external sensor ports VoltageFloat = float(PortReadingInInt) * 0.004888; //CR3.0 }else{ //battery monitor VoltageFloat = float(PortReadingInInt) * 0.004888 * 2; //used for battery reading where we first divided by 2 so must now compensate for that. CR3.0 } Serial.print(VoltageFloat,2);//used on the ground to test system; 2 places past decimal point Serial.print(F(" ")); //put two spaces after each reading if ((PortCountByte == 2) || PortCountByte == 5) Serial.print (F("|| ")); //every 3 ports, put "|| " between readings } PortDataAddressLong = PortDataAddressLong + 2L; //advance to the next port data address CR3.0 #endif } } void IncrementEEPROM_Pointer(){ /* EEPROM is 2^17 = 131,072 bytes going from address 0 to 131071. A block is DataBlockSize (16 bytes). Addresses 0 through 15 are for the control block. This first data block goes from address 16 through [(16+16) -1] = 31. The next block starts at (16 + 16 =) 32 and goes through 47. The max number of data blocks is {[2^17] - control block}/16 = (131072 - 16)/16 = 8191 block of data. The last data block starts at 16 + (16*8190) = 131,056. If we went one more data block, it would start at 131072 which is beyond available memory. DataBlockSize is in a #define so is a substitution within the compiler. Therefore it does not have Byte added to the end. CR3.0 */ ReadEEPROM_Pointer(CalledByIncrementEEPROM_PointerByte); //it outputs to StartOfNextMemoryBlockLong; this is the start of the block that was just written. StartOfNextMemoryBlockLong = StartOfNextMemoryBlockLong + DataBlockSize;//increment to address of the next data block start. If =< 131071 - DataBlockSize, we return without locking EEPROM. If >131071 - DataBlockSize we lock EEPROM and return if (StartOfNextMemoryBlockLong > (131072 - DataBlockSize)){//If true, there is not enough room for this next DataBlock. Lock the EEPROM and return. CR3.0 ControlBlockWriteEEPROM(MemoryLockFlagAddressByte,true); } } /******************************************************************************** L E V E L 4 S U B R O U T I N E S *********************************************************************************/ void DisplayErrorStateQ(){ byte ErrorCode = ControlBlockReadEEPROM(ErrorCodeAddressByte); if (ErrorCode != 0){ Serial.println(); Serial.println(F("********************************************")); } switch (ErrorCode){ case 1: Serial.println(F("Out of range read detected.")); break; case 2: Serial.println(F("Out of range write detected.")); break; case 3: Serial.println(F("ControlTheFlightParameters() Ready for Launch EEPROM read back failure.")); break; case 4: Serial.println(F("ControlTheFlightParameters() In Flight EEPROM read back failure.")); break; case 5: Serial.println(F("JustWait() EEPROM read back failure.")); break; case 6: Serial.println(F("PrepareForLaunch() EEPROM read back failure.")); break; case 7: Serial.println(F("WriteEEPROM() asked to write to locked memory.")); break; } if (ErrorCode != 0){ Serial.println(F("********************************************")); Serial.println(); } } void PrepareForLaunch(){ //StartOfNextMemoryBlockPointerBaseAddressByte ControlBlockWriteEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte,byte(StartOfDataMemoryLong)); //initialize StartOfNextMemoryBlockLong to decimal 20 ControlBlockWriteEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+1,byte(StartOfDataMemoryLong>>8)); ControlBlockWriteEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+2,byte(StartOfDataMemoryLong>>16)); ControlBlockWriteEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+3,byte(StartOfDataMemoryLong>>24)); ControlBlockWriteEEPROM(ReadyForLaunchFlagAddressByte, true); ControlBlockWriteEEPROM(MemoryLockFlagAddressByte, false); //set MemoryLock to false ControlBlockWriteEEPROM(ErrorCodeAddressByte,0); //set to system normal //verify all writes worked if ((ControlBlockReadEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte) != byte(StartOfDataMemoryLong))||(ControlBlockReadEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+1) != byte(StartOfDataMemoryLong>>8))||(ControlBlockReadEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+2) != byte(StartOfDataMemoryLong>>16))||(ControlBlockReadEEPROM(StartOfNextMemoryBlockPointerBaseAddressByte+3) != byte(StartOfDataMemoryLong>>24))||(ControlBlockReadEEPROM(MemoryLockFlagAddressByte)!= false)||(ReadEEPROM(ReadyForLaunchFlagAddressByte)!=true)||(ReadEEPROM(ErrorCodeAddressByte)!=0)){ ControlBlockWriteEEPROM(ErrorCodeAddressByte, PrepareForLaunchReadBackFailureFlagByte); } } float LivePortVoltageReadingFloat(byte PortNumberByte){ float PortReadingFloat = 0; //CR2.0 PortReadingFloat = float(analogRead(LogicalAnalogPinByte[PortNumberByte]));//convert from int to float PortReadingFloat = PortReadingFloat*0.004888; //convert from pure number to voltage at 4.888 mV per step return PortReadingFloat; } /******************************************************************************** L E V E L 5 S U B R O U T I N E S **********************************************************************/ void NonFaultStopProgram(){ //effectively stop program and also flickers LED while(true){ //CR1.9 digitalWrite(ExternalLED, ExternalLED_OnByte); delay(100); digitalWrite(ExternalLED, ExternalLED_OffByte); delay(100); } } void FaultStopProgram(){ //effectively stop program and turns off LED while(true){ //CR1.9 digitalWrite(ExternalLED, ExternalLED_OffByte); } } void TimerConstantWriteEEPROM(byte eeAddressByte, byte data){ //subroutine accepts a single byte for the address and converts it to a long because this is the control block which will always be contain less than 255 bytes. This subroutine is only used by CalibrateClockQ() to prevent wild writes to this constant. All other writes block writes to address 8 and 9. UnprotectedWriteEEPROM(long(eeAddressByte), data); } void ControlBlockWriteEEPROM(byte eeAddressByte, byte data){ //subroutine accepts a single byte for the address and converts it to a long because this is the control block which will always be contain less than 255 bytes WriteEEPROM(long(eeAddressByte), data); } byte ControlBlockReadEEPROM(byte eeAddressByte){ //subroutine accepts a single byte for the address and converts it to a long because this is the control block which will always be contain less than 255 bytes return ReadEEPROM(long(eeAddressByte)); } /******************************************************************************** E E P R O M S U B R O U T I N E S *********************************************************************************/ //Based on eeprom example from SparkFun Electronics June 11th, 2017 void WriteEEPROM(long eeAddress, byte data){ if ((eeAddress == 8)||(eeAddress == 9)){//timer calibration address should not be written to except by calibration program WriteEEPROM(ErrorCodeAddressByte,AttemptMadeToWriteToOneSecondMillisecondsConstant); //note that this seems to be a recursive call but is not because I am not writing to timer calibration address return; //set error value but keep collecting data }else{//all other writes pass through UnprotectedWriteEEPROM(eeAddress,data); } } void UnprotectedWriteEEPROM(long eeAddress, byte data){ //subroutine accepts a 4 byte address and a single byte of data to write //The EEPROM's spec sheet shows a Block Select Bit (B0) and two bytes of address. When eeAddress is < 65536, the lower block is accessed so B0 = 0. All of my addresses are this case as verified with a scope. if (eeAddress > 131071L){ //max address is 2^17 - 1 = 131071 ControlBlockWriteEEPROM(ErrorCodeAddressByte,OutOfRangeWriteFlagByte); return; } if ((ReadEEPROM(MemoryLockFlagAddressByte)==true) && (eeAddress >15)){ //this permits writes to the control parameters even when EEPROM is locked. CR3.1 ControlBlockWriteEEPROM(ErrorCodeAddressByte,WriteEEPROM_AttemptMadeToWriteToLockedMemoryFlagByte); return; //if Lock EEPROM flag is true, do not write to EEPROM. Is set false by "P". } if (eeAddress < 65536L) { Wire.beginTransmission(EEPROM_ADR_LOW_BLOCK); eeAddress &= 0xFFFF; //Erase the first 16 bits of the long variable } else { Wire.beginTransmission(EEPROM_ADR_HIGH_BLOCK); } Wire.write((int)(eeAddress >> 8)); // MSB Wire.write((int)(eeAddress & 0xFF)); // LSB Wire.write(data); //write single byte Wire.endTransmission(); delay(10); //Write cycle time is max of 5 ms so wait 10. //verify write worked if (ReadEEPROM(eeAddress) != data){ Serial.println(F("EEPROM write failed. Error 10.")); ControlBlockWriteEEPROM(ErrorCodeAddressByte,ReadBackFromEEPROM_Mismatch); //CR2.0 } } byte ReadEEPROM(long eeaddress){ //subroutine accepts a 4 byte address and a returns a single byte of data if (eeaddress > 131071L){ ControlBlockWriteEEPROM(ErrorCodeAddressByte, OutOfRangeReadFlagByte); return 0; } if (eeaddress < 65536) { //send block address based on eeaddress Wire.beginTransmission(EEPROM_ADR_LOW_BLOCK); }else{ Wire.beginTransmission(EEPROM_ADR_HIGH_BLOCK); } //then send data address within the block Wire.write((int)(eeaddress >> 8)); // MSB Wire.write((int)(eeaddress & 0xFF)); // LSB Wire.endTransmission(); if (eeaddress < 65536){ Wire.requestFrom(EEPROM_ADR_LOW_BLOCK, 1); }else{ Wire.requestFrom(EEPROM_ADR_HIGH_BLOCK, 1); } byte rdata = 0x33; //if Wire.available() returns false, 0x33 is returned from subrotine if (Wire.available()) rdata = Wire.read(); //if EEPROM and its I2C is available, return the single byte. return rdata; }