Esempio n. 1
0
/**
 * Use core variables to lookup and calculate the derived variables. This
 * function uses the core variables to lookup and calculate further second
 * order variables such as load, VE, Lamdda, Transient fuel correction, engine
 * temperature enrichment, Injector dead time, etc.
 */
void generateDerivedVars(){
	extern unsigned long RPMWeighted;
	extern KeyUserDebug KeyUserDebugs;
	extern const volatile fixedConfig1 fixedConfigs1;                                                                                                
	extern const volatile fixedConfig2 fixedConfigs2;
	/* Determine load based on options */
	if(!(fixedConfigs2.algorithmSettings.loadType)){ /* Use MAP as load */
		DerivedVars->LoadMain = CoreVars->MAP;
	}else if(fixedConfigs2.algorithmSettings.loadType == LOAD_TPS){ /* Use TPS as load */
		DerivedVars->LoadMain = CoreVars->TPS;
	}else if(fixedConfigs2.algorithmSettings.loadType == LOAD_AAP){ /* Use AAP corrected MAP as load */
		DerivedVars->LoadMain = ((unsigned long)CoreVars->MAP * CoreVars->AAP) / KPA(100);
		// TODO add maf calc load option here
	}else if(fixedConfigs2.algorithmSettings.algorithmType == ALGO_MAF){
		//For now keep Load based on MAP for other uses, during the fuel calc algorithmType
		//will catch the MAF mode and use its reading
		DerivedVars->LoadMain = CoreVars->MAP;
	}else{ /* Default to MAP, but throw error */
		DerivedVars->LoadMain = CoreVars->MAP;
	}

	// Back calculate KPA using a MAF sensor. Air Flow is in CC/min, looked up from a 2d table based on the signal from the MAF sensor.
	if(fixedConfigs2.algorithmSettings.algorithmType == ALGO_MAF){
		unsigned long collectiveFlow = lookupTwoDTableUSV((twoDTableUSMAF*)&TablesC.SmallTablesC.MAFVersusVoltageTable, CoreVars->MAF, TWODTABLEUS_MAF_LENGTH);
		collectiveFlow <<= 16;
		unsigned long cylFill = ((collectiveFlow / fixedConfigs1.engineSettings.cylinderCount) / ((RPMWeighted / RPM_FACTOR)
							/ (fixedConfigs1.engineSettings.strokesPerCycle / 2)));
		KeyUserDebugs.zsp9 = (cylFill * CYLINDER_FLOW_FACTOR) / fixedConfigs1.engineSettings.perCylinderVolume; //Calculated KPA
		//KeyUserDebugs.zsp8 = lookupTwoDTableUSV((twoDTableUSMAF*)&TablesC.SmallTablesC.MAFVersusVoltageTable, CoreVars->MAF, TWODTABLEUS_MAF_LENGTH);
	}

	/* Look up target Lambda with RPM and Load */
//#if CONFIG == SEANKR1_ID //FIXME temp hack until a real lambda table is built
//	DerivedVars->Lambda = LR(0.80); //From what I read E85 makes best power in the low .8s
//#else
	DerivedVars->Lambda = lookupMainTable(CoreVars->RPM, DerivedVars->LoadMain, LAMBDA_TABLE_LOCATION_ID);
//#endif

	/* Look up injector dead time with battery voltage */
	DerivedVars->IDT = lookupTwoDTableUS((twoDTableUS*)&TablesA.SmallTablesA.injectorDeadTimeTable, CoreVars->BRV);

	if(!(fixedConfigs2.algorithmSettings.dwellType)){
		DerivedVars->Dwell = lookupTwoDTableUS((twoDTableUS*)&TablesA.SmallTablesA.dwellDesiredVersusVoltageTable, CoreVars->BRV);
	}else if(fixedConfigs2.algorithmSettings.dwellType == DWELL_RPM){
		DerivedVars->Dwell = lookupTwoDTableUS((twoDTableUS*)&TablesA.SmallTablesA.dwellVersusRPMTable, CoreVars->RPM);
	}else if(fixedConfigs2.algorithmSettings.dwellType == DWELL_FIXED){
		DerivedVars->Dwell = fixedConfigs2.algorithmSettings.dwellFixedPeriod;
	}else{
		DerivedVars->Dwell = 0;
	}

	unsigned long tempAdvance = ANGLE_FACTOR * (unsigned long)lookupMainTable(CoreVars->RPM, DerivedVars->LoadMain, IGNITION_ADVANCE_TABLE_MAIN_LOCATION_ID);
	DerivedVars->Advance = (unsigned short)(tempAdvance / 1024); // This calculation will change when the timing tables get shrunk to a more reasonable 8 bit size with appropriate scaling
	// Move this magic number to an appropriate place and/or refactor timing calcs/values/etc

/// @todo TODO make generic!!!!
// to go generic we need:
// angle between ignition events (if have tpd) (or total angle and number of events)
// max % dwell
// minimum spark time
// a setting to choose which behaviour (don't limit/% dwell limit/min spark time/other?)
#if CONFIG == HOTEL_ID
	/// @bug hack for hyundai! 135 = 3/4 of 180 = one cycle...
	unsigned long threeQuartersOfAvailableTime = ((unsigned long)CoreVars->DRPM * 135 * ANGLE_FACTOR) / TICKS_PER_DEGREE_MULTIPLIER;
	if(DerivedVars->Dwell > threeQuartersOfAvailableTime){
		DerivedVars->Dwell = threeQuartersOfAvailableTime;
	}
#endif

	/* Look up the engine temperature enrichment percentage with temperature */
	DerivedVars->ETE = lookupTwoDTableUS((twoDTableUS*)&TablesA.SmallTablesA.engineTempEnrichmentTablePercent, CoreVars->CHT);
	/* TODO The above needs some careful thought put into it around different loads and correction effects. */


	/* Calculate the Transient Fuel Correction */
	if(TRUE /*WWTFC*/){ /* Do ONLY WW correction if enabled */
		// Do ww stuff, maybe pre done via RTC/RTI for consistent period?
		DerivedVars->TFCTotal = 0; /* TODO replace with real code */
	}else if(FALSE /*STDTFC*/){ /* Do any combination of standard approximate methods */
		/* Initialse the variable as a base */
		DerivedVars->TFCTotal = 0;
		/* Based on the rate of change of MAP and some history/taper time */
		if(FALSE /*MAPTFC*/){
			// Do MAP based
			DerivedVars->TFCTotal += 0;
		}

		/* Based on the rate of change of TPS and some history/taper time */
		if(FALSE /*TPSTFC*/){
			// Do TPS based
			DerivedVars->TFCTotal += 0;
		}

		/* Based on the rate of change of RPM and some history/taper time */
		if(FALSE /*RPMTFC*/){
			// Do RPM based
			DerivedVars->TFCTotal += 0;
		}
	}else{ /* Default to no correction */
		DerivedVars->TFCTotal = 0;
		/* Don't throw error as correction may not be required */
	}
}
Esempio n. 2
0
/** @brief Decode a packet and respond
 *
 * This is the core function that controls which functionality is run when a
 * packet is received in full by the ISR code and control is passed back to the
 * main loop code. The vast majority of communications action happens here.
 */
