This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CCS/TMS320F28335: Issue with Code Composer versions

Part Number: TMS320F28335
Other Parts Discussed in Thread: SYSBIOS

Tool/software: Code Composer Studio

I have come across an issue with a project that I compiled with two different versions of code composer, one on my desktop and one on my laptop.

I started on my desktop which is running:

Code composer 5.4.0.00091
Compiler v6.2.11
Sys/Bios 6.35.1.29

On the laptop I have:

Code composer 5.5.0.00077
Compiler v6.2.11 (same)
Sys/Bios 6.35.4.50

Both of the code at first appear to run the same.  The issue appears once I start using two of the timer interrupts. 

I'm using two different timers with the sys/bios command Timer_setPeriodMicroSecs(), one appears to work fine while the other runs a few times and then I stop getting an interrupt for the timer.

Again the code works fine when compile on the desktop but does NOT when I compile on the laptop.

Any ideas as to what could be causing this issue?

Ed Hildebrandt

  • Hi Ed,

    There's not much to go on with just this info.  I looked for differences in the C28 Timer code between those 2 SYS/BIOS versions and the differences were minimal and only related to ONESHOT Timer instances (I assume you're using CONTINUOUS since you state that you expect it to run more than a few times).

    Can you possibly provide your source code (C files and .cfg)?

    Thanks,

    - Rob

  • Is it possible for you to try SYS/BIOS 6.35.4.50 in the desktop environment?

    - Rob

  • Robert,

    Thanks for the response. I am using the timer in one shot mode. My firmware will try to retrigger it but I keep track if the timer is use.

    I did install Sys-Bios 6.35.4.50 and can confirm that this is the source of the issue. If I compile with 6.35.4.50 I always have the timer issue, if I compile with 6.35.1.29 it all works fine. I also tried a two versions of XDCTools, (3.25.3.72 & 3.25.0.48) but this makes no difference.

    I have attached app.cfg as well as the c file that has the handler for timers

    void Injector_Timer_Group2(void)
    inline void Injector_Handle_Timer(Uint16 Bank_Number)

    Ed4503.app.cfg

    //#############################################################################
    //
    // FILE:   Injector_Control.c
    //
    // TITLE: Injector Control Routine
    //
    //		  The injectors are split into 4 Banks
    // 		  The four banks are split into 2 groups
    //
    //        	#####################################################
    //			## Injector 0	##			##			##	1  #  1 #
    //			## Injector 1	##			##			##  2  # 13 #
    //			## Injector 2  	##  Bank 1A	##	Group1	##  3  #  5 #
    //			## Injector 3	##			## (Timer1)	##  4  #  9 #
    //			## Injector 4	##			##			##     #    #
    //			#####################################################
    //			## Injector 5	##			##			##  5  # 10 #
    //			## Injector 6	##			##			##  6  #  6 #
    //			## Injector 7  	##  Bank 1B	##	Group2	##  7  # 14 #
    //			## Injector 8	##			## (Timer2)	##  8  #  2 #
    //			## Injector 9	##			##			##     #    #
    //			#####################################################
    //			## Injector 10	##			##			##  9  #  3 #
    //			## Injector 11	##			##			## 10  # 15 #
    //			## Injector 12 	##  Bank 2A	##	Group1	## 11  #  7 #
    //			## Injector 13	##			## (Timer1)	## 12  # 11 #
    //			## Injector 14	##			##			##     #    #
    //			#####################################################
    //			## Injector 15	##			##			## 13  # 12 #
    //			## Injector 16	##			##			## 14  #  8 #
    //			## Injector 17 	##  Bank 2B	##	Group2	## 15  # 16 #
    //			## Injector 18	##			## (Timer2)	## 16  #  4 #
    //			## Injector 19	##			##			##     #    #
    //			#####################################################
    //
    //
    //#############################################################################
    //
    //  Ver | dd mmm yyyy | Who  | Description of changes
    // =====|=============|======|=================================================
    //  000 | 09 MAR 2012 | EIH  | Original version
    //		|			  |		 |
    //#############################################################################
    
    #include "CommonHeader.h"
    
    #include "Version.h"
    #include "Error.h"
    #include "Injector_Control.h"
    #include "SRS_TRS.h"
    #include "PID_Control.h"
    #include "Injector_Test.h"
    #include "PWM_control.h"
    #include "ECap_init.h"
    #include "DMA_Driver.h"
    #include "Event_Log.h"
    #include "SCI_control.h"
    #include "Response_Time.h"
    #include "Indicators.h"
    #include "Digital_Driver.h"
    #include "Serial_EEPROM_Data.h"
    #include "Watchdog.h"
    #include "Engine_Protect.h"
    #include <xdc/runtime/Timestamp.h>
    #include "SystemControl.h"
    
    volatile ENG_PERF_DATA		Engine_Data;
    volatile ENG_SPECS_STRUCT	Engine_Specs;
    		 FUEL_TABLE_STRUCT  Fuel_Table;
    //		 ENG_CONFIG_STRUCT	Engine_Config;
    
    // 140619 - Add this to toggle IRTs from terminal.
    Uint16 IRT_Enable = FALSE;
    
    //Uint16 inject_error;
    
    
    #ifdef INJ_TIMING_DEBUG
    #pragma DATA_SECTION(Timing_Debug,"ext_ram_data");
    
    		 TIMING_DEBUG	Timing_Debug[SIZE_TIMING_DATA];
    		 Uint16 timing_counter;
    #endif
    
    
    Uint16 Min_Drive_Count[MAX_CYLINDERS];
    
    //Uint16 debug_inj_fired[30];
    //Uint16 debug_index = 0;
    
    //#define FULL_TIMER_CONTROL
    #define INJ_CTRL_EVENT_LOGGING
    #define INJ_TIMER_OVERRUN_LOGGING
    #define INJ_MISS_CORRECTION
    
    //**********************************************************************************************************************************************************
    // Output #				Function
    //	0					Injector 1
    //	1					Injector 2
    //	2					Injector 3
    //	3					Injector 4
    //  4					Flyback Control Bank 1
    //	5					Injector 5
    //	6					Injector 6
    //	7					Injector 7
    //	8					Injector 8
    //  9					Flyback Control Bank 2
    //	10					Injector 9
    //	11					Injector 10
    //	12					Injector 11
    //	13					Injector 12
    //  14					Flyback Control Bank 3
    //	15					Injector 13
    //	16					Injector 14
    //	17					Injector 15
    //	18					Injector 16
    //  19					Flyback Control Bank 4
    //**********************************************************************************************************************************************************
    //		 						  |          Bank 1A        |        Bank 1B        |       BANK 2A         |       Bank 2B         |       Unused         |
    //								  |	   0     1     2     3  |  4     5     6     7  |  8     9    10    11  | 12    13    14    15  | 16    17    18    19 |
    // delete Feb 25, 2019 appears not to be used
    const Uint16 FIRING_ORDER[]		= {    0,    7,    8,   15,    2,    5,   10,   13,    3,    4,   11,   12,    1,    6,    9,   14,   99,   99,   99,   99};
    const Uint16 OUTPUT_NUMBER[]	= {    0,    1,    2,    3,    5,    6,    7,    8,   10,   11,   12,   13,   15,   16,   17,   18,    4,    9,   14,   19};
    const Uint16 NEXT_IN_ORDER[]	= {    7,    6,    5,    4,   11,   10,    9,    8,   15,   14,   13,   12,    1,    3,    0,    2,   99,   99,   99,   99};
    const float TDC[]				= {    0,270.0, 90.0,180.0,202.5,112.5,292.5, 22.5, 45.0,315.0,135.0,225.0,247.5,157.5,337.5, 67.5,999.9,999.9,999.9,999.9};
    const Uint16 BANK_ORDER[]		= {    0,    0,    0,    0,    1,    1,    1,    1,    2,    2,    2,    2,    3,    3,    3,    3,    0,    1,    2,    3};
    
    //**********************************************************************************************************************************************************
    
    // 	     Injector   Degrees	    Next
    // 1		0 		  0.0		7
    // 2		7		 22.5		8
    // 3		8		 45.0		15
    // 4		15		 57.5		2
    // 5		2		 90.0		5
    // 6		5		112.5		10
    // 7		10		135.5		13
    // 8		13		157.5		3
    // 9		3		180.0		4
    // 10		4 		202.5		11
    // 11		11		225.0		12
    // 12		12		247.5		1
    // 13		1		270.0		6
    // 14		6		292.5		9
    // 15		9		315.0		14
    // 16		14		337.5		0
    
    
    
    
    
    typedef struct
    {
    	Uint16 Bank;
    	Uint16 Busy;
    }TIMER_DATA;
    
    TIMER_DATA Timer1;
    TIMER_DATA Timer2;
    
    #define TIMER_LOG_SIZE		(Uint16)(50)
    
    typedef struct
    {
    	Uint32 time;
    	Uint16 Timer_Number;
    	Uint16 Timer_Value;
    	Uint16 Timer_Error;
    	Uint16 Bank_Number;
    	Uint16 Bank_State;
    }TIMER_LOG;
    
    TIMER_LOG Timer_Log[TIMER_LOG_SIZE];
    Uint16 Timer_Log_Index;
    //Uint16 Dummy_Timer_Active;
    
    Uint16 Debug_Timer2_Flag = FALSE;
    
    //*****************************************************************************
    //    Function: void Injector_Init(void)
    //
    // Description: Initialize variables for injection control
    //
    //*****************************************************************************
    void Injector_Init(void)
    {
    	Uint16 num;
    	Uint16 key;
    
    
    //	inject_error = FALSE;
    	Engine_Specs_Init();
    	Fuel_Table_Init();
    
    	Engine_Data.Test_LOI_uS 					= 1000;
    	Engine_Data.Enable_Engine					= FALSE;					// This will prevent the engine from running
    
    //	Engine_Data.Safety_Lockout					= FALSE;
    	Clear_Safety_Lockout();
    
    	for (num =0 ; num < MAX_CYLINDERS ; num++)
    	{
    		Engine_Data.Injector[num].Data_Valid 	= FALSE;
    		Engine_Data.Injector[num].Cylinder_Num 	= FIRING_ORDER[num];
    		Engine_Data.Injector[num].TDC			= TDC[num];
    		Engine_Data.Injector[num].Total_PW 		= 0;
    		Engine_Data.Injector[num].Enable        = TRUE;
    
    		Min_Drive_Count[num] = 0;
    
    	}
    
    	// for test purposes only
    //	Engine_Data.Injector[7].Enable = FALSE;
    
    	Engine_Data.Current_Injector = 0;					// This will be the next injector to fire
    
    	Engine_Data.Actual_RPM 	= 0;
    //	Engine_Data.Target_rpm 	= DEFAULT_TARGET_RPM;
    
    
    	Engine_Data.Bank[BANK1A].Current_Bank_PW = 0;
    	Engine_Data.Bank[BANK1B].Current_Bank_PW = 0;
    	Engine_Data.Bank[BANK2A].Current_Bank_PW = 0;
    	Engine_Data.Bank[BANK2B].Current_Bank_PW = 0;
    
    	Engine_Data.Bank[BANK1A].State = bank_idle;
    	Engine_Data.Bank[BANK1B].State = bank_idle;
    	Engine_Data.Bank[BANK2A].State = bank_idle;
    	Engine_Data.Bank[BANK2B].State = bank_idle;
    
    	Engine_Data.Bank[BANK1A].Control_Data_Valid = FALSE;
    	Engine_Data.Bank[BANK1B].Control_Data_Valid = FALSE;
    	Engine_Data.Bank[BANK2A].Control_Data_Valid = FALSE;
    	Engine_Data.Bank[BANK2B].Control_Data_Valid = FALSE;
    
    	Engine_Data.Bank[BANK1A].Current_Injector = 0;
    	Engine_Data.Bank[BANK1B].Current_Injector = 0;
    	Engine_Data.Bank[BANK2A].Current_Injector = 0;
    	Engine_Data.Bank[BANK2B].Current_Injector = 0;
    
    
    //	Engine_Data.Group1_Active_Bank = BANK1A;
    //	Engine_Data.Group2_Active_Bank = BANK2A;
    
    //	Engine_Data.PWM_Reduced_Drive = PWM_REDUCED_DRIVE_DEFAULT;
    //	Engine_Data.PWM_Minimum_Drive = PWM_MINIMUM_DRIVE_DEFAULT;
    
    	// make sure all PWM outputs are off
    	Injector_End(BANK1A);
    	Injector_End(BANK1B);
    	Injector_End(BANK2A);
    	Injector_End(BANK2B);
    
    //	Engine_Data.Current_LOI_Deg = DEFAULT_LOI_DEGREES;
    	Engine_Data.Prelim_EOE 		= DEFAULT_EOE_DEGREES;
    
    	Engine_Data.Current_BOI_Deg = 0;
    //	Engine_Data.Est_Torque      = 0;
    
    	Injectors_Reset_Timers();
    
    //	Engine_Data.BOI_DEG_Manual		= DEFAULT_BOI_MANUAL;		// Manual Value for BOI (No Fuel Table)
    	Engine_Data.BOI_Mode		= BOI_Calculation;
    //	Engine_Data.BOI_Mode		= BOI_Fuel_Table;
    
    
    
    	Timer_Log_Index = 0;
    	Timer_Log_Init();
    
    	key = Hwi_disable();
    	Timer_stop(timerGroup1);
    	Timer_stop(timerGroup2);
    	Hwi_restore(key);
    
    	Debug_Timer2_Flag = FALSE;
    
    	Engine_Data.First_Injector_Fired = FALSE;
    	Engine_Data.Enable_Override		 = FALSE;
    	Engine_Data.Engine_Ready		 = FALSE;
    
    	// make dummy timer calls
    //	Injector_Set_Timer(BANK1A,50);
    //	Injector_Set_Timer(BANK1B,50);
    //	Dummy_Timer_Active = TRUE;
    
    #ifdef INJ_TIMING_DEBUG
    	memset((Uint16*)&Timing_Debug	, 	0x0000, sizeof(Timing_Debug)		);
    	timing_counter = 0;
    #endif
    
    	Injector_Test_Init();
    }
    
    /*****************************************************************************
    **    Function:
    **
    ** Description:
    **
    *****************************************************************************/
    void Injectors_Reset_Timers(void)
    {
    	Timer1.Bank 			= 0;
    	Timer1.Busy 			= FALSE;
    
    	Timer2.Bank 			= 0;
    	Timer2.Busy 			= FALSE;
    }
    
    /*****************************************************************************
    **    Function:  void Timer_Log_Init(void)
    **
    ** Description:  Init the log
    **
    *****************************************************************************/
    void Timer_Log_Init(void)
    {
    	memset(&Timer_Log, 0x0000, sizeof(Timer_Log));
    }
    
    /*****************************************************************************
    **    Function: void Clear_Engine_Data_Stopped(void)
    **
    ** Description: This routine will be called when the engine is stopped
    **
    *****************************************************************************/
    void Clear_Data_Engine_Stopped(void)
    {
    	Engine_Data.Current_Injector = 0;			// This will be the next injector to fire
    	Engine_Data.Actual_RPM 	= 0;
    	Engine_Data.First_Injector_Fired = FALSE;
    }
    
    /*****************************************************************************
    **    Function: void Engine_Specs_Init(void)
    **
    ** Description: Init the engine specification structure
    **				Hard coded for now
    **
    *****************************************************************************/
    void Engine_Specs_Init(void)
    {
    	Engine_Specs.Cylider_Displacement 	= ENGINE_CU_IN_CYL;
    	Engine_Specs.Num_Cylinders			= ENGINE_NUM_CYLINDERS;
    }
    
    
    /*****************************************************************************
    *****************************************************************************/
    
    //  75,185,306,417,529,610,691,775,862,952
    //  3,4.5,6,8,10,12.5,14.5,16,18,20
    
    
    const float Default_Fuel_Table[FT_RPM_SIZE][FT_LOI_SIZE] =
    	{
    
    #if 0
    			//   1.5    3.5    5.5   7.5     9.5    11.5    13.5    15.5    17.5    19.5 (LOI)
    //			{ -3.15,  -0.93,   0.10, 0.78,  1.29,   1.69,   2.03,   2.32,   2.57,   2.79 },  // 0   -  100 RPM
    //			{ -3.38,  -0.89,   0.27, 1.04,  1.61,   2.06,   2.44,   2.77,   3.05,   3.31 },  // 100 -  200 RPM
    //			{ -3.61,  -0.84,   0.45, 1.30,  1.93,   2.44,   2.86,   3.22,   3.53,   3.82 },  // 200 -  300 RPM
    //			{ -3.85,  -0.80,   0.62, 1.55,  2.25,   2.81,   3.27,   3.67,   4.02,   4.33 },  // 300 -  400 RPM
    //			{ -4.08,  -0.75,   0.79, 1.81,  2.57,   3.18,   3.69,   4.12,   4.50,   4.84 },  // 400 -  500 RPM
    //			{ -4.31,  -0.71,   0.97, 2.07,  2.89,   3.55,   4.10,   4.57,   4.98,   5.35 },  // 500 -  600 RPM
    //			{ -4.54,  -0.66,   1.14, 2.33,  3.22,   3.93,   4.52,   5.02,   5.46,   5.86 },  // 600 -  700 RPM
    //			{ -4.78,  -0.62,   1.31, 2.59,  3.54,   4.30,   4.93,   5.47,   5.94,   6.37 },  // 700 -  800 RPM
    //			{ -5.01,  -0.57,   1.49, 2.85,  3.86,   4.67,   5.34,   5.92,   6.43,   6.88 },  // 800 -  900 RPM
    //			{ -5.24,  -0.53,   1.66, 3.10,  4.18,   5.04,   5.76,   6.37,   6.91,   7.39 }   // 900 - 1000 RPM
    
    
    #elif 1
    			//  3.0     4.5   6.0    8.0    10.0    12.5    14.5    16.0   18.0    20.0     (LOI)
    			{  -4.02, -4.19, -4.52, -5.02, -5.69,  -6.52,  -8.08,  -8.68, -10.01, -11.51},      // 0 -  185 RPM
    			{  -4.02, -4.19, -4.52, -5.02, -5.69,  -6.52,  -8.08,  -8.68, -10.01, -11.51},      // 185 -  306 RPM
    			{  -4.00, -4.04, -4.10, -4.20, -4.33,  -4.49,  -4.80,  -4.92,  -5.18,  -5.47},      // 306 -  417 RPM
    			{  -4.03, -4.24, -4.66, -5.30, -6.15,  -7.21,  -9.20,  -9.97, -11.67, -13.58},      // 417 -  529 RPM
    			{  -4.07, -4.61, -5.68, -7.29, -9.45, -12.13, -17.18, -19.13, -23.43, -28.27},      // 529 -  610 RPM
    			{  -4.05, -4.41, -5.13, -6.22, -7.66,  -9.47,  -12.86,-14.17, -17.07, -20.32},      // 610 -  691 RPM
    			{  -3.29, -3.47, -3.81, -4.32, -5.00,  -5.85,  -7.45,  -8.07,  -9.43, -10.97},      // 691 -  775 RPM
    			{  -2.45, -2.61, -2.94, -3.43, -4.08,  -4.90,  -6.42,  -7.01,  -8.32,  -9.78},      // 775 -  862 RPM
    			{  -2.64, -2.85, -3.26, -3.89, -4.73,  -5.77,  -7.74,  -8.49, -10.17, -12.05},      // 862 -  952 RPM
    			{  -2.64, -2.85, -3.26, -3.89, -4.73,  -5.77,  -7.74,  -8.49, -10.17, -12.05}    	// 952 - 1000 RPM
    
    #elif 0
    //			{ -0.08, -0.33, -0.52, -1.02, -1.69, -2.52, -4.08,  -4.68,  -6.01,  -7.51	},	// 0   -   75 RPM
    //			{ -0.08, -0.33, -0.52, -1.02, -1.69, -2.52, -4.08,  -4.68,  -6.01,  -7.51	},  //  75 -  185 RPM
    //			{ -0.02, -0.07, -0.10, -0.20, -0.33, -0.49, -0.80,  -0.92,  -1.18,  -1.47	},  // 185 -  306 RPM
    //			{ -0.11, -0.42, -0.66, -1.30, -2.15, -3.21, -5.20,  -5.97,  -7.67,  -9.58	},  // 306 -  417 RPM
    //			{ -0.27, -1.08, -1.68, -3.29, -5.45, -8.13, -13.18, -15.13, -19.43, -24.27	},  // 417 -  529 RPM
    //			{ -0.18, -0.72, -1.13, -2.22, -3.66, -5.47, -8.86,  -10.17, -13.07, -16.32	},  // 529 -  610 RPM
    //			{  0.64,  0.39,  0.19, -0.32, -1.00, -1.85, -3.45,  -4.07,  -5.43,  -6.97	},  // 610 -  691 RPM
    //			{  1.49,  1.24,  1.06,  0.57, -0.08, -0.90, -2.42,  -3.01,  -4.32,  -5.78	},  // 691 -  775 RPM
    //			{ -0.08, -0.33, -0.52, -1.02, -1.69, -2.52, -4.08,  -4.68,  -6.01,  -7.51	},  // 775 -  862 RPM
    //			{ -0.08, -0.33, -0.52, -1.02, -1.69, -2.52, -4.08,  -4.68,  -6.01,  -7.51	},  // 862 -  952 RPM
    #endif
    
    	};
    
    const float Default_FT_RPM_Values[FT_RPM_SIZE] 	= {   75,  185,  306,  417, 529,   610,  691,  775,  862,  952	};		//RPM Value in RPM
    const float Default_FT_LOI_Values[FT_LOI_SIZE]	= {    3,  4.5,    6,    8,  10,  12.5, 14.5,   16,   18,   20	};		//LOI Values in ft-lb
    
    
    //*****************************************************************************
    //    Function:
    //
    // Description:
    //
    //*****************************************************************************
    void Fuel_Table_Init(void)
    {
    	Uint16 numX,numY;
    
    	for (numX = 0 ; numX <FT_RPM_SIZE ; numX++)
    	{
    		for (numY = 0 ; numY <FT_LOI_SIZE ; numY++)
    		{
    			Fuel_Table.Table[numX][numY] = Default_Fuel_Table[numX][numY];
    		}
    		Fuel_Table.RPM_Values[numX] 	= Default_FT_RPM_Values[numX];
       		Fuel_Table.LOI_Values[numX] 	= Default_FT_LOI_Values[numX];
    	}
    }
    
    /*****************************************************************************
    **    Function: float Engine_Fuel_Table_Lookup(float RPM,LOI torque)
    **
    ** Description: This routine will return the BOI in degrees.
    ** 				This will be calculated from the RPM and Length of Inject using the
    ** 				Fuel table
    *****************************************************************************/
    float Engine_Fuel_Table_Lookup(float RPM,float LOI)
    {
    	float BOI_Deg;
    	Uint16 RPM_Index;
    	Uint16 LOI_Index;
    
    
    	RPM_Index = 0;
    
    	while (RPM > Fuel_Table.RPM_Values[RPM_Index])
    	{
    		RPM_Index++;
    	}
    
    	if (RPM_Index >= FT_RPM_SIZE)
    	{
    		RPM_Index = FT_RPM_SIZE - 1;
    		// Post Error
    	}
    
    	LOI_Index = 0;
    
    	while (LOI > Fuel_Table.LOI_Values[LOI_Index])
    	{
    		LOI_Index++;
    	}
    
    	if (LOI_Index >= FT_LOI_SIZE)
    	{
    		LOI_Index = FT_LOI_SIZE - 1;
    		// Post Error
    	}
    
    	BOI_Deg = Fuel_Table.Table[RPM_Index][LOI_Index];
    
    	if (BOI_Deg == 99.9)
    	{
    //		BOI_Deg = Engine_Data.Prelim_EOE - Engine_Data.Current_LOI_Deg;
    		BOI_Deg = Engine_Data.Prelim_EOE - PID_Get_PID_Value(LOI_DEGREES);
    	}
    
    	return BOI_Deg;
    }
    
    
    //*****************************************************************************
    //    Function:
    //
    // Description:
    //
    //*****************************************************************************
    float BOI_Calculate(float myLOI)
    {
    	float myBOI;
    
    //	myBOI = myLOI *  EEprom.BOI_Config.BOI_Scale + EEprom.BOI_Config.BOI_Offset;
    	myBOI = myLOI *  EEprom.Config.BOI_Scale + EEprom.Config.BOI_Offset;
    
    	return myBOI;
    }
    
    //*****************************************************************************
    //    Function: void Injector_Swi(void)
    //
    // Description: This is the software interrupt that calculates all the injector
    //				timing.  It is triggered by the TRS interrupt
    //
    //*****************************************************************************
    #pragma CODE_SECTION(Injector_Swi, "secureRamFuncs")
    void Injector_Swi(void)
    {
    	Uint16 Bank_Number;
    
    	Uint16 response_time_uS;
    	Uint16 myInjector;
    
    	float response_time_deg;
    	float TRS_to_BOE_Deg;
    	float LOI_Deg;
    	float BOI_Deg_Engine;
    	float BOE_Deg_Injector;
    	float rpm;
    	float Next_TRS_Deg;
    	float TDC_Degrees;
    	float BOI_Deg_Injector;
    #if 1
    	rpm	= Calculate_RPM();
    #else
    	rpm = Speed.Current_RPM;
    #endif
    
    	if(rpm < 10)
    	{
    		return;
    	}
    
    	if (rpm > 1000)
    	{
    		return;
    	}
    
    	// copy global structure data to local variables
    	myInjector 			= Engine_Data.Current_Injector;							// get the next injector to be fired
    	response_time_uS 	= ResponseTime.IRT[myInjector];							// Get the response time for the current injector
    	LOI_Deg 			= PID_Get_PID_Value(LOI_DEGREES);						// Get length of Injection from PID control routines
    	TDC_Degrees 		= Engine_Data.Injector[myInjector].TDC;					// Get Top dead center for this injector
    
    	switch(Engine_Data.BOI_Mode)
    	{
    		case BOI_Manual:
    			BOI_Deg_Engine 	= Test_Signals.BOI;
    		break;
    
    		case BOI_Fuel_Table:
    			BOI_Deg_Engine 	= Engine_Fuel_Table_Lookup(rpm, LOI_Deg);
    		break;
    
    		case BOI_Calculation:
    			BOI_Deg_Engine = BOI_Calculate(LOI_Deg);
    		break;
    
    	}
    
    	// now calculate the cylinder specific timing degrees
    	// convert the injector response time (uS) to degrees
    	response_time_deg 	= uS_To_Degrees(response_time_uS, rpm);
    
    	// Calculate the specific BOI for the current injector
    	BOI_Deg_Injector 	= Add_Degrees( BOI_Deg_Engine, TDC_Degrees);
    
    	// Add the response time to the BOI (subtract)
    	BOE_Deg_Injector 	= Add_Degrees(BOI_Deg_Injector,-response_time_deg);
    
    	// get the flywheel position on the next TRS pulse
    	Next_TRS_Deg 		= Get_Next_TRS_Degrees();
    
    	TRS_to_BOE_Deg 		= Add_Degrees(-Next_TRS_Deg, BOE_Deg_Injector);
    
    	// check to see if we missed an injector
    	// for now just detect the situation,
    	// once we have confirm we may want to make sure we cue up the correct injector
    	// so that we don't miss a whole revolution of injections
    	// note that TRS_to_BOE_Deg will always be between 0 and 360.0 (never negative)
    	if ( (TRS_to_BOE_Deg > 180.0) && (rpm > 100) && (Engine_Data.Enable_Engine == TRUE) )
    	{
    		Errors.Injector_Miss[myInjector].New_Flag = ERROR_FLAG_10S;
    
    #ifdef INJ_MISS_CORRECTION
    		// if we missed an injection we should jump to next injector
    		// this is most of what we did above but with the next injector
    
    		if(myInjector >15)
    		{
    			myInjector =0;
    		}
    
    		myInjector =  NEXT_IN_ORDER[ myInjector ];
    
    		response_time_uS 	= ResponseTime.IRT[myInjector];
    		TDC_Degrees 		= Engine_Data.Injector[myInjector].TDC;
    
    		response_time_deg 	= uS_To_Degrees(response_time_uS, rpm);
    
    		BOI_Deg_Injector 	= Add_Degrees( BOI_Deg_Engine, TDC_Degrees);
    
    		BOE_Deg_Injector 	= Add_Degrees(BOI_Deg_Injector,-response_time_deg);
    
    		TRS_to_BOE_Deg 		= Add_Degrees(-Next_TRS_Deg, BOE_Deg_Injector);
    #endif
    	}
    
    	// need to add a RPM check in this routine
    	if ( (TRS_to_BOE_Deg < MAX_TRS_TO_BOE_DEG) && (rpm > MIN_RPM_FOR_INJECTION) && (Engine_Data.Enable_Engine == TRUE)  )
    	{
    
    		if (Engine_Data.Injector[myInjector].Enable == TRUE)
    		{
    			Engine_Data.Injector[myInjector].TRS_To_BOE_uS	= Degrees_To_uS(TRS_to_BOE_Deg,rpm);
    			Engine_Data.Injector[myInjector].Total_PW		= Degrees_To_uS( LOI_Deg+response_time_deg , rpm);
    			Engine_Data.Injector[myInjector].TRS_Number     = Get_Next_TRS_Number();
    			Engine_Data.Injector[myInjector].Data_Valid 	= TRUE;
    
    			Injector_Prep(myInjector);						// This will configure the output on the PCB
    			Bank_Number = Injector_Get_Bank(myInjector);
    
    			Set_TRS_Trigger(Bank_Number, Engine_Data.Injector[myInjector].TRS_Number, Engine_Data.Injector[myInjector].TRS_To_BOE_uS,myInjector);
    		}
    
    		// replace this with the sub-routine below???
    		myInjector =  NEXT_IN_ORDER[ myInjector ];
    
    		Engine_Data.First_Injector_Fired = TRUE;
    	}
    
    #ifdef INJ_TIMING_DEBUG
    
    	if ( (timing_counter < SIZE_TIMING_DATA) && (rpm > 50) )
    	{
    		Timing_Debug[timing_counter].Time 			= Timestamp_get32();
    		Timing_Debug[timing_counter].Cylinder 		= myInjector;
    		Timing_Debug[timing_counter].RPM			= rpm;
    		Timing_Debug[timing_counter].TRS_to_BOE_Deg	= TRS_to_BOE_Deg;
    		Timing_Debug[timing_counter].Next_TRS_Deg 	= Next_TRS_Deg;
    		Timing_Debug[timing_counter].LOI_Deg		 = LOI_Deg;
    
    		timing_counter++;
    	}
    #endif
    
    	// Copy Local variables back to global structures
    	Engine_Data.Current_BOI_Deg 	= BOI_Deg_Engine;
    	Engine_Data.Actual_RPM 			= rpm;
    
    	Engine_Data.Injector[myInjector].BOI_DEG = BOI_Deg_Injector;
    	Engine_Data.Current_Injector = myInjector;
    }
    
    //*****************************************************************************
    //    Function: void Dump_Timing_Debug(void)
    //
    // Description:
    //
    //*****************************************************************************
    
    #ifdef INJ_TIMING_DEBUG
    void Dump_Timing_Debug(void)
    {
    	char tx_String[100];
    	Uint16 num;
    
    	SCI_puts(PORT_B,"---<Timing Debug>---\n");
    	Print_Date_Time(tx_String);
    	SCI_puts(PORT_B,tx_String);
    
    	SCI_puts(PORT_B,"\n");
    	Show_Version();
    
    	SCI_puts(PORT_B, "#,Time,Cylinder,RPM,TRS to BOE,Next TRS,LOI\n");
    
    	for (num = 0 ; num < timing_counter ; num++)
    	{
    		sprintf(tx_String,"%u,%lu,%u,%6.1f,%6.2f,%6.2f,%6.2f\n",
    				num,
    				Timing_Debug[num].Time,
    				Timing_Debug[num].Cylinder,
    				Timing_Debug[num].RPM,
    				Timing_Debug[num].TRS_to_BOE_Deg,
    				Timing_Debug[num].Next_TRS_Deg,
    				Timing_Debug[num].LOI_Deg
    		);
    
    		SCI_puts(PORT_B, tx_String);
    	}
    }
    #endif
    
    //*****************************************************************************
    //    Function: float Degrees_To_uS(float degrees, float Speed)
    //
    // Description: Calculates time in uS from degree and Speed in RPM
    //
    //*****************************************************************************
    inline Uint16 Degrees_To_uS(float degrees, float Speed)
    {
    	float degrees_per_second;
    	float time_uS;
    
    	if (Speed > 0)
    	{
    		degrees_per_second = 6 * Speed;		// degrees_per_second = 360 * Speed / 60;
    
    		time_uS =  degrees / degrees_per_second * SECONDS_TO_USECONDS;
    
    		time_uS = time_uS + 0.5;			// This will make sure that the value
    											// is rounded to the closest whole uS
    	}
    	else
    	{
    		time_uS = 0;
    	}
    
    	return (Uint16)time_uS;
    }
    
    //*****************************************************************************
    //    Function: float uS_To_Degrees(float time_uS, float Speed)
    //
    // Description: Calculates degrees from time in micros and Speed in RPM
    //
    //*****************************************************************************
    inline float uS_To_Degrees(float time_uS, float Speed)
    {
    	float degrees_per_second;
    	float degrees;
    
    	if (Speed > 0)
    	{
    		degrees_per_second = 6 * Speed;		// degrees_per_second = 360 * Speed / 60;
    
    		degrees =  time_uS / SECONDS_TO_USECONDS * degrees_per_second;
    	}
    	else
    	{
    		degrees = 0;
    	}
    	return degrees;
    }
    
    //*****************************************************************************
    //    Function: float add_Degrees(float deg1,float deg2)
    //
    // Description: Adds to values in degrees making sure to roll over at 359.99999
    //				needs to handles this in both directions as we could have
    //				negative numbers
    //
    //*****************************************************************************
    inline float Add_Degrees(float deg1,float deg2)
    {
    	float result;
    	Uint16 loop_Count;
    
    	result = deg1 + deg2;
    
    	loop_Count = 0;
    	while (result >=360)
    	{
    		result = result - 360.0;
    		loop_Count++;
    		if (loop_Count > 5)
    		{
    			break;
    		}
    	}
    
    	loop_Count = 0;
    	while (result < 0)
    	{
    		result = result + 360.0;
    		loop_Count++;
    		if (loop_Count > 5)
    		{
    			break;
    		}
    	}
    
    	return result;
    }
    //*****************************************************************************
    //    Function: Uint16 Injector_Get_Bank(Uint16 Injector_Number)
    //
    // Description: Returns the Bank numb (0-3) based on the injector
    //				number that is passed
    //
    //*****************************************************************************
    Uint16 Injector_Get_Bank(Uint16 Injector_Number)
    {
    	return BANK_ORDER[Injector_Number];
    }
    
    //*****************************************************************************
    //    Function: void InjectorPrep(Uint16 Injector_Number)
    //
    // Description: Routine that is run right before an injector is turned on.
    //				  * Calculates the length of injection
    //				  * Copies the injector specific pulse width to the bank pulse width
    //			      * Sets flag to indicate bank data is valid
    //				  * enables the specific injector channel in the CPLD
    //*****************************************************************************
    #ifdef FULL_TIMER_CONTROL
    #define  DEFAULT_PWM_FULL		(Uint16)(500)
    #define  DEFAULT_PWM_REDUCED	(Uint16)(1500)
    
    #endif
    
    void Injector_Prep(Uint16 Injector_Number)
    {
    	Uint16 bank_num;
    //	Uint16 irt;
    //	Uint16 injection_pulse_width;
    #ifdef FULL_TIMER_CONTROL
    	Uint16 total_pulse_width;
    #endif
    
    
    //	if (Injector_Number < Engine_Specs.Num_Cylinders)
    	if (Injector_Number < MAX_CYLINDERS)
    	{
    		bank_num = Injector_Get_Bank(Injector_Number);
    
    		Engine_Data.Bank[bank_num].Current_Bank_PW 		= Engine_Data.Injector[Injector_Number].Total_PW;
    		Engine_Data.Bank[bank_num].Control_Data_Valid 	= TRUE;
    		Engine_Data.Bank[bank_num].Current_Injector 	= Injector_Number;
    		Engine_Data.Bank[bank_num].Current_Output 		= OUTPUT_NUMBER[Injector_Number];
    		Engine_Data.Bank[bank_num].Current_Injector		= Injector_Number;
    
    #ifdef FULL_TIMER_CONTROL
    //		irt 					= ResponseTime.IRT[Injector_Number];
    //		injection_pulse_width 	= Engine_Data.Injector[Injector_Number].Injector_PW;
    		total_pulse_width		= Engine_Data.Injector[Injector_Number].Total_PW;
    
    		Engine_Data.Bank[bank_num].PWM_Full_Time		= DEFAULT_PWM_FULL;
    
    		if (total_pulse_width > (DEFAULT_PWM_FULL + DEFAULT_PWM_REDUCED))
    		{
    			Engine_Data.Bank[bank_num].PWM_Reduced_Time		= DEFAULT_PWM_REDUCED;
    			Engine_Data.Bank[bank_num].PWM_Minimum_Time		= total_pulse_width - (DEFAULT_PWM_FULL + DEFAULT_PWM_REDUCED);
    		}
    		else if (total_pulse_width > 700)
    		{
    			Engine_Data.Bank[bank_num].PWM_Reduced_Time		= total_pulse_width - DEFAULT_PWM_FULL;
    			Engine_Data.Bank[bank_num].PWM_Minimum_Time		= 0;
    		}
    		else
    		{
    			Engine_Data.Bank[bank_num].PWM_Reduced_Time		= 0;
    			Engine_Data.Bank[bank_num].PWM_Minimum_Time		= 0;
    		}
    #endif
    
    
    		Injector_Output_Select(Engine_Data.Bank[bank_num].Current_Output,bank_num);
    	}
    }
    
    //*****************************************************************************
    //    Function: void Injector_End(Uint16 Bank_Num)
    //
    // Description:	Called ad the end of an injection event
    //				  * Make sure that the PWM is off
    //				  * Make sure that the injector channel is de-selected
    //				  * Set Bank Data to invalid
    //				  * Reset state machine to idle
    //*****************************************************************************
    void Injector_End(Uint16 Bank_Num)
    {
    	Set_PWM(Bank_Num,PWM_OFF);
    	Injector_Bank_Deselect(Bank_Num);
    	Engine_Data.Bank[Bank_Num].Control_Data_Valid = FALSE;
    
    	// check if we're switching from full drive
    	// if we are the injector never fired and we need to post an error flag
    	if (Engine_Data.Bank[Bank_Num].State == bank_full_drive)
    	{
    //		Error_Flag.Injector_Open[Engine_Data.Bank[Bank_Num].Current_Injector] = ERROR_FLAG_10S;
    		Errors.Injector_Open[Engine_Data.Bank[Bank_Num].Current_Injector].New_Flag = ERROR_FLAG_10S;
    	}
    	Engine_Data.Bank[Bank_Num].State = bank_idle;
    }
    
    //*****************************************************************************
    //    Function: void InjectorTimerGroup1(void)
    //
    // Description: This routine is called when the time for BANK1A or BANK1B expires
    //				This routine is called by SYS/BIOS
    //
    //*****************************************************************************
    void Injector_Timer_Group1(void)
    {
    	Timer1.Busy			= FALSE;
    
    	Update_Timer_Log(1, 99, 99, 99, 0);
    
    	Injector_Handle_Timer(Timer1.Bank);
    }
    
    //*****************************************************************************
    //    Function: void InjectorTimerGroup2(void)
    //
    // Description: This routine is called when the time for BANK2A or BANK2B expires
    //				This routine is called by SYS/BIOS
    //
    //*****************************************************************************
    void Injector_Timer_Group2(void)
    {
    	Timer2.Busy			= FALSE;
    	Update_Timer_Log(2, 99, 99, 99, 0);
    
    	Injector_Handle_Timer(Timer2.Bank);
    }
    
    //*****************************************************************************
    //    Function: void Show_Timers(void)
    //
    // Description:
    //
    //*****************************************************************************
    void Show_Timers(void)
    {
    	char text[100];
    
    //	Uint16 *myPtr;
    
    	SCI_puts(PORT_B,"Timer 1 ");
    
    	if (Timer1.Busy)	SCI_puts(PORT_B,"Busy\n");	else	SCI_puts(PORT_B,"Free\n");
    
    	SCI_puts(PORT_B,"Timer 2 ");
    	if (Timer2.Busy)	SCI_puts(PORT_B,"Busy\n");	else	SCI_puts(PORT_B,"Free\n");
    
    	sprintf(text,"CPU Timer 0 = %10lu,  CPU Timer 2 = %10lu\n",CpuTimer0Regs.TIM.all, CpuTimer2Regs.TIM.all);
    	SCI_puts(PORT_B,text);
    //	sprintf(text,"IER = %04x, IFR = %04x,\n",IER, IFR);
    //	SCI_puts(PORT_B,text);
    
    //	myPtr = 0x0000;
    
    }
    
    //*****************************************************************************
    //    Function: void Dump_Timers(void)
    //
    // Description:
    //
    //*****************************************************************************
    void Dump_Timers(void)
    {
    	Uint16 num;
    	char text[100];
    	Uint16 zerocount = 0;
    
    	SCI_puts(PORT_B,"-----------------------------------\n");
    	SCI_puts(PORT_B,"---<START of Timer Log Download>---\n");
    	SCI_puts(PORT_B,"\nDOWNLOAD TIME: ");
    
    	Print_Date_Time(text);
    	SCI_puts(PORT_B,text);
    
    	SCI_puts(PORT_B,"\n");
    	Show_Version();
    
    	SCI_puts(PORT_B,"\n");
    	SCI_puts(PORT_B,"-----------------------------------\n");
    
    	SCI_puts(PORT_B,",Micro Time,,,,,,Timer#,Value, Bank,State, Error\n");
    
    	//for (num = 0; num < Timer_Log_Index ; num++)
    	for (num = 0; num < TIMER_LOG_SIZE ; num++)
    	{
    		// Don't print lines if they are all zeros.
    		if(Timer_Log[num].time == 0
    		&& Timer_Log[num].Timer_Number == 0
    		&& Timer_Log[num].Timer_Value == 0
    		&& Timer_Log[num].Bank_Number == 0
    		&& Timer_Log[num].Bank_State == 0
    		&& Timer_Log[num].Timer_Error == 0
    		)
    		{
    			zerocount++;
    		}
    		else
    		{
    			sprintf(text,",%10lu,,,,,,%6u,%5u,%5u,%5u,%5u\n",
    				Timer_Log[num].time,
    				Timer_Log[num].Timer_Number,
    				Timer_Log[num].Timer_Value,
    				Timer_Log[num].Bank_Number,
    				Timer_Log[num].Bank_State,
    				Timer_Log[num].Timer_Error		);
    
    			SCI_puts(PORT_B,text);
    		}
    
    	}
    
    	sprintf(text,"Number of Non-Printed Lines: %u  \n"
    	,zerocount
    	);
    	//Explicit Null String Termination to avoid overflow
    	text[99] = '\0';
    	SCI_puts(PORT_B,text);
    
    	sprintf(text,"   Number of Non-Zero Lines: %u  \n"
    	,(TIMER_LOG_SIZE - zerocount)
    	);
    	//Explicit Null String Termination to avoid overflow
    	text[99] = '\0';
    	SCI_puts(PORT_B,text);
    	SCI_puts(PORT_B,"----<END of Timer Log Download>----\n");
    }
    
    //*****************************************************************************
    //    Function: void Update_Timer_Log(void)
    //
    // Description:
    //
    //*****************************************************************************
    void Update_Timer_Log(Uint16 Timer_Num, Uint32 Timer_Val, Uint16 Bank, Uint16 State, Uint16 Error)
    {
    	if (Timer_Log_Index < TIMER_LOG_SIZE)
    	{
    		Timer_Log[Timer_Log_Index].time 			= Timestamp_get32();
    		Timer_Log[Timer_Log_Index].Timer_Number 	= Timer_Num;
    		Timer_Log[Timer_Log_Index].Timer_Value	 	= Timer_Val;
    		Timer_Log[Timer_Log_Index].Bank_Number	 	= Bank;
    		Timer_Log[Timer_Log_Index].Bank_State	 	= State;
    		Timer_Log[Timer_Log_Index].Timer_Error	 	= Error;
    
    		Timer_Log_Index++;
    
    		if(Timer_Log_Index == TIMER_LOG_SIZE)
    		{
    			Timer_Log_Index = 0;
    		}
    	}
    }
    
    //*****************************************************************************
    //    Function: void InjectorHandleTimer(Uint16 Bank_Number)
    //
    // Description:	This routine will handle the timer t=interrupts from group1 and group2
    //
    //*****************************************************************************
    #ifdef FULL_TIMER_CONTROL
    inline void Injector_Handle_Timer(Uint16 Bank_Number)
    {
    	Uint16 PulseWidth;
    
    	if (Engine_Data.Bank[Bank_Number].Control_Data_Valid == TRUE)
    	{
    		if (Engine_Data.Bank[Bank_Number].State == bank_idle)
    		{
    #ifdef ENABLE_IRT
    //			Response_Time_Start_Sample(Engine_Data.Bank[Bank_Number].Current_Injector);
    			IRT_Start_Sample(Engine_Data.Bank[Bank_Number].Current_Injector);
    #endif
    
    //			PulseWidth = Engine_Data.Bank[Bank_Number].Current_Bank_PW;
    			PulseWidth = Engine_Data.Bank[Bank_Number].PWM_Full_Time;
    
    			if (PulseWidth > 0)
    			{
    				Set_PWM(Bank_Number,PWM_FULL_DRIVE);
    				Engine_Data.Bank[Bank_Number].State = bank_full_drive;								// Update State machine
    				Injector_Set_Timer(Bank_Number, (Uint32)PulseWidth);								// Set Timer
    			}
    			else
    			{
    				Injector_End(Bank_Number);
    			}
    		}
    		else if (Engine_Data.Bank[Bank_Number].State == bank_full_drive)
    		{
    			PulseWidth = Engine_Data.Bank[Bank_Number].PWM_Reduced_Time;
    
    			if (PulseWidth > 0)
    			{
    				Set_PWM(PWM_NUMBER_1,EEprom.Config.PWM_Reduced_Drive);
    				Engine_Data.Bank[Bank_Number].State = bank_reduced_drive;							// Update State machine
    				Injector_Set_Timer(Bank_Number, (Uint32)PulseWidth);							// Set Timer
    			}
    			else
    			{
    				Injector_End(Bank_Number);
    			}
    		}
    		else if (Engine_Data.Bank[Bank_Number].State == bank_reduced_drive)
    		{
    			PulseWidth = Engine_Data.Bank[Bank_Number].PWM_Minimum_Time;
    
    			if (PulseWidth > 0)
    			{
    				Set_PWM(PWM_NUMBER_1,EEprom.Config.PWM_Minimum_Drive);
    				Engine_Data.Bank[Bank_Number].State = bank_min_drive;								// Update State machine
    				Injector_Set_Timer(Bank_Number, (Uint32)PulseWidth);								// Set Timer
    			}
    			else
    			{
    				Injector_End(Bank_Number);
    			}
    		}
    		else																					// Not in idle so we must be active turn off injector
    		{
    			Injector_End(Bank_Number);
    
    		}
    	}
    //	else if (Dummy_Timer_Active)
    //	{
    //		// -------- do nothing here ------
    //		// ignore if we are still booting up, these are the dummy
    //		// calls to make sure that timer 2 is working
    //	}
    	else	// this should not happen, but just to be safe it does turn the PWM off
    	{
    		Injector_End(Bank_Number);
    		Dump_Event("NOT GOOD", Bank_Number);
    		// Post fault here?
    	}
    }
    #else
    inline void Injector_Handle_Timer(Uint16 Bank_Number)
    {
    	Uint16 PulseWidth;
    
    	if (Engine_Data.Bank[Bank_Number].Control_Data_Valid == TRUE)
    	{
    		if (Engine_Data.Bank[Bank_Number].State == bank_idle)
    		{
    			Set_PWM(Bank_Number,PWM_FULL_DRIVE);
    
    			PulseWidth = Engine_Data.Bank[Bank_Number].Current_Bank_PW;
    
    #ifdef ENABLE_IRT_FROM_HANDLE_INJECTOR
    			if(IRT_Enable == TRUE)
    			{
    				// only calculate IRT if the PW is long enough to do a good calculation
    				if (PulseWidth > MIN_PW_FOR_IRT)
    				{
    					IRT_Start_Sample(Engine_Data.Bank[Bank_Number].Current_Injector);
    				}
    			}
    #endif
    			Engine_Data.Bank[Bank_Number].State = bank_full_drive;								// Update State machine
    			Injector_Set_Timer(Bank_Number, (Uint32)PulseWidth);								// Set Timer
    		}
    		else																					// Not in idle so we must be active turn off injector
    		{
    			Injector_End(Bank_Number);
    		}
    	}
    //	else if (Dummy_Timer_Active)
    //	{
    //		// -------- do nothing here ------
    //		// ignore if we are still booting up, these are the dummy
    //		// calls to make sure that timer 2 is working
    //	}
    	else	// this should not happen, but just to be safe it does turn the PWM off
    	{
    		Injector_End(Bank_Number);
    		Dump_Event("NOT GOOD", Bank_Number);
    		// Post fault here?
    	}
    }
    
    #endif
    
    //*****************************************************************************
    //    Function: void InjectorSetTimer(Uint16 Bank_Num, Uint16 uSec)
    //
    // Description: Sets and starts the appropriate timer
    //
    //*****************************************************************************
    void Injector_Set_Timer(Uint16 Bank_Num, Uint32 uSec)
    {
    	Uint16 key;
    
    //	Dummy_Timer_Active = FALSE;
    
    	if ( (Bank_Num == BANK1A) || (Bank_Num == BANK2A))
    	{
    		if (Timer1.Busy == FALSE)
    		{
    			Timer1.Busy = TRUE;
    			Timer1.Bank = Bank_Num;
    
    			key = Hwi_disable();
    			Timer_setPeriodMicroSecs(timerGroup1,uSec);
    			Timer_start(timerGroup1);
    			Hwi_restore(key);
    
    			Update_Timer_Log(1, uSec, Bank_Num, Engine_Data.Bank[Bank_Num].State, 0);
    
    		}
    		else
    		{
    			#ifdef TIMER_OVERRUN_CAUSES_SAFETY_LOCKOUT
    			Set_Safety_Lockout();
    			// add 140620
    			Errors.Timer_1_Overrun.New_Flag = ERROR_FLAG_30S;
    
    			Timer1.Busy = FALSE;
    
    			#ifdef INJ_TIMER_OVERRUN_LOGGING
    				Event_Record_Que( EVENT_TIMER_OVERRUN , EVENT_ID_VER_DEFAULT , EVENT_DATA_ON, EVENT_DATA_UNUSED ,1.0,0.0);
    			#endif
    
    			Update_Timer_Log(1, uSec, Bank_Num, Engine_Data.Bank[Bank_Num].State, 1);
    			#else
    
    			Update_Timer_Log(5, uSec, Bank_Num, Engine_Data.Bank[Bank_Num].State, 2);
    			key = Hwi_disable();
    			//DelayUs(uSec);
    			uSTimer_delay(uSec);
    			Hwi_restore(key);
    
    			Update_Timer_Log(5, 69, 69, 69, 2);
    			Injector_Handle_Timer(Bank_Num);
    
    			#endif
    		}
    	}
    	else		// Bank 1B and Bank 2B
    	{
    		if (Timer2.Busy == FALSE)
    		{
    			Timer2.Busy = TRUE;
    			Timer2.Bank = Bank_Num;
    
    			key = Hwi_disable();
    			Debug_Timer2_Flag = TRUE;
    			Timer_setPeriodMicroSecs(timerGroup2,uSec);
    //			Timer_setPeriodMicroSecs(timerGroup2,100);
    			Timer_start(timerGroup2);
    			Hwi_restore(key);
    
    			Update_Timer_Log(2, uSec, Bank_Num, Engine_Data.Bank[Bank_Num].State, 0);
    		}
    		else
    		{
    			#ifdef TIMER_OVERRUN_CAUSES_SAFETY_LOCKOUT
    			Set_Safety_Lockout();
    			// add 140620
    			Errors.Timer_2_Overrun.New_Flag = ERROR_FLAG_30S;
    
    			Timer2.Busy = FALSE;
    
    			#ifdef INJ_TIMER_OVERRUN_LOGGING
    				Event_Record_Que( EVENT_TIMER_OVERRUN , EVENT_ID_VER_DEFAULT , EVENT_DATA_ON, EVENT_DATA_UNUSED ,2.0,0.0);
    			#endif
    
    			Update_Timer_Log(2, uSec, Bank_Num, Engine_Data.Bank[Bank_Num].State, 1);
    			#else
    
    			Update_Timer_Log(6, uSec, Bank_Num, Engine_Data.Bank[Bank_Num].State, 2);
    			key = Hwi_disable();
    			//DelayUs(uSec);
    			uSTimer_delay(uSec);
    			Hwi_restore(key);
    
    			Update_Timer_Log(6, 69, 69, 69, 2);
    			Injector_Handle_Timer(Bank_Num);
    
    			#endif
    		}
    	}
    }
    
    //*****************************************************************************
    //    Function: void Injector_Capture_Bank_1A(void)
    //
    // Description: This is the capture port for the over current on
    // 				Bank 1A.  It uses eCAP3 (GPIO09)
    //
    //*****************************************************************************
    void Injector_Capture_Bank_1A(void)
    {
    	ECap3Regs.ECCLR.bit.CEVT1 = 1;
    	ECap3Regs.ECCLR.bit.INT = 1;
    
    	// we only want to use Capture event 1 so reset the counter
    	ECap3Regs.ECCTL2.bit.REARM = EC_ARM;        		// arm one-shot (Works in both modes)
    
    
    #ifndef FULL_TIMER_CONTROL
    	if (Engine_Data.Bank[BANK1A].Control_Data_Valid == TRUE)
    	{
    		switch(Engine_Data.Bank[BANK1A].State)
    		{
    			case bank_full_drive:
    				Engine_Data.Bank[BANK1A].State = bank_reduced_drive;
    				Set_PWM(PWM_NUMBER_1,EEprom.Config.PWM_Reduced_Drive);
    			break;
    
    			case bank_reduced_drive:
    				Engine_Data.Bank[BANK1A].State = bank_min_drive;
    				Set_PWM(PWM_NUMBER_1,EEprom.Config.PWM_Minimum_Drive);
    				Min_Drive_Count[Engine_Data.Bank[BANK1A].Current_Injector] = 0;
    			break;
    
    			case bank_min_drive:
    				// Because the injector can "bounce" we might run into this state a couple times
    				//  if we get a lot of triggers in this state we have a problem
    				if (Min_Drive_Count[Engine_Data.Bank[BANK1A].Current_Injector] < 3)
    				{
    					Min_Drive_Count[Engine_Data.Bank[BANK1A].Current_Injector]++;
    				}
    				else
    				{
    //					Error_Flag.Injector_Sequence[Engine_Data.Bank[BANK1A].Current_Injector] = ERROR_FLAG_10S;
    					Errors.Injector_Sequence[Engine_Data.Bank[BANK1A].Current_Injector].New_Flag = ERROR_FLAG_10S;
    				}
    			break;
    
    			default:
    //				Injector_End(BANK1A);
    			break;
    		}
    	}
    	else
    	{
    		Injector_End(BANK1A);
    		// Post fault here?
    	}
    #endif
    
    }
    
    //*****************************************************************************
    //    Function: void Injector_Capture_Bank_1B(void)
    //
    // Description: This is the capture port for the over current on
    // 				Bank 1B.  It uses eCAP4 (GPIO11)
    //
    //*****************************************************************************
    void Injector_Capture_Bank_1B(void)
    {
    	ECap4Regs.ECCLR.bit.CEVT1 = 1;
    	ECap4Regs.ECCLR.bit.INT = 1;
    
    	// we only want to use Capture event 1 so reset the counter
    	ECap4Regs.ECCTL2.bit.REARM = EC_ARM;        		// arm one-shot (Works in both modes)
    
    	#ifndef FULL_TIMER_CONTROL
    	if (Engine_Data.Bank[BANK1B].Control_Data_Valid == TRUE)
    	{
    		switch(Engine_Data.Bank[BANK1B].State)
    		{
    			case bank_full_drive:
    				Engine_Data.Bank[BANK1B].State = bank_reduced_drive;
    				Set_PWM(PWM_NUMBER_2,EEprom.Config.PWM_Reduced_Drive);
    			break;
    
    			case bank_reduced_drive:
    				Engine_Data.Bank[BANK1B].State = bank_min_drive;
    				Set_PWM(PWM_NUMBER_2,EEprom.Config.PWM_Minimum_Drive);
    				Min_Drive_Count[Engine_Data.Bank[BANK1B].Current_Injector] = 0;
    			break;
    
    			case bank_min_drive:
    				// Because the injector can "bounce" we might run into this state a couple times
    				//  if we get a lot of triggers in this state we have a problem
    				if (Min_Drive_Count[Engine_Data.Bank[BANK1B].Current_Injector] < 3)
    				{
    					Min_Drive_Count[Engine_Data.Bank[BANK1B].Current_Injector]++;
    				}
    				else
    				{
    //					Error_Flag.Injector_Sequence[Engine_Data.Bank[BANK1B].Current_Injector] = ERROR_FLAG_10S;
    					Errors.Injector_Sequence[Engine_Data.Bank[BANK1B].Current_Injector].New_Flag = ERROR_FLAG_10S;
    				}
    			break;
    
    			default:
    //				Injector_End(BANK1B);
    			break;
    		}
    	}
    	else
    	{
    		Injector_End(BANK1B);
    		// Post fault here?
    	}
    #endif
    
    }
    
    //*****************************************************************************
    //    Function: void Injector_Capture_Bank_2A(void)
    //
    // Description: This is the capture port for the over current on
    // 				Bank 2A.  It uses eCAP5 (GPIO03)
    //
    //*****************************************************************************
    void Injector_Capture_Bank_2A(void)
    {
    	ECap5Regs.ECCLR.bit.CEVT1 = 1;
    	ECap5Regs.ECCLR.bit.INT = 1;
    
    	// we only want to use Capture event 1 so reset the counter
    	ECap5Regs.ECCTL2.bit.REARM = EC_ARM;        		// arm one-shot (Works in both modes)
    
    #ifndef FULL_TIMER_CONTROL
    	if (Engine_Data.Bank[BANK2A].Control_Data_Valid == TRUE)
    	{
    		switch(Engine_Data.Bank[BANK2A].State)
    		{
    			case bank_full_drive:
    				Engine_Data.Bank[BANK2A].State = bank_reduced_drive;
    				Set_PWM(PWM_NUMBER_3,EEprom.Config.PWM_Reduced_Drive);
    			break;
    
    			case bank_reduced_drive:
    				Engine_Data.Bank[BANK2A].State = bank_min_drive;
    				Set_PWM(PWM_NUMBER_3,EEprom.Config.PWM_Minimum_Drive);
    				Min_Drive_Count[Engine_Data.Bank[BANK2A].Current_Injector] = 0;
    			break;
    
    			case bank_min_drive:
    				// Because the injector can "bounce" we might run into this state a couple times
    				//  if we get a lot of triggers in this state we have a problem
    				if (Min_Drive_Count[Engine_Data.Bank[BANK2A].Current_Injector] < 3)
    				{
    					Min_Drive_Count[Engine_Data.Bank[BANK2A].Current_Injector]++;
    				}
    				else
    				{
    //					Error_Flag.Injector_Sequence[Engine_Data.Bank[BANK2A].Current_Injector] = ERROR_FLAG_10S;
    					Errors.Injector_Sequence[Engine_Data.Bank[BANK2A].Current_Injector].New_Flag = ERROR_FLAG_10S;
    				}
    			break;
    
    			default:
    //				Injector_End(BANK2A);
    			break;
    		}
    	}
    	else
    	{
    		Injector_End(BANK2A);
    		// Post fault here?
    	}
    #endif
    }
    
    //*****************************************************************************
    //    Function: void Injector_Capture_Bank_2B(void)
    //
    // Description: This is the capture port for the over current on
    // 				Bank 2B.  It uses eCAP6 (GPIO01)
    //
    //*****************************************************************************
    void Injector_Capture_Bank_2B(void)
    {
    	ECap6Regs.ECCLR.bit.CEVT1 = 1;
    	ECap6Regs.ECCLR.bit.INT = 1;
    
    	// we only want to use Capture event 1 so reset the counter
    	ECap6Regs.ECCTL2.bit.REARM = EC_ARM;        		// arm one-shot (Works in both modes)
    
    #ifndef FULL_TIMER_CONTROL
    	if (Engine_Data.Bank[BANK2B].Control_Data_Valid == TRUE)
    	{
    		switch(Engine_Data.Bank[BANK2B].State)
    		{
    			case bank_full_drive:
    				Engine_Data.Bank[BANK2B].State = bank_reduced_drive;
    				Set_PWM(PWM_NUMBER_4,EEprom.Config.PWM_Reduced_Drive);
    			break;
    
    			case bank_reduced_drive:
    				Engine_Data.Bank[BANK2B].State = bank_min_drive;
    				Set_PWM(PWM_NUMBER_4,EEprom.Config.PWM_Minimum_Drive);
    				Min_Drive_Count[Engine_Data.Bank[BANK2B].Current_Injector] = 0;
    
    			break;
    
    			case bank_min_drive:
    				// Because the injector can "bounce" we might run into this state a couple times
    				//  if we get a lot of triggers in this state we have a problem
    				if (Min_Drive_Count[Engine_Data.Bank[BANK2B].Current_Injector] < 3)
    				{
    					Min_Drive_Count[Engine_Data.Bank[BANK2B].Current_Injector]++;
    				}
    				else
    				{
    //					Error_Flag.Injector_Sequence[Engine_Data.Bank[BANK2B].Current_Injector] = ERROR_FLAG_10S;
    					Errors.Injector_Sequence[Engine_Data.Bank[BANK2B].Current_Injector].New_Flag = ERROR_FLAG_10S;
    				}
    			break;
    
    			default:
    //				Injector_End(BANK2B);
    			break;
    		}
    	}
    	else
    	{
    		Injector_End(BANK2B);
    		// Post fault here?
    	}
    #endif
    
    }
    
    //*****************************************************************************
    //    Function: void Injector_Control(void)
    //
    // Description: This routine will get called from the low priority clock talk
    //				10 times per second
    //
    //*****************************************************************************
    void Injector_Control(void)
    {
    	float Engine_RPM;
    //	Uint16 engine_ready;
    
    	// don't run till we have completed the init
    	if (System_Init_Done == FALSE)
    	{
    		return;
    	}
    
    	Engine_RPM = Get_RPM();
    	// Changed 140611 during field testing, but probably don't want to use a filter for the over-speed detection
    	// Assuming the noise isn't a problem with steady injector response times.
    	//#ifdef USE_RPM_FIR05_FILTER
    	//Engine_RPM = PID.Current_RPM;
    	//#else
    	//Engine_RPM = Get_RPM();
    	//#endif
    
    #if 0
    
    	if ( (DIG_ENGINE_ENABLE == TRUE) && (J1939_Data.Throttle_Notch != TH_STOP)	&& (Engine_Data.Safety_Lockout == FALSE) && (J1939_Data.Target_RPM != 0) )
    	{
    		Engine_Data.Engine_Ready = TRUE;
    ///		Test_Signals.debug[0]++;
    //		engine_ready = TRUE;
    	}
    	else if ( (DIG_ENGINE_ENABLE == TRUE)  &&  (Engine_Data.Enable_Override == TRUE)  &&  (Engine_Data.Safety_Lockout == FALSE) )
    	{
    		Engine_Data.Engine_Ready = TRUE;
    //		Test_Signals.debug[1]++;
    //		engine_ready = TRUE;
    	}
    	else
    	{
    		Engine_Data.Engine_Ready = FALSE;
    //		Test_Signals.debug[2]++;
    //		engine_ready = FALSE;
    	}
    #else
    	Engine_Data.Engine_Ready = Get_Engine_Ready();
    #endif
    
    
    	// Process Digital Inputs
    	if (Engine_Data.Engine_Ready)
    	{
    		if (Engine_Data.Enable_Engine == FALSE)
    		{
    			Engine_Data.Enable_Engine = TRUE;
    
    			#ifdef INJ_CTRL_EVENT_LOGGING
    		    	Event_Record_Que( EVENT_ID_ENGINE_ENABLE , EVENT_ID_VER_DEFAULT , EVENT_DATA_ON, EVENT_DATA_UNUSED ,0.0,0.0);
    			#endif
    
    		}
    	}
    	else
    	{
    		if (Engine_Data.Enable_Engine == TRUE)
    		{
    			Engine_Data.Enable_Engine = FALSE;
    			#ifdef INJ_CTRL_EVENT_LOGGING
    				Event_Record_Que( EVENT_ID_ENGINE_ENABLE , EVENT_ID_VER_DEFAULT , EVENT_DATA_OFF, EVENT_DATA_UNUSED ,0.0,0.0);
    			#endif
    		}
    	}
    
    	// Process LED Outputs
    
    	if (Engine_Data.Enable_Engine == TRUE)
    	{
    		Indicators_Set_Mode(LED_ENGINE_ENABLE, IND_MODE_OFF);
    	}
    	else
    	{
    		Indicators_Set_Mode(LED_ENGINE_ENABLE, IND_MODE_ON);
    	}
    #ifdef EDH
    	if (Engine_RPM < 0.5)
    	{
    		Indicators_Set_Mode(LED_ENGINE_STATUS, IND_MODE_OFF);
    	}
    	else if (Engine_RPM < 50)
    	{
    		Indicators_Set_Mode(LED_ENGINE_STATUS, IND_MODE_SLOW_FLASH);
    	}
    	else if (Engine_RPM < 300)
    	{
    		Indicators_Set_Mode(LED_ENGINE_STATUS, IND_MODE_FAST_FLASH);
    	}
    	else
    	{
    		Indicators_Set_Mode(LED_ENGINE_STATUS, IND_MODE_ON);
    	}
    #endif
    
    	if (Engine_RPM > RPM_SAFETY_LIMIT)
    	{
    		Engine_Protect_Handle_OverSpeed(Engine_RPM);
    	}
    
    //	Set_Target_RPM();
    }
    
    //*****************************************************************************
    //    Function: Injector_Output_Select(Uint16 Injector_Num, Uint16 bank_num)
    //
    // Description: This routine will select and active one injector
    //              from one of the four banks.  Setting Injector_Num
    //    			to 0 will de-activate all the injectors in that bank
    //
    //				All the GPIO pins for this function are on GPA (GPIO 0-31)
    //
    //              Inputs: 	Injector A = GPIO16
    //							Injector B = GPIO17
    //							Injector C = GPIO20
    //							Bank CTRL1 = GPIO24
    //							Bank CTRL2 = GPIO25
    //							Bank CTRL3 = GPIO26
    //							Bank CTRL4 = GPIO27
    //
    //				Port A
    //				3322 2222 2222 1111 1111 1100 0000 0000
    //				1098 7654 3210 9876 5432 1098 7654 3210
    //				XXXX 4321 XXXC XXBA XXXX XXXX XXXX XXXX
    //				1111 0000 1110 1100 1111 1111 1111 1111 Mask
    //                F    0    E    C    F    F    F    F
    //*****************************************************************************
    void Injector_Output_Select(Uint16 Injector_Num, Uint16 bank_num)
    {
    	Uint32 PortA_Value;
    
    	Injector_Num = (Injector_Num % 5) + 1;					// Find the relative injector number
    
    	PortA_Value = GpioDataRegs.GPADAT.all;					// Read the value of GPIO Port A
    
    	PortA_Value &= 0xF0ECFFFF;								// Mask off the value for all the bits we will be setting
    
    	PortA_Value |= (Uint32)( (Injector_Num & 0x0003)) << 16;// Set the first two digit of the Injector Number
    	PortA_Value |= (Uint32)( (Injector_Num & 0x0004)) << 18;// Set the Last (third) digit of the Injector Number
    
    	GpioDataRegs.GPADAT.all = PortA_Value;					// Write back the new value to port A
    
    	switch (bank_num)
    	{
    		case BANK1A:
    			PortA_Value |= 0x01000000;
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn on Bank CTRL1 (clock)
    
    			PortA_Value &= 0xFEFFFFFF;								// Also provides a short delay (~0.05uS)
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn off Bank CTRL1 (clock)
    
    		break;
    
    		case BANK1B:
    			PortA_Value |= 0x02000000;
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn on Bank CTRL2 (clock)
    
    			PortA_Value &= 0xFDFFFFFF;								// Also provides a short delay (~0.05uS)
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn off Bank CTRL2 (clock)
    		break;
    
    		case BANK2A:
    			PortA_Value |= 0x04000000;
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn on Bank CTRL3 (clock)
    
    			PortA_Value &= 0xFBFFFFFF;								// Also provides a short delay (~0.05uS)
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn off Bank CTRL3 (clock)
    		break;
    
    		case BANK2B:
    			PortA_Value |= 0x08000000;
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn on Bank CTRL4 (clock)
    
    			PortA_Value &= 0xF7FFFFFF;								// Also provides a short delay (~0.05uS)
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn off Bank CTRL4 (clock)
    		break;
    
    		default:
    		break;
    	}
    }
    
    //*****************************************************************************
    //    Function: void Injector_Bank_Deselect(Uint16 Bank_Num)
    //
    // Description: This routine will de-select all outputs on a bank
    //*****************************************************************************
    void Injector_Bank_Deselect(Uint16 Bank_Num)
    {
    	Uint32 PortA_Value;
    
    	PortA_Value = GpioDataRegs.GPADAT.all;					// Read the value of GPIO Port A
    
    	PortA_Value &= 0xF0ECFFFF;								// Mask off the value for all the bits we will be setting
    
    	GpioDataRegs.GPADAT.all = PortA_Value;					// Write back the new value to port A
    
    	switch (Bank_Num)
    	{
    		case BANK1A:
    			PortA_Value |= 0x01000000;
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn on Bank CTRL1 (clock)
    
    			PortA_Value &= 0xFEFFFFFF;								// Also provides a short delay (~0.05uS)
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn off Bank CTRL1 (clock)
    		break;
    
    		case BANK1B:
    			PortA_Value |= 0x02000000;
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn on Bank CTRL2 (clock)
    
    			PortA_Value &= 0xFDFFFFFF;								// Also provides a short delay (~0.05uS)
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn off Bank CTRL2 (clock)
    		break;
    
    		case BANK2A:
    			PortA_Value |= 0x04000000;
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn on Bank CTRL3 (clock)
    
    			PortA_Value &= 0xFBFFFFFF;								// Also provides a short delay (~0.05uS)
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn off Bank CTRL3 (clock)
    		break;
    
    		case BANK2B:
    			PortA_Value |= 0x08000000;
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn on Bank CTRL4 (clock)
    
    			PortA_Value &= 0xF7FFFFFF;								// Also provides a short delay (~0.05uS)
    			GpioDataRegs.GPADAT.all = PortA_Value;					// Turn off Bank CTRL4 (clock)
    		break;
    
    		default:
    		break;
    	}
    }
    
    /**********************************************************************
    ** Function:
    **
    ** Description:
    **
    **********************************************************************/
    float Injector_Get_BOI_Deg(void)
    {
    	return Engine_Data.Current_BOI_Deg;
    }
    
    /**********************************************************************
    ** Function:extern Uint16 Get_Timer_Busy_Status	(Uint16 Timer_Number)
    **
    ** Description:  return 1 if Timer_Number is busy, otherwise 0.
    **
    **********************************************************************/
    Uint16 Get_Timer_Busy_Status (Uint16 Timer_Number)
    {
    	if(Timer_Number == TIMER_ONE)
    	{
    		if(Timer1.Busy == TRUE)
    		{
    			return 1;
    		}
    		else
    		{
    			return 0;
    		}
    	}
    	else if(Timer_Number == TIMER_TWO)
    	{
    		if(Timer2.Busy == TRUE)
    		{
    			return 1;
    		}
    		else
    		{
    			return 0;
    		}
    	}
    
    	// Also return 0 if Timer_Number is incorrect...
    	return 0;
    }
    
    /**********************************************************************
    ** Function:extern void Handle_Timer (Uint16 Bank_Number)
    **
    ** Description:  Handle timer if Timer 1 or 2 is busy
    **
    **********************************************************************/
    void Handle_Timer (Uint16 Bank_Number)
    {
    	Injector_Handle_Timer(Bank_Number);
    }
    
    
    /**********************************************************************
    ** Function:extern void Set_Timer_Busy_To_False	(Uint16 Timer_Number)
    **
    ** Description:  set corresponding timer to false.
    **
    **********************************************************************/
    void Set_Timer_Busy_To_False (Uint16 Timer_Number)
    {
    	if(Timer_Number == TIMER_ONE)
    	{
    		Timer1.Busy = FALSE;
    	}
    	else if(Timer_Number == TIMER_TWO)
    	{
    		Timer2.Busy = FALSE;
    	}
    }
    
    /**********************************************************************
    ** Function:extern Uint16 Get_Engine_Ready(void)
    **
    ** Description:  determine if the engine is ready to start
    **
    **********************************************************************/
    Uint16 Get_Engine_Ready(void)
    {
    	Uint16 myEnable;
    	switch (PID.RPM_Mode)
    	{
    		case RPM_Mode_J1939:
    			if ( (DIG_ENGINE_ENABLE == TRUE) && (J1939_Data.Throttle_Notch != TH_STOP)	&& (Engine_Data.Safety_Lockout == FALSE) && (J1939_Data.Target_RPM != 0) )
    			{
    				myEnable = TRUE;
    			}
    			else
    			{
    				myEnable =  FALSE;
    			}
    		break;
    
    		case RPM_Mode_Digital:
    			if ( (DIG_ENGINE_ENABLE == TRUE) && (Dig_Signals.Throttle != TH_STOP)	&& (Engine_Data.Safety_Lockout == FALSE) && (J1939_Data.Target_RPM != 0) )
    			{
    				myEnable =  TRUE;
    			}
    			else
    			{
    				myEnable =  FALSE;
    			}
    		break;
    
    		case RPM_Mode_Manual:
    			if ( (DIG_ENGINE_ENABLE == TRUE)  &&  (Engine_Data.Enable_Override == TRUE)  &&  (Engine_Data.Safety_Lockout == FALSE) )
    			{
    				myEnable =  TRUE;
    			}
    			else
    			{
    				myEnable =  FALSE;
    			}
    		break;
    
    		case RPM_Mode_Manual_Notch:
    			if ( (Test_Signals.Manual_Notch != TH_STOP)  &&  (Engine_Data.Enable_Override == TRUE)  &&  (Engine_Data.Safety_Lockout == FALSE) )
    			{
    				myEnable =  TRUE;
    			}
    			else
    			{
    				myEnable =  FALSE;
    			}
    		break;
    
    		default:
    			myEnable =  FALSE;
    		break;
    	}
    	return myEnable;
    }
    
    //***************************** END OF FILE ***********************************
    

  • Ed,

    Thank you for the code, a lot of stuff going on there.

    I see you're using timers 0 & 2 (although your selection of timer 0 seems to use 'null' in the timerId field, you might want to make that an explicit 0).  Is timer 0 the one that stops working (used with Injector_Timer_Group1())?

    I ask because timer 0 is handled somewhat differently than timers 1 & 2.  Timer 0 uses interrupt #38 which makes it a PIE interrupt.  Timers 1 & 2 use interrupt #13 & #14 (respectively) which are "normal" interrupts.  In order to deduce the problem further, could you try using timer 1 instead of timer 0?  If it works with timer 1 then we can examine handling of PIE interrupts for a potential problem.

    Thanks,

    - Rob

  • Rob,

    Its not timer 0 that is causing the issue, its timer 2.  I confirmed this by changing my timerGroup1 to timer 1 and confirmed that I would then get the fault happening with this timer.  Note that I was already using Timer 1 for the Timestamp Provider so I had to change this to timer 0 for the the test.  I also confirm that under Sys-bios 6.35.1.29 everything seems to work fine no matter how the timers are configured.  Some more clues that I hope you can shed some light on.

    You mention that Ifor timerGroup1 I have the timer ID configured as null but I definitely have selected 0 on the graphical config tool.
    Also the source looks correct to me as well:
    Program.global.timerGroup1 = Timer.create(0, "&Injector_Timer_Group1", timer0Params);

    One other question for the Timestamp Provide I see that I can select timer 0, 1 or 2 which make sense to me since the TMS32f28335 has the 3 main 32 bit timers.  But on the times module you can select timers 0 to 8.  What timers are used for the 3 to 8?

    Thanks for the help

    Ed

  • Ed,

    Ed Hildebrandt14 said:
    Its not timer 0 that is causing the issue, its timer 2.  I confirmed this by changing my timerGroup1 to timer 1 and confirmed that I would then get the fault happening with this timer.  Note that I was already using Timer 1 for the Timestamp Provider so I had to change this to timer 0 for the the test.

    Thanks.

    Not that this is a solution, but since you're having trouble with timer 2 periodically ticking, perhaps you can assign timer 2 to the TimestampProvider and use timers 0 & 1 for the periodic timers.

    I don't have much else to suggest at this point but am still thinking/looking.

    Ed Hildebrandt14 said:
    Also the source looks correct to me as well:
    Program.global.timerGroup1 = Timer.create(0, "&Injector_Timer_Group1", timer0Params);

    Odd, the app.cfg you attached (actually named 4503.app.cfg) contains this:
        Program.global.timerGroup1 = Timer.create(null, "&Injector_Timer_Group1", timer0Params);

    Regardless, you are doing it correctly.

    Ed Hildebrandt14 said:
    One other question for the Timestamp Provide I see that I can select timer 0, 1 or 2 which make sense to me since the TMS32f28335 has the 3 main 32 bit timers.  But on the times module you can select timers 0 to 8.  What timers are used for the 3 to 8?

    What leads you to believe that you can select timers 0 to 8?  The ti.sysbios.family.c28.Timer module defines NUM_TIMER_DEVICES = 3, and the available timer ID bitmask is 0x7 (one bit per timer).

    Regards,

    - Rob

  • Rob,

    I tried using timer 0 and timer 1 but timer 1 has the same problem as timer 2.  For now I'm just sticking with the older version of Sys-Bios, just would really like to get to the bottom of the problem as I would like to us an up to date version at some point.

    The reason I thought I could assign timers 3-8 was that the GUI interface allow selections from 0 to 8.  However I found that if you select anything greater than 2 you will get a compile error.

    For now I will keep using the older version of sys-bios, but it may be interesting to try other versions as well when I get some time.  I may also try and run the system with the debugger to see what is actually happening with the timer but that will also have to wait.

    Thanks for the help.

    Ed

  • Ed,

    We (TI) would certainly like to get to the bottom of this too.

    I should be able to recreate this situation with a simple test (2 one-shot timers using Timer_setPeriodMicroSecs() and TimestampProvider using the remaining timer), and see if I get the same failure.  However, your system is quite complex and the failure is likely due to an undesirable interaction between components, but we shall see.

    Please report any new developments/discoveries back to this thread.

    Regards,

    - Rob

  • Rob is still looking into this.
  • Ed,

    In Injector_Set_Timer() the Boolean return values of Timer_setPeriodMicroSecs() are not being checked, for example:

        key = Hwi_disable();
        Timer_setPeriodMicroSecs(timerGroup1,uSec);
        Timer_start(timerGroup1);
        Hwi_restore(key);

    Should the “uSec” values be the same when running both older and newer builds?  Or is it possible, with different timing in the later build, that the values specified might be out of range at some point, causing the call to Timer_setPeriodMicroSecs() to fail?

    If you always check the return of Timer_setPeriodMicroSecs(), is it always returning true, even when the timer stops ticking?

    Thanks,
    Scott

  • You are correct that I have not been checking the return value and this is something I should add. 

    Under what circumstance would this return a zero?

    To answer your second question, the firmware is exactly the same when I made the change to the Sys/Bios version.

    The only change I make to get the timer to fail is what version of Sys/Bios I use. 
    If I use version 6.35.1.29 everything is fine.
    If I use version 6.35.4.50 the timer issues happens

    Ed

  • Timer_setPeriodMicroSecs() will return false if the timer peripheral does not support the requested period.  For C28 timers this would happen if the requested period would overflow the period register (PRD).

    I’d asked the second question because switching SYS/BIOS versions will mean some difference in timing for your app.  Some APIs might be slower, some might be faster.  I don’t expect much difference in timing from simply swapping SYS/BIOS, but was wondering if the firmware will always be requesting the same periods, or if changes to overall application timing might cause it to request different period values, and if one of these might be too high. I'm only guessing that this might be the issue.  Seeing that the return value is not checked got me wondering.

    I compared the sources from the two kernel versions, and the latest kernel sources, and don’t see anything obvious that might explain this.

    If this isn’t the problem, can you please post the register values for the timer once it stalls?  A screen snip of the CpuTimerXRegs view from CCS would be good.

    And also, can you tell me roughly what periods are being requested at what rates?  I can try to recreate these conditions in a simpler app.

    Thanks,
    Scott

  • Scott,

    The requested time period will always be different.  The period can vary a fair bit with the shortest periods being around 1mS and the longest about 50-60mS.  With a 32bit timer running at 150MHz I should be able to time event up to 28.6 seconds should there should be no chance that I'm requesting a time that is too long.  I ran a few checks with the debugger and found that when the fault occurs I'm requesting about 9mS (~9000uS) which should be setting the timer to a value of about 1350000.

    I also got a screen shot of the timer registers as you requested.

    Ed

  • Ed,

    Thanks for sending this.  In this snapshot, is this the configuration where Timer 2 is stalled, Timer 0 is ticking as expected, and Timer 1 is timestamp?

    Can you show IER and IFR registers when stalled?

    Thanks,
    Scott

  • Scott,

    First of all I want to say thanks for the quick response.  Just an FYI I will be out of the office for the next three weeks so this will be my last post before early June.  

    The configuration that you listed for the timers is correct.

    I captured the interrupt registers as requested.

    Thanks again for all the help.

    Ed

  • OK, thanks Ed.

    It looks like Timer 2 has it’s interrupt flag bit set in the IFR, but the corresponding bit is not set to enable the interrupt in the IER.

    The C28 timer changes between the two releases you’ve tried were mostly related to interrupt bit masking.  I will research this to see if I can figure out what is going on.

    One quick question before you leave… is your firmware doing anything specific with masking of the timer interrupts?  Or are you leaving this to be handled entirely by SYS/BIOS?

    Thanks,
    Scott

  • Scott,
    I've let Sys/Bios handle all the setup of the timers and the masking of the interrupts.
    Thanks
    Ed
  • Ed,

    I’ve run a few different overnight stress tests over the last week and am not able to see any failures.  I started with our latest kernel release on a newer device, and then rewound to the 6.35.4.50 release you are having issue with, running on the eZdspTMS320F28335.  The tests have the timers sweeping across ranges of short intervals, with re-start both inside the timer ISR and/or from a task context, and with some randomization mixed in.  No timeouts are missed, and the timer IER bits are not left in a cleared state as you describe.

    So… it is still a big mystery why you are seeing an issue.

    I have several questions:

    1) There was a support forum thread and an email thread between you and a coworker 6 years ago, for what sounds like the same problem.  Those threads dried up and we don't know if there was any resolution.  I reviewed the two files you attached to this present forum thread, and the project you’d sent at that time, and see many changes.  I tried thinking through some scenarios where the shared code for servicing the timers might be doing something incorrect, but don’t see anything that should not be supported by the Timer code.  My questions: Were you able to solve the problem you’d seen 6 years ago, and if so, what changes did you make?

    2) In the function Injector_Set_Timer() there are calls to uSTimer_delay(uSec).  It seems these are used when a "timer overrun" is detected, versus using the timer itself.  Maybe to mimic the delay as if the timer was used(?)  Is it possible that this is happening just prior to the problem?  It seems that if “FULL_TIMER_CONTROL” is defined there may be a chance that the timer does not get re-started, so the IER bit being cleared may make sense.  Also, how long are these delays?  And will delays of this long be compatible with the rest of the app?  I don’t fully understand the app but it seems to me that this may be a potential problem area.

    3) In your screenshot above (and from what you’d reported 6 years ago too) comparing the IER and IFR bit values, both the INT4 and INT14 bits are cleared in the IER but the corresponding IFR bits are set.  Does the INT4 bit being cleared in IER make sense?  I’m wondering if maybe some part of the application is writing to IER and clearing bits inappropriately.

    4) Is it possible for you to create a simpler test case that we can run to debug?

    Thanks,
    Scott