/** @brief Send an error
 *
 * This function is only for use inside the communication handling function.
 * Use of it outside this environment is not supported and behaviour when used
 * as such is undefined.
 *
 * @author Fred Cooke
 *
 * @warning ONLY use this function from within the communication handler.
 *
 * @see sendErrorIfClear()
 * @see sendErrorBusyWait()
 *
 * @todo TODO clean up the mess of commented out crap in here!
 * @todo TODO decide on errorCode or errorID and consistencise it everywhere.
 *
 * @param errorCode is the error ID to be passed out to listening devices.
 */
void sendErrorInternal(unsigned short errorID){
//	set buffer in use, consider blocking interrupts to do this cleanly


	//	TXBufferInUseFlags = 0;
	/* No need for atomic block here as one of two conditions will always be */
	/* true when calling this. Either we have been flagged to receive and    */
	/* decode a packet, or we are in an ISR. In either case it is safe to    */
	/* check the flags and initiate the sequence if they are clear.          */
//	if(RXTXSerialStateFlags & TX_IN_PROGRESS){
		/* It's OK to return without resetting as it will be done by */
		/* either of those processes if they are underway. The other */
		/* processes are not overridden because they have priority.  */
//		return;
//	}else{ /* Turn off reception */
		/* It's OK to turn this off if nothing was currently being received */
//		SCI0CR2 &= SCICR2_RX_ISR_DISABLE;
//		SCI0CR2 &= SCICR2_RX_DISABLE;

		/* Build up the packet */
		/* Set the pointer to the start */
		TXBufferCurrentPositionHandler = (unsigned char*)&TXBuffer;
		/* Set the length */
//		TXPacketLengthToSend = 5; /* Flags + Payload ID + Error Code */
		// Flags = empty
		*TXBufferCurrentPositionHandler = 0x00;
		TXBufferCurrentPositionHandler++;
		/* Set the payload ID */
		*((unsigned short*)TXBufferCurrentPositionHandler) = asyncErrorCodePacket;
		TXBufferCurrentPositionHandler += 2;

		finaliseAndSend(errorID);
//	}
}
/** @brief Send a debug message
 *
 * Sends a null terminated debug message out on the broadcast address of all available interfaces.
 *
 * @author Fred Cooke
 *
 * @warning ONLY use this function from within the communication handler.
 *
 * @see sendDebugIfClear()
 * @see sendDebugBusyWait()
 *
 * @note This function exists as a convenience to developers, do not publish code that calls this function.
 *
 * @todo TODO clean up the mess of commented out crap in here!
 *
 * @param message is a pointer to the null terminated debug message string.
 */
void sendDebugInternal(unsigned char* message){

//	set buffer in use, consider blocking interrupts to do this cleanly

//	if(TRUE){
//		Counters.serialDebugUnsentCounter++;
//		return;
//	}
	// wrong :
	/* No need for atomic block here as one of two conditions will always be */
	/* true when calling this. Either we have been flagged to receive and    */
	/* decode a packet, or we are in an ISR. In either case it is safe to    */
	/* check the flags and initiate the sequence if they are clear.          */
	//if(RXTXSerialStateFlags & TX_IN_PROGRESS){
		// wrong :
		/* It's OK to return without resetting as it will be done by */
		/* either of those processes if they are underway. The other */
		/* processes are not overridden because they have priority.  */
		//TXBufferInUseFlags = 0;
		//return;
//	}else{ /* Turn off reception */
		/* It's OK to turn this off if nothing was currently being received */
	//	SCI0CR2 &= SCICR2_RX_ISR_DISABLE;
	//	SCI0CR2 &= SCICR2_RX_DISABLE;

		/* Build up the packet */
		/* Set the pointer to the start and init the length */
		TXBufferCurrentPositionHandler = (unsigned char*)&TXBuffer;

		/* Load a protocol with length header into the TX buffer ready for masking */
		*TXBufferCurrentPositionHandler = 0x11;
		TXBufferCurrentPositionHandler++;

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

		/* Store the length location */
		unsigned short* TXLength = (unsigned short*)TXBufferCurrentPositionHandler;
		TXBufferCurrentPositionHandler += 2;

		/* Copy the string into place and record the length copied */
		unsigned short messageLength = stringCopy(TXBufferCurrentPositionHandler, message);
		*TXLength = messageLength;
		TXBufferCurrentPositionHandler += messageLength;

		finaliseAndSend(0);
	//}
}
Exemple #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 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.
 *
 * @author Fred Cooke
 */