void decodePacketAndRespond(){
	/* Extract and build up the header fields */
	TXBufferCurrentPositionHandler = (unsigned char*)&TXBuffer;

	/* Initialised here such that override is possible */
	TXBufferCurrentPositionSCI0 = (unsigned char*)&TXBuffer;
	TXBufferCurrentPositionCAN0 = (unsigned char*)&TXBuffer;

	// How big was the packet that we got back
	unsigned short RXPacketLengthReceived = (unsigned short)RXBufferCurrentPosition - (unsigned short)&RXBuffer;

	/* Check that the packet is big enough for header,ID,checksum */
	if(RXPacketLengthReceived < 4){
		resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
		FLAG_AND_INC_FLAGGABLE(FLAG_SERIAL_PACKETS_UNDER_LENGTH_OFFSET);
		KeyUserDebugs.serialAndCommsCodeErrors++;
		return;
	}

	/* Pull out the received checksum and calculate the real one, then check */
	unsigned char RXReceivedChecksum = (unsigned char)*(RXBufferCurrentPosition - 1);
	unsigned char RXCalculatedChecksum = checksum((unsigned char*)&RXBuffer, RXPacketLengthReceived - 1);
	if(RXCalculatedChecksum != RXReceivedChecksum){
		resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
		FLAG_AND_INC_FLAGGABLE(FLAG_SERIAL_CHECKSUM_MISMATCHES_OFFSET);
		KeyUserDebugs.serialAndCommsCodeErrors++;
		return;
	}

	/* Start this off as full packet length and build down to the actual length */
	RXCalculatedPayloadLength = RXPacketLengthReceived;

	/* Grab the RX header flags out of the RX buffer */
	RXBufferCurrentPosition = (unsigned char*)&RXBuffer;
	RXHeaderFlags = *RXBufferCurrentPosition;
	RXBufferCurrentPosition++;
	RXCalculatedPayloadLength--;

	/* Flag that we are transmitting! */
	TXBufferInUseFlags |= COM_SET_SCI0_INTERFACE_ID;
	// SCI0 only for now...

	/* Load a blank header into the TX buffer ready for masking */
	TXHeaderFlags = TXBufferCurrentPositionHandler;
	*TXHeaderFlags = 0;
	TXBufferCurrentPositionHandler++;

	/* Grab the payload ID for processing and load the return ID */
	RXHeaderPayloadID = *((unsigned short*)RXBufferCurrentPosition);
	*((unsigned short*)TXBufferCurrentPositionHandler) = RXHeaderPayloadID + 1;
	RXBufferCurrentPosition += 2;
	TXBufferCurrentPositionHandler += 2;
	RXCalculatedPayloadLength -= 2;

	/* Check that the length is sufficient for the fields configured. Packets
	 * that are too long will be caught and rejected on an individual payload
	 * ID basis as the information required to handle that is not available at
	 * this point. Packets that are too short are rejected immediately!
	 */
	if(((RXHeaderFlags & HEADER_HAS_LENGTH) && (RXHeaderFlags & HEADER_HAS_SEQUENCE) && (RXPacketLengthReceived < 7))
		|| ((RXHeaderFlags & HEADER_HAS_LENGTH) && (RXPacketLengthReceived < 6))
		|| ((RXHeaderFlags & HEADER_HAS_SEQUENCE) && (RXPacketLengthReceived < 5))){
		finaliseAndSend(packetTooShortForSpecifiedFields);
		resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
		return;
	}

	/* Subtract checksum to get final length */
	RXCalculatedPayloadLength--;

	if(RXHeaderFlags & HEADER_HAS_SEQUENCE){
		*TXBufferCurrentPositionHandler = *RXBufferCurrentPosition;
		RXBufferCurrentPosition++;
		TXBufferCurrentPositionHandler++;
		RXCalculatedPayloadLength--;
		*TXHeaderFlags |= HEADER_HAS_SEQUENCE;
	}

	if(RXHeaderFlags & HEADER_HAS_LENGTH){
		RXHeaderPayloadLength = *((unsigned short*)RXBufferCurrentPosition);
		RXBufferCurrentPosition += 2;
		RXCalculatedPayloadLength -= 2;
		/* Already subtracted one for checksum */
		if(RXHeaderPayloadLength != RXCalculatedPayloadLength){
			finaliseAndSend(payloadLengthHeaderMismatch);
			resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
			return;
		}
	}

	/* Calculate the position of the end of the stored packet for later use as a buffer */
	void* leftOverBuffer = (void*)((unsigned short)&RXBuffer + RXPacketLengthReceived);

	unsigned short errorID = 0;
	/* This is where all the communication logic resides.
	 *
	 * Please Note: Length and its flag should be set by each return packet
	 * type handler if required or desired. If an ack has been requested,
	 * ensure the negative ack flag is set if the operation failed.
	 */
	switch (RXHeaderPayloadID){
	// FreeEMS Core Comms Interface cases
		case requestInterfaceVersion:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(interfaceVersion);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&interfaceVersion, sizeof(interfaceVersion));
			TXBufferCurrentPositionHandler += sizeof(interfaceVersion);
			break;
		}
		case requestFirmwareVersion:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(firmwareVersion);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&firmwareVersion, sizeof(firmwareVersion));
			TXBufferCurrentPositionHandler += sizeof(firmwareVersion);
			break;
		}
		case requestMaxPacketSize:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* Load the size into place */
			*((unsigned short*)TXBufferCurrentPositionHandler) = RX_BUFFER_SIZE;
			TXBufferCurrentPositionHandler += 2;
			break;
		}
		case requestEchoPacketReturn:
		{
			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = RXPacketLengthReceived;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&RXBuffer, RXPacketLengthReceived);
			/* Note, there is no overflow check here because the TX buffer is slightly       */
			/* bigger than the RX buffer and there is overflow checking for receives anyway. */
			TXBufferCurrentPositionHandler += RXPacketLengthReceived;
			break;
		}
		case requestSoftSystemReset:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
			}else{ // Perform soft system reset
				_start();
			}
			break;
		}
		case requestHardSystemReset:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
			}else{
				/* This is how the serial monitor does it. */
				COPCTL = 0x01; /* Arm with shortest time */
				ARMCOP = 0xFF; /* Write bad value, should cause immediate reset */
				/* Using _start() only resets the app ignoring the monitor switch. It does not work */
				/* properly because the location of _start is not the master reset vector location. */
			}
			break;
		}
		case requestReInitOfSystem:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
			}else{
				init();
			}
			break;
		}
	// FreeEMS Vanilla Firmware Specific cases
		case clearCountersAndFlagsToZero:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned short zeroCounter;
			unsigned char* counterPointer = (unsigned char*) &Counters;
			for(zeroCounter = 0;zeroCounter < sizeof(Counter);zeroCounter++){
				*counterPointer = 0;
				counterPointer++;
			}
			KeyUserDebugs.flaggableFlags = 0;
			unsigned char* flaggablePointer = (unsigned char*) &Flaggables;
			for(zeroCounter = 0;zeroCounter < sizeof(Flaggable);zeroCounter++){
				*flaggablePointer = 0;
				flaggablePointer++;
			}
			break;
		}
		case requestDecoderName:
		case requestFirmwareBuildDate:
		case requestCompilerVersion:
		case requestOperatingSystem:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned char* stringToSend = 0;
			switch (RXHeaderPayloadID) {
				case requestDecoderName:
					stringToSend = (unsigned char*)decoderName;
					break;
				case requestFirmwareBuildDate:
					stringToSend = (unsigned char*)buildTimeAndDate;
					break;
				case requestCompilerVersion:
					stringToSend = (unsigned char*)compilerVersion;
					break;
				case requestOperatingSystem:
					stringToSend = (unsigned char*)operatingSystem;
					break;
			}
			/* This type must have a length field, set that up and load the body into place at the same time */
			*((unsigned short*)TXBufferCurrentPositionHandler) = stringCopy((TXBufferCurrentPositionHandler + 2), stringToSend);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			// Update with length field and string length.
			TXBufferCurrentPositionHandler += 2 + *((unsigned short*)TXBufferCurrentPositionHandler);
			break;
		}
		case updateBlockInRAM:
		{
			// Subtract six to allow for the locationID, size, offset
			if(RXCalculatedPayloadLength < 7){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Look up the memory location details
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			// Don't let anyone write to running variables unless we are running BenchTest firmware!
			if((details.flags & block_is_read_only) && compare((unsigned char*)&decoderName, (unsigned char*)BENCH_TEST_NAME, sizeof(BENCH_TEST_NAME))){
				errorID = attemptToWriteToReadOnlyBlock;
				break;
			}

			// Subtract six to allow for the locationID, size, offset
			if((RXCalculatedPayloadLength - 6) != size){
				errorID = payloadNotEqualToSpecifiedValue;
				break;
			}

			// If either of these is zero then this block is not in RAM!
			if((details.RAMPage == 0) || (details.RAMAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region manipulation where it does not make sense or is unsafe.
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = uncheckedTableManipulationNotAllowed;
				break;
			}

			// Save page values for restore
			unsigned char oldRamPage = RPAGE;
			// Set the viewable RAM page
			RPAGE = details.RAMPage;

			/// TODO @todo factor this out into validation delegation function once the number of types increases somewhat
			//
			if((details.flags & block_is_main_table) || (details.flags & block_is_2dus_table)){
				void* bufferToCheck;

				// For sub regions, construct an image for verification
				if(size != details.size){
					// Copy data from destination location to buffer
					memcpy(leftOverBuffer, details.RAMAddress, details.size);

					// Copy data from rx buffer to buffer over writing old data
					memcpy(leftOverBuffer + offset, RXBufferCurrentPosition, size);

					bufferToCheck = leftOverBuffer;
				}else{
					bufferToCheck = RXBufferCurrentPosition;
				}

				// Verify all tables
				if(details.flags & block_is_main_table){
					errorID = validateMainTable((mainTable*)bufferToCheck);
				}else if(details.flags & block_is_2dus_table){
					errorID = validateTwoDTable((twoDTableUS*)bufferToCheck);
				}// TODO add other table types here

				// If the validation failed, report it
				if(errorID != 0){
					RPAGE = oldRamPage; // Restore the original RAM page, even when getting an error condition.
					break;
				}
			}

			// Copy from the RX buffer to the block of RAM
			memcpy((unsigned char*)(details.RAMAddress + offset), RXBufferCurrentPosition, size);

			// Check that the write was successful
			unsigned char index = compare(RXBufferCurrentPosition, (unsigned char*)(details.RAMAddress + offset), size);

			// Restore the original RAM and flash pages
			RPAGE = oldRamPage;

			if(index != 0){
				errorID = MEMORY_WRITE_ERROR;
			}
			break;
		}
		case updateBlockInFlash:
		{
			// Subtract six to allow for the locationID, size, offset
			if(RXCalculatedPayloadLength < 7){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Look up the memory location details
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			// Subtract six to allow for the locationID, size, offset
			if((RXCalculatedPayloadLength - 6) != size){
				errorID = payloadNotEqualToSpecifiedValue;
				break;
			}

			// If either of these is zero then this block is not in flash!
			if((details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region manipulation where it does not make sense or is unsafe.
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = uncheckedTableManipulationNotAllowed;
				break;
			}

			/// TODO @todo factor this out into validation delegation function once the number of types increases somewhat
			//
			if((details.flags & block_is_main_table) || (details.flags & block_is_2dus_table)){
				void* bufferToCheck;

				// For sub regions, construct an image for verification
				if(size != details.size){
					/* Save page value for restore and set the visible page */
					unsigned char oldFlashPage = PPAGE;
					PPAGE = details.FlashPage;

					// Copy data from destination location to buffer
					memcpy(leftOverBuffer, details.FlashAddress, details.size);

					/* Restore the original flash page */
					PPAGE = oldFlashPage;

					// Copy data from rx buffer to buffer over writing old data
					memcpy(leftOverBuffer + offset, RXBufferCurrentPosition, size);

					bufferToCheck = leftOverBuffer;
				}else{
					bufferToCheck = RXBufferCurrentPosition;
				}

				// Verify all tables
				if(details.flags & block_is_main_table){
					errorID = validateMainTable((mainTable*)bufferToCheck);
				}else if(details.flags & block_is_2dus_table){
					errorID = validateTwoDTable((twoDTableUS*)bufferToCheck);
				}// TODO add other table types here

				// If the validation failed, report it
				if(errorID != 0){
					break;
				}
			}

			/* Copy the flash details and populate the RAM details with the buffer location */
			blockDetails burnDetails;
			burnDetails.FlashPage = details.FlashPage;
			burnDetails.FlashAddress = details.FlashAddress + offset;
			burnDetails.RAMPage = RPAGE;
			burnDetails.RAMAddress = RXBufferCurrentPosition;
			burnDetails.size = size;

			/* Copy from the RX buffer to the block of flash */
			errorID = writeBlock(&burnDetails, leftOverBuffer);
			if(errorID != 0){
				break;
			}

			/* If present in RAM, update that too */
			if((details.RAMPage != 0) && (details.RAMAddress != 0)){
				/* Save page values for restore */
				unsigned char oldRamPage = RPAGE;
				/* Set the viewable RAM page */
				RPAGE = details.RAMPage;

				/* Copy from the RX buffer to the block of RAM */
				memcpy((unsigned char*)(details.RAMAddress + offset), RXBufferCurrentPosition, size);

				/* Check that the write was successful */
				unsigned char index = compare(RXBufferCurrentPosition, (unsigned char*)(details.RAMAddress + offset), size);

				/* Restore the original RAM and flash pages */
				RPAGE = oldRamPage;

				if(index != 0){
					errorID = MEMORY_WRITE_ERROR;
				}
			}

			break;
		}
		case retrieveBlockFromRAM:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			if((details.RAMPage == 0) || (details.RAMAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which returns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}

			// This type must have a length field, set that up
			*((unsigned short*)TXBufferCurrentPositionHandler) = size;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			/* Save page value for restore and set the visible page */
			unsigned char oldRamPage = RPAGE;
			RPAGE = details.RAMPage;

			/* Copy the block of RAM to the TX buffer */
			memcpy(TXBufferCurrentPositionHandler, (unsigned char*)(details.RAMAddress + offset), size);
			TXBufferCurrentPositionHandler += size;

			/* Restore the original RAM and flash pages */
			RPAGE = oldRamPage;

			break;
		}
		case retrieveBlockFromFlash:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			if((details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which returns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}

			// This type must have a length field, set that up
			*((unsigned short*)TXBufferCurrentPositionHandler) = size;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			/* Save page value for restore and set the visible page */
			unsigned char oldFlashPage = PPAGE;
			PPAGE = details.FlashPage;

			/* Copy the block of flash to the TX buffer */
			memcpy(TXBufferCurrentPositionHandler, (unsigned char*)(details.FlashAddress + offset), size);
			TXBufferCurrentPositionHandler += size;

			/* Restore the original RAM and flash pages */
			PPAGE = oldFlashPage;

			break;
		}
		case burnBlockFromRamToFlash:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check that all data we need is present */
			if((details.RAMPage == 0) || (details.RAMAddress == 0) || (details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which burns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}


			// adjust details block to feed to represent the subsection of ram and flash that we want to burn down.
			details.RAMAddress += offset;
			details.FlashAddress += offset;
			details.size = size;

			/* Write the block down from RAM to Flash */
			errorID = writeBlock(&details, leftOverBuffer);
			break;
		}
		case requestDatalogPacket: // Set type through standard configuration methods
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Set the length field up */
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			unsigned short* localLength = (unsigned short*)TXBufferCurrentPositionHandler;
			TXBufferCurrentPositionHandler += 2;

			/* Fill out the log and send */
			*localLength = populateBasicDatalog();
			break;
		}
		case setAsyncDatalogType:
		{
			if(RXCalculatedPayloadLength != 1){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned char newDatalogType = *((unsigned char*)RXBufferCurrentPosition);
			if(newDatalogType > asyncDatalogLastType){
				errorID = noSuchAsyncDatalogType;
				break;
			}

			TablesB.SmallTablesB.loggingSettings.datalogStreamType = newDatalogType;
			break;
		}
		case retrieveArbitraryMemory:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned short length = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			// Make sure the buffer can handle the block
			if(length > TX_MAX_PAYLOAD_SIZE){
				errorID = requestedLengthTooLarge;
				break;
			}

			void* address = (void*) *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			// Ensure we don't try to read past the end of the address space
			if(((unsigned short)address) <= ((0xFFFF - length) + 1)){
				// TODO Possibly check and limit ranges
				errorID = requestedAddressDisallowed;
				break;
			}

			unsigned char RAMPage = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;
			// Ensure RAM page is valid. Being too high is not possible.
			if(RAMPage < RPAGE_MIN){
				errorID = requestedRAMPageInvalid;
				break;
			}

			unsigned char FlashPage = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;
			// Ensure Flash page is valid. Being too high is not possible.
			if(FlashPage < PPAGE_MIN){
				errorID = requestedFlashPageInvalid;
				break;
			}

			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = length + 6;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			/* Put the request payload into the reply */
			*((unsigned short*)TXBufferCurrentPositionHandler) = (unsigned short) address;
			TXBufferCurrentPositionHandler += 2;
			*((unsigned short*)TXBufferCurrentPositionHandler) = length;
			TXBufferCurrentPositionHandler += 2;
			*((unsigned char*)TXBufferCurrentPositionHandler) = RAMPage;
			TXBufferCurrentPositionHandler++;
			*((unsigned char*)TXBufferCurrentPositionHandler) = FlashPage;
			TXBufferCurrentPositionHandler++;

			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, address, length);
			TXBufferCurrentPositionHandler += length;

			break;
		}
		case retrieveListOfLocationIDs:
		{
			if(RXCalculatedPayloadLength != 3){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the type of list that we want
			unsigned char listType = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;

			// Extract the mask for the qualities that we want
			unsigned short blockDetailsMask = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// This type must have a length field, set that up
			unsigned short * listLength = (unsigned short*)TXBufferCurrentPositionHandler;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			// Zero the counter before we start, woops!
			*listLength = 0;

			unsigned long locationID;
			blockDetails details;
			for(locationID = 0;locationID < 65536;locationID++){
				unsigned short locationIDDoesntExist;
				locationIDDoesntExist = lookupBlockDetails((unsigned short)locationID, &details);

				if(!locationIDDoesntExist){
					if((listType == 0x00) || // get all
							((listType == 0x01) && (details.flags & blockDetailsMask)) || // get OR of bits
							((listType == 0x02) && (!(~(details.flags) & blockDetailsMask)))){ // get AND of bits
						*((unsigned short*)TXBufferCurrentPositionHandler) = (unsigned short)locationID;
						TXBufferCurrentPositionHandler += 2;
						*listLength += 2;
					}
				}
			}

			break;
		}
		case retrieveLocationIDDetails:
		{
			if(RXCalculatedPayloadLength != 2){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// This type must have a length field, set that up
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(blockDetails);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			// Write straight to output buffer to save time/code
			errorID = lookupBlockDetails(locationID, (blockDetails*)TXBufferCurrentPositionHandler);

			if(errorID != 0){
				break;
			}

			// Adjust TX buffer position if successful
			TXBufferCurrentPositionHandler += sizeof(blockDetails);

			break;
		}
		case requestUnitTestOverSerial:
		{
			/*
			 * The idea here is to call this function with arguments, and data
			 * and have the result sent back for comparison with an expected
			 * result that isn't divulged to the firmware.
			 *
			 * It is intended that all testable functions be callable through
			 * this mechanism and that any number of test executions can be
			 * performed by an external suite using different parameters and
			 * data sets and matching expected results.
			 *
			 * The usual error mechanism shall be used to indicate some sort of
			 * either internal or test failure and returned errors shall be
			 * suitably descriptive to allow diagnosis and fixing of issues.
			 */

			// Must at least have test ID
			if(RXCalculatedPayloadLength < 2){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// grab unit test ID from payload
			unsigned short unitTestID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			switch(unitTestID){
				case testEmptyTest:
				{
					// Must be only the ID
					if(RXCalculatedPayloadLength != 2){
						errorID = payloadShorterThanRequiredForTest;
						break;
					}

					*((unsigned short*)TXBufferCurrentPositionHandler) = unitTestID;
					TXBufferCurrentPositionHandler +=2;

					break;
				}
				case testTwoDTableUSLookup:
				{
					// ID + Value + Table
					if(RXCalculatedPayloadLength != (2 + 2 + sizeof(twoDTableUS))){
						errorID = payloadShorterThanRequiredForTest;
						break;
					}

					unsigned short Value = *((unsigned short*)RXBufferCurrentPosition);
					RXBufferCurrentPosition += 2;

					twoDTableUS* Table = ((twoDTableUS*)RXBufferCurrentPosition);
					RXBufferCurrentPosition += sizeof(twoDTableUS);

					unsigned short result = lookupTwoDTableUS(Table, Value);

					*((unsigned short*)TXBufferCurrentPositionHandler) = result;
					TXBufferCurrentPositionHandler +=2;

					break;
				}
				// http://issues.freeems.org/view.php?id=156
				//
				/// TODO @todo test all things listed below:
				// lookupPagedMainTableCellValue - pass this RPAGE so that it remains unchanged
				// validateMainTable
				// validateTwoDTable
				// set table values - leave this till last, currently unused by mtx, likely to be removed anyway
				// generateDerivedVars - convert to pointers, remove headers, privatise a lot of data!
				// calculateFuelAndIgnition - ditto
				// scheduling algorithm - ditto
				// safeAdd
				// safeTrim
				// safeScale
				// sleep (milliseconds)
				// sleepMicro (microseconds)
				// checksum
				// stringCopy
				// compare
				// utils that can't be checked: sampleLoopADC sampleBlockADC sampleEachADC - can check for how long each takes! adjustPWM (test only anyway), resetToNonRunningState and setupPagedRAM (would interfere with functioning of device)
				// init code may be able to be partially checked
				// most other code at this stage is ISR code, flash writing code, or could interfere with the running of the engine
				// more testable code will appear with time, such as the HAL layer, and most accessory functions.
				default:
				{
					errorID = noSuchUnitTestID;
				}

			// each case:
				// checks length, fails if wrong
				// parses data into args
				// calls function on data/args
				// assembles response OR sets error
				// breaks
			}
			break;
		}
		case startBenchTestSequence:
		{
			// see TODO on include at top and modify this line appropriately
			if(!(compare((unsigned char*)&decoderName, (unsigned char*)BENCH_TEST_NAME, sizeof(BENCH_TEST_NAME)))){
				if(RXCalculatedPayloadLength < 1){
					errorID = payloadLengthTypeMismatch;
					break;
				}

				unsigned char localTestMode = *((unsigned char*)RXBufferCurrentPosition); //1; // The only mode, for now.
				RXBufferCurrentPosition++;
				if(localTestMode > TEST_MODE_BUMP_UP_CYCLES){
					errorID = unimplementedTestMode;
					break;
				}else if((localTestMode == TEST_MODE_STOP) && (RXCalculatedPayloadLength == 1)){
					if(!(coreStatusA & BENCH_TEST_ON)){
						errorID = benchTestNotRunningToStop;
						break;
					}

					// Ensure we succeed at stopping it as quickly as possible.
					ATOMIC_START();
					KeyUserDebugs.currentEvent = testEventsPerCycle - 1; // Gets incremented then compared with testEventsPerCycle
					testNumberOfCycles = 1;                              // Gets decremented then compared with zero
					ATOMIC_END();

					// eventually save and return where it got to
					break;
				}else if((localTestMode == TEST_MODE_BUMP_UP_CYCLES) && (RXCalculatedPayloadLength == 2)){
					if(!(coreStatusA & BENCH_TEST_ON)){
						errorID = benchTestNotRunningToBump;
						break;
					}

					// Get bump value from payload
					unsigned char bumpCycles = *((unsigned char*)RXBufferCurrentPosition); //1; // The only mode, for now.
					RXBufferCurrentPosition++;

					if(bumpCycles == 0){
						errorID = bumpingByZeroMakesNoSense;
						break;
					}

					// Bump count by value from payload
					testNumberOfCycles += bumpCycles;
					// Given that this function is only for situations when A it's getting near to
					// zero and B the user is watching, not checking for overflow is reasonable.
					break;
				}else if((localTestMode == TEST_MODE_ITERATIONS) && (RXCalculatedPayloadLength == 24)){
					testMode = localTestMode;
					// do nothing to fall through, or move other code into here
				}else{
					errorID = packetSizeWrongForTestMode;
					break;
				}

				if(coreStatusA & BENCH_TEST_ON){
					errorID = benchTestAlreadyRunning;
					break;
				}

				testEventsPerCycle = *((unsigned char*)RXBufferCurrentPosition); //100;  // @ 10ms  =  1s
				RXBufferCurrentPosition++;
				if(testEventsPerCycle == 0){
					errorID = invalidEventsPerCycle;
					break;
				}

				testNumberOfCycles = *((unsigned short*)RXBufferCurrentPosition); //20;   // @ 1s    = 20s
				RXBufferCurrentPosition += 2;
				if(testNumberOfCycles == 0){
					errorID = invalidNumberOfCycles;
					break;
				}

				testTicksPerEvent = *((unsigned short*)RXBufferCurrentPosition); //12500; // @ 0.8us = 10ms
				RXBufferCurrentPosition += 2;
				if(testTicksPerEvent < decoderMaxCodeTime){
					errorID = tooShortOfAnEventPeriod;
					break;
				}

				// Pluck the arrays out of the packet for the loop below
				unsigned char* testEventNumbers = RXBufferCurrentPosition;
				RXBufferCurrentPosition += 6;
				unsigned short* testPulseWidths = (unsigned short*)RXBufferCurrentPosition;
				RXBufferCurrentPosition += 12;

				// Reset the clock for reading timeout
				Clocks.timeoutADCreadingClock = 0; // make this optional, such that we can use real inputs to determine pw and/or dwell.

				// Validate and transfer the per-channel data
				unsigned char channel;
				unsigned char configuredChannels = 6;
				for(channel = 0;channel < 6;channel++){
					if(testPulseWidths[channel] > injectorSwitchOnCodeTime){ // See next block for warning.
						// use as-is
						outputEventDelayFinalPeriod[channel] = decoderMaxCodeTime;
						outputEventPulseWidthsMath[channel] = testPulseWidths[channel];
						outputEventInputEventNumbers[channel] = testEventNumbers[channel];
					}else if(testPulseWidths[channel] > 3){
						// less than the code time, and not special, error!
						errorID = tooShortOfAPulseWidthToTest;
						// Warning, PWs close to this could be slightly longer than requested, that will change in later revisions.
						break;
					}else if(testPulseWidths[channel] == 3){
						testMode++; // Dirty hack to avoid dealing with Dave for the time being.
						testNumberOfMissing = channel;
					}else if(testPulseWidths[channel] == 2){
						// use the dwell from the core maths and input vars.
						outputEventDelayFinalPeriod[channel] = decoderMaxCodeTime;
						outputEventPulseWidthsMath[channel] = DerivedVars->Dwell;
						outputEventInputEventNumbers[channel] = testEventNumbers[channel];
					}else if(testPulseWidths[channel] == 1){
						// use the reference pulse width from the core maths and input vars.
						outputEventDelayFinalPeriod[channel] = decoderMaxCodeTime;
						outputEventPulseWidthsMath[channel] = DerivedVars->RefPW;
						outputEventInputEventNumbers[channel] = testEventNumbers[channel];
					}else{ // is zero
						// Set this channel to zero for and therefore off, don't set this channel.
						outputEventInputEventNumbers[channel] = 0xFF; // Off.
						configuredChannels--;
					}
				}

				if(configuredChannels == 0){
					errorID = noChannelsConfiguredToTest;
					break;
				}

				if(errorID == 0){
					// Let the first iteration roll it over to zero.
					KeyUserDebugs.currentEvent = 0xFF; // Needs to be here in case of multiple runs, init is not sufficient

					if(testMode == TEST_MODE_DODGY_MISSING_TOOTH){
						if(testEventsPerCycle <= 127){
							testEventsPerCycle *= 2;
						}else{
							errorID = tooManyEventsPerCycleMissingTth;
							break;
						}

						// Store the time per event in RPM such that it can be updated dynamically
						CoreVars->RPM = testTicksPerEvent;

						// The channels to use rely on the defaults from initialisers! Custom builds can break BenchTest mode!

						// Un-schedule anything that got scheduled
						outputEventInputEventNumbers[2] = 0xFF;
						outputEventInputEventNumbers[3] = 0xFF;
						outputEventInputEventNumbers[4] = 0xFF;
						outputEventInputEventNumbers[5] = 0xFF;
					}else if(testMode > TEST_MODE_DODGY_MISSING_TOOTH){
						errorID = unimplementedTestMode;
						break;
					}

					// Trigger decoder interrupt to fire thus starting the loop!
					TIE = 0x01; // The ISR does the rest!

					// Nothing went wrong, now set flag.
					coreStatusA |= BENCH_TEST_ON;
				}else{
					break;
				}


/* http://issues.freeems.org/view.php?id=155
 *
 * The following block has been left in, as I still do not know why it won't work as intended:
 *
 * - It should fire all 6 output pins with a 52ms duration pulse, exactly once.
 * - The SAME code run from anywhere else (pre main loop, in main loop, in rtc, in decoder) works fine, just not here in commsCore.c
 * - The interrupts run, but the pin doesn't change state, despite the registers being configured correctly
 *
 * I've tried quite a bit:
 *
 * - Moving this code around
 * - Checking memory definitions
 * - Completely rewriting the output ISR
 * - Adding significant debug to output ISR
 * - Checking for register contents in output ISR
 * - Checking for key things modified in this file
 * - General head scratching and confused searching
 */

//				outputEventPinNumbers[0] = 0; // 1 ign
//				outputEventPinNumbers[1] = 1; // 2 ign
//				outputEventPinNumbers[2] = 2; // 3 ign/1 fuel
//				outputEventPinNumbers[3] = 3; // 4 ign/2 fuel
//				outputEventPinNumbers[4] = 4; // 3 fuel
//				outputEventPinNumbers[5] = 5; // 4 fuel
//				outputEventDelayFinalPeriod[0] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[1] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[2] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[3] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[4] = decoderMaxCodeTime;
//				outputEventDelayFinalPeriod[5] = decoderMaxCodeTime;
//				outputEventPulseWidthsMath[0] = SHORTMAX;
//				outputEventPulseWidthsMath[1] = SHORTMAX;
//				outputEventPulseWidthsMath[2] = SHORTMAX;
//				outputEventPulseWidthsMath[3] = SHORTMAX;
//				outputEventPulseWidthsMath[4] = SHORTMAX;
//				outputEventPulseWidthsMath[5] = SHORTMAX;
//
//				unsigned short edgeTimeStamp = TCNT;
//				// call sched output with args
//				LongTime timeStamp;
//				/* Install the low word */
//				timeStamp.timeShorts[1] = edgeTimeStamp;
//				/* Find out what our timer value means and put it in the high word */
//				if(TFLGOF && !(edgeTimeStamp & 0x8000)){ /* see 10.3.5 paragraph 4 of 68hc11 ref manual for details */
//					timeStamp.timeShorts[0] = timerExtensionClock + 1;
//				}else{
//					timeStamp.timeShorts[0] = timerExtensionClock;
//				}
//
//				schedulePortTPin(0, timeStamp);
//				schedulePortTPin(1, timeStamp);
//				schedulePortTPin(2, timeStamp);
//				schedulePortTPin(3, timeStamp);
//				schedulePortTPin(4, timeStamp);
//				schedulePortTPin(5, timeStamp);
//
//				sleep(1000);
			}else{
				errorID = thisIsNotTheBenchTestDecoder;
			}
			break;
		}
		default:
		{
			if((RXHeaderPayloadID % 2) == 1){
				errorID = invalidPayloadID;
			}else{
				errorID = unrecognisedPayloadID;
			}
			break;
		}
	}

	// Always reply, if errorID is zero it's just an ack.
	finaliseAndSend(errorID);

	/* Switch reception back on now that we are done with the received data */
	resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
}
Esempio n. 3
0
/** @brief The main function!
 *
 * The centre of the application is here. From here all non-ISR code is called
 * directly or indirectly. The two coarse blocks are init and the main loop.
 * Init is called first to set everything up and then the main loop is entered
 * where the flow of control continues until the device is switched off or
 * reset (excluding asynchronous ISR code). Currently the main loop only runs
 * the fuel, ignition and scheduling calculation code, and the communications
 * code and only when actually required. The intention is to maintain a very
 * low latency for calculations such that the behaviour of the device more
 * closely reflects the attached engines rapidly changing requirements. When
 * accessory code is added a new scheduling algorithm will be required to keep
 * the latency low without starving any particular blocks of CPU time.
 */
int  main(){ /// @todo TODO maybe move this to paged flash ?
	// Set everything up.
	init();

	/// @todo TODO Add verification reporting code here that disables the timer interrupts such that no events ever get scheduled, and then sits looping sending error packets out about what is wrong. set a whole bunch of flags and check them here sending a packet for each with a unique errorID for each and thus a unique easy to understand message for each on the PC side. BEFORE the priming code such that no fuel gets injected. Will need to modularise the comms stuff to process packets based on calls from this section too, avoid excess duplication if possible.

	// TODO move this to a function so that it can be called on a hot restart post being asleep.
	#define NUMBER_OF_OUTPUT_PINS 6
	unsigned char outputEvent;
	unsigned char activeFuelChannels[NUMBER_OF_OUTPUT_PINS] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF};
	for(outputEvent = 0;outputEvent < fixedConfigs1.schedulingSettings.numberOfConfiguredOutputEvents;outputEvent++){
		if(fixedConfigs1.schedulingSettings.schedulingConfigurationBits[outputEvent] == 1 && fixedConfigs1.schedulingSettings.outputEventPinNumbers[outputEvent] < NUMBER_OF_OUTPUT_PINS) { // todo remove second condition?
			activeFuelChannels[fixedConfigs1.schedulingSettings.outputEventPinNumbers[outputEvent]] = outputEvent;
		}
	}
	sampleEachADC(ADCBuffers);       // Read sensors - from utils.h/.c
	generateCoreVars();              // Calculate BRV - from coreVarsGenerator.h/.c
	generateDerivedVars();           // Calculate IDT - from derivedVarsGenerator.h/.c
	// lookupTwoDTanlesUS is in tableLookup.h/.c
	unsigned short primingPulseWidth = lookupTwoDTableUS((twoDTableUS*)&TablesA.SmallTablesA.primingVolumeTable, CoreVars->CHT);
	primingPulseWidth = safeAdd(primingPulseWidth, DerivedVars->IDT);
	unsigned short edgeTimeStamp = TCNT;
	// call sched output with args
	LongTime timeStamp;
	/* Install the low word */
	timeStamp.timeShorts[1] = edgeTimeStamp;
	/* Find out what our timer value means and put it in the high word */
	if(TFLGOF && !(edgeTimeStamp & 0x8000)){ /* see 10.3.5 paragraph 4 of 68hc11 ref manual for details */
		timeStamp.timeShorts[0] = timerExtensionClock + 1;
	}else{
		timeStamp.timeShorts[0] = timerExtensionClock;
	}
	unsigned char outputPin;
	for(outputPin = 0; outputPin< NUMBER_OF_OUTPUT_PINS; outputPin++){
		if(activeFuelChannels[outputPin] < MAX_NUMBER_OF_OUTPUT_EVENTS){
			outputEventPulseWidthsMath[activeFuelChannels[outputPin]] = primingPulseWidth;
			outputEventDelayFinalPeriod[activeFuelChannels[outputPin]] = SHORTHALF;
			schedulePortTPin(activeFuelChannels[outputPin], timeStamp); // from 
		}
	}

	// Run forever repeating.
	while(TRUE){
		//unsigned short start = realTimeClockMillis;
		/* If ADCs require forced sampling, sample now */
		if(coreStatusA & FORCE_READING){
			ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
			/* Atomic block to ensure a full set of readings are taken together */

			/* Check to ensure that a reading wasn't take before we entered a non interruptable state */
			if(coreStatusA & FORCE_READING){ // do we still need to do this TODO ?

				sampleEachADC(ADCBuffersRecord); // TODO still need to do a pair of loops and clock these two functions for performance.
				//sampleLoopADC(&ADCBuffers);
				resetToNonRunningState(EVENT_ARRIVAL_TIMEOUT);
				Counters.timeoutADCreadings++;

				/* Set flag to say calc required */
				coreStatusA |= CALC_FUEL_IGN;

				/* Clear force reading flag */
				coreStatusA &= CLEAR_FORCE_READING;
			}

			ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
		}

		/* If required, do main fuel and ignition calcs first */
		if(coreStatusA & CALC_FUEL_IGN){
			ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
			/* Atomic block to ensure that we don't clear the flag for the next data set when things are tight */

			/* Switch input bank so that we have a stable set of the latest data */
			if(ADCBuffers == &ADCBuffers1){
				ticksPerDegree = &ticksPerDegree0; // TODO temp, remove, maybe
				ticksPerDegreeRecord = &ticksPerDegree1; // TODO temp, remove, maybe
				ADCBuffers = &ADCBuffers0;
				ADCBuffersRecord = &ADCBuffers1;
			}else{
				ticksPerDegree = &ticksPerDegree1; // TODO temp, remove, maybe
				ticksPerDegreeRecord = &ticksPerDegree0; // TODO temp, remove, maybe
				ADCBuffers = &ADCBuffers1;
				ADCBuffersRecord = &ADCBuffers0;
			}

			/* Clear the calc required flag */
			coreStatusA &= CLEAR_CALC_FUEL_IGN;

			ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/

			// TODO DEBUG/TUNING MACRO HERE!
			/* Keep track of how many calcs we are managing per second... */
			Counters.calculationsPerformed++;

			/* Generate the core variables from sensor input and recorded tooth timings */
			generateCoreVars();
			// TODO DEBUG/TUNING MACRO HERE!

			/* Generate the derived variables from the core variables based on settings */
			generateDerivedVars();
			// TODO DEBUG/TUNING MACRO HERE!

			/* Perform the calculations TODO possibly move this to the software interrupt if it makes sense to do so */
			calculateFuelAndIgnition();
			// TODO DEBUG/TUNING MACRO HERE!

			/* Calculate the scheduling based on configuration and previously calculated variables */
			scheduleOutputs();
			// TODO DEBUG/TUNING MACRO HERE!
		}else{
			/* In the event that no calcs are required, sleep a little before returning to retry. */
			sleepMicro(3000); // TODO tune this, and then replace it completely. not doing this will cause the ISR lockouts to run for too high a proportion of the time
			/* Using 0.8 ticks as micros so it will run for a little longer than the math did */
		}


		if(!(TXBufferInUseFlags)){
			/* If the flag for com packet processing is set and the TX buffer is available process the data! */
			if(RXStateFlags & RX_READY_TO_PROCESS){
				/* Clear the flag */
				RXStateFlags &= RX_CLEAR_READY_TO_PROCESS;

				/* Handle the incoming packet */
				decodePacketAndRespond();
			}else{// if(lastCalcCount != Counters.calculationsPerformed){ // substitute true for full speed continuous stream test...

				/* send asynchronous data log if required */
				switch (TablesB.SmallTablesB.loggingSettings.datalogStreamType) {
					case asyncDatalogOff:
					{
						break;
					}
					case asyncDatalogBasic:
					{
						/* Flag that we are transmitting! */
						TXBufferInUseFlags |= COM_SET_SCI0_INTERFACE_ID;
						// SCI0 only for now...

						// headers including length...                 *length = configuredBasicDatalogLength;
						TXBufferCurrentPositionHandler = (unsigned char*)&TXBuffer;

						/* Initialised here such that override is possible */
						TXBufferCurrentPositionSCI0 = (unsigned char*)&TXBuffer;
						TXBufferCurrentPositionCAN0 = (unsigned char*)&TXBuffer;

						/* Set the flags : firmware, no ack, no addrs, has length */
						*TXBufferCurrentPositionHandler = HEADER_HAS_LENGTH;
						TXBufferCurrentPositionHandler++;

						/* Set the payload ID */
						*((unsigned short*)TXBufferCurrentPositionHandler) = responseBasicDatalog;
						TXBufferCurrentPositionHandler += 2;

						/* Set the length */
						unsigned short* localLength = (unsigned short*)TXBufferCurrentPositionHandler;
						TXBufferCurrentPositionHandler += 2;

						/* populate data log */
						*localLength = populateBasicDatalog();
						finaliseAndSend(0);
						break;
					}
					case asyncDatalogScratchPad:
					{
						break;
					}
					case asyncDatalogStructs:
					{
						break;
					}
					case asyncDatalogPosition:
					{
						break;
					}
					case asyncDatalogBlockBytes:
					{
						break;
					}
					case asyncDatalogBlockWords:
					{
						break;
					}
					case asyncDatalogBlockLongs:
					{
						break;
					}
					case asyncDatalogStreamByte:
					{
						/* Flag that we are transmitting! */
						TXBufferInUseFlags |= COM_SET_SCI0_INTERFACE_ID;
						// SCI0 only for now...

						// headers including length...                 *length = configuredBasicDatalogLength;
						TXBufferCurrentPositionHandler = (unsigned char*)&TXBuffer;

						/* Initialised here such that override is possible */
						TXBufferCurrentPositionSCI0 = (unsigned char*)&TXBuffer;
						TXBufferCurrentPositionCAN0 = (unsigned char*)&TXBuffer;

						/* Set the flags all zeros */
						*TXBufferCurrentPositionHandler = 0;
						TXBufferCurrentPositionHandler++;

						/* Set the payload ID */
						*((unsigned short*)TXBufferCurrentPositionHandler) = responseByteLADatalog;
						TXBufferCurrentPositionHandler += 2;

						/** Store PTIT for now, later make address of byte configurable TODO @todo */
						*((unsigned char*)TXBufferCurrentPositionHandler) = PTIT;
						TXBufferCurrentPositionHandler++;

						finaliseAndSend(0);
						break;
					}
					case asyncDatalogStreamWord:
					{
						break;
					}
					case asyncDatalogStreamLong:
					{
						break;
					}
				}
				// mechanism to ensure we only send something if the data has been updated
				lastCalcCount = Counters.calculationsPerformed;
			}
		}

		performSimpleGPIO();

		// PWM experimentation
		adjustPWM();
	}
}
Esempio n. 4
0
/** @brief Decode a packet and respond
 *
 * This is the core function that controls which functionality is run when a
 * packet is received in full by the ISR code and control is passed back to the
 * main loop code. The vast majority of communications action happens here.
 *
 * @author Fred Cooke
 */
void decodePacketAndRespond(){
	/* Extract and build up the header fields */
	RXBufferCurrentPosition = (unsigned char*)&RXBuffer;
	TXBufferCurrentPositionHandler = (unsigned char*)&TXBuffer;

	/* Initialised here such that override is possible */
	TXBufferCurrentPositionSCI0 = (unsigned char*)&TXBuffer;
	TXBufferCurrentPositionCAN0 = (unsigned char*)&TXBuffer;

	/* Start this off as full packet length and build down to the actual length */
	RXCalculatedPayloadLength = RXPacketLengthReceived;

	/* Grab the RX header flags out of the RX buffer */
	RXHeaderFlags = *RXBufferCurrentPosition;
	RXBufferCurrentPosition++;
	RXCalculatedPayloadLength--;

	/* Flag that we are transmitting! */
	TXBufferInUseFlags |= COM_SET_SCI0_INTERFACE_ID;
	// SCI0 only for now...

	/* Load a blank header into the TX buffer ready for masking */
	unsigned char* TXHeaderFlags = TXBufferCurrentPositionHandler;
	*TXHeaderFlags = 0;
	TXBufferCurrentPositionHandler++;

	/* Grab the payload ID for processing and load the return ID */
	RXHeaderPayloadID = *((unsigned short*)RXBufferCurrentPosition);
	*((unsigned short*)TXBufferCurrentPositionHandler) = RXHeaderPayloadID + 1;
	RXBufferCurrentPosition += 2;
	TXBufferCurrentPositionHandler += 2;
	RXCalculatedPayloadLength -= 2;

	/* Check that the length is sufficient for the fields configured. Packets
	 * that are too long will be caught and rejected on an individual payload
	 * ID basis as the information required to handle that is not available at
	 * this point. Packets that are too short are rejected immediately!
	 */
	if(((RXHeaderFlags & HEADER_HAS_LENGTH) && (RXHeaderFlags & HEADER_HAS_SEQUENCE) && (RXPacketLengthReceived < 7))
		|| ((RXHeaderFlags & HEADER_HAS_LENGTH) && (RXPacketLengthReceived < 6))
		|| ((RXHeaderFlags & HEADER_HAS_SEQUENCE) && (RXPacketLengthReceived < 5))){
		finaliseAndSend(packetTooShortForSpecifiedFields);
		resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
		return;
	}

	/* Subtract checksum to get final length */
	RXCalculatedPayloadLength--;

	if(RXHeaderFlags & HEADER_HAS_SEQUENCE){
		*TXBufferCurrentPositionHandler = *RXBufferCurrentPosition;
		RXBufferCurrentPosition++;
		TXBufferCurrentPositionHandler++;
		RXCalculatedPayloadLength--;
		*TXHeaderFlags |= HEADER_HAS_SEQUENCE;
	}

	if(RXHeaderFlags & HEADER_HAS_LENGTH){
		RXHeaderPayloadLength = *((unsigned short*)RXBufferCurrentPosition);
		RXBufferCurrentPosition += 2;
		RXCalculatedPayloadLength -= 2;
		/* Already subtracted one for checksum */
		if(RXHeaderPayloadLength != RXCalculatedPayloadLength){
			finaliseAndSend(payloadLengthHeaderMismatch);
			resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
			return;
		}
	}

	/* Calculate the position of the end of the stored packet for later use as a buffer */
	void* leftOverBuffer = (void*)((unsigned short)&RXBuffer + RXPacketLengthReceived);

	unsigned short errorID = 0;
	/* This is where all the communication logic resides.
	 *
	 * Please Note: Length and it's flag should be set by each return packet
	 * type handler if required or desired.	If an ack has been requested,
	 * ensure the negative ack flag is set if the operation failed.
	 */
	switch (RXHeaderPayloadID){
		case requestInterfaceVersion:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(interfaceVersionAndType);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&interfaceVersionAndType, sizeof(interfaceVersionAndType));
			TXBufferCurrentPositionHandler += sizeof(interfaceVersionAndType);
			break;
		}
		case requestFirmwareVersion:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = sizeof(firmwareVersion);
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&firmwareVersion, sizeof(firmwareVersion));
			TXBufferCurrentPositionHandler += sizeof(firmwareVersion);
			break;
		}
		case requestMaxPacketSize:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* Load the size into place */
			*((unsigned short*)TXBufferCurrentPositionHandler) = RX_BUFFER_SIZE;
			TXBufferCurrentPositionHandler += 2;
			break;
		}
		case requestEchoPacketReturn:
		{
			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = RXPacketLengthReceived;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;
			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, (void*)&RXBuffer, RXPacketLengthReceived);
			/* Note, there is no overflow check here because the TX buffer is slightly       */
			/* bigger than the RX buffer and there is overflow checking for receives anyway. */
			TXBufferCurrentPositionHandler += RXPacketLengthReceived;
			break;
		}
		case requestSoftSystemReset:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}
			/* Perform soft system reset */
			_start();
		}
		case requestHardSystemReset:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* This is how the serial monitor does it. */
			COPCTL = 0x01; /* Arm with shortest time */
			ARMCOP = 0xFF; /* Write bad value, should cause immediate reset */
			/* Using _start() only resets the app ignoring the monitor switch. It does not work */
			/* properly because the location of _start is not the master reset vector location. */
		}
		case requestReInitOfSystem:
		{
			if(RXCalculatedPayloadLength != 0){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			init();
			break;
		}
		case updateBlockInRAM:
		{
			// Subtract six to allow for the locationID, size, offset
			if(RXCalculatedPayloadLength < 7){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Look up the memory location details
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			// Subtract six to allow for the locationID, size, offset
			if((RXCalculatedPayloadLength - 6) != size){
				errorID = payloadNotEqualToSpecifiedValue;
				break;
			}

			// If either of these is zero then this block is not in RAM!
			if((details.RAMPage == 0) || (details.RAMAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region manipulation where it does not make sense or is unsafe.
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = uncheckedTableManipulationNotAllowed;
				break;
			}

			// Save page values for restore
			unsigned char oldRamPage = RPAGE;
			// Set the viewable RAM page
			RPAGE = details.RAMPage;

			/// TODO @todo factor this out into validation delegation function once the number of types increases somewhat
			//
			if((details.flags & block_is_main_table) || (details.flags & block_is_2dus_table)){
				void* bufferToCheck;

				// For sub regions, construct an image for verification
				if(size != details.size){
					// Copy data from destination location to buffer
					memcpy(leftOverBuffer, details.RAMAddress, details.size);

					// Copy data from rx buffer to buffer over writing old data
					memcpy(leftOverBuffer + offset, RXBufferCurrentPosition, size);

					bufferToCheck = leftOverBuffer;
				}else{
					bufferToCheck = RXBufferCurrentPosition;
				}

				// Verify all tables
				if(details.flags & block_is_main_table){
					errorID = validateMainTable((mainTable*)bufferToCheck);
				}else if(details.flags & block_is_2dus_table){
					errorID = validateTwoDTable((twoDTableUS*)bufferToCheck);
				}// TODO add other table types here

				// If the validation failed, report it
				if(errorID != 0){
					break;
				}
			}

			// Copy from the RX buffer to the block of RAM
			memcpy((unsigned char*)(details.RAMAddress + offset), RXBufferCurrentPosition, size);

			// Check that the write was successful
			unsigned char index = compare(RXBufferCurrentPosition, (unsigned char*)(details.RAMAddress + offset), size);

			// Restore the original RAM and flash pages
			RPAGE = oldRamPage;

			if(index != 0){
				errorID = MEMORY_WRITE_ERROR;
			}
			break;
		}
		case updateBlockInFlash:
		{
			// Subtract six to allow for the locationID, size, offset
			if(RXCalculatedPayloadLength < 7){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Look up the memory location details
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			// Subtract six to allow for the locationID, size, offset
			if((RXCalculatedPayloadLength - 6) != size){
				errorID = payloadNotEqualToSpecifiedValue;
				break;
			}

			// If either of these is zero then this block is not in flash!
			if((details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region manipulation where it does not make sense or is unsafe.
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = uncheckedTableManipulationNotAllowed;
				break;
			}

			/// TODO @todo factor this out into validation delegation function once the number of types increases somewhat
			//
			if((details.flags & block_is_main_table) || (details.flags & block_is_2dus_table)){
				void* bufferToCheck;

				// For sub regions, construct an image for verification
				if(size != details.size){
					/* Save page value for restore and set the visible page */
					unsigned char oldFlashPage = PPAGE;
					PPAGE = details.FlashPage;

					// Copy data from destination location to buffer
					memcpy(leftOverBuffer, details.FlashAddress, details.size);

					/* Restore the original flash page */
					PPAGE = oldFlashPage;

					// Copy data from rx buffer to buffer over writing old data
					memcpy(leftOverBuffer + offset, RXBufferCurrentPosition, size);

					bufferToCheck = leftOverBuffer;
				}else{
					bufferToCheck = RXBufferCurrentPosition;
				}

				// Verify all tables
				if(details.flags & block_is_main_table){
					errorID = validateMainTable((mainTable*)bufferToCheck);
				}else if(details.flags & block_is_2dus_table){
					errorID = validateTwoDTable((twoDTableUS*)bufferToCheck);
				}// TODO add other table types here

				// If the validation failed, report it
				if(errorID != 0){
					break;
				}
			}

			/* Copy the flash details and populate the RAM details with the buffer location */
			blockDetails burnDetails;
			burnDetails.FlashPage = details.FlashPage;
			burnDetails.FlashAddress = details.FlashAddress + offset;
			burnDetails.RAMPage = RPAGE;
			burnDetails.RAMAddress = RXBufferCurrentPosition;
			burnDetails.size = size;

			/* Copy from the RX buffer to the block of flash */
			errorID = writeBlock(&burnDetails, leftOverBuffer);
			if(errorID != 0){
				break;
			}

			/* If present in RAM, update that too */
			if((details.RAMPage != 0) && (details.RAMAddress != 0)){
				/* Save page values for restore */
				unsigned char oldRamPage = RPAGE;
				/* Set the viewable RAM page */
				RPAGE = details.RAMPage;

				/* Copy from the RX buffer to the block of RAM */
				memcpy((unsigned char*)(details.RAMAddress + offset), RXBufferCurrentPosition, size);

				/* Check that the write was successful */
				unsigned char index = compare(RXBufferCurrentPosition, (unsigned char*)(details.RAMAddress + offset), size);

				/* Restore the original RAM and flash pages */
				RPAGE = oldRamPage;

				if(index != 0){
					errorID = MEMORY_WRITE_ERROR;
				}
			}

			break;
		}
		case retrieveBlockFromRAM:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			if((details.RAMPage == 0) || (details.RAMAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which returns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}

			/* Save page value for restore and set the visible page */
			unsigned char oldRamPage = RPAGE;
			RPAGE = details.RAMPage;

			/* Copy the block of RAM to the TX buffer */
			memcpy(TXBufferCurrentPositionHandler, (unsigned char*)(details.RAMAddress + offset), size);
			TXBufferCurrentPositionHandler += size;

			/* Restore the original RAM and flash pages */
			RPAGE = oldRamPage;

			break;
		}
		case retrieveBlockFromFlash:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			if((details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Special behaviour for size of zero which returns the whole block
			if((size == 0) && (offset == 0)){
				size = details.size;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}

			/* Save page value for restore and set the visible page */
			unsigned char oldFlashPage = PPAGE;
			PPAGE = details.FlashPage;

			/* Copy the block of flash to the TX buffer */
			memcpy(TXBufferCurrentPositionHandler, (unsigned char*)(details.FlashAddress + offset), size);
			TXBufferCurrentPositionHandler += size;

			/* Restore the original RAM and flash pages */
			PPAGE = oldFlashPage;

			break;
		}
		case burnBlockFromRamToFlash:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the offset to place the data at
			unsigned short offset = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Extract the size of the data to be stored
			unsigned short size = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check that all data we need is present */
			if((details.RAMPage == 0) || (details.RAMAddress == 0) || (details.FlashPage == 0) || (details.FlashAddress == 0)){
				errorID = invalidMemoryActionForID;
				break;
			}

			// Check that size and offset describe a region that is not out of bounds
			if((size == 0) || (offset > (details.size - 1)) || (size > (details.size - offset))){
				errorID = invalidSizeOffsetCombination;
				break;
			}

			// Don't allow sub region retrieval where it does not make sense or is unsafe. (keep it symmetric for djandruczyk)
			if((size != details.size) && !(details.flags & block_is_indexable)){
				errorID = doesNotMakeSenseToRetrievePartially;
				break;
			}


			// adjust details block to feed to represent the subsection of ram and flash that we want to burn down.
			details.RAMAddress += offset;
			details.FlashAddress += offset;
			details.size = size;

			/* Write the block down from RAM to Flash */
			errorID = writeBlock(&details, leftOverBuffer);
			break;
		}
		case adjustMainTableCell:
		{
			if(RXCalculatedPayloadLength != 8){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a main table */
			if(!(details.flags & block_is_main_table)){
				errorID = invalidIDForMainTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short RPMIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			unsigned short LoadIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			unsigned short cellValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedMainTableCellValue(details.RAMPage, details.RAMAddress, RPMIndex, LoadIndex, cellValue);
			break;
		}
		case adjustMainTableRPMAxis:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a main table */
			if(!(details.flags & block_is_main_table)){
				errorID = invalidIDForMainTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short RPMIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;
			unsigned short RPMValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedMainTableRPMValue(details.RAMPage, details.RAMAddress, RPMIndex, RPMValue);
			break;
		}
		case adjustMainTableLoadAxis:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a main table */
			if(!(details.flags & block_is_main_table)){
				errorID = invalidIDForMainTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short LoadIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;
			unsigned short LoadValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedMainTableLoadValue(details.RAMPage, details.RAMAddress, LoadIndex, LoadValue);
			break;
		}
		case adjust2dTableAxis:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a 2d table */
			if(!(details.flags & block_is_2dus_table)){
				errorID = invalidIDForTwoDTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short axisIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;
			unsigned short axisValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedTwoDTableAxisValue(details.RAMPage, details.RAMAddress, axisIndex, axisValue);
			break;
		}
		case adjust2dTableCell:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			/* Extract the flash location ID from the received data */
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;

			/* Look up the memory location details */
			blockDetails details;
			lookupBlockDetails(locationID, &details);

			/* Check the ID to ensure it is a 2d table */
			if(!(details.flags & block_is_2dus_table)){
				errorID = invalidIDForTwoDTableAction;
				break;
			}

			/* Extract the cell value and coordinates */
			unsigned short cellIndex = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition -= 2;
			unsigned short cellValue = *((unsigned short*)RXBufferCurrentPosition);

			/* Attempt to set the value */
			errorID = setPagedTwoDTableCellValue(details.RAMPage, details.RAMAddress, cellIndex, cellValue);
			break;
		}
		case requestBasicDatalog:
		{
			if((RXCalculatedPayloadLength > 2) || (RXCalculatedPayloadLength == 1)){
				errorID = payloadLengthTypeMismatch;
				break;
			}else if(RXCalculatedPayloadLength == 2){
				unsigned short newConfiguredLength = *((unsigned short*)RXBufferCurrentPosition);
				if(newConfiguredLength > maxBasicDatalogLength){
					errorID = datalogLengthExceedsMax;
					break;
				}else{
					configuredBasicDatalogLength = newConfiguredLength;
				}
			}// fall through to use existing configured length

			/* Set the length field up */
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			*(unsigned short*)TXBufferCurrentPositionHandler = configuredBasicDatalogLength;
			TXBufferCurrentPositionHandler += 2;

			/* Fill out the log and send */
			populateBasicDatalog();
			break;
		}
		case requestConfigurableDatalog:
		{
			/// perform function TODO @todo REWORK review this
			errorID = unimplementedFunction;
			break;
		}
		case setAsyncDatalogType:
		{
			if(RXCalculatedPayloadLength != 1){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned char newDatalogType = *((unsigned char*)RXBufferCurrentPosition);
			if(newDatalogType > 0x01){
				errorID = noSuchAsyncDatalogType;
				break;
			}

			TablesB.SmallTablesB.datalogStreamType = newDatalogType;
			break;
		}
		case forwardPacketOverCAN:
		{
			/// perform function TODO @todo REWORK review this
			errorID = unimplementedFunction;
			break;
		}
		case forwardPacketOverOtherUART:
		{
			/// perform function TODO @todo REWORK review this
			errorID = unimplementedFunction;
			break;
		}
		case retrieveArbitraryMemory:
		{
			if(RXCalculatedPayloadLength != 6){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			unsigned short length = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			// Make sure the buffer can handle the block
			if(length > TX_MAX_PAYLOAD_SIZE){
				errorID = requestedLengthTooLarge;
				break;
			}

			void* address = (void*) *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;
			// Ensure we don't try to read past the end of the address space
			if(((unsigned short)address) <= ((0xFFFF - length) + 1)){
				// TODO Possibly check and limit ranges
				errorID = requestedAddressDisallowed;
				break;
			}

			unsigned char RAMPage = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;
			// Ensure RAM page is valid. Being too high is not possible.
			if(RAMPage < RPAGE_MIN){
				errorID = requestedRAMPageInvalid;
				break;
			}

			unsigned char FlashPage = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;
			// Ensure Flash page is valid. Being too high is not possible.
			if(FlashPage < PPAGE_MIN){
				errorID = requestedFlashPageInvalid;
				break;
			}

			/* This type must have a length field, set that up */
			*((unsigned short*)TXBufferCurrentPositionHandler) = length + 6;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			/* Put the request payload into the reply */
			*((unsigned short*)TXBufferCurrentPositionHandler) = (unsigned short) address;
			TXBufferCurrentPositionHandler += 2;
			*((unsigned short*)TXBufferCurrentPositionHandler) = length;
			TXBufferCurrentPositionHandler += 2;
			*((unsigned char*)TXBufferCurrentPositionHandler) = RAMPage;
			TXBufferCurrentPositionHandler++;
			*((unsigned char*)TXBufferCurrentPositionHandler) = FlashPage;
			TXBufferCurrentPositionHandler++;

			/* Load the body into place */
			memcpy((void*)TXBufferCurrentPositionHandler, address, length);
			TXBufferCurrentPositionHandler += length;

			break;
		}
		case retrieveListOfLocationIDs:
		{
			if(RXCalculatedPayloadLength != 3){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the type of list that we want
			unsigned char listType = *((unsigned char*)RXBufferCurrentPosition);
			RXBufferCurrentPosition++;

			// Extract the mask for the qualities that we want
			unsigned short blockDetailsMask = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// This type must have a length field, set that up
			unsigned short * listLength = (unsigned short*)TXBufferCurrentPositionHandler;
			*TXHeaderFlags |= HEADER_HAS_LENGTH;
			TXBufferCurrentPositionHandler += 2;

			// Zero the counter before we start, woops!
			*listLength = 0;

			unsigned long locationID;
			blockDetails details;
			for(locationID = 0;locationID < 65536;locationID++){
				unsigned short locationIDDoesntExist;
				locationIDDoesntExist = lookupBlockDetails((unsigned short)locationID, &details);

				if(!locationIDDoesntExist){
					if((listType == 0x00) || // get all
							((listType == 0x01) && (details.flags & blockDetailsMask)) || // get OR of bits
							((listType == 0x02) && (!(~(details.flags) & blockDetailsMask)))){ // get AND of bits
						*((unsigned short*)TXBufferCurrentPositionHandler) = (unsigned short)locationID;
						TXBufferCurrentPositionHandler += 2;
						*listLength += 2;
					}
				}
			}

			break;
		}
		case retrieveLocationIDDetails:
		{
			if(RXCalculatedPayloadLength != 2){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// Extract the RAM location ID
			unsigned short locationID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			// Write straight to output buffer to save time/code
			errorID = lookupBlockDetails(locationID, (blockDetails*)TXBufferCurrentPositionHandler);

			if(errorID != 0){
				break;
			}

			// Adjust TX buffer position if successful
			TXBufferCurrentPositionHandler += sizeof(blockDetails);

			break;
		}
		case requestUnitTestOverSerial:
		{
			/*
			 * The idea here is to call this function with arguments, and data
			 * and have the result sent back for comparison with an expected
			 * result that isn't divulged to the firmware.
			 *
			 * It is intended that all testable functions be callable through
			 * this mechanism and that any number of test executions can be
			 * performed by an external suite using different parameters and
			 * data sets and matching expected results.
			 *
			 * The usual error mechanism shall be used to indicate some sort of
			 * either internal or test failure and returned errors shall be
			 * suitably descriptive to allow diagnosis and fixing of issues.
			 */

			// Must at least have test ID
			if(RXCalculatedPayloadLength < 2){
				errorID = payloadLengthTypeMismatch;
				break;
			}

			// grab unit test ID from payload
			unsigned short unitTestID = *((unsigned short*)RXBufferCurrentPosition);
			RXBufferCurrentPosition += 2;

			switch(unitTestID){
				case emptyTest:
				{
					// Must be only the ID
					if(RXCalculatedPayloadLength != 2){
						errorID = payloadShorterThanRequiredForTest;
						break;
					}

					*((unsigned short*)TXBufferCurrentPositionHandler) = unitTestID;
					TXBufferCurrentPositionHandler +=2;

					break;
				}
				case twoDTableUSLookup:
				{
					// ID + Value + Table
					if(RXCalculatedPayloadLength != (2 + 2 + sizeof(twoDTableUS))){
						errorID = payloadShorterThanRequiredForTest;
						break;
					}

					unsigned short Value = *((unsigned short*)RXBufferCurrentPosition);
					RXBufferCurrentPosition += 2;

					twoDTableUS* Table = ((twoDTableUS*)RXBufferCurrentPosition);
					RXBufferCurrentPosition += sizeof(twoDTableUS);

					unsigned short result = lookupTwoDTableUS(Table, Value);

					*((unsigned short*)TXBufferCurrentPositionHandler) = result;
					TXBufferCurrentPositionHandler +=2;

					break;
				}
				default:
				{
					errorID = noSuchUnitTestID;
				}

			// each case:
				// checks length, fails if wrong
				// parses data into args
				// calls function on data/args
				// assembles response OR sets error
				// breaks
			}
			break;
		}
		default:
		{
			if((RXHeaderPayloadID % 2) == 1){
				errorID = invalidPayloadID;
			}else{
				errorID = unrecognisedPayloadID;
			}
		}
	}

	// Always reply, if errorID is zero it's just an ack.
	finaliseAndSend(errorID);

	/* Switch reception back on now that we are done with the received data */
	resetReceiveState(CLEAR_ALL_SOURCE_ID_FLAGS);
	PORTK |= BIT0;
}
/** @brief Generate the derived variables.
 *
 * This function uses the core variables to lookup and calculate further second
 * order variables such as load, VE, Lamdda, Transient fuel correction, engine
 * temperature enrichment, Injector dead time, etc.
 *
 * @author Fred Cooke
 */
void generateDerivedVars(){
	/*&&&&&&&&&&&&&&&&&&&& Use basic variables to lookup and calculate derived variables &&&&&&&&&&&&&&&&&&&*/


	/* Determine load based on options */
	if(TRUE){ /* Use MAP as load */
		DerivedVars->LoadMain = CoreVars->MAP;
	}else if(FALSE){ /* Use TPS as load */
		DerivedVars->LoadMain = CoreVars->TPS;
	}else if(FALSE){ /* Use AAP corrected MAP as load */
		DerivedVars->LoadMain = ((unsigned long)CoreVars->MAP * CoreVars->AAP) / seaLevelKPa;
	}else{ /* Default to MAP, but throw error */
		DerivedVars->LoadMain = CoreVars->MAP;
		/* If anyone is listening, let them know something is wrong */
		sendErrorIfClear(LOAD_NOT_CONFIGURED_CODE); // or maybe queue it?
	}


	/* Look up VE with RPM and Load */
	DerivedVars->VEMain = lookupMainTable(CoreVars->RPM, DerivedVars->LoadMain, VETableMainLocationID);


	/* Look up target Lambda with RPM and Load */
	DerivedVars->Lambda = lookupMainTable(CoreVars->RPM, DerivedVars->LoadMain, LambdaTableLocationID);


	/* Look up injector dead time with battery voltage */
	DerivedVars->IDT = lookupTwoDTableUS((twoDTableUS*)&TablesA.SmallTablesA.injectorDeadTimeTable, CoreVars->BRV);

	// temp dwell and advance vars...
	DerivedVars->Dwell = lookupTwoDTableUS((twoDTableUS*)&TablesA.SmallTablesA.dwellDesiredVersusVoltageTable, CoreVars->BRV);
	unsigned long tempAdvance = oneDegree * (unsigned long)lookupMainTable(CoreVars->RPM, DerivedVars->LoadMain, IgnitionAdvanceTableMainLocationID);
	DerivedVars->Advance = (unsigned short)(tempAdvance / 1024); // This calculation will change when the timing tables get shrunk to a more reasonable 8 bit size with appropriate scaling
	// Move this magic number to an appropriate place and/or refactor timing calcs/values/etc

/// @todo TODO make generic!!!!
// to go generic we need:
// angle between ignition events (if have tpd) (or total angle and number of events)
// max % dwell
// minimum spark time
// a setting to choose which behaviour (don't limit/% dwell limit/min spark time/other?)
#ifdef HOTEL
	/// @bug hack for hyundai! 135 = 3/4 of 180 = one cycle...
	unsigned long threeQuartersOfAvailableTime = ((unsigned long)CoreVars->DRPM * 135 * oneDegree) / ticks_per_degree_multiplier;
	if(DerivedVars->Dwell > threeQuartersOfAvailableTime){
		DerivedVars->Dwell = threeQuartersOfAvailableTime;
	}
#endif

	/* Look up the engine temperature enrichment percentage with temperature */
	DerivedVars->ETE = lookupTwoDTableUS((twoDTableUS*)&TablesA.SmallTablesA.engineTempEnrichmentTablePercent, CoreVars->CHT);
	/* TODO The above needs some careful thought put into it around different loads and correction effects. */


	/* Calculate the Transient Fuel Correction */
	if(TRUE /*WWTFC*/){ /* Do ONLY WW correction if enabled */
		// Do ww stuff, maybe pre done via RTC/RTI for consistent period?
		DerivedVars->TFCTotal = 0; /* TODO replace with real code */
	}else if(FALSE /*STDTFC*/){ /* Do any combination of standard approximate methods */
		/* Initialse the variable as a base */
		DerivedVars->TFCTotal = 0;
		/* Based on the rate of change of MAP and some history/taper time */
		if(FALSE /*MAPTFC*/){
			// Do MAP based
			DerivedVars->TFCTotal += 0;
		}

		/* Based on the rate of change of TPS and some history/taper time */
		if(FALSE /*TPSTFC*/){
			// Do TPS based
			DerivedVars->TFCTotal += 0;
		}

		/* Based on the rate of change of RPM and some history/taper time */
		if(FALSE /*RPMTFC*/){
			// Do RPM based
			DerivedVars->TFCTotal += 0;
		}
	}else{ /* Default to no correction */
		DerivedVars->TFCTotal = 0;
		/* Don't throw error as correction may not be required */
	}

	/*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
}