int  main(){ // TODO maybe move this to paged flash ?
	// Set everything up.
	init();

	//LongNoTime.timeLong = 54;
	// 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(ADCArraysRecord); // TODO still need to do a pair of loops and clock these two functions for performance.
				//sampleLoopADC(&ADCArrays);
				resetToNonRunningState();
				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(ADCArrays == &ADCArrays1){
				RPM = &RPM0; // TODO temp, remove
				RPMRecord = &RPM1; // TODO temp, remove
				ADCArrays = &ADCArrays0;
				ADCArraysRecord = &ADCArrays1;
				mathSampleTimeStamp = &ISRLatencyVars.mathSampleTimeStamp0; // TODO temp, remove
				mathSampleTimeStampRecord = &ISRLatencyVars.mathSampleTimeStamp1; // TODO temp, remove
			}else{
				RPM = &RPM1; // TODO temp, remove
				RPMRecord = &RPM0; // TODO temp, remove
				ADCArrays = &ADCArrays1;
				ADCArraysRecord = &ADCArrays0;
				mathSampleTimeStamp = &ISRLatencyVars.mathSampleTimeStamp1; // TODO temp, remove
				mathSampleTimeStampRecord = &ISRLatencyVars.mathSampleTimeStamp0; // TODO temp, remove
			}

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

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

			/* Store the latency from sample time to runtime */
			ISRLatencyVars.mathLatency = TCNT - *mathSampleTimeStamp;
			/* Keep track of how many calcs we are managing per second... */
			Counters.calculationsPerformed++;
			/* ...and how long they take each */
			unsigned short mathStartTime = TCNT;

			/* Generate the core variables from sensor input and recorded tooth timings */
			generateCoreVars();

			RuntimeVars.genCoreVarsRuntime = TCNT - mathStartTime;
			unsigned short derivedStartTime = TCNT;

			/* Generate the derived variables from the core variables based on settings */
			generateDerivedVars();

			RuntimeVars.genDerivedVarsRuntime = TCNT - derivedStartTime;
			unsigned short calcsStartTime = TCNT;

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

			RuntimeVars.calcsRuntime = TCNT - calcsStartTime;
			/* Record the runtime of all the math total */
			RuntimeVars.mathTotalRuntime = TCNT - mathStartTime;

			RuntimeVars.mathSumRuntime = RuntimeVars.calcsRuntime + RuntimeVars.genCoreVarsRuntime + RuntimeVars.genDerivedVarsRuntime;

			ATOMIC_START(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
			/* Atomic block to ensure that outputBank and outputBank Offsets match */

			/* Switch banks to the latest data */
			if(injectorMainPulseWidthsMath == injectorMainPulseWidths1){
				currentDwellMath = &currentDwell0;
				currentDwellRealtime = &currentDwell1;
				injectorMainPulseWidthsMath = injectorMainPulseWidths0;
				injectorMainPulseWidthsRealtime = injectorMainPulseWidths1;
				injectorStagedPulseWidthsMath = injectorStagedPulseWidths0;
				injectorStagedPulseWidthsRealtime = injectorStagedPulseWidths1;
			}else{
				currentDwellMath = &currentDwell1;
				currentDwellRealtime = &currentDwell0;
				injectorMainPulseWidthsMath = injectorMainPulseWidths1;
				injectorMainPulseWidthsRealtime = injectorMainPulseWidths0;
				injectorStagedPulseWidthsMath = injectorStagedPulseWidths1;
				injectorStagedPulseWidthsRealtime = injectorStagedPulseWidths0;
			}

			ATOMIC_END(); /*&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*/
		}else{
			/* In the event that no calcs are required, sleep a little before returning to retry. */
			sleepMicro(RuntimeVars.mathTotalRuntime); // 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.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*)TXBufferCurrentPositionHandler) = configuredBasicDatalogLength;
						TXBufferCurrentPositionHandler += 2;

						/* populate data log */
						populateBasicDatalog();
						finaliseAndSend(0);
						break;
					}
					case asyncDatalogConfig:
					{
						/// TODO @todo
						break;
					}
					case asyncDatalogTrigger:
					{
						/// TODO @todo
						break;
					}
					case asyncDatalogADC:
					{
						/// TODO @todo
						break;
					}
					case asyncDatalogCircBuf:
					{
						/// TODO @todo
						break;
					}
					case asyncDatalogCircCAS:
					{
						/// TODO @todo
						break;
					}
					case asyncDatalogLogic:
					{
						/// TODO @todo
						break;
					}
				}
				// mechanism to ensure we only send something if the data has been updated
				lastCalcCount = Counters.calculationsPerformed;
			}
		}
		// on once per cycle for main loop heart beat (J0)
		PORTJ ^= 0x01;


		// debug...
		if(SCI0CR2 & SCICR2_RX_ENABLE){
			PORTK |= BIT2;
		}else{
			PORTK &= NBIT2;
		}

		if(SCI0CR2 & SCICR2_RX_ISR_ENABLE){
			PORTK |= BIT3;
		}else{
			PORTK &= NBIT3;
		}

		// PWM experimentation
		adjustPWM();
	}
}
Exemple #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.
 */
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);
}
/** @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;
}
Exemple #6
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();
	}
}