/*************************************************************************
**************************************************************************
**   MegaShift - V1.100
**
**   (C) 2007, 2008, 2009 - B. A. Bowling and A. C. Grippo and L. Gardiner
**
**   This header must appear on all derivatives of this code. Any code 
**   modifications made for any reason become the exclusive property of 
**   Bowling and Grippo. 
**   This code is intended for off-road, experimental use under controlled 
**   conditions only.
**   All code revisions must be shared with other users free of charge 
**   and without additional restrictions on the forums at 
**   http://www.msgpio.com/forums/index.php 
**   This code, nor any derivative of it, may not be used in any form, 
**   in whole or in part, on non-Bowling and Grippo hardware under any cicrumstances.
**   No commercial use of this code, in whole or in part, is permitted without 
**   express written permmission from Bowling and Grippo
**
***************************************************************************
**************************************************************************/

/**
ABOUT THIS CODE

This code is intended primarily as a tutorial in C for embedded use on HCS12 
processors.  

This code contains many inefficiencies, and has many areas that are implemented 
in ways that experienced programmers would consider naive. Most abstractions have been 
avoided to make the code as easy to follow as possible for the beginning embedded 
programmer.
 
The code is written in a manner that a novice programmer might do, so that it can be 
understood more easily by those just starting out with embedded code on the HCS12.  

**/

/**************************************************************************
***************************************************************************
**
**   The MegaShift controller is a dedicated controller for the 
**   electronically shifted automatic transmissions. 
**   It uses the GPIO hardware with the MC9S12C64 processor from Bowling and Grippo. 
**   Documentation:    http://www.msgpio.com/manuals/mshift/index.html
**   Dscussion Forums: http://www.msgpio.com/forums/index.php
**   
**   The MegaShift code has the following features: 
**   
**   - Fully configurable automatic or manual mode, 
**   - Provisions for 'paddle' shifters on steering wheel,  
**   - LED display of current gear,
**   - CAN communication with MS-II for integrated driveline management, 
**   - and many others. 
**   
**   In Auto mode: 
**   
**   - Gear is selected according to a 4 forward gear by 12 mph x 12 kpa 
**     shift table which selects the gear based on the current vehicle speed and load, 
**   - TCC engagement if:
**         - load is below a threshold,
**         - gear is at or above a user specification, 
**         - speed is above user threshold, and 
**         - trans temp is above a user threshold 
**   - TCC engagement in 2nd and 3rd gear if trans temp is high,
**   - In shift_mode==2, the trans can go directly to the target gear from the table, 
**     otherwise it shifts sequentially until it reaches the target gear.  
**   
**   In Manual mode: 
**   
**   - Full manual control, unless (optionally) rpm exceeds user-specified redline or drops below 
**     user-specified minimum, 
**   - TCC engagement only if transmission temperature is high (>180°F) and load is low. 
**   - The torque converter clutch always unlocks during downshifts. It unlocks under 
**     certain conditions during upshifts, depending on the vehicle speed and engine load. 
**     It is safe to upshift under light load while the TCC is locked. Most OEM systems 
**     keep the torque converter clutch applied during light throttle upshifts. 
**   
**   Note that MegaShift only controls the forward gears. You still require a 
**   mechanical shifter to place the transmission into Park, Reverse, or Neutral. 
**   
**   The transmission temperature is sensed by a built in temperature sensor that 
**   has the standard GM sensor response curve. 
**   
**   Two PWM channels are used for the pressure control solenoid and 
**   the 3/2 shift solenoid. Bit banged PWM can be user-specified for 
**   the output1 and output2 (solA and solB) outputs. All other controls 
**   are simple on/off switches (except PWM TCC control when selected). 
**   
**************************************************************************
**************************************************************************/

/* --- General Code Layout -----------------------------------------------

This is a list of general blocks in the code.

Copyright and other notices
General description of the function of the code
Compiler/Linker/Locator notes
The 9S12C64 Microcontroller Memory Notes
MegaShift GPIO Port Assignments Notes
I/O Port Quick_Start
CAN Comms Notes
Port DDR (data direction register) Configuration Notes
Miscellaneous Notes
Transmission Error Code Notes
Defines, Includes, ISR Definitions and Interrupt Table
Function Prototypes
Table Includes and Table Size Defines (including default values)
Revision Number and Signature
Global Variables
Function MAIN
 - define variables
 - Initalize PLL (Phase Locked Loop)
 - Copy Flash to RAM
 - Set Up Input/Ouput ports
 - Set Up Timer Ports
 - Set Up CRG RTI Interrupt
 - Set up SCI Serial Communications Interface
 - Set Up PWM on Timer Pins
 - Set Up ADC Channels 
 - Set variable block addresses to be used for CAN communications
 - Initialize Variables
 - MAIN LOOP
   -- Update RPM, MAP & Battery Voltage with CAN
   -- Determine Current Gear
   -- Update Rolling Load Average
   -- Update Rolling Shift Switch Inputs
   -- Set Pressure Control PWM Duty Cycle
   -- Check if Need to Switch to Auto Shift Mode
   -- Set the Torque Converter Clutch (TCC)
   -- Set SOL32 PWM Duty Cycle
   -- Read Switches switchA, switchB, switchC and lookup manual_gear
   -- If check/do shift
     --- direct shift if enabled
	 	   ---- downshifts checked first
	     ---- upshifts second
	 --- else auto sequential shift or manual mode
	    ---- downshifts checked first
		  ---- upshifts second
   -- Burn Flash if flagged
   -- Check for reinit command
   -- Check for CAN reset
   -- Slow Speedometer 
 - End MAIN LOOP
 
 Functions:
  - tcc_unlock()
  - tcc_lock()
  - reset_gear()
  - reset_switches()
  - VSS_reset()
  - reset()
  - get_adc()
  - switch_page()
  - intrp_1dctable()
  - intrp_2dctable()
  - auto_gear_lookup()
  - VSS_timer()
  - Timer_Clock_ISR()  -- this is where the millisecond and other timer operations happen
  - ISR_TimerOverflow()
  - Serial_Comm_ISR()
  - CanInit()
  - CanTxIsr()
  - CanRxIsr()
  - can_xsub01()
  - solA()
  - solB()
  - UnimplementedISR()
  - fburner()
  - Flash_Init()
  - Flash_Write_Word()
  - waitAwhile()
  
*/

/** MegaShift I/O Port Assignments ******************************************
    ------------------------------

Function             Port        Circuit      AMP     User Options
--------             ----        -------      ---     ------------

VSS							     PT0					VR1			      2
Brake Sense			     PAD07				GPI4          3	
Paddle DOWN			     PAD06        GPI5		      4	
4WD input            PE1          GPI1          5  -- also digital datalog input
switchB							 PAD01				GPI2 (VB1)    6  -- spare ADC input if 'Ford' pot-shift lever used
LED2 						     PM3          GPO2          7	
LED3  	  			     PM5          GPO3          8	
LED4  					     PB4          GPO4          9
LED1						     PM4          GPO1         10
Spare Output1        PT7          VB1-jumper   11 
Spare Output2        PA0          VB2          12
ISS                  PT5          VR3          14  
Paddle UP				     PT6          VR2		       15
Sol A			           PE4          VB3          23	
non-CAN MAP/TPS/MAF  PAD05        EGT1         24  -- spare ADC input if CAN used
switchC							 PAD03				EGT3         25  -- spare ADC input if 'Ford' pot-shift lever used
switchA							 PAD00				EGT4         26 
line pressure sensor PAD04        EGT2         27  -- spare ADC input if 'Ford' pot-shift lever used 
Temp Sensor			     PAD02				GPI3		     30	 -- spare ADC input if temp sensor not used
Speedo Output		     PT4					PWM1		     31	
TCC							     PT3					PWM2		     32
PC							     PT2					PWM3         33
Sol32						     PT1          PWM4         34
Sol B						     PM2          VB4          35	
	 
*/

/** I/O Port Quick_Start ******************************************
    --------------------

Function             Port    I/O    ON    OFF   READ               NOTES
--------             ----    ---    --    ---   ----               -----

Brake Sense			     PAD07		I     --    --    (PORTAD0 & 0x80)   - on/off			
Paddle DOWN			     PAD06    I     --    --    (PORTAD0 & 0x40)   - on/off   	
non-CAN MAP/TPS/MAF  PAD05    I     --    --    ATD0DR5            - ADC count   
line pressure sensor PAD04    I     --    --    ATD0DR4            - ADC count     
switchC							 PAD03		I     --    --    ATD0DR3            - ADC count  
Temp Sensor			     PAD02		I     --    --    ATD0DR2            - ADC count    
switchB							 PAD01		I     --    --    ATD0DR1            - ADC count  
switchA							 PAD00		I     --    --    ATD0DR0            - ADC count  

Spare Output1        PT7      O     *pPTTpin[7] |= 0x80;  *pPTTpin[7] &= ~0x80; ---
Paddle UP				     PT6      I     --    --    PTT & 0x40         - on/off
ISS                  PT5      I     Interrupt ISS_timer()          - interrupt called on edge     			
Speedo Output		     PT4			O			PWMDTY4 = 0;          PWMDTY4 = PWMPER4;    PWMDTY4
TCC							     PT3			O		  PWMDTY3 = 0;          PWMDTY3 = PWMPER3;    PWMDTY4
PC							     PT2			O		  PWMDTY2 = 0;          PWMDTY2 = PWMPER2;    PWMDTY4
Sol32						     PT1      O     PWMDTY1 = 0;          PWMDTY1 = PWMPER1;    PWMDTY4
VSS							     PT0			I     Interrupt VSS_timer()          - interrupt called on edge

LED4  					     PB4      O     PORTB |= 0x10;        PORTB &= ~0x10;       ---

LED3  	  			     PM5      O     *pPTMpin[5] |= 0x20;  *pPTMpin[5] &= ~0x20; ---	   
LED1						     PM4      O     *pPTMpin[4] |= 0x10;  *pPTMpin[4] &= ~0x10; ---
LED2 						     PM3      O     *pPTMpin[3] |= 0x08;  *pPTMpin[3] &= ~0x08; ---
Sol B						     PM2      O     *pPTMpin[2] |= 0x04;  *pPTMpin[2] &= ~0x04; ---

Sol A			           PE4      O    	PORTE |= 0x10;        PORTE &= ~0x10;       ---
4WD input            PE1      I     --    --  (PORTE & 0x02)       - on/off        

Spare Output2        PA0      O     PORTA |= 0x01;        PORTA &= ~0x01;       ---

	 
*/

/*************************************************************************

Codewarrior Compiler for MShift Code for GPIO
---------------------------------------------

This code was developed entirely on Codewarrior 4.6 Special Edition. This compiler (actually a 
compiler/linker/locator) is available for free from Freescale (which used to be called Motorola):

http://www.freescale.com/webapp/sps/site/overview.jsp?code=CW_SPECIALEDITIONS&tid=CWH

You have to register to get the software. (Note that the link may eventually become stale. If it 
does, start at www.freescale.com and search for 'codewarrior special edition')

The 'Special Edition' of Codewarrior has a 32 Kbyte code compile limit, 
and a maximum of 32 files (the number of files and code size are listed on the bottom 
left side of the Freescale Codewarrior IDE - integrated development environment). 
This code project is well under those limits at 23 files and ~20 Kbytes.

The files required to create a loadable S19 file for the GPIO board using Codewarrior are:

- main.c             (this file)
- hcs12def.h         (various register definitions, it gives names to memory addresses such as ports)
- Monitor_linker.prm (memory layout file)
- cltfactor.inc      (the file that is used to convert the trans temp ADC count into degrees Farenheit)
- loadfactor.inc     (the file that is used to convert the load signal, MAP/TPS/MAF, into 
                      volts for the controller if not using CAN)
- linefactor.inc     (the file that is used to convert the line pressure sensor output to psi)
- sprfactor.inc      (a spare file for use with the ADC ports in custom code)
- Flash.asm
- flash.h
- reset.asm

This project does not need the processor expert ("beans") enabled in the 
Codewarrior project, the required info is embedded into the above files. If you do decide 
to use the processor expert, you need to set it to 'MC9S12C64MFA' with the small memory model.

**************************************************************************/

/** Compile time errors *************************************************

This code will generate 2 messages when compiling. This is normal. 
The two errors are:

- Warning: C4201: pragma ROM_VAR was not handled 

  main.c line 1416

  Comment: "1416" might be another line where a "#pragma ROM_VAR DEFAULT" statement is. It is 
  not entirely clear why this message happens, but it does not affect the generated code. 
  Deleting the 'ignored' statement simply moves the error to another pragma statement, 
  which *might* cause problems, so this is best left alone.

- L1128: Cutting value main startup data member from 0x3C8000 to 0x8000 
 
  Comment: This is because the linker/locator has to assign actual physical addresses to 
  the code, not paged addresses (3C is the page number), so this is normal (note that it 
  is labeled a 'warning' not an 'error'). In fact we would be worried if we didn't get 
  this message while using paged code!

Any other errors should be found, understood, and fixed, of course.

**/

/**** Decimal, Binary, and Hexadecimal numbers in C *************************

Note that in Codewarrior, any number starting with 0x (or 0X) is considered a 
hexadecimal number (example 0xF1B0) - sometimes written in documentation with a 
preceeeding $ or a following h, and any number stating with a 1 to 9 is considered 
a decimal number. C has no native syntax for expressing a binary number in source 
code (though one can be user defined - google it). Hexadecimal is used instead, 
and is convenient because of the smaller size, and each digit corresponds to a 
4 bit binary block.)
 
An easy way to convert between binary, hexadecimal, and decimal is to open the 
Windows calculator (Start/All Programs/Accessories/Calculator) in 'scientific' mode 
(under 'View') then click between the various formats (it has hexadecimal, decimal, 
octal, and binary). The number in the display is converted as you click different 
formats)

*/

/**** The 9S12C64 Microcontroller Memory ***************************************

The Freescale 9S12C64 microcontroller used on the GPIO board (and with this 
MegaShift code) has a 16 bit, 24 MHz processor. 16 bit means it can address memory 
locations from:
 
  0000 0000 0000 0000 to 1111 1111 1111 1111 binary locations 
= 0 to 65535 decimal 
= 0x0000 to 0xFFFF hexadecimal

The 9S12C64 microcontroller has 3 types of memory:

1) 4 kilobytes of RAM (Ramdom Access Memory) - used for values that need to be changed 
   frequently (all the values that MegaTune normally changes are changed in RAM, except the 
   thermistor and MAF tables). They can then be 'burned to flash with the 'Burn to ECU' button. 
   RAM loses its contents if the power supply to the GPIO main board is interuppted, even very 
   briefly. At start-up these values are copied from the flash memory 
   (below) into the two 1Kbyte inpram. and in2ram. stuctures in RAM.
   
2) 64 kilobytes of flash EEPROM memory - flash EEPROM (Electrically Eraseable Programmable Read 
   Only Memory) memory is retained even if the power is removed for long periods. Flash 
   can be read easily, but is much slower and more difficult to write (since it is erasable only 
   in 1024-byte sectors, and must be erased before even a single bit can be written), 
   so it is used for program instructions and values that don't change 
   much (like the thermistor tables). Note that the compiler may refer to flash as 
   ROM (read only memory), because though it is not 'read-only' it serves much the 
   same purpose as ROM in some other microcontrollers. (Note that rumour has it that the 9S12C64 
   actually has 128 kiloBytes of flash memory, which can be accessed using the paging mechanism
   described below.)
   
3) Registers - used to configure the microcontroller (I/O pins, timer ports, 
   pulse width modulation (PWM), and to store the status of specific operations etc.)
   
The memory is addressed as a number from 0x0000 to 0xFFFF. The memory addresses are arranged 
as:

  0x0000 to 0x03FF - Registers
  0x03FF to 0x3000 - 16K fixed Flash EEPROM (page 0x003D)
  0x3000 to 0x3FFF - RAM (mappable to any 4K boundary)
  0x4000 to 0x7FFF - 16K fixed Flash EEPROM (fixed page 0x003E)
  0x8000 to 0xBFFF - 16K fixed Flash EEPROM (ppage = 0x003C)
  0xC000 to 0xFEFF - 16K fixed Flash EEPROM (fixed page 0x003F)
  0xFF00 to 0xFFFF - Interrupt Vectors (BDM if active)
  
and the memory 'segments' are defined in the monitor_linker.prm file in 
the PRM folder as:

SEGMENTS																		 
    RAM = READ_WRITE 0x3000 TO 0x3FFF;          // 4096 bytes
    
    // unbanked FLASH ROM 
    CLT_ROM  = READ_ONLY 0x4000 TO 0x47FF;
    MAT_ROM  = READ_ONLY 0x4800 TO 0x4FFF;
    EGO_ROM  = READ_ONLY 0x5000 TO 0x53FF;
    MAF_ROM  = READ_ONLY 0x5400 TO 0x5BFF;
    INP_ROM  = READ_ONLY 0x5C00 TO 0x63FF;
    OVF_ROM  = READ_ONLY 0x6400 TO 0x6FFF;
    ROM_7000 = READ_ONLY  0x7000 TO 0x7FFF;

//  ROM_8000 = READ_ONLY  0x8000 TO 0xBFFF; 
    // banked FLASH ROM 
    PAGE_3C  = READ_ONLY  0x3C8000 TO 0x3CBFFF;  // 16384 bytes, FUNCTION_ROM, MAIN_ROM
    PAGE_3D  = READ_ONLY  0x3D8000 TO 0x3DBFFF;  // 16384 bytes, OTHER_ROM
    ROM_C000 = READ_ONLY  0xC000 TO 0xF77E;      // 14206 bytes, NON_BANKED, COPY, RUNTIME

END

In the monitor_linker.prm file is a section called 'PLACEMENT' which 
contians the user-specified labels (you can a call them whatever you 
want - except for reserved words) that the pragma code_seg directives 
use. This allows us to separate the pragma statements from the memory 
addresses, and makes things like changing to other processor variants, 
etc. much easier. 

PLACEMENT
    _PRESTART, STARTUP,
    VIRTUAL_TABLE_SEGMENT,
    DEFAULT_ROM                  INTO  ROM_7000; 
    MAIN_ROM                     INTO  PAGE_3C;
    OTHER_ROM                    INTO  PAGE_3D;
    NON_BANKED, RUNTIME,
    COPY                         INTO  ROM_C000;
    ROM_VAR, STRINGS             INTO  CLT_ROM,MAT_ROM,EGO_ROM,MAF_ROM,INP_ROM,OVF_ROM;
    DEFAULT_RAM                  INTO  RAM;
END

'pragma's are directives to the compiler at compile time (not when the code is executed)
that direct the actions of the compiler in a particular portion of a program without 
affecting the program as a whole. Most commonly, we use them to specifiy where the compiler 
(actually the linker) should put things in memory.

The rpm file, pragma directives, and ppage statements in the code work together. The prm file
tells the comiler how the memory is organized. The pragma statments tell the linker where to 
put sections of the code in memnory. The ppage statements tell the running processor where 
to look in memory for code and values, etc.

Note that some apparently different memory areas overlap:

- Flash locations in the range 0xC000 - 0xFFFF are the same as flash 
  locations 0x3F8000 - 0x3FBFFF.
- Flash locations in the range 0x4000 - 0x7FFF are the same as flash 
  locations 0x3E8000 - 0x3EBFFF.
- Flash locations in the range 0x0000 - 0x3FFF are the same as flash 
  locations 0x3D8000 - 0x3DBFFF.

Only the FLASH area 0x0000 - 0x3FFF overlaps with Flash area 0x3D8000 - 
0x3DBFFF. (Not the SFR registers and internal RAM). This is because the 
SFR Registers and internal RAM have higher priority from Flash in the bus 
arbitrator, so when they overlap with some of the Flash memory, they win 
precedence over the Flash that occupies the same address range, and so the 
internal registers and RAM are accessed as needed, rather than the 
underlying Flash locations.

In other words, Flash portions that are not covered with SFR registers and 
internal RAM are accessible through the 0x0000 - 0x7FFF address range. 
These Flash locations are also accessible through PPAGE pages 0x3D and 0x3E 
in the 0x8000 - 0xBFFF program page window - where no Flash locations are 
usually covered by SFR registers or internal RAM (unless INITRM is set to 
this address range).

For more information on the prm file, ppaged memory, #pragmas and how to use them in C 
programming, see:

http://www.freescale.com/files/microcontrollers/doc/app_note/AN2216.pdf

or search the freescale site (http://www.freescale.com) for document AN2216.pdf 

In general, you won't need to know these unless you are doing a major re-working
of the code. For adding features, etc., you just need to know where specific 
variables are located.

Memory locations for specific variables can be found in the Monitor.map file that 
the linker creates. These locations will be very helpful (i.e., necessary) to write 
the INI file that MegaTune uses.

Also, note that all the variables you want to see in MegaTune,
that are outpc from MS-II to the laptop rather than inputs to MS-II, need 
to be in the outpc. stucture to be recognized by MegaTune (they also have to be 
set up in the INI file).

If you are looking for a concise introduction to programming the HCS12 (but for the 
D256 variant) try: 

https://www.ee.nmt.edu/~rison/ee308_spr06/lectures.html 

(the site may generate a security certificate error, proceed anyways)              

For a complete textbook on programming the HCS12 family of processors, including MegaSquirt-II's 
MC9S12C64MFA, see:

"The HCS12/9S12 : An Introduction to Hardware and Software Interfacing" by Han-Way Huang 

Hardcover: 880 pages 
Publisher: Delmar Learning
Language: English 
ISBN-10: 1401898122 
ISBN-13: 978-1401898120 

(This is the standard 3rd year university electrical engineering textbook on 
this topic. It also includes a CD with freeware compilers and IDEs, among other things.)

 **/


/**** Transmission Line Pressure Gauge ************************************

The line pressure gauge is a 0-500 psi pressure sensor. It is Digi-Key MSP6907-ND for $114 
(www.digikey.com). 
It has:
 - a 0.5-4.5 Volt output (accuracy ±5 psi), 
 - just a three wire hookup (5V, ground, and signal).
 - a 1/8 NPT fitting - the same as the trans. However, a 90° elbow, or maybe even some tubing, 
   will be required in most installs to keep the sensor away from the trans tunnel. 
 
The line pressure is available for datalogging the line pressure with the MShift code and the 
GPIO. 
The line pressure has error checking built into the look-up table:
 - 990 is used to represent an under-pressure error (0.00 to 0.49 Volts), 
 - 999 is used to represent an over-pressure error (4.51 to 5.00 Volts).

No part of the code is conditional on the line pressure,  but it is highly recommended that 
it be installed and monitored carefully to prevent damage to the transmission.
*/

/*
 Note that all the standard output transistors recommended for the circuits on the 
 GPIO board (TIP120 in PWMx, VB921 in VBx, ZTX450 in GPOx) are NPN - they are ON 
 (i.e., will allow current to flow) when the base (which is connected to the port pin
 through a 1K Ohm resistor) is pulled high, i.e., set to 1 in software, and 
 OFF if the port pin is set to zero. 
 (Note that if a pull-up circuit is used, 'current flowing' means an external 
 nominal voltage signal of zero (actually about 0.7 Volts) and 'no current flowing' 
 means full pull-up voltage is applied)
*/

/* ------------- CAN Communications -----------------------------------------------

CAN data is requested from the MS-II 8 bytes every can_var_rate (.128 ms tics) of the 
Timer_Clock_ISR(). The data is sent in the CanTxIsr() subroutine, and received in the
CanRxIsr() subroutine. The received data is the entire outpc. structure that MS-II 
normally sends to MegaTune. In the MShift processor, this data is placed into the msvar 
array, where the rpm and kPa are extracted for use in the program.

Every time a CAN transmit or recive routine is called, the CANtx or CANrx values in the 
outpc. stucture are incremented (up to 65535, then they roll-over). This can be helpful
for de-bugging CAN communications.

*/

/*
Port DDR (data direction register) Configuration
------------------------------------------------
Many of the I/O port pins on the 9S12C64 processor can be 
configured as either an input or an output. The way you tell 
the processor which should be what is by filling zeros 
or ones in the data direction registers.

0=input, 1=output, x=not assigned (default is input)

For the GPIO, we want:

Bit		 76543210     decimal   hexadecimal
			 --------     -------   -----------
PA     xxxxxxx1			 = 01      = 0x0001
AD		 00xx0000			 = 00      = 0x0000
PB		 xxx1xxxx			 = 16      = 0x0010
PE		 xxx1xxx1			 = 16      = 0x0011
PM		 xx1111xx			 = 60      = 0x003C
PT		 10011110			 =158      = 0x009E

These registers are typically defined with DDRx names. Note that 
the data direction registers are memory addresses, 
but there are convenient names defined for these addresses 
in the hcs12def.h file (open it and look for things like DDRA for 
port A, etc.).

BTW, these user configurable input/output ports are called 
"general purpose I/O" ports which is where the GPIO board gets its name.

*/

/*
Misc.  Notes:
-----------------

VSS is 40 tooth VR on output shaft on 4L60E. Typical frequency is

24" diameter tire = 2 foot * 3.1415 = 6.283 feet = 0.00119 miles/tire rev

The driveshaft and output shaft turn at (1 tire rev)/(drive ratio output shaft rev). 
E.g. 1/3.08

= 0.0003864 mile/rev = 3.864 x 10^-4

So at 10 mph, we have: 

freq = [10 mph ÷ (3600 sec/hr)] ÷ 0.0003864 mile/rev * 40 teeth/rev

     = (10 * 40)/(3600*.0003864) = 287.6 teeth/sec (Hertz) 
     
     = 3.5 milliseconds/tooth
     
     at 100 mph, the frequency is 10x, and the time it 0.1x
     So range for 0 to 200 mph is 0 Hertz to ~6000 Hz.

**/ 

/****************************************************************
******************* TRANSMISSION ERROR CODES ********************
*****************************************************************

Error is an unsigned char (0-255)

   bit    dec  description
   ---    ---  -----------
00000000 = 0   = functioning normally
00000001 = 1   = transmission temperature > 215°F
00000010 = 2   = 
00000100 = 4   = shift blocked by over/under rev
00001000 = 8   = VSS input does not match engine input/gear
00010000 = 16  = gear requested exceeds number available
00100000 = 32  = torque converter slip out of range
01000000 = 64  = reset gear shift switches unexpectedly
10000000 = 128 = 

**/

// --- Defines, Includes, ISR Definitions and Interrupt Table ----
// Note that #define statements and other compiler directives do not use a terminating semi-colon

#include "hcs12def.h"      /* common defines and macros */
                           // This is where things like PORTE are set as names rather than memory addresses.
                           // This is done entirely for programmer convenience, so that you can write 
                           // PORTE instead of  0x0008

#include "flash.h"         // flashburner defines, structures
#include <string.h>        // standard C string handling functions

#define INTERRUPT interrupt         // so we can use upper or lower case
#define ENABLE_INTERRUPTS  asm cli; // 'cli' is an assembly language directive 
                                    //  meaning 'enable interrupts'
#define DISABLE_INTERRUPTS asm sei; // 'sei' is an assembly language directive 
                                    //  meaning 'disable interrupts'
#define NEAR near                   // You can obtain more compact code by using 
                                    // near types of addressing modes that use relatively
                                    // small offsets embedded within each instruction,
                                    // i.e. if we agree to have the compiler generate 
                                    // code constrained to only access locations "nearby"
                                    // to some default location or location 0. More 
                                    // compact code also implies less instruction 
                                    // fetching per execution hence performance is better. 
#define VECT_ATTR @0xFF80           // vector interrupt space from absolute address 
                                    // $FF80 to $FFFF (65456 to 65535 decimal)

#pragma CODE_SEG NON_BANKED /* Interrupt section for this module. 
                               Placement will be in NON_BANKED area. */
                               
extern void near _Startup(void);       /* Startup routine function prototype */

/**** Interrupt Service Routines ********************************** 

When the HC9S12 processor detects an interrupt, you can have it automatically
jump to part of the program which tells it what to do outside of the normal flow 
of the program. The code an interrupt triggers a jump to is called an ISR (Interrupt 
Service Routine). The ISR is located at one of a specific set of memory 
locations called Interrupt Vectors that tell the HCS12 the address of the ISR
for each type of interrupt.

The HC9S12 know where to return to because the return address is pushed onto 
the stack before the HC9S12 jumps to the ISR. In assembly language you use 
the RTI (Return from Interrupt) instruction to pull the return address off 
of the stack when the processor exits the ISR.

Even if the ISR changes registers, all registers are pushed onto the stack 
(a set of memory location used to store the CPU status) before jumping to 
the ISR code. When the processor executes the RTI instruction at the end of 
the ISR, the registers are pulled off of the stack before returning to
program main loop execution.

To return from the ISR You must return from the ISR using the RTI instruction 
in assembly language (return; in C). The RTI instruction tells the HCS12 to 
pull all the registers off of the stack and return to the address where 
it was processing when the interrupt occurred.

MShift never call ISRs directly, ISRs run 'asynchronously' in response to 
interrupt requests. Thus, to the main loop code, each ISR appears to just 
start up on its own. Since the main loop doesn't call the ISR,
it cannot pass arguments to the ISR or accept a return value. Therefore, a 
ISR in C must be a function with no parameters and a void return type. The key 
word INTERRUPT tells the compiler that the code is an interrupt service routine, 
and also not to 'optimize' it right out of the code because it is never called 
in the main loop.
*/
                               
INTERRUPT void UnimplementedISR(void); 
INTERRUPT void VSS_timer(void);
INTERRUPT void ISS_timer(void);
INTERRUPT void Timer_Overflow_ISR(void);
INTERRUPT void Timer_Clock_ISR(void);
INTERRUPT void Serial_Comm_ISR(void);
INTERRUPT void CanTxIsr(void);
INTERRUPT void CanRxIsr(void);

typedef void (* NEAR tIsrFunc)(void);
const tIsrFunc _vect[] VECT_ATTR = {      // Interrupt table placed at 0xFF80
        UnimplementedISR,                 // vector 63, 0xFF80
        UnimplementedISR,                 // vector 62, 0xFF82
        UnimplementedISR,                 // vector 61, 0xFF84
        UnimplementedISR,                 // vector 60, 0xFF86
        UnimplementedISR,                 // vector 59, 0xFF88
        UnimplementedISR,                 // vector 58, 0xFF8A
        UnimplementedISR,                 // vector 57, 0xFF8C - PWM emergency shut-down
        UnimplementedISR,                 // vector 56, 0xFF8E
        UnimplementedISR,                 // vector 55, 0xFF90
        UnimplementedISR,                 // vector 54, 0xFF92
        UnimplementedISR,                 // vector 53, 0xFF94
        UnimplementedISR,                 // vector 52, 0xFF96
        UnimplementedISR,                 // vector 51, 0xFF98
        UnimplementedISR,                 // vector 50, 0xFF9A
        UnimplementedISR,                 // vector 49, 0xFF9C
        UnimplementedISR,                 // vector 48, 0xFF9E
        UnimplementedISR,                 // vector 47, 0xFFA0
        UnimplementedISR,                 // vector 46, 0xFFA2
        UnimplementedISR,                 // vector 45, 0xFFA4
        UnimplementedISR,                 // vector 44, 0xFFA6
        UnimplementedISR,                 // vector 43, 0xFFA8
        UnimplementedISR,                 // vector 42, 0xFFAA
        UnimplementedISR,                 // vector 41, 0xFFAC
        UnimplementedISR,                 // vector 40, 0xFFAE              
        CanTxIsr,                         // vector 39, 0xFFB0
        CanRxIsr,                         // vector 38, 0xFFB2
        CanRxIsr,                         // vector 37, 0xFFB4
        UnimplementedISR,                 // vector 36, 0xFFB6
        UnimplementedISR,                 // vector 35, 0xFFB8
        UnimplementedISR,                 // vector 34, 0xFFBA
        UnimplementedISR,                 // vector 33, 0xFFBC
        UnimplementedISR,                 // vector 32, 0xFFBE
        UnimplementedISR,                 // vector 31, 0xFFC0
        UnimplementedISR,                 // vector 30, 0xFFC2
        UnimplementedISR,                 // vector 29, 0xFFC4
        UnimplementedISR,                 // vector 28, 0xFFC6
        UnimplementedISR,                 // vector 27, 0xFFC8
        UnimplementedISR,                 // vector 26, 0xFFCA
        UnimplementedISR,                 // vector 25, 0xFFCC
        UnimplementedISR,                 // vector 24, 0xFFCE
        UnimplementedISR,                 // vector 23, 0xFFD0
        UnimplementedISR,                 // vector 22, 0xFFD2
        UnimplementedISR,                 // vector 21, 0xFFD4
        Serial_Comm_ISR,                     // vector 20, 0xFFD6      
        UnimplementedISR,                 // vector 19, 0xFFD8
        UnimplementedISR,                 // vector 18, 0xFFDA
        UnimplementedISR,                 // vector 17, 0xFFBC
        Timer_Overflow_ISR,               // vector 16, 0xFFDE
        UnimplementedISR,                 // vector 15, 0xFFE0
        UnimplementedISR,                 // vector 14, 0xFFE2
        ISS_timer,                        // vector 13, 0xFFE4
        UnimplementedISR,                 // vector 12, 0xFFE6
        UnimplementedISR,                 // vector 11, 0xFFE8
        UnimplementedISR,                 // vector 10, 0xFFEA
        UnimplementedISR,                 // vector 09, 0xFFEC
        VSS_timer,                        // vector 08, 0xFFEE
        Timer_Clock_ISR,                  // vector 07, 0xFFF0 - RTI
        UnimplementedISR,                 // vector 06, 0xFFF2 - IRQ (PE1)
        UnimplementedISR,                 // vector 05, 0xFFF4 - XIRQ (PE0)
        UnimplementedISR,                 // vector 04, 0xFFF6 - SWI
        UnimplementedISR,                 // vector 03, 0xFFF8
        UnimplementedISR,                 // vector 02, 0xFFFA - COP failure reset
        UnimplementedISR,                 // vector 01, 0xFFFC - Clock monitor failure reset
        _Startup                          // Reset vector, 0xFFFE
   };
 

// ******************************** FUNCTION PROTOYPES **********************************   
// Functions must be 'prototyped' before they are used. A function protoype
// looks like this:
//
//    void function_name(variable_type variable_name1, variable_type variable_name2, ...);
//
// where:
//       - the first item is the type of the value the function returns 
//         ('void' means the function does not return a value), 
//       - the function_name must be unique and not one of the reserved words,
//       -  a list of the variable passed (preceeded by their data type) is 
//          enclosed is round brackets. 
//
// The names in the function must match those in the function itself, but not necessarily those
// that are passed from the main program (or other function).
//
// Note that the variables passed to the function must match the variable type 
// (int, char, unsigned char, etc.) listed in the function protoypes (below).
//
// Also note that some operations will require different types for the variables being 
// operated on. The can often be temporarily 'cast' to another type using the ('type') operator. 
// This seems confusing, but the gist of it is that if you have a variable that is a char 
// that needs to be a 16 bit int to be compatible with a specific calculation, you can just 
// add "(int)" in front of it in the code.
//
// For example:
//         int integer_number;             // normally an integer number 
//    		 char letter='A';               
//				 integer_number=(int) letter;    // which works because letter is treated as an integer 
                                           // because of the cast
// void send_can(unsigned char buf);
 
#pragma CODE_SEG ROM_7000

void switch_page(unsigned char sub_no);

int intrp_1dctable(char sgnx, int x, unsigned char n, int * x_table, 
  char * z_table);

int intrp_2dctable(unsigned int x, int y, unsigned char nx, unsigned char ny,
  unsigned int * x_table, int * y_table, unsigned char * z_table);
unsigned int intrp_1ditable(unsigned int x, unsigned char nx, 
                  unsigned int * x_table, unsigned int * z_table);

unsigned char auto_gear_lookup(int x, int y, unsigned char nx, unsigned char ny,
  unsigned int * x_table, int * y_table, unsigned char * z_table); 
  
void blink_gpo3(int flashCount);
void waitAwhile(unsigned int cycles); 

#pragma CODE_SEG DEFAULT    // end CODE_SEG NON_BANKED for interrupts

// --- Table Includes and Table Size Defines ---
/* The '#include' preprocessor directive directs the compiler to process 
external header files. 

Syntax:
        #include <header-file>
   or
        #include "source-file"

When enclosing the file with < and >, then the implementation searches the known 
header directories for the file and processes it. 
When enclosed with double quotation marks, then the entire contents of the 
source-file is replaced at this point.
*/
  
#include "cltfactor.inc"    // cltfactor.inc defines a 1024 x 1 lookup table.
                            // we set the ADC level to be ten bits later: 
                            // 2¹° = 0000 0100 0000 0000 = 1024 = 0x0400 
                            // Each value corresponds to one ADC increment,
                            // giving the temperature*10 in Farenheit 
                            // at that ADC value. (if Metric_Units is set, the 
                            // value is converted to Celsius immediately)
                            // The ADC start at 0 Volts for ADC count value of 0, 
                            // and run to 5.00 Volts at ADC 1024
                            // (so each ADC represent a difference of 5.00/1024
                            // or 0.00488 Volts/count)
                            
#include "sprfactor.inc"    // spare table for future use  
                        
#include "linefactor.inc"   // linefactor.inc defines a 1024 x 1 lookup table that turns a
                            // 0 to 5 volt signal into transmission line pressure from 0 to 
                            // 500 psi (990 = signal output out-of-range low, 999 = signal 
                            // output out of range high)
                                                     
#include "loadfactor.inc"   // loadfactor.inc defines a 1024 x 1 lookup table that takes an  
                            // ADC count, and turns it into a 0 to 5 Volt 'load' signal
                            // The input can be MAP, TPS, or MAF (used for non-CAN MAP)
                            
#define NO_TBLES 	16        // tables are cltfactor, sprfactor, presfactor, loadfactor, 
                            // in1flash, in2flash, txbuf, outpc (+blank tables)
                            
#define NO_LOADS 	12        // Number of loads in shift table and pressure control table
#define NO_MPH  	12	      // Number of MPH in shift table auto gear table
#define NO_TEMPS 	10        // number of temps in temperature table(s)
#define NO_GEARS   5        // Number of Gears (1-4 forward gears), 0 for neutral/reverse
#define NPORT      7        // Number of spare ports
#define SER_TOUT   5        // Serial time out in seconds (if corrupt data received)
#define NO_MSVAR_BYTES  112 // Number of variable bytes in the received data from MS-II

// CAN and Serial defined values
#define	MSG_CMD	        0
#define	MSG_REQ	        1
#define	MSG_RSP	        2
#define	MSG_XSUB	      3
#define	MSG_BURN	      4
#define	NO_VAR_BLKS	   16
#define NO_CANMSG      10
#define MAX_CANBOARDS  16

// Error status words: 
//    -bits 0-7 are current errors
//    -bits 8-15 are corresponding latched errors
#define	XMT_ERR		   	0x0101
#define	CLR_XMT_ERR		0xFFFE
#define	XMT_TOUT	  	0x0202
#define	CLR_XMT_TOUT	0xFFFD
#define	RCV_ERR		  	0x0404
#define	CLR_RCV_ERR		0xFFFB
#define	SYS_ERR		    0x0808

/****** INPRAM User Inputs *******************************************/
// User inputs from PC/tuning interface - 1 set in flash first, then copy into RAM
// To access the RAM variables, use the inpram. prefix

typedef struct {
// In C, variables must be 'declared' before they can be used. 
// Their 'variable type' must be given.
//
// This code uses a number of variable types:
//
//   Type             Bits              Range               MegaTune INI Designation
//   ----             ----              -----               ------------------------
// - char               8            -128 to 127                    S08
// - unsigned char      8               0 to 255                    U08
// .................................................................................
// - int               16          -32768 to 32767                  S16
//   (== short)
// - unsigned int      16               0 to 65535                  U16
//   (== unsigned short int)
// .................................................................................
// - long              32            -2³¹ to 2³¹ - 1                S32
//   (== long int)     
// - unsigned long     32               0 to 2³² - 1                U32
//   (== unsigned long int)
// .................................................................................
//
// Variables are declared with a statement like: 
//     data_type variable_name;
//            or
//     data_type variable_name = 16; 
//     (which also initializes the variable)
//
// See the application of this below.
//
// In general, the shortest variable that will do the job
// (spans the range of values needed in any circumstance by the program) 
// uses the least space, leaving more room in memory for 
// other variables and code.
//
// These variable declarations can be prefaced by: 
// - const (doesn't change ever - and can be set as 
//   constant by the compiler)
// - volatile (can be changed externally to the program - by a register result, 
//             for example, and therefore should not be 'optimized' by the compiler)
// 
// In addition to the range of values a variable type can hold, there are issues associated with
// what happens if the range is exceeded. In general, the value 'wraps around'. That is,
// it goes to the next value in the range as if the highest and lowest values were connected.
//
// For example, for an unsigned int, you would have:
// 
// 0,1,2,3,4,.....253,254,255,0,1,2,3....
//
// so adding 1 to 255 = 0, add 2 to 255 = 1, etc. This is often referred to as 'overflow'.
// Similarly, subtracting 1 from 0 is 255, and so on. This is sometimes called 'underflow'.
//
// This is easier to understand if you look at the binary representation that 
// the CPU actually uses:
//
//   255 in binary forum is: 11111111, and this uses 8 bits (short int). Add 1 to it, and you get
//   1+11111111 = 100000000
//
// But when you try to stuff that into the 8 bits of an unsigned int, the most significant 
// bit (MSB - the leftmost) is truncated, so you get 00000000
//
// This applies not just to constants stored in variables, but also to calculations. Suppose 
// you have two variables with values A=10000 and B=20000, and you want to multiply then to get C.
//
// You might expect C = A*B = 10000 * 20000 = 200000000
// However, if C is an unsigned short, then C will equal 20000000 % 255 = 95 
// (% is the modulus, the remainder after dividing the first number by the
// second number as many times as it will go)
// However, if C is an unsigned int, then C will equal 20000000 % 65535 = 11825
// Only if C is a long does it equal the expected value of 20000000.
//
// Note that this wrap-around can also happen in intermediate calculations, so you
// must be aware of this in the order you write assignments if you expect large numbers. 
// For example setting: 
// C = (A/B) * 1000000;
// may not give the same result as: 
// C = (A*1000) / (B*1000); 
// if the intermediate calculations overflow or undeflow (and this can depend on the type 
// of A, B, and C).
// (Note that values grouped in parenthesis are calculated first, creating the intermediate 
// results.)
//
// Also be aware that the compiler (and the HSC12 processor) does not recognized 
// 'floating point' numbers (those with non-zero decimal portions), in either 
// constants or intermediate calculations. 
//
// In another example, suppose you want to multiply A by PI=3.1415926... to get B. 
// You cannot write 
// B = A * 3.14159; (since decimal values are not allowed), 
// instead you have to write something like 
// B = (A * 314159)/100000; (and you can see why things *might* overflow!)
//
// Finally, you should not write
// B = A * (314159/100000); since the intermediate value (314159/100000) will be 
// truncated to 3 (no floating point numbers), and the result will be B = A * 3;
// (Note: also see 'casts'.)
//
// The Codewarrior compiler will often produce a "possible loss of data" warning if
// it can determine that a calculation or variable might overflow, but it is not 
// fool-proof by any means.

unsigned char InputCaptureEdge;    // Input capture: 0 = rising edge, 1 falling
unsigned int vss_table[NO_MPH];    // vehicle speed tables
int LOAD_table[NO_LOADS], temp_table[NO_TEMPS];  // deg x 10 (C or F)
unsigned int gear_table[NO_GEARS]; // gear ratio * 1000

unsigned char CAN_enabled, load_type;  // CAN connected, 0=no, 1 =yes 

int noTCC_temp,            // no TCC lock below this temp (to aid warm-up) (x10)
    minTCC_gear;           // minimum gear (2-4) for TCC lock   

unsigned int noTCC_load;   // no TCC lock above this load
unsigned char LUF_taper;   // not used

unsigned char PulseTol;    // % tolerance for next input pulse

unsigned int axle_ratio;   // rear axle ratio * 1000 (i.e. 3.08 = 3080),
unsigned int tire_diam,    // tire diameter (inches * 100)
         under_rev_limit;  // Do not allow upshift if it we result in rpm less than under_rev_limit

unsigned char trans_type,  // transmission type, 0=4L60E, 1=4R70W
 shift_mode;               // 0=manual, 1=auto (sequential shift), 2=auto (skip shift)
 
unsigned char Metric_Units,// 0= coolant & mat in deg F; 1= deg C
 error_check;              // 0 = no error checking (stim), 1 = take action on reported errors  
 
int rpm_limit;             // Max engine rpm for downshift

unsigned char debounce;    // Threshold for switch manifold debounce
                           // Initially set to mid-point,
                           // incemented/decremented by one each time through loop
                           // until all switchs are <10% or >90% of debounce value

unsigned char no_teeth;    // divide factor for input VSS pulses (teeth/rev)
 
unsigned int
 TCC_PWM_Pd,			         // TCC PWM period (usec) - keep between 10-25 KHz (100-40 us)
 PC_PWM_Pd,                // PC PWM period (usec) - keep between 10-25 KHz (100-40 us)
 SOL32_PWM_Pd;             // Sol32 PWM period (usec)

unsigned char 
 BatFac,                   // Battery PWM correction factor (% (x10) per volt)
 ms2canID,msvarBLK,        // Code specific CAN variables
 PCneutral;                // PC duty cyle in neutral  
unsigned long baud;        // baud rate

unsigned char board_type;  // board type (1-255) of this board;
                           // type=0, reserved.
                           // type=1, ECU (MS II)
                           // type=2, Router board
                           // type=3, Generic I/O board
                           // type=4, Transmission Controller, ......
unsigned char mycan_id;    // can_id (address) of this board (< MAX_CANBOARDS). Always 0 for ECU.
unsigned int can_var_rate; // rate at which to get CAN variables from MS-II -
                           // 8 bytes (4 words) per can_var_rate, 0.128 ms tics

unsigned char auto_table[NO_LOADS][NO_MPH];   // The target gear for each LOAD & MPH

unsigned char pc_table[NO_LOADS][NO_MPH];     // The PWM% for PC solenoid by load and mph

unsigned int trans_temp_limit; // Apply TCC in all applicable gears above this temp, regardless of load

unsigned int pulse_mile;    // pulses per mile for speedometer output

unsigned char rpm_check;    // 0 if no rpm checking (stim or non-CAN), 1 if rpm checking

unsigned char gear_hyst;    // shift hysteresis, in mph x10

unsigned char kpa_hyst;     // shift hysteresis, in kpa x10

unsigned char max_shift_pressure; // maximum shift line pressure

unsigned int pressure_delay;// delay for line pressure to change before shift (milliseconds x121)

unsigned int shift_delay;   // delay after activating solenoids for shift to complete (milliseconds x121)

unsigned int LOADshortcount;// short term load filtering factor

unsigned int LOADlongcount; // short term load filtering factor

signed int LOADmult;        // multpiler for non-CAN voltage to kPa conversion (x100)

signed int LOADzero;        // y-axis intercept for non-CAN conversion (x100)

unsigned int minTCCspeed;   // minimum TCC lock speed

signed char PCtemp[NO_TEMPS]; // Temperature adjustments for line pressure (x10)

unsigned char Output1;     // Binary output pattern for output1 (solA)

unsigned char Output2;     // Binary output pattern for output2 (solB)

unsigned char Output3;     // Binary output pattern for output3 (3/2sol)

unsigned char out3dc;      // output3 duty cyle when on

unsigned int sp1speed, sp1speed_hyst; // spare port 1 speed condition
signed char sp1speed_cond;            // 00 = no condition, 01 = greater than, 10 = less than
unsigned int sp1rpm, sp1rpm_hyst;     // spare port 1 rpm condition
signed char sp1rpm_cond;
unsigned int sp1load, sp1load_hyst;   // spare port 1 load conditions
signed char sp1load_cond;
unsigned char sp1gear, sp1gear_cond;  // spare port 1 gear conditions

unsigned int sp2speed, sp2speed_hyst; // spare port 2 speed condition
signed char sp2speed_cond;
unsigned int sp2rpm, sp2rpm_hyst;     // spare port 2 rpm condition
signed char sp2rpm_cond;
unsigned int sp2load, sp2load_hyst;   // spare port 2 load conditions
signed char sp2load_cond;
unsigned char sp2gear, sp2gear_cond;  // spare port 2 gear conditions

unsigned char Input1;      // Binary output pattern for input1 (swA = N)

unsigned char Input2;      // Binary output pattern for input2 (swB = R)

unsigned char Input3;      // Binary output pattern for input3 (swC = P)

unsigned char mlever_mode; // manual level mode:
                           // 0 for swA,swB, swC  (GM)
                           // 1 for voltage based (Ford)
                           
unsigned int mlever_vP;    // if mlever_mode=1, voltage in Park
unsigned int mlever_vR;    // if mlever_mode=1, voltage in Reverse
unsigned int mlever_vN;    // if mlever_mode=1, voltage in Neutral
unsigned int mlever_v4;    // if mlever_mode=1, voltage in 4th
unsigned int mlever_v3;    // if mlever_mode=1, voltage in 3rd
unsigned int mlever_v2;    // if mlever_mode=1, voltage in 2nd                       
unsigned int mlever_v1;    // if mlever_mode=1, voltage in 1st

unsigned char swBDC_OD;    // 0 if using lever for fourth, 1 if separate switch (ground for 4th)                           

unsigned char cltch_enable;// 0 if spare ports, 1 if use as clutch outputs (active during shift)
unsigned char clutch1up;   // R-P, N-R, 4-N, 3-4, 2-3, 1-2; start at right, 0=off, 1=on  
unsigned char clutch2up;   // R-P, N-R, 4-N, 3-4, 2-3, 1-2; start at right, 0=off, 1=on
unsigned char clutch1dwn;  // P-R, R-N, N-4, 4-3, 3-2, 2-1; start at right, 0=off, 1=on  
unsigned char clutch2dwn;  // P-R, R-N, N-4, 4-3, 3-2, 2-1; start at right, 0=off, 1=on
                                       
unsigned int FWD_factor;   // speedo adjustment when PE1 is low (in four wheel drive)                                       

unsigned char upshift_retard;  // in degrees x10
unsigned char dwnshift_retard; // in degrees x10

unsigned char gear2_retard;    // second gear retard (degx10)
unsigned char gear3_retard;    // third gear retard (degx10)
unsigned char gear4_retard;    // fourth gear retard (degx10)
unsigned int Tretard_load;     // timing retard above load (kpax10)
unsigned char LUF_off;         // Lock-up feel solenoid parameters (%, kPa, %)
unsigned int  LUF_loLoad, LUF_hiLoad; 
unsigned char LUF_loPWM, LUF_hiPWM;
unsigned int digital_threshold; // ADC to digital on/off threshold
unsigned int shftr_accum;       // shifter accumulator - button sensitivity (lower is more sensitive)
unsigned int hyst_enable_speed; // hysteresis disabled below this speed (x10)
unsigned char vss_mask;         // vss input mask (%)
unsigned char stdin_cfg;        // standard inputs configuration
                                // bit 0: 0=just log,   1=use FWD as speedo switcher
                                // bit 1: 0=just log,   1=use as trans temperature
                                // bit 2: 0=report ADC, 1=report temperature (from cltfactor.inc)
unsigned char spare1;           // unused
unsigned char min_speed;        // minimum speed, will set to zero below this (VSS interrupt still active)
unsigned int vss_error_max;     // number of vss errors before rest
unsigned char line_units;       // line pressure (use table or report ADC)
unsigned int inj_flow;          // injector flow rate (lbs/hour) x10 for ALL injectors for mileage calcs
unsigned char open_time;        // injector open time (msecX100) for mileage calcs
unsigned int fuel_density;      // fuel density (pounds/gallon)
unsigned char nSquirts;         // number of squirt (divide by 2 if alternating)
unsigned int refresh_int;       // the interval time (msec) between dithers for output1 and output2
unsigned int refresh_dur;       // the 100% duty cycle time for output1 and output2
unsigned int dither_intPC;      // the interval time (msec) between dithers for the pressure control output
unsigned int dither_durPC;      // the 100% duty cycle time for the pressure control output
unsigned char out12_pwm_off;    // output1 and output2 off duration (in 0.128 clock tics)
unsigned char out12_pwm_on;     // output1 and output2 on duration (in 0.128 clock tics)
unsigned char brake_ON_polarity;// 1 == high, 0 == low
unsigned int WOT_tps_threshold; // WOT TPS threshold (in ADC counts in code, % of 1024 in MT)
unsigned char iss_mask;         // vss input mask (%)
unsigned char ic2_usage;        // 0 = 00000000 = not used
                                // 1 = 00000001 = ISS
                                // 2 = 00000010 = tach
                                // 7th (leftmost) bit used for ISS input edge capture:
                                //    0 = rising
                                //    1 = falling
unsigned char iss_divider;      // ISS/tach divider (number of teeth/rev)                                

} inputs1;

 typedef struct {
  int spare1;   // spare1 in in2ram
  int spare2;   // spare2 in in2ram
  int spare3;   // spare3 in in2ram
  int spare4;   // spare4 in in2ram
  int spare5;   // spare5 in in2ram
  int spare6;   // spare6 in in2ram
  int spare7;   // spare7 in in2ram
  int spare8;   // spare8 in in2ram
  int spare9;   // spare9 in in2ram
  int spare10;  // spare10 in in2ram
  int spare11;  // spare11 in in2ram
  int spare12;  // spare12 in in2ram
  int spare13;  // spare13 in in2ram
  int spare14;  // spare14 in in2ram
  int spare15;  // spare15 in in2ram    
 } inputs2;
 
/*typedef struct {
   unsigned short xrate;   // =0 means the message is only transmitted when requested, 
                           //  otherwise it indicates the message will be transmitted automatically 
                           //  every xrate ms after receiving the first request. [Not currently used]
   unsigned char no_bytes; // The total number of bytes of all the variables in the message.                             
                           //  Must be < 112 = size of outpc.
   unsigned char offset[MAX_BYTES_OUTMSG];    //  This holds the byte offsets of each of the    
                           //  bytes in the message relative to start of  outpc. For a short, 
                           //  specify 2 consecutive offsets in the proper order for the requesting 
                           //  processor.                            
} can_outmsg;
*/
#pragma ROM_VAR INP_ROM

// flash copy of inputs - initialized

#define SECTOR_BYTES         1024 // bytes
#define SECTOR_WORDS         ((int)(SECTOR_BYTES/2))
#define N_SECTORS(theStruct) ((int)((sizeof(theStruct)+SECTOR_BYTES-1)/SECTOR_BYTES))
#define N_PADDING(theStruct) ((int)(N_SECTORS(theStruct)*SECTOR_BYTES - sizeof(theStruct)))


// Assign values to the in1flash parameters
const inputs1 in1flash = {
1,                // input capture edge for VSS, 0=rising edge, 1 = falling edge
{12,              // vss_table[MPH no = 0] , MPH (KPH), use for 12x12 auto shift table
  18,26,36,48,60,70,80,90,100,120,140},
{25,              // LOAD_table[LOAD/tps no = 0],  kPa, for use in 12x12 auto shift table
  35,45,55,65,70,75,80,85,90,95,100},
{-400,            // temp_table[TEMP no = 0],  deg x 10
    -200,0,200,400,600,800,1000,1300,1600},
{2290,3060,1630,1000,700}, // gear_table[], gear ratios * 1000	 (0th is reverse)
1,                // CAN_enabled = 1 = yes; 
0,                // load type
700,              // noTCC_temp (degrees x10)
3,                // minTCC_gear
900,              // noTCC_load (kpa x10)
20,               // LUF taper time to go to 100% PWM DC (sec x10)
25,               // PulseTol,     % tolerance for next input pulse timing during    
                  // normal running    
3080,2600,        // axle_ratio, tire_diam
1500,             // under_rev_limt - no upshift if it will result in less than this
                  // (switch to auto in in manual and not first gear)
0,                // trans_type, 0=4L60E
1,                // shift_mode; 0=manual, 1=auto (sequential), 2=auto (skip shift)
0,                // Measure_Units,    0= deg F/inches/mi/hr; 1= deg C/cm/km/hr
1,                // error checking enabled by default
6500,             // rpm_imit,  Max rpm after downshift (or switch to auto if in manual mode)
50,               // Switch manifold debounce facor (start at 1/2 of debounce, run until 
                  // less than 0.1*debounce or > 0.9*debounce)
40,               // no_teeth, divide factor for input VSS pulses, 40 teeth for 4L60E VSS
10417,            // TCC_PWM_Pd (us) (=96 Hertz) - not used at present
3413,             // PC_PWM_Pd,  Injector PWM period (us) (=293 Hz)
20000,            // SOL32_PWM_Pd, 3/2 Solenoid period (us) (=50 Hz)
0,                // BatFac,  Battery PWM correction factor (% (x10) per volt)
0,7,20,           // ms2canID, msvarBLK, PCneutral
115200,           // baud rate
3,                // board type
1,                // mycan_id of this board, always 0 for ECU, 1 for all others until configured
78,               // can_var_rate; rate at which to get CAN variables from MS-II -
                  // 8 bytes (4 words) per can_var_rate in 0.128 ms tics;
                  // can get all 112 bytes of outpc in 14 x canvarate tics.

/* 12x12 Target Gear Table for Auto Mode **/   
{{1,            // auto_table[LOAD no=0][MPH no=0],  target gear
    1,2,3,4,4,4,4,4,4,4,4},
{1,             // auto_table[LOAD no=1][MPH no=0],  target gear
    1,2,3,3,4,4,4,4,4,4,4},
{1,             // auto_table[LOAD no=2][MPH no=0],  target gear
    1,2,2,3,3,4,4,4,4,4,4},
{1,             // auto_table[LOAD no=3][MPH no=0],  target gear
    1,2,2,3,3,4,4,4,4,4,4},
{1,             // auto_table[LOAD no=4][MPH no=0],  target gear
    1,1,2,2,3,3,4,4,4,4,4},
{1,             // auto_table[LOAD no=5][MPH no=0],  target gear
    1,1,2,2,2,3,3,4,4,4,4},
{1,             // auto_table[LOAD no=6][MPH no=0],  target gear
    1,1,2,2,2,3,3,4,4,4,4},
{1,             // auto_table[LOAD no=7][MPH no=0],  target gear
    1,1,2,2,2,2,3,3,4,4,4},
{1,             // auto_table[LOAD no=8][MPH no=0],  target gear
    1,1,2,2,2,2,3,3,3,4,4},
{1,             // auto_table[LOAD no=9][MPH no=0],  target gear
    1,1,1,2,2,2,2,3,3,4,4},
{1,             // auto_table[LOAD no=10][MPH no=0], target gear
    1,1,1,1,2,2,2,3,3,4,4},
{1,             // auto_table[LOAD no=11][MPH no=0], target gear
    1,1,1,1,2,2,2,2,3,3,4}},

/* 12x12 Pressure Control PWM Table **/   
{{40,             // pc_table[LOAD no=0][MPH no=0], PWM%
    40,40,40,40,40,40,40,40,40,40,40},
{37,             // pc_table[LOAD no=1][MPH no=0],  PWM%
    37,37,37,37,37,37,37,37,37,37,37},
{34,             // pc_table[LOAD no=2][MPH no=0],  PWM%
    34,34,34,34,34,34,34,34,34,34,34},
{30,             // pc_table[LOAD no=3][MPH no=0],  PWM%
    30,30,30,30,30,30,30,30,30,30,30},
{26,             // pc_table[LOAD no=4][MPH no=0],  PWM%
    26,26,26,26,26,26,26,26,26,26,26},
{23,             // pc_table[LOAD no=5][MPH no=0],  PWM%
    23,23,23,23,23,23,23,23,22,21,20},
{20,             // pc_table[LOAD no=6][MPH no=0],  PWM%
    20,20,20,20,20,20,20,20,20,19,18},
{18,             // pc_table[LOAD no=7][MPH no=0],  PWM%
    18,18,18,18,18,18,17,16,15,14,13},
{13,             // pc_table[LOAD no=8][MPH no=0],  PWM%
    13,13,13,13,13,13,12,11,10,9,8},
{8,             // pc_table[LOAD no=9][MPH no=0],   PWM%
    8,8,8,8,8,8,8,8,6,4,2},
{4,             // pc_table[LOAD no=10][MPH no=0],  PWM%
    4,3,3,2,1,0,0,0,0,0,0},
{0,             // pc_table[LOAD no=11][MPH no=0],  PWM%
    0,0,0,0,0,0,0,0,0,0,0}},
    
210,         // trans temperature limit
2002,        // pulse/mile for speedometer output
0,           // no rpm_check (for stim or non-CAN)
35,          // gear hysteresis (mph x10)
100,         // gear hysteresis (kPa x10)
20,          // max shift pressure (PC DC%)
10,          // pressure delay ( 10 = ~85 milliseconds)
20,          // shift delay    (20 = ~ 1/5 second)
400,         // LOADshortcount (400 = ~1/10 sec)
60,          // LOADlongcount  (60 = ~8 sec)
2000,        // LOADmult
0,           // LOADzero
410,         // minTCCspeed

{0,          // PCtemp[],  PWM% adjustment for temperature
     0,0,0,0,0,0,0,0,0},

//------------------------- 4 3 2 1 R N P 
79,          // Output1 = 0 1 0 0 1 1 1 1 = 79 (solA for the 4L60E) 
31,          // Output2 = 0 0 0 1 1 1 1 1 = 31 (solB for the 4L60E) 
96,          // Output3 = 0 1 1 0 0 0 0 0 = 96 (3-2 soln for the 4L60E)
 
90,          // Output3 (3/2 Sol duty cycle when on)

400,40,      // spare port 1 speed condition
1,           // 0x00 = no condition, 0x01 = greater than, 0x10 = less than
5000,200,    // spare port 1 rpm condition
0,           // 0x00 = no condition, 0x01 = greater than, 0x10 = less than
800,100,     // spare port 1 load condition
0,           // 0x00 = no condition, 0x01 = greater than, 0x10 = less than
3,0,         // spare port 1 gear condition

600,60,      // spare port 2 speed condition
1,           // 0x00 = no condition, 0x01 = greater than, 0x10 = less than
5000,200,    // spare port 2 rpm condition
0,           // 0x00 = no condition, 0x01 = greater than, 0x10 = less than
800,100,     // spare port 2 load condition
0,           // 0x00 = no condition, 0x01 = greater than, 0x10 = less than
3,0,         // spare port 2 gear condition

//------------------------ 4 3 2 1 R N P 
115,         // Input1 =   1 1 1 0 0 1 1 (pressure switch manifold switch A)
56,          // Input2 =   0 1 1 1 0 0 0 (pressure switch manifold switch B)
31,          // Input3 =   0 0 1 1 1 1 1 (pressure switch manifold switch C)

0,           // mlever_mode (0 for GM- digital switches, 1 for Ford-ADC)
819,         // ADC count in park for Ford  - 4.0V 
717,         // ADC count in neutral for Ford  - 3.5V
614,         // ADC count in reverse for Ford  - 3.0V
512,         // ADC count in drive for Ford (4th)  - 2.5V
410,         // ADC count in 3 for Ford  - 2.0V
307,         // ADC count in 2 for Ford  - 1.5V
205,         // ADC count in 1 for Ford  - 1.0V

0,           // swBDC_OD: 0 use gear lever for 4th, 1 = use separate switch

0,           // clutch enable (0=spare ports, 1=use as clutches)
0,           // clutch1 upshift pattern
0,           // clutch2 upshift pattern
0,           // clutch1 downshift pattern
0,           // clutch2 downshift pattern

2690,        // FWD_factor (x1000)

50,0,0,10,50,    // timing retards - up/dwnshift, 2nd, 3rd, 4th
900,             // timing retard above load (kpax10)
0,500,1000,30,95,// LUF off state, loLoad, hiLoad, loPWM%, hiPWM%

204,         // ADC to digital on/off threshold = 1 Volt
400,         // shifter accumulator - button sensitivity (lower is more sensitive)
50,          // Hystersesis enable minimum speed (5 mph/kph)
75,          // VSS interrupt mask
7,           // binary 00000111 - use FWD to switch speedo, use temp sensor, use as temperature not ADC
0,           // spare1
100,         // minimum speed (x10)
400,         // maximum vss errors before a reset is invoked
0,           // line_units - 0=use temp table, 1=use ADC
2400,100,    // injector flow (pphx10) of ALL injectors, open_time (msecx100)
6073,        // fuel density (pounds/gallon)
1,           // number of squirts (divide by 2 if alternating)        
500,         // PWM refresh interval (msec)
0,           // PWM refresh duration (msec)
10000,       // PC dither interval (msec)
0,           // PC dither duration (msec)
0,           // output1 and output2 off duration (in 0.128 clock tics)
100,         // output1 and output2 on duration (in 0.128 clock tics)
1,           // Brake ON polarity (0=low, 1=high)
952,         // 952/1024 = 93% TPS for WOT
50,          // ISS mask (%)
0,           // ic2 usage (0 = not used)
40           // iss/tach divider (teeth/rev)

};

// Pad in1flash to end of sector
const unsigned char in1padding[N_PADDING(inputs1)] ={0}; 

// Put values in in2flash spare variables (spare1 to spare15)
const inputs2 in2flash = {
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15
};

// Pad in2flash to end of sector     
const unsigned char in2padding[N_PADDING(inputs2)] ={0};
                                // 1024 bytes = 1 block flash
                              
#pragma ROM_VAR DEFAULT

#pragma ROM_VAR OVF_ROM

// IAC stepper motor sequence
const unsigned char IACCoilA[8] = {0,0,1,1,0,0,1,1};
const unsigned char IACCoilB[8] = {1,0,0,1,1,0,0,1};
// array of TC overflow numbers at which to increment seconds counter.
const unsigned int TC_ovfla[60] = {
23,
46,
69,
92,
114,
137,
160,
183,
206,
229,
252,
275,
298,
320,
343,
366,
389,
412,
435,
458,
481,
504,
526,
549,
572,
595,
618,
641,
664,
687,
710,
732,
755,
778,
801,
824,
847,
870,
893,
916,
938,
961,
984,
1007,
1030,
1053,
1076,
1099,
1122,
1144,
1167,
1190,
1213,
1236,
1259,
1282,
1305,
1328,
1350,
1373
};

// --- Revision Number and Signature ---

const char RevNum[20] =  {    // revision no:
  // Only change for major rev and/or interface change. (The last character is added by MS-II.)
    "MegaShift 1.100    "
  // 123456789.123456789. 
},
 Signature[32] = {            // program title.
  // Change this every time you tweak a feature.  (The last character is added by MS-II.)
    "**  V1.006 MegaShift by B&G  **"
  // 123456789.123456789.123456789.12
 };

// #pragma ROM_VAR DEFAULT

//***** RAM copy of inputs *********************************************
// This is where the RAM variables (copied from flash memory) get 
// their inpram. & in2ram. prefixes (inpram = INPut RAM)
 
inputs1 inpram;
inputs2 in2ram;

// --- Global Variables ----
// The variables declared below are global, and available to all functions.
unsigned char restart; 
unsigned int VSS_error;
unsigned char error_flags; // check states for error reporting - same definition as outpc.error
int last_tps,last_LOAD,last_mph,last_kpa,high_mph;
char switchA, switchB, switchC;          // switch states for switch manifold gear determination
unsigned char switchAacc, switchBacc, switchCacc; // switch state debounce variables
int UP_avg, DWN_avg, MODE_avg; // Shift button 'pressed' average, shift only 
                               // after reaches user threshold

unsigned char pulse_no,ICint,ICintmask[2],OCint,OCintmask[2];
int vss_timer_overflow;
volatile unsigned short *pTIC[2],*pTOC[2]; // The asterisk (*) in the variable declaration 
                                           // indicates this is a "pointer".
                                           // That means that these variables hold the address
                                           // in memory of another variable.
unsigned long IgnTimerComp[2],dtpred;
unsigned int mms,millisec,burn_flag;
unsigned int TC_ovflow,TC_ov_ix;
unsigned long lmms,
  ltch_lmms,
  rcv_timeout,adc_lmms;
unsigned char flocker,tpsaclk;
unsigned char next_adc,first_adc,
	txmode,tble_idx,burn_idx,    // txmode is 1st received character in serial comms, burn_idx is the table to burn
	reinit_flag;
unsigned int speedo_total,speedo_toggle,speedo_counter;
unsigned long ic1_period[21]; // VSS period array - take the average
unsigned long ic2_period[21]; // ISS period array - take the average
unsigned char ic1_count, ic1_recount;
unsigned char ic2_count;
signed int adcval;            // signed for possible negative temperatures
long ic1_per_acc;             // VSS averaging subtotal
long ic2_per_acc;             // ISS averaging subtotal
long vss_divide;              // Calculated from number of teeth on VSS sensor, etc. 
long vss_teeth_4wd;           // vss tooth count
unsigned txcnt,txgoal,rxoffset,rxnbytes,rxcnt,kill_ser_t,vfy_fail,
  can_mmsclk,can_varoff;
char kill_ser,vfy_flg;
char SOLAst;                   // SOLB state indicator
char SOLBst;                   // SOLB state indicator
char VSS_rcvd;                 // flag for VSS tooth
int start_time;                // quarter mile (and 0-60 foot, 0-60 mph) start time
int pulsewidth1;               // from CAN for fuel efficiency calcs
char dither_flag;              // PWM dither and refresh flags
int dither_counter;            // dither counter for zero to dither_int+dither_dur
int refresh_counter;           // refresh counter for output1 and output2 from zero 
                               // to refresh_int+refresh_dur                              
unsigned int out12_pwm_count;  // output1 and output2 counter (in 0.128 clock tics)
unsigned long short_period_vss; // calculated mask times for VSS timer
unsigned long long_period_vss;  // calculated mask times for VSS timer
unsigned long short_period_iss; // calculated mask times for VSS timer
unsigned long long_period_iss;  // calculated mask times for VSS timer
unsigned short tcsav1;         // Last VSS time
unsigned short tcsav2;         // Last ISS/non-CAN tach time
unsigned char LUF_counter;     // Lock-UP Feel counter (0.10 sec tics)
unsigned char WOT_flag;        // 0 if not WOT, 1+ otherwise  (increment to 10, then reset)
unsigned char prev_gearlever;  // position of gear lever before checking state again

// CAN variables
unsigned long  cansendclk,canclk,ltch_CAN=0xFFFFFFFF;
char *canvar_blkptr[NO_VAR_BLKS]; // The asterisk (*) in the variable declaration 
                                  // indicates this is a "pointer".
                                  // That means that these variables hold the address 
                                  // of another variable in memory.
unsigned short can_status;
unsigned char can_clr_stat,can_reset,can_id,getCANdat=0,burnCANdat=0,sendCANAdj=0;
struct canmsg {
  /* CAN Xmt mssge ring buffer:
      can[0] is to hold Rx,TxISR messages,
      can[1] for main loop messages (so don't get clobbered by ISR)
      cxno     = no msgs in queue waiting to be sent out
      cxno_in  = index for inserting a msg in queue (incr after insert)
      cxno_out = index for sending out a msg (incr after load CAN buf)
      msg_type = CMD,REQ,RESP,XSUB (= set value, request value, respond 
          to a request for value, execute a subroutine)
      varblk,varoffset,varbyte = blk no of data structure, byte offset 
          from start of structure, no. bytes of data
      datbuf   = the actual data (max of 8 bytes)
      dest     = id no. of device to which msg being sent.
      (more CAN documentation in interrupt routines) 
  */
  unsigned char cxno,cxno_in,cxno_out;
  unsigned char cx_msg_type[NO_CANMSG], cx_myvarblk[NO_CANMSG], 
	  cx_destvarblk[NO_CANMSG], cx_dest[NO_CANMSG],cx_varbyt[NO_CANMSG];
//	  	  cx_outmsg_no[NO_CANMSG];     // only applies to OUTMSGs:
  unsigned short cx_myvaroff[NO_CANMSG],cx_destvaroff[NO_CANMSG];
  unsigned char cx_datbuf[NO_CANMSG][8];  // max msg data = 8 bytes
}  can[2];   // end canmsg structure

// unsigned char n_outbytes[MAX_OUTMSGS];  // counts bytes (1-24) in an outmsg
// unsigned char n_inbytes[NO_TBLES];      // counts received outmsg bytes in gpio

// pointers for spare port pins
volatile unsigned char *pPTMpin[8], *pPTTpin[8];
unsigned char dummyReg,lst_pval[NPORT]; 

// allocate space in ram for flash burner core
volatile unsigned char RamBurnPgm[36]; 

// ****** Set Up OUTPC. Structure for Serial Communications with MegaTune *******
// These are the rs232 outputs to PC via the serial port, and the corresponding 
// values should be in the MegaTune INI [OutputChannels] section.

typedef struct {
unsigned int seconds,os_rpm;              // clock, output shaft rpm (x10)
unsigned int speedo;                      // vehicle speed (x10) 
                                          // (in mph or kph, depending on Metric_Units)
unsigned long odo;                        // trip odometer (x1000)
char auto_mode;                           // 0=manual, 1=auto (table), 2=auto (sequential)
char upshift_request;                     // shift up requested
char downshift_request;                   // shift down requested
signed char manual_gear;                  // manual valve position determined from switch manifold
signed char current_gear;                 // current gear determined from state of shift solenoids
signed char target_gear;                  // next gear to select
unsigned char lock_TCC;                   // 0=unlocked torque converter clutch, 1=locked clutch
unsigned int engine_rpm;                  // engine rpm (from CAN or estimated from VSS and gear)
unsigned char error;                      // error status codes
unsigned int LOAD;                        // LOAD from MS-II via CAN - (kpa x 10)
signed int clt;                           // trans temperature deg(C/F)x 10
unsigned char brake;                      // brake - 0 = off, 1 = on  (may eventually use a variable ADC value)
unsigned long ic1_per_avg;                // average of last 20 VSS teeth                                    
int LOAD_short,LOAD_long;                 // LOAD averages
int aux_volts;													  // auxillary data channel (used for load if no CAN)
unsigned char PC_duty;                    // Pressure Control valve duty cyle (% for PWM)
unsigned char converter_slip;             // torque converter slip (%)
unsigned long loop_count;                 // Main loop counter 
unsigned long vss_teeth;                  // VSS tooth counter
unsigned int line_pressure;               // line pressure
unsigned int CANtx, CANrx;                // CAN transmit & receive counters
unsigned int dbug;                        // spare debugging variable brought out in MegaTune (default is VSS tooth error count)
unsigned int vBatt;                       // battery voltage from MS-II via CAN (x10)
unsigned char upbutton, downbutton;       // shift button status (not necessarily a shift request)
unsigned int is_rpm;                      // calculated input shaft rpm
unsigned char sp1;                        // spare port 1 status, 1=on, 0=off
unsigned char sp2;                        // spare port 1 status, 1=on, 0=off
unsigned int swADC,swBDC,swCDC;           // manual gear lever position ADC count on three channels
unsigned char FWD;                        // 2 or 4WD for speedo adjustment - 0 = FWD, 1 = 2WD
unsigned int adv_deg;                     // ignition advance from MS-II via CAN
unsigned char solst;                      // 0x000 = 0 - sol A off, sol B off, sol3/2 off
                                          // 0x001 = 1 - sol A on,  sol B off, sol3/2 off
                                          // 0x010 = 2 - sol A off, sol B on,  sol3/2 off
                                          // 0x011 = 3 - sol A on,  sol B on,  sol3/2 off
                                          // 0x100 = 0 - sol A off, sol B off, sol3/2 on
                                          // 0x101 = 1 - sol A on,  sol B off, sol3/2 on
                                          // 0x110 = 2 - sol A off, sol B on,  sol3/2 on
                                          // 0x111 = 3 - sol A on,  sol B on,  sol3/2 on
unsigned int mileage;                     // fuel efficiency (mpg X 100)
unsigned int tps;                         // TPS value from MS-II over CAN
unsigned long ic2_per_avg;                // average of last 20 ISS teeth
                                 
} variables;     // end outpc. structure

variables outpc, txbuf;

// Create a buffer variable to hold the MS-II output variables 
// recieved over CAN for use by GPIO
unsigned char msvar[NO_MSVAR_BYTES];
  // engine_rpm is 2 bytes at offset=6,
  // LOAD (map) is 2 bytes at offset=18, and is x10,
  // vBatt is 2 bytes at offset=26, and is x10.
  // There is much more on this below, including all the outpc variables and offsets.

#pragma ROM_VAR OVF_ROM

// Define a stucture for the table descriptor that gives the address in RAM, the address 
// in Flash (ROM), and the number of bytes
typedef struct {
   unsigned int *addrRam;
   unsigned int *addrFlash;
   unsigned int  n_bytes;
} tableDescriptor;

// Use the tableDescriptor stucture we just defined to create an array of table descriptions 
// sized NO_TABLES. They will hold the descriptions of the tables (and the indexes are used 
// in MegaTune to 'locate' the table with command like a06, which refers to the 6th table). 
// These are constant. That is, the descriptions - memory locations - of the tables are constant, 
// even if the elements of the tables change values.

const tableDescriptor tables[NO_TBLES] =  {
  //  RAM copy                    FLASH copy                     Table Size 
  {  NULL,                   (unsigned int *)cltfactor_table,  sizeof(cltfactor_table)  },
  {  NULL,                   (unsigned int *)sprfactor_table,  sizeof(sprfactor_table)  },
  {  NULL,                   (unsigned int *)linefactor_table, sizeof(linefactor_table) }, 
  {  NULL,                   (unsigned int *)loadfactor_table, sizeof(loadfactor_table) }, 
  { (unsigned int *)&inpram, (unsigned int *)&in1flash,        sizeof(inputs1)          }, 
  { (unsigned int *)&in2ram, (unsigned int *)&in2flash,        sizeof(inputs2)          },
  { (unsigned int *)&txbuf,   NULL,                            sizeof(txbuf)            },
  { (unsigned int *)&outpc,   NULL,                            sizeof(outpc)            },
//  {  NULL,                   (unsigned int *)&outmsg,          sizeof(outmsg)           },
  {  NULL,                    NULL,                            0                        },
  {  NULL,                    NULL,                            0                        },
  {  NULL,                    NULL,                            0                        },
  {  NULL,                    NULL,                            0                        },
  {  NULL,                    NULL,                            0                        },
  {  NULL,                    NULL,                            0                        },
  {  NULL,                    NULL,                            0                        },
//  {  NULL,                   (unsigned int *)&RevNum,          sizeof(RevNum)           }
};

// In the above, the "&" before a variable name means "at the address of, 
// so &inpram is the address of the inpram table

#pragma ROM_VAR DEFAULT  // codewarrior generates error message here, is seemingly OK though

// Calculate and define values for working with tables based on the tables array we just created.
#define tableInit(iTable) (void)memcpy(tables[iTable].addrRam, tables[iTable].addrFlash, tables[iTable].n_bytes)
#define tableByteRam(iTable, iByte)   ((unsigned char *)tables[iTable].addrRam + iByte)
#define tableWordRam(iTable, iWord)   (tables[iTable].addrRam + iWord)
#define tableByteFlash(iTable, iByte) ((unsigned char *)tables[iTable].addrFlash + iByte)
#define tableWordFlash(iTable, iWord) (tables[iTable].addrFlash + iWord)
#define tableBytes(iTable)            (tables[iTable].n_bytes)
#define tableWords(iTable)            ((tables[iTable].n_bytes+1)/2) // Round up

// Prototypes - Note: ISRs (interrupt service routines) are prototyped above.
// These are the manadatory function prototypes.

#pragma CODE_SEG MAIN_ROM
void main(void); // the main function, i.e., where the program starts
char tcc_lock (void); 
char tcc_unlock (void);
char Calc_LUF(void);
void reset_gear (void);
void reset_switches(void);

#pragma CODE_SEG DEFAULT
void VSS_reset(void);
int get_adc(char chan1); // this is the function that reads the ADC channels
void fburner(unsigned int* progAdr, unsigned int* bufferPtr, 
  					 unsigned int no_words); // fburner(pointer to destination, pointer to source, number of words)
void CanInit(void);
void can_xsub01(void);
void solA(unsigned char);
void solB(unsigned char);
void spr1(unsigned char);
void spr2(unsigned char);
void Flash_Init(unsigned long oscclk);
void Flash_Erase_Sector(unsigned int *address);
void Flash_Write_Word(unsigned int *address, unsigned int data);

#pragma CODE_SEG DEFAULT

extern void reboot(void);      // 'extern' means that the code can call the function defined in another module directly if necessary
extern void monitor(void);
extern void SpSub(void);
extern void NoOp(void);

// ---------------------------------------------------------------------------------------
// ---------------------------------- Function MAIN --------------------------------------
// ---------------------------------------------------------------------------------------
 
#pragma CODE_SEG MAIN_ROM // Put following code in MAIN_ROM == ppage 0x3C
 
void main(void) {
// This is where the program actually starts. Everything before this was mainly telling the 
// compiler what and where to put values in memory when we load the code using the serial 
// monitor. So the values above exist in the S19 file we will make (that set up both the 
// program and the defaults) but the above doesn't affect the program on a restart of 
// MegaSquirt (which is why the flash memory retains its values).

// First set-up variables, etc., then go into an infinte loop
int ix;
long ltmp;
char tmp1, tmp2;               // intermediate status of spare port 1 and 2
unsigned long ultmp, ultmp1, ultmp2;
unsigned char tempPC, tempPC1;
long tempPC2;                  // temp variable to hold PC solenoid duty cycle while we adjust and error check it
unsigned int scount, lcount;   // Loop counts for LOAD
int LOAD;                      // the map value (kPax10) from CAN/MS2 or TPS/MAP on EGT1
char lock_TCC = 0;             // set tcc unlocked
int engine_rpm = 0;
int upperswitch, lowerswitch;  // switch thresholds for switch manifold debounce
int rev_distance;              // tire revs/mile or revs/km
int higherADC, lowerADC;       // manual gear lever ADC gear range variables
unsigned char dc_32;           // 3/2 duty cycle calculated from inpram.out2dc

PPAGE = 0x3C;  // PPAGE register is defined in hcs12def.h 
               // as memory location 0x0030

 /* In order to access the 16K flash blocks in the address range 0x8000–.0xBFFF 
    the PPAGE register (0X0030) must be loaded with the corresponding value for 
    this range:

    PAGE    PAGE Visible with PPAGE Contents
    ----    --------------------------------    
    0x3C    $3C
    0x3D    $3D
    0x3E    $3E
    0x3F    $3F

    For the MC9S12C64, the flash page 3F is also visible in the 0xC000–.0xFFFF range 
    if ROMON is set. 
    For the MC9S12C64, the flash page 3E is also visible in the 0x4000–.0x7FFF range
    if ROMHM is cleared and ROMON is set. 
    For the MC9S12C64, the flash page 3D is also visible in the 0x0000–.0x3FFF range 
    if ROMON is set...  
    
    The main code is responsible for switching PPAGE as needed to select the 
    desired 16K block of memory to be mapped into the "window" from $8000-$BFFF.
    */
    
  restart = 1; //set reboot flag  

  //  Initalize PLL (Phase Locked Loop) - reset default 
  //  is Oscillator clock 8 MHz oscillator, 
  //  PLL freq = 48 MHz, 24 MHz bus, 
  //  divide by 16 for timer of 2/3 usec tic
  PLLCTL &= 0xBF;     // Turn off PLL so can change freq
                      // A &= B is equivalent to A  = A & B, where & means a bit-wise compare
                      // Note that this is different from A = &B, which assignes the address 
                      // of B to the variable A
  SYNR    = 0x02;     // set PLL/ Bus freq to 48/ 24 MHz
  REFDV   = 0x00;
  PLLCTL |= 0x40;     // Turn on PLL. A |= B is the same as A = A | B 
  
  // wait for PLL lock
  while (!(CRGFLG & 0x08)); // Note that & is a bitwise operator, && is the relational operator
  CLKSEL = 0x80;      // select PLL as clock
  
  // wait for clock transition to finish
  for (ix = 0; ix < 60; ix++);
  
  // Copy Flash to RAM
  // open flash programming capability
  Flash_Init(8000);
  // inp_spare used to force inpflash, flashve_table into sectors.
  // Must use in program or it won't use up the entire sector. Codewarrior 
  // ignores the pragma making this stupid statement necessary.
  ix = in1padding[0];
  ix = in2padding[0];
      
  if((int)RamBurnPgm & 0x0001)	{                    // odd address - copy to even one
    (void)memcpy((void *)RamBurnPgm,NoOp,1);         // copy noop to 1st location
    (void)memcpy((void *)&RamBurnPgm[1],SpSub,32);   // copy flashburn core program to RAM
  }  // End if((int)RamBurnPgm & 0x0001)
  else                                               // even address
    (void)memcpy((void *)RamBurnPgm,SpSub,32);       // copy flashburn core program to RAM

  // load all user inputs from Flash to RAM
  // indices are from 'const tableDescriptor tables[NO_TBLES]' and start at 0
  tableInit(4);
  tableInit(5);
  
  // ** MShift I/O Port Data Direction Register Settings ***
  //
  // These set the pport pins as inputs (0) or outputs (1).
  // Default is input.
  //
	//************************************************************
	
  DDRA |= 0x01;    // port A0 - output; 0x01 = 00000001 (A0 is a spare output)
  DDRB |= 0x10;    // port B4 - output; 0x10 = 00010000
  DDRE |= 0x10;	   // port E4 - output; 0x10 = 00010000 (E0 is a input only!)  
  DDRM |= 0x3C;    // port M2, M3, M4, M5 are  outputs, full drive by default; 0x3C = 00111100 
  DDRT |= 0x9E;    // port T1, T2, T3, T4, T5 & T7 are outputs, T0, T5, & T6 are inputs; 0x9E = 10011110
  
  /** Note: ***************************************************  
  serial Tx =  PS1
  serial Rx =  PS0
  CAN    Tx =	 PM1
  CAN    Rx =  PM0
 **************************************************************/ 
   
  //  Set pointers to real port addresses
  for(ix = 0; ix < 8; ix++)  {
    pPTMpin[ix] = pPTM;   // port M
    pPTTpin[ix] = pPTT;   // port T
  } // end for (ix = 0...

  // --- Set Up Timer Ports ---
  // Enable Timer Input Capture for PT0/PT5
  // --------------------------------------------------------------------------------
  // To use the Input Capture Function:
  //      - Enable the timer subsystem (set TEN bit of TSCR1)
  //      - Set the prescaler (TSCR2)
  // The 9S12 allows you to slow down the clock which drives the counter.
  // You can slow down the clock by dividing the 24 MHz clock by 2, 4, 8, 16, 32, 64 or 128.
  // You do this by writing to the prescaler bits (PR2:0) of the Timer System Control
  // Register 2 (TSCR2) Register at address 0x004D.
  //
  //   PR  Divide    Freq          Overflow Time
  //   000    1      24 MHz          2.7307 ms
  //   001    2      12 MHz          5.4613 ms
  //   010    4       6 MHz         10.9227 ms
  //   011    8       3 MHz         21.8453 ms
  //   100   16       1.5 MHz       43.6907 ms
  //   101   32       0.75 MHz      87.3813 ms
  //   110   64       0.375 MHz    174.7627 ms
  //   111  128       0.1875 MHz   349.5253 ms
  //
  // The clock ticks are counted in the variable TCNT (which is defined in the hsc12def.h file 
  // at memory location 0x0044). There are 0xFFFF 'tics' (65,536 counts).
  // In our case each 'tic' is 1/0.1875MHz  = 5.333 µs
  //
  // To set up the timer:
  //  - Configure the processor to use a particular pin of PORTT for input
  //    capture
  //  - Configure the processor to set which edge (rising, falling, or either) you want to capture
  //  - Configure the processor to determine if you want an interrupt to be generated when the
  //    capture occurs
  
  TIOS = 0x1E;  // Input Capture or Output Compare Channel Configuration:
                //  - 0 The corresponding channel acts as an input capture
                //  - 1 The corresponding channel acts as an output compare
                // 0x1E = 00011110
                // Timer ch 0 (PT0/IOC0) = IC, 
                //       ch 5 (PT5/IOC5) = IC, 
                //       ch 1-4          = OC & PWM,
                //    - PT0 is VSS in input 
                //    - PT1 is 3/2 solenoid output (output3)
                //    - PT2 is PC output (PWM)
                //    - PT3 is TCC output  (is PWM in some cases)
                //    - PT4 is speedo output (is PWM but duty is set to zero or 100%)
                //    - PT5 is input shaft sensor input or non-CAN tach input
                //    - PT6 is paddle upshift input
                //    - PT7 is spare output1
                //    - used digital I/O
                // ch 6,7 = I/O output
  TSCR1 = 0x80; // Timer System Control Register 1, 10000000 = set port T0 to use timer 
                // (Timer Enable (TEN) bit = 1)
  TSCR2 = 0x07; // Timer System Control Register 12, 0x07 = 00000111
                // Set prescaler to divide by 128
                // 349.53 millisecond overflow time, 5.333 µsec period
                // for a 40 tooth input wheel, this is
                // 1/(0.34953*40) = 0.0715 rev/sec, less than 1/2 mile/hour
                // if teeth come is at 2 x 5.333 µsec, this is about
                // 1/(0.00001066*40) = 2344 revs/sec, much faster than your car can go!
              


  // Set the VSS Input Capture Edge
  if (inpram.InputCaptureEdge == 0) // InputCaptureEdge is for VSS
    {
     TCTL4 = 0x01;    // 0x01 = 00000001
    } 
    else // InputCaptureEdge == 1
    {
     TCTL4 = 0x02;    // 0x02 = 00000010
    }                 // VRx circuits are designed to trigger on rising edge
                      // Set edge to capture (EDGxB EDGxA of TCTL 3-4 regs)
                      // EDGxB EDGxA
                      //   0     0     Disabled
                      //   0     1     Rising Edge
                      //   1     0     Falling Edge
                      //   1     1     Either Edge
                      //
                      //   address  register bit7  bit6  bit5  bit4  bit3  bit2  bit1  bit0
                      //   0x004A    TCTL3   EDG7B EDG7A EDG6B EDG6A EDG5B EDG5A EDG4B EDG4A
                      //   0x004B    TCTL4   EDG4B EDG3A EDG2B EDG2A EDG1B EDG1A EDG0B EDG0A
                      // 0x02 = 00000010, ~0x04 = ~00000100 = 11111011

  // Set the ISS Input Capture Edge
  if ((inpram.ic2_usage & 0x03) > 0)  // if either option set for ic2 use
    {  
    if ((inpram.ic2_usage & 0x80) >> 7)  // if IC2 (ISS) capture edge set to falling
      {
      TCTL3 = 0x08;    // 00001000 = EDG5B = 1, EDG5A = 0 == falling edge
      } 
      else
      {
      TCTL3 = 0x04;    // 00000100 = EDG5B = 0, EDG5A = 1 == rising edge  
      } // end if ic2_usage & 0x80
    }
    else
    {
    TCTL3 = 0x00;    // clear the register - disable the interrupt
    } // end if ic2_usage & 0x03
                                 
  TFLG1 |= 0x21;       // 0x21 = 00100001 -> Clear IC0 Flag bits 0 (PT0) and 5 (PT5) in the 'main timer interrupt flag1' register
  TIE   |= 0x21;       // 0x21 = 00100001 -> Set Timer Interrupt Enable (TIE) on pin 0 (PT0) and pin 5 (PT5)

  // turn off output pins
   SOLBst = 1;               // SOLAst, SOLBst = 1 -> P,R,N,1
   SOLAst = 1;               // SOLAst, SOLBst = 1 -> P,R,N,1
  *pPTMpin[2] &= ~0x04;      // 0x04 = 00000100, ~0x04 = 11111011, turn off    Sol B @ PM2
  *pPTMpin[3] &= ~0x08;      // 0x08 = 00001000, ~0x08 = 11110111, turn off     LED2 @ PM3
  *pPTMpin[4] &= ~0x10;      // 0x10 = 00010000, ~0x10 = 11101111, turn off     LED1 @ PM4
  *pPTMpin[5] &= ~0x20;      // 0x20 = 00100000, ~0x20 = 11011111, turn off     LED3 @ PM5
   PORTA      &= ~0x01;      // 0x01 = 00000001, ~0x01 = 11111110, turn off   Spare2 @ PA0
   PORTB      &= ~0x10;      // 0x10 = 00010000, ~0x10 = 11101111, turn off     LED4 @ PB4
   PORTE      &= ~0x10;      // 0x10 = 00010000, ~0x10 = 11101111, turn off    Sol A @ PE4
//  *pPTTpin[1] &= ~0x02;      // 0x02 = 00000010, ~0x02 = 11111101, turn off  Sol 3/2 @ PT2
//  *pPTTpin[2] &= ~0x04;      // 0x04 = 00000100, ~0x04 = 11111011, turn off       PC @ PT2   
//  *pPTTpin[3] &= ~0x08;      // 0x08 = 00001000, ~0x08 = 11110111, turn off      TCC @ PT3 
//  *pPTTpin[4] &= ~0x10;      // 0x10 = 00010000, ~0x10 = 11101111, turn off speedo output @ PT4
  *pPTTpin[7] &= ~0x80;      // 0x01 = 10000000, ~0x80 = 01111111, turn off   Spare1 @ PT7 
  
  // The asterisk (*) in the above statements are placed in front of 'pointers' 
  // (variables that hold the memory address of other variables). The * is the 
  // 'indirection operator' - it says to take the value at the address of the 
  // variable pointed to.
  // For example, if A is a variable at adress 0x00001000, and the value is 123, 
  // then a pointer to A could be set by
  // char A, *pA  <- declare the regular and pointer variable 
  // pA = &A      <- assign the address of A to the pointer pA
  // A = 123      <- assign the value of 123 to the variable A
  // then
  // pA = 0x00001000
  // and
  // *pA = 123
  // 
  // This is more than just two ways to do the same thing though. Only single values 
  // can be passed to C functions, and passing a pointer is one way around that to 
  // pass a array. 
  // That is, we pass the address of the start of the array (pA) and let the function 
  // increment that value to access the whole array.

  // Seyt up the data direction ports and pull-ups
  
  DDRE  = 0x10;              // Port E all inputs, except PE4 which is output (solA)
  PUCR  = 0x00;              // disable all pull-ups, will use external pull-ups where needed
  INTCR = 0x00;              // disable interrupt on PE1  

  DDRP  = 0x00;              // port P all inputs
  PERP  = 0xFF;              // enable pullup resistance for port P;    0xFF = 255 = 11111111

  DDRJ &= 0x3F;              // port J pins 0 to 5 outputs,             0x3F =  63 = 00111111
  PERJ |= 0xC0;              // enable pullup resistance for port J6,7; 0xC0 = 192 = 11000000 

  DDRS &= 0xF3;              // port S all outputs, except 2,3;         0xF3 = 243 = 11110011
  PERS |= 0x0C;              // enable pullup resistance for port S2,3; 0x0C =  12 = 00001100

  reinit_flag = 0;

  // Set Up CRG RTI Interrupt for .128 ms clock. CRG from 8MHz oscillator.
  mms      = 0;     // 0.128 ms tics
  millisec = 0;     // 1.024 ms clock (8 tics) for adcs
  lmms     = 0;
  cansendclk = 7812;
  ltch_lmms = 0;
  can_mmsclk = 0;   // .128 ms tics
  outpc.seconds = 0;// (1.0035) seconds
  burn_flag  = 0;
  ic1_count=1;
  ic1_recount = 1;
  ic2_count = 1;
 
  RTICTL  = 0x10;   // load timeout register for .128 ms (smallest possible)
  CRGINT |= 0x80;   // enable interrupt
  CRGFLG  = 0x80;   // clear interrupt flag (0 writes have no effect)

  // ------ Set up SCI Serial Communications Interface (RS232) ----------------
  /* To use SCI0 for 8 bit, non-parity communication:

      1. Select the baud rate via SCI0BDL Baud Rate Register, at memory 
         location 0x00C9 - defined in hcs12def.h (set below). 
         SCI baud rate = SCI module clock / (16 x BR) =
                       = 24,000,000/ (16*baud)
                       = 1,500,000/baud
                       = for baud == 115200 this is 13.0  

      2. Enable transmission and/or reception as desired by setting the 
         TIE (Transmitter Interrupt Enable) and RIE (Receiver Full 
         Interrupt Enable Bit) bits in the SCI0CR2 Control Register,
         which is at memory location 0x00CB. 
         This is register is set up as:
         
         bit        7       6      5      4      3     2     1     0
                   TIE    TCIE    RIE    ILIE    TE    RE   RWU   SBK

      3. Tramit and Receive, in interrupt service routine Serial_Comm_ISR():

           - transmit  - poll the TDRE flag and write data to the SCI0DRL register at 
                         address $00D7 when the TDRE flag is set. The data we write 
                         to the SCI0DRL register (0x00CF) is the data that will be 
                         immediately output from the TX pin. 
           - receive   - poll the RDRF register (SCI0SR1). When the RDRF status 
                         register (0x00CC) is set ((SCI0SR1 & 0x20) <> 0), we can then 
                         read the incoming data by reading the SCI1DRL register. The 
                         data we read in from this register is the input via the RX pin. 
*/
// Set the baud rate
  SCI0BDL = (unsigned char)(1500000/inpram.baud);   // = 1500000/115200 = 13
  ltmp = (150000000/inpram.baud) - ((long)SCI0BDL*100);
  if(ltmp > 50) SCI0BDL++;   // round up

  SCI0CR1 = 0x00;   // = 00000000 -> parity even -- 0000000x 
                    //               parity disabled
                    //               idle char starts after start bit
                    //               idle line wakeup
                    //               one start bit, eight data, one stop
                    //               only meaningful if LOOPS =1
                    //               SCI enabled in wait mode
                    //               LOOP disabled -- x0000000
                    
  SCI0CR2 = 0x24;   // = 00100100 -> TIE = 0                            -- x0000000, 
                    //               RIE = 1,(enable recieve interuupt) -- 00x00000,
                    //               TE  = 0 (Transmitter Enable Bit)   -- 0000x000, 
                    //               RE  = 1 (Receiver Enable Bit)      -- 00000x00,
                    //  (these will be reconfigured in Serial_Comm_ISR()) 
  txcnt     = 0;
  rxcnt     = 0;
  txmode    = 0;    // receive == 0, transmit > 0
  txgoal    = 0;
  kill_ser  = 0;
  vfy_flg   = 0;
  rcv_timeout = 0xFFFFFFFF;
  
// Initialize PWM Dither and Refresh Variables
dither_flag = 0;          // reset PWM dither and refresh flags
dither_counter = 0;       // dither counter to zero
refresh_counter = 0;      // refresh counter to zero  
  
/******************* Initialize IC_period array for VSS/ISS ****************************/
// So we start at zero speed until array is filled (which will happen very fast at any real 
// speed, but is never updated when stopped)

for (ix=1;ix<21;ix++)
  {
  ic1_period[ix] = 0x0FFFF; // set the VSS period high (speed low)  
  ic2_period[ix] = 0x0FFFF; // set the ISS period high (speed low)
  }

/******************* Set Up PWM on Timer Pins ******************************************

We want PWM on ports T1, T2, T3, & T4 (T0 is VSS input)
We want:
          Sol32:   T1 = PWM1 =  50 Hz = 0.0200 second period, variable duty cycle
          PC:      T2 = PWM2 = 293 Hz = 0.0034 second period, variable duty cycle
          TCC:     T3 = PWM3 =  50 Hz = 0.0200 second period, variable duty cycle 
                                                              (either 0% or 100% in this code, 
                                                              may be variable rate apply in 
                                                              future)
          speedout T4 = PWM4 = don't care - will use 0% or 100% only in this code
                                            (may use PWM in future for high speed signaling)         

1. Choose 8-bit mode (PWMCTL = 0x00 = default)  Could use 16 bit mode, but we don't need it.
2. Choose high polarity (PWMPOL = 0x1E for pins 1, 2, 3)
3. Choose left-aligned (PWMCAE = 0x00)
4. Select clock mode in PWMCLK = 0x1E:
     - PCLKn = 0 for 2N,
     - PCLKn = 1 for 2(N+1) × M.
5. Select N in PWMPRCLK register:
     - PCKA for channels 5, 4, 1, 0 (ISS, speedo, sol32, VSS);
     - PCKB for channels 7, 6, 3, 2 (spareoutput2, paddle up, TCC, PC).
6. If PCLKn = 1, select M = 1 to 255
     - PWMSCLA = M for channels 5, 4, 1, 0
     - PWMSCLB = M for channels 7, 6, 3, 2.
7. Select PWMPERn, normally between 100 and 255 (increased values give increased control resolution).
8. Enable desired PWM channels: PWME 1, 2, 3 and 4 for us so PWME |= 0x1E
9. Select PWMDTYn, normally between 0 and PWMPERn. Then
     Duty Cycle n = (PWMDTYn/PWMPERn) × 100%
   Change duty cycle to control speed of motor or intensity of light, etc.
10. For 0% duty cycle, choose PWMDTYn = 0
*/
 
// Set up PWM outputs - PWM 1, 2, 3, & 4
// PT0,1,4,5 are on clkA - Sol 3/2 and speedo output
// PT2,3,6,7 are on clkB - PC and TCC
  MODRR    = 0x1E; // Make Port T pins 1, 2, 3 & 4 be PWM (0x1E = 00011110), 
                   // PC and 3/2 SOL 
                   // (PT3 is on/off TCC, PT4 is on/off speedo output,
                   //  we set these by setting PWM% to 0 or 100% - may use PWM in future)
                   // The MODRR register allows for mapping 
                   // of PWM channels to port T in the absence of port P 
                   // pins for the low pin count processors (like the 
                   // 48 pin MS-II MC9S12C64 processor).
  PWME     = 0x00; // disable PWMs initially  
  PWMPOL   = 0x1E; // polarity = 1, => go hi when start
 
// Set prescaler to 16. This divides bus clk (= PLLCLK/2 = 24 MHz)
// by 16. This gives 1.5 MHz timer, 1 tic = 2/3 usec.
// PWMPERx should be between 100 and 255, as it is 8 bit number 
// (100 gives us 1% increments, 255 is absolute maximum)
// We need to scale the clocks to get a 'tic' rate that works for both 
// the 50 and 293 Hertz requirements 

  PWMCLK   = 0x1E; // select scaled clocks SB, SA
  
  PWMPRCLK = 0x22; // prescale A,B clocks = bus/4 = 24/6 = 6 MHz
                   // 0x22 = 00100010 -> divide clock by 4

  PWMSCLA  = 0xFF; // PWM clk = SA clk/(2*SCLA) = 6MHz/510 = 0.085 msec clk
                   // for  50 Hertz, we will have 235 tic periods
                   
                              
  PWMSCLB  = 0x80; // PWM clk = SB clk/(2*SCLB) =  6MHz/256 = 0.0427 msec clk
                   // for 293 Hertz, we will have 80 tic periods
                   // can range from ~10 to 255 tics, or
                   // ~2340 Hertz to 92 Hertz
                      
  PWMCAE   = 0x00; // standard left align pulse:
                   //                            -----
                   //                           |     |______
                   //                             duty
                   //                            <--period--->

  TSCR2 |= 0x80;   // enable timer overflow interrupt (TOI)
  pTIC[0] = pTC0;
  ICintmask[0] = 0x01;
  pTOC[0] = pTC5;

  TCTL2 |= 0x88;   // bit OM1,3,5 = 1. OC output line high or 
	                 // low iaw OL1,3,5 (not toggle or disable)
  TCTL1 |= 0x08;
//  TSCR2 |= 0x80;   // enable Timer Overflow Interrupt (TOI)

  // set PWM period1 (usec) for 3/2 solenoid 
  PWMPER1 = (unsigned char) ((((long)inpram.SOL32_PWM_Pd*11765)+500000)/1000000);
                                                  // (period and duty cycle constant)
                                                  // 90% PWM% except 0% in first and 3/2 downshift
                                                  // 11.765 is (1/0.085) ticks/millisecond
                                                  // nominal value is 50 Hertz = 20 milliseconds 
                                                  // -> PWMPER1 = 20 * 11.765 = 235 tics
                                                  // 1000000 is because SOL32_PWM_Pd is in usec, not msec
                                                  //   *and* 11765 is supposed to be 11.765
                                                  // +500000 is for proper rounding (rather than truncate)
  
  // set PWM period2 for pressure control solenoid                                                       

  PWMPER2 = (unsigned char) ((((long)inpram.PC_PWM_Pd*23419)+500000)/1000000);
                                                  // (period is constant, duty cycle varies with load
                                                  // as set in pc_table[12x12])
                                                  // 23.419 is (1/0.0427) ticks/millisecond
                                                  // nominal value is 293 Hertz = 3.41 milliseconds 
                                                  // -> PWMPER2 = 3.41 * 23.419 = 80 tics
  
  // set PWM period3 for torque converter clutch                                   

  PWMPER3 = (unsigned char) ((((long)inpram.TCC_PWM_Pd*71430)+500000)/1000000);      
                                                  // (period is constant, DC varies from 0% to 100%)
                                                  // (period and duty cycle constant)
                                                 // (period is constant, duty cycle varies with load
                                                  // as set in pc_table[12x12])
                                                  // 71.430 is (1/0.014) ticks/millisecond
                                                  // nominal value is 293 Hertz = 3.41 milliseconds 
                                                  // -> PWMPER2 = 3.41 * 23.419 = 243 tics  
  
  // set PWM period4 (usec) for speedo output
  
  PWMPER4 = 120; // 120 usec default for speedo output - short so we can get high frequencies 
                 //                                      because can only turn on/off at 1/2 freq

  // Initialize PWM Duty Cycles
  PWMDTY1 =    0;       // set Sol32 PWM duty to 0% (90% in 2, 3, 4)
  outpc.solst &= ~0x04; // set third bit to zero for datalog (3/2 sol behavior)
  PWMDTY2 =    0;       // set PC PWM duty to 0%
  if ((inpram.cltch_enable == 0) || (inpram.cltch_enable == 1))
    {
    PWMDTY3 = 0;      // turn off TCC
    outpc.lock_TCC = tcc_unlock(); // set the flag for datalog
    } 
    else // LUF in use, set duty cycle to OFF state
    {
    PWMDTY3 = (char) ((inpram.LUF_off*PWMPER3)/100);  // set PT3 to off state
    outpc.lock_TCC = tcc_unlock(); // set the flag for datalog
    }
  PWMDTY4 =    0;   // speedo output off
  PWMCNT1 = 0x00;   // clear counter (reset PWM)

  pTIC[1] = pTC0;
  ICintmask[1] = 0x01;
  
  
  // --- Set Up ADC Channels ---
  // Set up analog-digital converter (ADC) channels so they continuously convert, 
  // we will read one value in the ISR_timer_clock() routine every 25 milliseconds.
  
  /* USING THE HCS12 A/D CONVERTER 
    (* indicates the values we use for MShift)
     
     1. Set up which pins are analog/digital converters and which are digital
        inputs using the ATD0DIEN register, 1=digital intput, 0=ADC
     
     2. Power up A/D Converter (ADPU = 1 in ATD0CTL2)

     3. Select number of conversions per sequence (S8C S4C S2C S1C in ATD0CTL3)
          S8C S4C S2C S1C = 0001 to 0111 for 1 to 7 conversions
          S8C S4C S2C S1C = 0000 or 1xxx for 8 conversions

     4. Set up resolution in ATD0CTL4 position 7 (0-7)
         - For  8-bit mode ( 255 divisions) write 1 to ATD0CTL4 position 7
         - For 10-bit mode (1024 divisions) write 0 to ATD0CTL4 position 7
           
     5. Select DJM in ATD0CTL5 position 7
        (a) DJM = 0 => Left justified data in the result registers
        (b) DJM = 1 => Right justified data in the result registers *

     6. Select DSGN in ATD0CTL5 position 6
        (a) DSGN = 0 => Unsigned data representation in the result register *
        (b) DSGN = 1 => Signed data representation in the result register
        The Available Result Data Formats are shown in the following table:
        
        SRES8 DJM DSGN   Data Format
        1      0    0    8-bit/left justified/unsigned   - Bits 15-8
        1      0    1    8-bit/left justified/signed     - Bits 15-8
        1      1    X    8-bit/right justified/unsigned  - Bits  7-0
        0      0    0    10-bit/left justified/unsigned  - Bits 15-6
        0      0    1    10-bit/left justified/signed    - Bits 15-6
        0      1    X    10-bit/right justified/unsigned - Bits  9-0 *

     7. Select SCAN in ATD0CTL5 postion 5:
         - SCAN = 0: Convert one sequence, then stop
         - SCAN = 1: Convert continuously *

     8. Select MULT in ATD0CTL5 position 4:
         - MULT = 0: Convert one channel eight the specified number of times
         - Choose channel to convert with CC, CB, CA of ATD0CTL5.
         - MULT = 1: Convert across several channels. CC, CB, CA of ATD0CTL5
           is the first channel to be converted. *

     9. After writing to ATD0CTL5, the A/D converter starts, and the SCF bit
        is cleared. After a sequence of conversions is completed, the SCF flag in
        ATD0STAT0 is set. You can read the results in ATD0DRx [0-7]H.

     10. If SCAN = 0, you need to write to ATD0CTL5 to start a new sequence. If
         SCAN = 1, the conversions continue automatically, and you can read new
         values in ADR[0-7]H.

     11. To get an interrupt after the sequence of conversions are completed, set
         ASCIE bit of ATD0CTL2. After the sequence of conversions, the ASCIF bit
         in ATD0CTL2 will be set, and an interrupt will be generated.

     12. On HCS12 EVBU, AD0 channels 0 and 1 are used to determine start-up
         program (D-Bug12, EEPROM or bootloader). Do not use AD0 channels
         0 or 1 unless absolutely necessary (you need more thann 14 A/D
         channels).

     13. To interpret the result, ATD0DRx = (Vin - VRL) / (VRH - VRL) * 1024
         Normally, VRL = 0 V, and VRH = 5 V, so ATD0DRx = (Vin / 5 V) * 1024
         Example: ATD0DR0 = 351 => Vin = 448/1024 * 5.0 = 1.71 V
  */

/*  We want ADC on AD0 (swA), AD1 (swB), AD2 (temp), AD3 (swC), AD4 (line pressure), 
    and AD5 (non_CAN MAP) only. The rest are digital inputs.
     
switchA							 PAD00				GPI1     5
switchB							 PAD01				GPI2     6
Temp Sensor			     PAD02				GPI3		30
switchC							 PAD03				EGT3    25
line pressure sensor PAD04        EGT2    27
non-CAN MAP/TPS/MAF  PAD05        EGT1    24
Paddle DOWN			     PAD06        GPI5		 4
Brake Sense			     PAD07				GPI4     3

-- These will be sampled by the get_adc() function, one every 25 milliseconds in 
   the Timer_Clock_ISR() function 
*/
//   - PAD00: switchA
//   - PAD01: switchB
//   - PAD02: temp sense
//   - PAD03: switchC
//   - PAD04: line pressure
//   - PAD05: non-CAN MAP
//   - PAD06: paddle downshift
//   - PAD07: brake sense
  ATD0DIEN = 0xC0;  // 1=digital input, 0=ADC
                    // 0x3F = 11000000
                    // ADC on temperature sensor, line pressure sensor, optional MAP only
                    // 3 manual shift lever inputs
                    // rest are digital inputs
                    // read digital input pins from PORTAD0
  next_adc =    0;	// specifies next ADC channel to be read
  ATD0CTL2 = 0x40;  // leave interrupt disabled, set fast flag clear
  ATD0CTL3 = 0x00;  // do 8 conversions/ sequence
  ATD0CTL4 = 0x67;  // 10-bit resolution, 16 tic conversion (max accuracy),
                    // prescaler divide by 16 => 2/3 us tic x 18 tics
                    // 0x67 = 01100111
  ATD0CTL5 = 0xB0;  // right justified, unsigned, continuous conversion,
                    // sample 8 channels starting with AN0
                    // 0xB0 = 10110000
  ATD0CTL2 |= 0x80; // turn on ADC0
  
  // wait for ADC engine charge-up or P/S ramp-up
  for(ix = 0; ix < 160; ix++)  {
    while(!(ATD0STAT0 >> 7));	    // wait until conversion complete
    ATD0STAT0 = 0x80;
  }  // end for (ix = 0...
       
  first_adc = 0;
  adc_lmms = lmms;

	/* Set Up variable block addresses to be used for 
	    CAN communications */
  canvar_blkptr[0] = (char *)&inpram; // assign the address of inpram to canvar_blkptr[0]
  canvar_blkptr[1] = (char *)&outpc;  // assign the address of outpc to canvar_blkptr[1]
  canvar_blkptr[2] = (char *)msvar;   // assign the address of msvar to canvar_blkptr[2]
  
  for(ix = 3; ix < NO_VAR_BLKS; ix++)  {	 // rest are spares for now
    canvar_blkptr[ix] = 0;
  } // end for(ix =3 ...

  // Initialize outpc. variables
  flocker = 0;  
  last_LOAD = 1000;         // kpa x 10
  outpc.LOAD_short = 1000;  // kpa x 100
  outpc.LOAD_long = outpc.LOAD_short;
  outpc.speedo = 0;
  outpc.auto_mode = 1;
  outpc.manual_gear = 0;
  outpc.target_gear = 0;
  outpc.current_gear = 1;
  outpc.lock_TCC = tcc_unlock();
  outpc.PC_duty = 0;
  outpc.vss_teeth = 0;
  outpc.loop_count = 0;
  outpc.aux_volts = 100;
  outpc.os_rpm = 0;         // output shaft rpm = 0
  outpc.adv_deg = 0;        // ignition advance from MS-II

  /* Initialize CAN comms */
  can_reset = 0;
  can_id = inpram.mycan_id;
  can_varoff = 0;  
  CanInit();   

  // Make IC highest priority interrupt
  // On HCS12 processors, you can promote a single interrupt source to 
  // have the highest priority by writing the LSB address of the appropriate 
  // interrupt vector to the HPRIO register.
  // The interrupt priority has meaning only when two or more interrupt requests 
  // are being processed at the same time to determine which ISR will be 
  // executed first. Once a certain ISR has started to execute, the interrupt 
  // priority does NOT mean another interrupt can interrupt the already executing ISR.
  //   HPRIO = 0xEE;
  
// enable global interrupts
ENABLE_INTERRUPTS
  
// Restart PWM counters
PWMCNT1 = 0x00;
PWMCNT2 = 0x00;  // clear counter
PWMCNT3 = 0x00;  // clear counter
PWMCNT4 = 0x00;
PWME   |= 0x1E;  // enable PWM 1,2,3 & 4 (0x1E = 00011110)
                 // PT1 = 3/2 SOL, 
                 // PT2 = PC,
                 // PT3 = TCC - on/off, 
                 // PT4 = speedo output

// --------------------- MegaShift ----------------------------------------------------

// Initialize Switch Manifold Variables
// set to 3rd gear == 110, we will poll the switches immediately so this isn't crucial
outpc.swADC = 1000;   // (1000/1024)*5.0 Volts
outpc.swBDC = 1000;   // (1000/1024)*5.0 Volts
outpc.swCDC = 100;    //  (100/1024)*5.0 Volts
switchA = 1;
switchB = 1;
switchC = 0;

// Initialize deboune parameters (reinitialize when shifting gears)
switchAacc = inpram.debounce >> 1;   // right shift the bits 1 position 
switchBacc = inpram.debounce >> 1;   // this is the same as dividing by 2 and discarding the remainder)
switchCacc = inpram.debounce >> 1;   // for example 01100111 (decimal 103) becomes 00110011 (51)

// Calculate VSS divide factor
// do it here to reduce computations in main loop - changes require restart
// VSSdivide is the number of inches travel for 20 VSS teeth (x100)
// This is:
// 20 teeth is 20/no_teeth of an output shaft revolution
// and (20/no_teeth)/axle_ratio of a tire revolution
// or  (20/no_teeth)/axle_ratio*(tire_diam*3.1416) linear distance (in inches x100)
// tire diam is inchesx100, axle ratio is x1000, no_teeth is x1
// therfore 20*1000*PI * (tire_diam)/(axle_ratio * no_teeth)  (in inches x100)                                             

vss_divide = (62832 * (long) inpram.tire_diam)/(long) ((long) inpram.axle_ratio * (long) inpram.no_teeth); // inches x 100
                                                                            // or cm x 100
     // Ex. for 26" tire, 3.08 axle, 40 teeth, VSSdivide = 1326 = 13.26 inches per 20 teeth 

// Initialize local variables
LOAD =   100;
scount =   0;
lcount =   0;

// rev_mile = (12*5280*10000/314159)/(inpram.tire_diam/100)                                                 

if (!inpram.Metric_Units)  // rev/mile
  {  
  rev_distance = (int)(2016811/(long)inpram.tire_diam);
  }
else // rev/km
  {
  rev_distance = (int)(3183099/(long)inpram.tire_diam);
  }
// with 3.08 gears, 26" tires and 40 teeth, this should be 775 revs/mile

outpc.auto_mode = inpram.shift_mode; // start in user specified shift mode 
  
  
// Set pulse/mile speedo counters
//
// number of VSS pulses/mile is:
//
//   rev_distance * axle_ratio * no_teeth
//
//
// the speedo output total count is:
//
//   (VSS pulses/mile)/(speedo pulse mile)
//
//   
 ultmp1 = ((long)rev_distance * (long)inpram.axle_ratio)/1000;
 ultmp2 = (ultmp1 * (long)inpram.no_teeth)/(long)inpram.pulse_mile;
 speedo_total = (int) (ultmp2);
 // these are used in the VSS interrupt routine VSS_timer0 
 speedo_toggle = speedo_total/2; // we will create approximately 50% duty cycle signal
 speedo_counter = 0;             // initialize speedo-counter

 
// Ex. for 26" tire, 3.08 gears and 40 teeth, with a 2002 pulse/mile output, this is:
//   speedo_total = (775 * 3.080 * 40)/2002 =  47.692 => 47
//   speedo_toggle = 23


// Initialize switch de-bounce values
upperswitch = ((inpram.debounce * 90)/100);
lowerswitch = ((inpram.debounce * 10)/100);
UP_avg = 500; // will move up and down according to button status
DWN_avg = 500;
MODE_avg = 500;
high_mph = 0;

// Calculate Output3 (3/2 soln) duty cycle in PWM ticks 
dc_32 = (uchar) ((inpram.out3dc * PWMPER1)/100);

// Set up COP timeout for main loop
// COPCTL = 0x44;      // 01000100 = 2^20 OSCCLK cycles = 131 ms timeout for reboot
// COPCTL = 0x42;      // 01000010 = 2^16 OSCCLK cycles = 131 ms timeout for reboot
  
// Mshift MAIN LOOP starts here ---------------------------------------------------------------------------
// Start an infinite FOR loop to keep cycling through the main loop

MAIN_LOOP:
for (;;)  { // start main loop

// Error codes
outpc.error = 0x01; // always set first bit - error code must be odd otherwise 
                    // a comms error has occurred                      
/*
bit --  & with  --  meaning
---     ------      -------
 0  --   0x01   --  1 = okay, 0 = serial comms error
 1  --   0x02   --  0 = okay, 1 = no CAN comms (load = 0.0)
 2  --   0x04   --  0 = okay, 1 = under/over rev shift attempted
 3  --   0x08   --  0 = okay, 1 = VSS reset occurred
 4  --   0x10   --  0 = okay, 1 = other VSS error - no signal, excessive noisy/drop-outs (check tooth error counter)
 5  --   0x20   --  0 = okay, 1 = gear out-of-range
 6  --   0x40   --  0 = okay, 1 = low voltage (<10)
 7  --   0x80   --  0 = okay, 1 = no brake signal  
*/

error_flags = 0x80; // == 10000000 - set brakes flag, will clear when brake signal received

if (inpram.error_check && (outpc.seconds > 30))
   {
   if (inpram.CAN_enabled)
     {
      // CAN comms error
      if (outpc.CANrx < 5) outpc.error |= 0x02;   // set CAN error flag
      // Low voltage error
      if (outpc.vBatt < 100) outpc.error |= 0x40; // set low voltage flag
      }
    } // end if (inpram.error_check...

// VSS error 
if (VSS_error > inpram.vss_error_max) 
   {
   outpc.error |= 0x10; // set VSS tooth count reset error 
   VSS_reset();         // reset VSS
   }
       
outpc.loop_count++;     // increment main loop counter

outpc.dbug = VSS_error; // set dbug to VSS error count unless used for debugging elsewhere in code


// Capture the highest mph seen since last stop, use to determine if we ought
// to switch to auto_mode (otherwise we would swap immediately on selecting 
// first if stopped)
if (outpc.speedo > high_mph) 
  {
   high_mph = outpc.speedo; // capture the highest mph seen since last stop
  }
else
  {
   if (outpc.speedo < 10) high_mph = 0; // reset high MPH if have stopped
  }
  
// Clear COP timeout counter
// When COP is enabled, the program must write 0x0055 and 0x00AA (in this order) to the ARMCOP
// register during the selected time-out period. As soon as this is done, the COP time-out period
// is restarted. If the program fails to do this and the COP times out, the processor will reset. 
// Also, if any value other than 0x0055 or 0x00AA is written, the processor is immediately reset.
// ARMCOP = 0x0055;
// ARMCOP = 0x00AA;	

/* Check for error states */

if (outpc.manual_gear > NO_GEARS || outpc.manual_gear < -1) // if out of range
  {
   reset_gear();
  } //end if current gear out of range


   
  
/*-------------------------- DETERMINE CURRENT GEAR --------------------------------*/
// Determine current gear from state of SolA, SolB:
// (and set gear inidcator LEDs)

// In 1st, Sol states are in 4th bit of Output1 and Output2
// or (0x08 & Output1), (0x08 & Output2)
//
// All the gears are:
//
// Gear     SolA              SolB
// 1st (0x08 & Output1), (0x08 & Output2)
// 2nd (0x10 & Output1), (0x10 & Output2)
// 3rd (0x20 & Output1), (0x20 & Output2)
// 4th (0x40 & Output1), (0x40 & Output2)

// SolA is PE4 (on if SOLAst = 1), i.e. everywhere we change PE4, we also set SOLAst
// SolB is PM2 (on if SOLBst = 1), i.e. everywhere we change PM2, we also set SOLBst 

if (outpc.manual_gear > 0) // Shift lever in one of the forward drive positons
  { // check solA and sol B states 
    if ((SOLAst*0x08)==(0x08 & inpram.Output1) && (SOLBst*0x08)==(0x08 & inpram.Output2)) 
      {  
      outpc.current_gear = 1;
      // switch LED1 on
      *pPTMpin[4] |= 0x10;
      // switch LED2 off
      *pPTMpin[3] &= ~0x08;
      // switch LED3 off
      *pPTMpin[5] &= ~0x20;
      // switch LED4 off
      PORTB &= ~0x10;
      goto finish_gear_LEDs;
      }  // end if ((SOLAst*0x08)==
      
    if ((SOLAst*0x10)==(0x10 & inpram.Output1) && (SOLBst*0x10)==(0x10 & inpram.Output2))
      {
      outpc.current_gear = 2;
      // switch LED1 on
      *pPTMpin[4] |= 0x10;
      // switch LED2 on
      *pPTMpin[3] |= 0x08;
      // switch LED3 off
      *pPTMpin[5] &= ~0x20;
      // switch LED4 off
      PORTB &= ~0x10;
      goto finish_gear_LEDs;
      }  // end if if ((SOLAst*0x10)==
      
    if ((SOLAst*0x20)==(0x20 & inpram.Output1) && (SOLBst*0x20)==(0x20 & inpram.Output2))
      {
      outpc.current_gear = 3;
      // switch LED1 on
      *pPTMpin[4] |= 0x10;
      // switch LED2 on
      *pPTMpin[3] |= 0x08;
      // switch LED3 on
      *pPTMpin[5] |= 0x20;
      // switch LED4 off
      PORTB &= ~0x10;
      goto finish_gear_LEDs;
      }  // end if ((SOLAst*0x20)==
      
    if ((SOLAst*0x40)==(0x40 & inpram.Output1) && (SOLBst*0x40)==(0x40 & inpram.Output2))
      {
      outpc.current_gear = 4;
      // switch LED1 on
      *pPTMpin[4] |= 0x10;
      // switch LED2 on
      *pPTMpin[3] |= 0x08;
      // switch LED3 on
      *pPTMpin[5] |= 0x20;
      // switch LED4 on
      PORTB |= 0x10;
      goto finish_gear_LEDs;
      } // end if ((SOLAst*0x40)==  
  }
else // else (outpc.manual_gear
     // park, reverse, neutral, use manual gear position
     // note the neutral and park are the same signal from the switch manifold
     // for the 4L60E
     // and the code has not been genralized to differentiate these
  {
   outpc.current_gear = outpc.manual_gear;
   
   // Must set SolA, SolB, Sol32 (according to output1,2,3)
   if (outpc.current_gear == 0) 
    {
      
      // switch Sol A
      // SolA state in first is 3rd bit in Output1 
      // (AND with 00000010 = 0x02 to determine if is set to 1), then divide by 0x02 to make == 1
      solA((0x02 & inpram.Output1)>>1);  // if 1 in Output1, then send 1 to turn solA on
            
      // switch Sol B
      // Sol B state in 1st set in 4th bit of Output2
      // to set, AND with 0x02 and divide by 0x02 to make 1
      solB((0x02 & inpram.Output2)>>1);  // if 1 in Output2, then send 1 to turn solB on
             
      // Set 3/2 Sol PWM
      // 3/2 sol state in 1st set in 4th bit of Output3
      // to set, AND with 0x02 and divide by 0x02 (right shift by 1) to make 1 
      PWMDTY1 = dc_32*((0x02 & inpram.Output3)>>1);
      
      // Set third bit of sol state for datalog
      if (PWMDTY1) 
        {
        outpc.solst |= 0x04;
        } 
        else 
        {
        outpc.solst &= ~0x04;
        }   // end if (PWMDTY1)

    } //end if current_gear = 0

   if (outpc.current_gear == -1) 
    {
      
      // switch Sol A
      // SolA state in first is 3rd bit in Output1 
      // (AND with 00000100 = 0x04 to determine if is set to 1)
      solA((0x04 & inpram.Output1)>>2);  // if 1 in Output1, then send 1 to turn solA on
            
      // switch Sol B
      // Sol B state in 1st set in 4th bit of Output2
      // to set, AND with 0x04 and divide by 0x04 to make 1 
      solB((0x04 & inpram.Output2)>>2);  // if 1 in Output2, then send 1 to turn solB on
           
      // Set 3/2 Sol PWM
      // 3/2 sol state in 1st set in 4th bit of Output3
      // to set, AND with 0x04 
      PWMDTY1 = dc_32*((0x04 & inpram.Output3)>>2);   // set Sol32 PWM duty to 90% in 2nd, 3rd, 4th
      
      // Set third bit of sol state for datalog
      if (PWMDTY1) 
        {
        outpc.solst |= 0x04;
        } 
        else 
        {
        outpc.solst &= ~0x04;
        } // end if (PWMDTY1)
        
    } //end if current_gear = -1
    
   // switch LED1 off
   *pPTMpin[4] &= ~0x10;
   // switch LED2 off
   *pPTMpin[3] &= ~0x08;
   // switch LED3 off
   *pPTMpin[5] &= ~0x20;
   // switch LED4 off
   PORTB &= ~0x10;
 
  } // end else PNR

  finish_gear_LEDs: // break out of gear determination/LED setting


/*-- UPDATE ROLLING LOAD AVERAGE 

The LOAD average is kept on two levels:

- a short term LOAD average (LOAD_short) created by adding the 
  difference between the current LOAD_short and measured LOAD to the previous LOAD 
  short term average (updated every ), used to smooth 
  the LOAD signal, HOWEVER the load averaging is only for decreasing loads
  
- a long term LOAD average (LOAD_long), used to detrermine driving mode 
  created by adding 1/LOADlongcount of the difference between the 
  current LOAD_short and LOAD_long to the previous LOAD_long term average 
  (updated everytime through the main loop) IF braking is not applied  

The LOAD averages are used to determine the 'aggressiveness' of the driving, 
and hence the shift strategy. Higher values indicate more agressive driving. 
This is not currently implemented, but the intent is to add a factor to the 
short term load average based on the long term average, perhaps:

LOAD_short = LOADshort + LOAD_long/10;

or possibly a look-up table value based on LOAD_long

 --*/
 
 scount++; // incrememnt short count each time through loop
     
 if (scount >= (inpram.LOADshortcount)) { // skip until reach LOADshortcount
  // reset short counter
  scount = 0;
  // increment long counter
  lcount++; // incerement lcount (long count) 

   if (lcount > 65000) lcount = 65000; // rail the lcount value           
    
   // Update short LOAD - only if not braking
  
//   if (outpc.brake == 0 ) { 
       // LOADshort is kpa x 100
       if ((outpc.LOAD) < outpc.LOAD_short) // if current LOAD is less than average 
        {
         outpc.LOAD_short -= (outpc.LOAD_short-outpc.LOAD)/10; // lower the average by 1/10th the difference
        }
       else // if current LOAD is higher than average
        {
        outpc.LOAD_short = outpc.LOAD; // jump to current LOAD     
        } // end if ((outpc.LOAD) < ...
//       };  // End If brake == 0
 
   // When long count reached, update LOAD_long from LOAD_short
   if (lcount>(inpram.LOADlongcount-1)) { // skip until reach LOADlongcount
      // reset long counter
      lcount = 0;
      // Update long LOAD average  
      outpc.LOAD_long += (outpc.LOAD_short-outpc.LOAD_long)/10;
  
      } // End If (lcount > LOADlongcount-1)
 
   }  // End If (scount > (LOADshortcount))


/*-- UPDATE ROLLING SHIFT SWITCH INPUTS 

The switch average is:

- upshift button average (UP_avg) is calculated by adding or subtracting 1 
 (depending on the input state) to the previous UP_avg. This is updated 
 every time through the main loop (so it is NOT updated during a shift - by design!).
  
- downshift button average (DWN_avg) is calculated by adding adding or 
  subtracting 1 (depending on the input state) to the previous DWN_avg. This is 
  updated everytime through the main loop.

A shift occurs when the button average exceeds the threshold value - assuming 
'manual mode' is selected, both are not pressed, etc. (this provides a software debounce, 
and sets the 'responsiveness' of the shift command)

 --*/

  // -------------------- Check Shift Buttons ------------------------------------------------ 

  /* Notes: PTT is defined in hcs12def.h and is memory 
  adresss (*((volatile unsigned char*)(0x0240)))
  where "volatile unsigned char" means that this is 
  an 8 bit postive only value (unsigned char) that 
  might be changed externally to the code logic ("volatile"),
  such as by an interrupt.
            
  By 'ANDing' PTT with 0x40, we are comparing 
  PTT to 0x40 (which is hex, the binary is 01000000 - 8 bits)
  This compares the current values bit by bit with 
  01000000, and set the corresponding PTT bit to
  1 if both are 1, otherwise it is set to zero. operations 
  like this are called 'bitwise'.
                    
  The net result is that since bit 6 (they are numbered 0 
  to 7 from left to right) is one it will be ANDed to 1 
  if bit 6 in PTT is one, since the rest of the ANDs will 
  be zero (anything ANDed with zero is zero). So the result is
  true (==1) if bit 6 is 'on', and false (==0) if bit 6 is 'off'.
      
  Note that we can AND or OR. AND is usually used to check a 
  bit state. Both AND and OR are used to set bit states.
       
  For example, if we want to set port B pin 3 high, we would use:
       
  PORTB |= 0x08; (i.e., if either PORTB or 000001000 are bitwise one, set that bit to one)
      
  To turn the same bit off, we use:
       
  PORTB &= ~0x08; (i.e., if both PORTB and ~00001000 == 11110111 are one, set to one, 
                  set to zero otherwise. Since the #3 bit of ~0x08 is never one, 
                  this sets the corresponding PORTB bit to zero)
  */
  
  // UP button is PT6, DWN button is PORTAD0 pin 6
    
  if ((PTT & 0x40) || (PORTAD0 & 0x40)) // pin PT6 or PORTAD0 not grounded/pressed
                                        // 0x04 = 01000000, is only true whenever pin 6 is equal to one 
                                        // (since ANDing with zero is always zero)
    {
     if ((PTT & 0x40) && !(PORTAD0 & 0x40)) // down shift button pressed
      {
      if (UP_avg   > 0  )   UP_avg--;  // decrement upshift average
      if (DWN_avg  < 19999)  DWN_avg++; // increment downshift average
      if (MODE_avg > 0  ) MODE_avg--;  // decrement mode change average
      outpc.upbutton   = 0;            // set shift states for MT
      outpc.downbutton = 1; 
      
      }
     if (!(PTT & 0x40) && (PORTAD0 & 0x40)) // upshift button pressed
      {
      if (UP_avg   < 19999) UP_avg++; // decrement upshift average
      if (DWN_avg  > 0)  DWN_avg--;  // increment downshift average
      if (MODE_avg > 0) MODE_avg--;  // decrement mode change average
      outpc.upbutton   = 1;          // set shift states for MT
      outpc.downbutton = 0; 
      
      }
    }
   else // both buttons pressed
    {
      if (UP_avg   > 0)     UP_avg--;  // decrement upshift average
      if (DWN_avg  > 0)    DWN_avg--;  // increment downshift average
      if (MODE_avg < 19999) MODE_avg++; // decrement mode change average
      outpc.upbutton   = 1;            // set shift states for MT
      outpc.downbutton = 1;   
     
    }       
   
    if ((PTT & 0x40) && (PORTAD0 & 0x40)) // neither pressed (not shifting and not setting mode)
    {
    if (UP_avg   > 0)   UP_avg--; // decrement upshift average
    if (DWN_avg  > 0)  DWN_avg--; // decrement downshift average
    if (MODE_avg > 0) MODE_avg--; // decrement mode change average 
    outpc.upbutton   = 0;         // set shift states for MT
    outpc.downbutton = 0; 
     
    }
          
  // Rail the shift button counts
  if (UP_avg  > 19999)    UP_avg  = 19999;     // rail the UP_avg count
  if (DWN_avg > 19999)   DWN_avg  = 19999;     // rail the DWN_avg count 
  if (MODE_avg > 19999) MODE_avg  = 19999;     // rail the DWN_avg count
  
  //-- Act on switch states --------------------------------------------

  // check states of both buttons first to allow mode changes
  if (MODE_avg > (3*inpram.shftr_accum))      // if both buttons have been pressed for a while
    {
    if (outpc.auto_mode == 0)                 // if not auto shifting
      {
       if (inpram.shift_mode>0) // if default is an auto mode
        {
        outpc.auto_mode = inpram.shift_mode;  // set to default
        } // end if inpram.shift_mode>0
       else
        {
        outpc.auto_mode = 1;   // otherwise set to sequential auto mode
        } // end else if inpram.shift_mode>0
       outpc.upshift_request   = 0;           // reset switches
       outpc.downshift_request = 0;      
      } // end if auto_mode == 0
    } // end if UP_avg ...

  // Check upshift  
  if (UP_avg > (DWN_avg+inpram.shftr_accum))           // if we have 400 more ups than downs, request upshift                          
    {
       // request shift
       outpc.upshift_request   = 1;      // if UP_avg >> UP threshold, request upshift
       outpc.downshift_request = 0;
       if (outpc.auto_mode > 0) outpc.auto_mode = 0; // switch to manual mode
    } // end if UP_avg > DWN_avg ...
    
  // Check downshift next
  if (DWN_avg > (UP_avg+inpram.shftr_accum))           // if we have 400 more downs than ups, request downshift
   {
      // request shift  
      outpc.upshift_request   = 0;      
      outpc.downshift_request = 1;       // if UP_avg >> UP threshold, request downshift
      if (outpc.auto_mode > 0) outpc.auto_mode = 0; // switch to manual mode
    } // end if DWN_avg > UP_avg ...

  if ((UP_avg < 20) && (DWN_avg < 20) && (MODE_avg < 20)) // if no buttons pressed
    {
      // if speed is less than 12 mph and haven't shifted to first in 2WD, 
      // and have actually moved (exceeded 20 mph)
      // set mode to default
      if ((outpc.speedo < 120) && (outpc.current_gear > 1)  && (high_mph > 200) && (outpc.FWD)) 
        {
         if (inpram.shift_mode > 0)  // if default mode is auto
          {
            outpc.auto_mode = inpram.shift_mode; // set to default
          }
         else // default mode is manual
          {
          outpc.auto_mode = 1; // set to auto sequential shift
          }
         
         // Delay to prevent oscillation 
         waitAwhile(5*(inpram.pressure_delay+inpram.shift_delay));
          
         // if have not received a brake signal, set flag
         if (error_flags & 0x80) outpc.error |= 0x80; // set bit 7 in error code if flag has 
                                                      // not been cleared by brake signal
         }   // end IF outpc.speedo < 120
    }   // end IF UP_avg < 20
    

  
if (!(dither_flag & 0x01)) {        // if not (!='not') (8-bit dither_flag ANDed with 00000001)  
  /*-- SET_PRESSURE_CONTROL_PWM Duty Cycle ------------------------------------*/
  // The Pressure Control solenoid controls main line pressure, pulsed at 
  // 0-60% duty cycles at 293Hz.
  //
  // Pressure control modulation percent varies from 0% at WOT to 60% at idle.
  //
  // Note that INI uses 100-PC_DC, so:
  //  - 60% PC_DC  (lowest pressure) in the code is specified as  40% in tuning software, 
  //  -  0% PC_DC (highest pressure) in the code is specified as 100% in tuning software.  

  // if gear=neutral, set pressure control PWM% to PCneutral%

  if (outpc.manual_gear == 0) { 

    PWMDTY2 =  (unsigned char) ((inpram.PCneutral*PWMPER2)/100);     // set PC PWM duty to PCneutral% in neutral (max bleed - lowest pressure)
                                                                     // default PWMPER2 is 244
    }   // End if  (outpc.current_gear == 0)

  if (outpc.manual_gear > 0) // Forward gears, reverse is below
   {   // In gear
  
    // Look-up pressure control solenoid (PC) duty cycle in pc_table[12x12] and set tempPC1
    // as a fractional count of PWMPER2 (tempPC1 is the look-up value, tempPC2 is the 
    // voltage/temperature adjustment, these are added to get the duty cycle count)
  
    tempPC  = 0; // variable to hold PC PWM % until all calcs done
    tempPC1 = 0; // reinitialize tempPCx
    tempPC2 = 0;
    
    tempPC1 = (uchar)((intrp_2dctable((uint)outpc.speedo/10, (int)outpc.LOAD/10, // in 'tics'
                                    (uchar)NO_MPH, (uchar)NO_LOADS, 
                                    (uint *)&inpram.vss_table[0], 
                                    (int *)&inpram.LOAD_table[0], 
                                    (uchar *)&inpram.pc_table[0][0]) * PWMPER2)/100); 

    // ------------- PC Voltage Correction -------------------------------------------------
    // The voltage difference is (120-vBatt)/10, since vBatt is x10
    // the correction is the voltage difference multiplied by the 
    // battery correction factor inpram BatFac/10
    // Note that BatFac is negative in INI, but positive in this code.
    // We move the divisors around a bit to avoid over/underflows.
                                                                                                                     
    if (outpc.vBatt < 120)                           // if less than 12.0 Volts, decrease 
                                                     // the DC (increase pressure), 
                                                     // base is 12.0 volts x 10
      {
      tempPC2 = (long) (((120 - outpc.vBatt)*inpram.BatFac)/100); // adjust for battery supply voltage less than 12 volts                                
      tempPC2 = (tempPC2*PWMPER2)/100;                            // and convert to number of PWM tics  

      if ((tempPC1-tempPC2) > 0)                       // make sure result will be positive
        {   
        tempPC = (uchar)(tempPC1 - (char) tempPC2);   // set the value by subtracting DC (increase pressure)
        } 
      }
    else  // battery voltage greater than 12.0 V
      {
      tempPC2 = (long) (((outpc.vBatt - 120)*inpram.BatFac)/100); // adjust for battery supply voltage greater than 12 volts
      tempPC2 = (tempPC2*PWMPER2)/100;                            // and convert to number of PWM tics

      if ((tempPC1+(char) tempPC2) < PWMPER2)          // make sure we don't try to set PWM DC to greater than 100%
        { 
        tempPC = (uchar)(tempPC1 + tempPC2);          // set the value by adding DC (decrease pressure) 
        }
      }; // end if (outpc.vBatt <= 120)
                                                                                                                                                    
     tempPC2 = 0; // reinitialize
   
    // ------------- PC Temperature Correction -------------------------------------------------
    // From PCtemp[] table
    // We move the divsors around a bit to avoid over/underflows.
                                                                                                                     
    if (inpram.stdin_cfg & 0x02) tempPC2 = intrp_1dctable(0,outpc.clt,NO_TEMPS,inpram.temp_table,inpram.PCtemp)* PWMPER2/100; // 1-D table lookup
    
    // 1D Interpolation Routine
    // intrp_1dctable(char sgnx, int x, unsigned char n, int * x_table, char * z_table)
    // where: 
    // sgnx     = 
    // x        = the index value for the lookup
    // n		    = the number of rows in (size of) the lookup table
    // x_table  = the name of the lookup table
    // z_table  = the 'looked up' values
    // return the interpolated value as an integer

      if ((tempPC-tempPC2) >= 0)                      // make sure result will be positive
        {  
        tempPC = (uchar)(tempPC - tempPC2);           // set the value by subtracting DC (increase pressure)
        }
      else                                            // result less than zero
        {
        tempPC = 0;                                   // do not let value go negative (or wrap around)
        }                                                                                                                                                   
      }   // End if (outpc.manual_gear > 0)


  // Set maximum line pressure (minimum current) in reverse/park (no bleed) OR if engine not running

  if ((outpc.manual_gear < 0) || ((inpram.CAN_enabled || ((inpram.ic2_usage & 0x02) > 1)) && (outpc.engine_rpm < 300))) 
     {
     PWMDTY2 = 0;  
     } 
     else if (outpc.manual_gear > 0)  // did neutral above
     {
     // set to the calculated value
     PWMDTY2 = tempPC;
     }  // end if (outpc.manual_gear < 0
  
  // Now convert the PWMDTY2 to line pressure for calcs and datalog.
  // we only do this here so that intermediate results of voltage and temperature
  // corrections do not show in datalogs.

  outpc.PC_duty = (unsigned char)(100-((PWMDTY2*100)/PWMPER2)); // set datalog value (100-value to have datalog value same as PC table)                                                              // input/table values are 0-60%  - adjusted by MT                               
  }        // end if !dither_flag



/*-- Check if Need to Switch to Auto Shift Mode -----------------------------------*/
  // If rpm_check && under_rev_limit > engine_rpm > rpm_limit switch to auomode
  
  if (inpram.rpm_check > 0)                   // if rpm checking not disabled
    {
    if ((outpc.engine_rpm < inpram.under_rev_limit) && (outpc.current_gear > 1))
      {
      if (inpram.shift_mode > 0)
        {
        outpc.auto_mode = inpram.shift_mode;   // choose default if auto
        }
      else
        {
        outpc.auto_mode = 1;                   // otherwise set to auto sequential
        }
      } // end if outpc.engine_rpm ...
 
    if (outpc.engine_rpm > inpram.rpm_limit)   // over rev - so switch to auto mode
      {    
      if (inpram.shift_mode > 0)
        {
        outpc.auto_mode = inpram.shift_mode;   // choose default if auto
        }
      else
        {
        outpc.auto_mode = 1;                   // otherwise set to auto sequential
        }
      } // end if outpc.engine_rpm > ...

    } // end if inpram.rpm_check ...
    


/*-- Set the Torque Converter Clutch (TCC) ----------------------------------------*/
  // if  current_gear > minTCC_gear && clt > noTCC_temp
  // We will check brakes in function) call.
  if ((outpc.clt >= inpram.noTCC_temp) || (inpram.stdin_cfg & 0x02)) // if greater than temp or if temp disabled
    { 
     if (outpc.current_gear >= inpram.minTCC_gear)
       {  //  if LOAD_short < noTCC_load then lock_TCC on PT3
       if (outpc.LOAD_short <= inpram.noTCC_load)
        {
        outpc.lock_TCC = tcc_lock(); // function call to lock TCC, returns 1 (unless brakes on) 
        }
        else    // else unlock TCC
        {
        outpc.lock_TCC = tcc_unlock(); // function call to unlock TCC, returns 0
        } // end if LOAD   
      } // end if current gear >= ...
    } // end if clt >= ...

  // if Trans_Temperature > trans_temp_limit (always lock TCC above min gear if trans is hot)
  if ((outpc.current_gear >= inpram.minTCC_gear) && ((outpc.clt >= inpram.trans_temp_limit) || (inpram.stdin_cfg & 0x02)))
   {
      // lock TCC
      outpc.lock_TCC = tcc_lock();  // function call to unlock TCC, returns 1 (unless brakes on)
   }; // end if ((current_gear ... 


//----- Set SOL32 PWM Duty Cycle -------------------------------------------------
// This is user configurable. Set based on bits in Output3 (x4321RNP)
// For 4L60E, it is on above 1st, so 01110000 = 112 


  switch (outpc.current_gear)
    {
        
    case -1: // reverse
      PWMDTY1 = dc_32*(0x01 & inpram.Output3);
      break;
  
    case 0: // neutral
      PWMDTY1 = dc_32*((0x02 & inpram.Output3)>>1);
      break;
      
// Note we do not check P (& 0x04), since it is assumed to be the same as N      

    case 1: // first
      PWMDTY1 = dc_32*((0x08 & inpram.Output3)>>3);
      break;
      
    case 2: // second
      PWMDTY1 = dc_32*((0x10 & inpram.Output3)>>4);
      break;
      
    case 3: // third
      PWMDTY1 = dc_32*((0x20 & inpram.Output3)>>5);
      break;
  
    case 4: // fourth
      PWMDTY1 = dc_32*((0x40 & inpram.Output3)>>6);     
      break;
    
    } //end switch

 // Set third bit of sol state for datalog
 if (PWMDTY1) 
   {
   outpc.solst |= 0x04;
   } 
   else 
   {
   outpc.solst &= ~0x04;
   }

/**** Determine Manual Shift Lever Position *********************************
; Read Switches switchA, switchB, switchC and lookup manual_gear 
; i.e., the position of the manual valve - so if we get a 3, then gear can set gear 
; to 1, 2, or 3 but not 4, etc.
; The pressure switch manifold (PSM) is a multiple switch assembly consisting of 
; 3 normally open (NO) pressure switches and 2 normally closed (N/C) pressure switches.
; Fluid from various hydraulic control circuits is fed to this the pressure switch 
; manifold which allows the ECU to determine which gear the transmission is currently 
; shifted into. The switch contacts are normally open and close when fluid pressure 
; causes them to. Depending upon the circuit, the switch may provide a ground path 
; when closed. The table below shows a pin that is grounded by the PSM as a "0", 
; while an open circuit shows a 1 = 12 volt pullup.
; Set LEDs
; If manual_switch_off then goto auto-shift mode (shift_mode=1)
--*/

// Each time through the main loop, determine switch states to find 
// the current manual gear

// For the 4L60E:
//------------------------ 4 3 2 1 R N P 
// 115,      // Input1 =   1 1 1 0 0 1 1 (pressure switch manifold switch A)
// 56,       // Input2 =   0 1 1 1 0 0 0 (pressure switch manifold switch B)
// 31        // Input3 =   0 0 1 1 1 1 1 (pressure switch manifold switch C)

// Switch                Port         Circuit   AMPseal
// ------                ----         -------   -------
// switchA							 PAD00				GPI1        5
// switchB							 PAD01				GPI2        6
// switchC							 PAD03				EGT3       25

// SwitchA is high if swADC > digital_threshold
// SwitchB is high if swBDC > digital_threshold
// SwitchC is high if swCDC > digital_threshold

// ******** Condition SWx inputs ********************************
// Set switchAacc, switchBacc, switchCacc (Switch A accumulator) to mid point 
// of user range (inpram.debounce) on start up or after a shift
// then increment/decrement by one each time through the loop 
// until all are <10 or >90% of debounce value
// then set switchA, switchB, switchC to zero if <10% and 1 if >90%

FIND_GEAR:    // If manual gear is in error state (99), loop to here until we get to current gear

prev_gearlever = outpc.manual_gear;  // store current manual gear position - will check later to see if lever has been 'shifted' 

if (inpram.mlever_mode == 0) // If GM scheme - 3 inputs
{ 
   // Note that switch A is different from switch B and C in that it is ADC based (a value from 0 to 1023 = 0 to 5 Volts) 
   // based on increasing voltage at the pin). So instead of checking to see if it is ON or OFF, we check to see 
   // if it is < digital_threshold - then it is assumed 0
   // if it is > digital threshold - then it is assumed 1
   // must compare to user pattern

   if (outpc.swADC > inpram.digital_threshold) 
      {     
       if (switchAacc < 255) switchAacc = switchAacc + 1;   // most programmers would write this as switchA += 1, or switchA++
      }  // End if if (swADC>digital_threshold)
      else 
      {
       if (switchAacc > 0) switchAacc = switchAacc - 1;
       }  // End else (swADC>digital_threshold)

   if (outpc.swBDC > inpram.digital_threshold) 
      { // Note that '&' is bitwise, '&&' is relational (i.e., the entire value is compared)
       if (switchBacc < 255) switchBacc = switchBacc + 1;
      }  // End if (swBDC>digital_threshold)
      else 
      {
       if (switchBacc > 0) switchBacc = switchBacc - 1;
       } // End else (swBDC>digital_threshold)

   if (outpc.swCDC > inpram.digital_threshold) 
      {    
       if (switchCacc < 255) switchCacc = switchCacc + 1;
      }   // End if (swCDC>digital_threshold)
      else 
      {
       if (switchCacc > 0) switchCacc = switchCacc - 1;
      }  // End else (swCDC>digital_threshold)

   // Check if each switch is <10% or >90%, rest to mid-way point if it is
   if (switchAacc > upperswitch) 
     {
       switchA = 1;
       switchAacc = inpram.debounce >> 1; 
     };
   if (switchAacc < lowerswitch)
     {
       switchA = 0;
       switchAacc = inpram.debounce >> 1; 
     };
   if (switchBacc > upperswitch) 
     {
       switchB = 1;
       switchBacc = inpram.debounce >> 1; 
     };
   if (switchBacc < lowerswitch)
     {
       switchB = 0;
       switchBacc = inpram.debounce >> 1; 
     };
   if (switchCacc > upperswitch) 
     {
       switchC = 1;
       switchCacc = inpram.debounce >> 1; 
     };
   if (switchCacc < lowerswitch)
     {
       switchC = 0;
       switchCacc = inpram.debounce >> 1; 
     };

   // Determine the shift lever position.
   // The values for the 4L60E are:
   //---------------------- 4 3 2 1 R N P 
   // Input1 = switchA  =   1 1 1 0 0 1 1 (pressure switch manifold switch A)
   // Input2 = switchB  =   0 1 1 1 0 0 0 (pressure switch manifold switch B)
   // Input3 = switchC  =   0 0 1 1 1 1 1 (pressure switch manifold switch C)
   // but the code will match whatever is put into Input1, Input2, and Input3
   // to find the manual gear lever position.
   // The bit offsets are:
   // P is 0x01
   // N is 0x02
   // R is 0x04
   // 1 is 0x08
   // 2 is 0x10
   // 3 is 0x20
   // 4 is 0x40

   // Check Neutral/Park - 101 for 460E
   if (    (inpram.Input1 & 0x01) == switchA 
        && (inpram.Input2 & 0x01) == switchB
        && (inpram.Input3 & 0x01) == switchC)
     {
     outpc.manual_gear = 0;
     outpc.lock_TCC = tcc_unlock(); // unlock TCC
     goto GOT_CGEAR;
     }

   // Check First - 011 for 4L60E
   if (    (inpram.Input1 & 0x08) == (switchA * 0x08) // 4th bit of Input1 compared 
                                                      // to switch A (multiply by 0x08 so 
                                                      // we can compare the whole byte)
                                                      // I.e. if switchA = 0, switchA * 0x08 = 00000000
                                                      //      if switchA = 1, switchA * 0x08 = 00001000
                                                      // Compare with Input1 & 0x08,
                                                      // I.e., 00001000 if Input1 4th bit for first gear is one
                                                      //       00000000 if Input1 4th bit for first gear is zero
        && (inpram.Input2 & 0x08) == (switchB * 0x08)
        && (inpram.Input3 & 0x08) == (switchC * 0x08))
     {
     outpc.manual_gear = 1;
     outpc.lock_TCC = tcc_unlock(); // unlock TCC
     goto GOT_CGEAR;
     }
  
   // Check Second - 111 for 4L60E
   if (    (inpram.Input1 & 0x10) == (switchA * 0x10) 
        && (inpram.Input2 & 0x10) == (switchB * 0x10)
        && (inpram.Input3 & 0x10) == (switchC * 0x10))
     {
     outpc.manual_gear = 2;
     goto GOT_CGEAR;
     }  

   // Check Third - 110 for 4L60E
   if (    (inpram.Input1 & 0x20) == (switchA * 0x20) 
        && (inpram.Input2 & 0x20) == (switchB * 0x20)
        && (inpram.Input3 & 0x20) == (switchC * 0x20))
     {
     outpc.manual_gear = 3;
     goto GOT_CGEAR;
     } 

   // Check Fourth - 001 for 4L60E
   if (    (inpram.Input1 & 0x40) == (switchA * 0x40) 
        && (inpram.Input2 & 0x40) == (switchB * 0x40)
        && (inpram.Input3 & 0x40) == (switchC * 0x40))
     {
     outpc.manual_gear = 4;
     goto GOT_CGEAR;
     } 
  
   // Check Reverse - 100 for 4L60E
   if (    (inpram.Input1 & 0x04) == (switchA * 0x04) 
        && (inpram.Input2 & 0x04) == (switchB * 0x04)
        && (inpram.Input3 & 0x04) == (switchC * 0x04))
     {
     outpc.manual_gear = -1;
     goto GOT_CGEAR;
     } 
   
   // Check for no gear - 000 for 4L60E
   // If no gear found, none of the above gotos were activated, so start over
     goto FIND_GEAR;
   }  // end if mlever_mode == 0

else // if voltage based manual gear lever position (Ford)
   {
   // Refresh the ADC count.
   // Check ADC versus user values (use 1/2 way between 
   // next lower and next higher values as thresholds.
   // Must get same gear twice in a row to break out of loop.
   
   FIND_ADC:

   // Find next higer and lower gear position ADC counts
   
   higherADC=1023; //initialize
   lowerADC=0;
   
   // Check each gear to find next higer and next lower ADC counts
   
   if (inpram.mlever_vP > outpc.swADC) // greater than
      {
      if (inpram.mlever_vP < higherADC) higherADC=inpram.mlever_vP;
      }

   if (inpram.mlever_vP < outpc.swADC) // less than
      {
      if (inpram.mlever_vP > lowerADC) lowerADC=inpram.mlever_vP;
      } 
 
   if (inpram.mlever_vR > outpc.swADC) // greater than
      {
      if (inpram.mlever_vR < higherADC) higherADC=inpram.mlever_vR;
      }

   if (inpram.mlever_vR < outpc.swADC) // less than
      {
      if (inpram.mlever_vR > lowerADC) lowerADC=inpram.mlever_vR;
      } 
    
   if (inpram.mlever_vN > outpc.swADC) // greater than
      {
      if (inpram.mlever_vN < higherADC) higherADC=inpram.mlever_vN;
      }

   if (inpram.mlever_vN < outpc.swADC) // less than
      {
      if (inpram.mlever_vN > lowerADC) lowerADC=inpram.mlever_vN;
      }

   if (inpram.mlever_v4 > outpc.swADC) // greater than
      {
      if (inpram.mlever_v4 < higherADC) higherADC=inpram.mlever_v4;
      }

   if (inpram.mlever_v4 < outpc.swADC) // less than
      {
      if (inpram.mlever_v4 > lowerADC) lowerADC=inpram.mlever_v4;
      }   

   if (inpram.mlever_v3 > outpc.swADC) // greater than
      {
      if (inpram.mlever_v3 < higherADC) higherADC=inpram.mlever_v3;
      }

   if (inpram.mlever_v3 < outpc.swADC) // less than
      {
      if (inpram.mlever_v3 > lowerADC) lowerADC=inpram.mlever_v3;
      }

   if (inpram.mlever_v2 > outpc.swADC) // greater than
      {
      if (inpram.mlever_v2 < higherADC) higherADC=inpram.mlever_v2;
      }
   
   if (inpram.mlever_v2 < outpc.swADC) // less than
      {
      if (inpram.mlever_v2 > lowerADC) lowerADC=inpram.mlever_v2;
      }
      
   if (inpram.mlever_v1 > outpc.swADC) // greater than
      {
      if (inpram.mlever_v1 < higherADC) higherADC=inpram.mlever_v1;
      }
   
   if (inpram.mlever_v1 < outpc.swADC) // less than
      {
      if (inpram.mlever_v1 > lowerADC) lowerADC=inpram.mlever_v1;
      }
                  

   // First
         if (((outpc.swADC < (inpram.mlever_v1+higherADC)/2)) && ((outpc.swADC > ((inpram.mlever_v1+lowerADC)/2))))
         {
         if (outpc.manual_gear != 1) 
            {
            outpc.manual_gear=1;       // set the gear
            goto FIND_ADC;             // loop to see if it is the same range
            } 
         else goto GOT_CGEAR;
         }
   // Second
         if (((outpc.swADC < (inpram.mlever_v2+higherADC)/2)) && ((outpc.swADC > ((inpram.mlever_v2+lowerADC)/2)))) 
         {
         if (outpc.manual_gear != 2) 
            {
            outpc.manual_gear=2;
            goto FIND_ADC; 
            } 
         else goto GOT_CGEAR;
         }
          
   // Third
         if (((outpc.swADC < (inpram.mlever_v3+higherADC)/2)) && ((outpc.swADC > ((inpram.mlever_v3+lowerADC)/2))))
         { 
         if (outpc.manual_gear != 3) 
            {
            outpc.manual_gear=3;
            goto FIND_ADC; 
            } 
         else goto GOT_CGEAR;
         }
     
   // Fourth
         if (!inpram.swBDC_OD)  // if not using separate OD switch
         { 
         if (((outpc.swADC < (inpram.mlever_v4+higherADC)/2)) && ((outpc.swADC > ((inpram.mlever_v4+lowerADC)/2))))
               { 
               if (outpc.manual_gear != 4 ) 
                 {
                 outpc.manual_gear=4;
                 goto FIND_ADC; 
                 } 
                 else goto GOT_CGEAR;
               }
            
         }
         else  // if using a separate OD switch
         {
         if ((outpc.swBDC < 100) && ((outpc.swADC < inpram.mlever_v3+higherADC)/2) && (outpc.swADC > ((inpram.mlever_v3+lowerADC)/2))) // swBDC grounded and in 3rd 
            {
            if (outpc.manual_gear != 4 ) 
              {
              outpc.manual_gear=4;
              goto FIND_ADC; 
              } 
              else goto GOT_CGEAR;
            }
         }
         
   // Neutral
         if (((outpc.swADC < (inpram.mlever_vN+higherADC)/2)) && ((outpc.swADC > ((inpram.mlever_vN+lowerADC)/2))))
         { 
         if (outpc.manual_gear != 0 ) 
            {
            outpc.manual_gear=0;
            goto FIND_ADC; 
            } 
            else goto GOT_CGEAR;
         }
         
   // Reverse
         if (((outpc.swADC < (inpram.mlever_vR+higherADC)/2)) && ((outpc.swADC > ((inpram.mlever_vR+lowerADC)/2))))
         { 
         if (outpc.manual_gear != -1 ) 
            {
            outpc.manual_gear=-1;
            goto FIND_ADC; 
            } 
            else goto GOT_CGEAR;
         }      
     
   // Park
         if (((outpc.swADC < (inpram.mlever_vP+higherADC)/2)) && ((outpc.swADC > ((inpram.mlever_vP+lowerADC)/2))))
         { 
         if (outpc.manual_gear != 0 ) 
            {
            outpc.manual_gear=0;      // set the gear
            goto FIND_ADC;            // loop again to see if it is the same range
            } 
         else goto GOT_CGEAR;
         }
        
   waitAwhile(13); // Wait 13/25*~100 = 52 milliseconds for ADC values to be re-read 
                   // (every 48 milliseconds in millisecond section of timer interupt).      

   goto FIND_ADC;  // loop until we get 2 the same in a row
   
   }  // end else voltage based lever
  
GOT_CGEAR: // Finished determining current gear (manual valve position)

/* If gear out of range*/
if (outpc.manual_gear > NO_GEARS) goto FIND_GEAR; // if in error state, loop until we have a valid gear

// -------------- Auto Shift Mode -------------------------------------------------------
// Get target gear from shift table
 
  // SET TARGET GEAR
  // and request shift as necessary 
  // from auto_gear_lookup(
  //      x = vss - int, will be divided by 10 in subroutine to match bins
  //      y = kpa - int, will be divided by 10 in subroutine to match bins
  //      nx = [NO_MPH],
  //      ny = [NO_LOADS],
  //      x_table = vss_table - unsigned int,
  //      y_table = LOAD_table - int,
  //      z_table) = auto_table - int. 

  outpc.target_gear = auto_gear_lookup(outpc.speedo,outpc.LOAD_short,NO_MPH,NO_LOADS,&inpram.vss_table[0],&inpram.LOAD_table[0],&inpram.auto_table[0][0]);

// If manual lever in park, reverse or neutral, unlock TCC and skip gear setting.
// Done *after* getting auto gear from table, for diagnostic reasons, 
// otherwise target gear looks 'dead' in MT

if (outpc.manual_gear < 1) // neutral, park = 0, reverse = -1
  { 
    outpc.lock_TCC = tcc_unlock();          // unlock the TCC if in P,N,R
    goto SKIP_SHIFT;       // skip gear selection code
  }; // end if (outpc.manual_gear < 1)

// Shift in auto mode = 1 (sequential) or 2 (direct) if target gear is not the current gear
if (outpc.auto_mode > 0 ) 
   {
	outpc.upshift_request   = 0; // reset shift requests if auto shifting
	outpc.downshift_request = 0;
      // set shift requests
     	if (outpc.target_gear < outpc.current_gear || outpc.current_gear > outpc.manual_gear)   
     	  {
     	   // if target gear is lower than current gear, or if have shifted manually to a lower gear
     	   // downshift
	       outpc.upshift_request   = 0;
         outpc.downshift_request = 1; // request downshift

         // Always Unlock TCC for downshift
         outpc.lock_TCC = tcc_unlock(); // function call returns 0 
         }  // End if target_gear < current_gear 

	    if (outpc.target_gear > outpc.current_gear) 
	       { 
	       // if the requested gear is greater than the current gear and less/equal than the manual position
	       // upshift
	         if (outpc.target_gear <= outpc.manual_gear)
	           {
	           outpc.upshift_request   = 1; // request upshift
	           outpc.downshift_request = 0;
	           }
	         else  // do not shift above manual gear position
	           {
	           outpc.upshift_request   = 0;
	           outpc.downshift_request = 0;
	           } // end else
	         
	      }  // End if target_gear > current_gear
	       
    } // End If outpc.auto_mode
    

if ((inpram.cltch_enable == 2) && (outpc.auto_mode == 2)) outpc.auto_mode = 1; // do not allow skip shift if clutch enabled

// ------------- Direct Jump to Target Gear --------------------------------------------
// If auto mode = 2, and shift required, skip direct to desired gear
// do not sequentially shift

if ((outpc.auto_mode == 2) && !(outpc.current_gear == outpc.target_gear)) 
{
  switch (outpc.target_gear) {
    
  case 1:
      // Shift direct to First
      
      // Check if downshift will overrev engine, if not, proceed with shift
             
          if (!inpram.rpm_check || ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[1])/ (long) inpram.gear_table[outpc.current_gear]) < inpram.rpm_limit)) 
          {

           // if line pressure is too high, or TCC is on, set and wait a bit
           if (outpc.lock_TCC == 1) outpc.lock_TCC = tcc_unlock(); // function call, unlock TCC and returns 0
      
           if (outpc.PC_duty < inpram.max_shift_pressure) 
             {
              PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
              waitAwhile(inpram.pressure_delay); // allow line pressure & TCC adjust
              // line pressure will be reset via the main loop when shift is complete
              } // end if PC_duty
            
            // switch Sol A
            solA((0x08 & inpram.Output1)>>3);  // if 1 in Output1, then send 1 to turn solA on                    
            
            // switch Sol B
            // Sol B state in 1st set in 4th bit of Output2
            // AND with 0x08 and divide by 0x08 to make 1 to set
            solB((0x08 & inpram.Output2)>>3);  // if 1 in Output2, then send 1 to turn solB on
                       
            // waitAwhile simply stops the main loop and allows the shift to complete.
            // This prevents the code from getting confused with rpm and MAP changes 
            // during a shift.
            // VSS processing, CAN messaging, ADC conversions, serial comms, and speedo 
            // output continue to be processed in interrupts.
            // Anything that you want to change during a shift (such as setting a output 
            // high to activate a timing bypass retard on an HEI 8-pin module) can be done 
            // in the waitAwhile function.
 
            outpc.current_gear = 1;   // set current gear
            
            // Set 3/2 Sol PWM
            // 3/2 sol state in 1st set in 4th bit of Output3
            // AND with 0x08 to set
            PWMDTY1 = dc_32*((0x08 & inpram.Output3)>>3);
            
            // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }
            
            waitAwhile(inpram.shift_delay);  // wait a fixed period
          
            // reset shift button counters
            UP_avg   = 500;
            DWN_avg  = 500;
            MODE_avg = 500;
                       
          }
          
          else {
           // do not shift - will overrev engine
           if (inpram.error_check) outpc.error |= 0x04; // set 3 bit of error code 
          };
      break;

  case 2: 
    
  
    // Shift direct to Second
    
    // Check if downshift will underrev engine, if not, proceed with shift
        
    if (!inpram.rpm_check || ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[2])/ (long) inpram.gear_table[outpc.current_gear]) < inpram.rpm_limit)) 
      {
    
       /*-- unlock TCC
          - switch OFF Sol A
          - switch ON Sol B
          - 3/2 Sol PWM to 90%
          --*/

          // if line pressure is too high, or TCC is on, set and wait a bit
          if (outpc.lock_TCC == 1) outpc.lock_TCC = tcc_unlock(); // function call, unlock TCC and returns 0
      
          if (outpc.PC_duty < inpram.max_shift_pressure) 
              {
               PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
               waitAwhile(inpram.pressure_delay); // allow line pressure & TCC adjust
               } // end if PC_duty
                       
          // switch Sol A
          // SolA state is 4th bit of Output1 (AND with 00010000 = 0x10) to determine if should be off or on
          solA((0x10 & inpram.Output1)>>4);  // if 1 in Output1, then send 1 to turn solA on

          // switch Sol B
          // Sol B in 2nd set in 5th bit of Output2
          // to set, AND with 0x10 and divide by 0x10 to make 1 
          solB((0x10 & inpram.Output2)>>4);  // if 1 in Output2, then send 1 to turn solB on
 
          outpc.current_gear = 2;   // set current gear
          
          // Set 3/2 Sol PWM
          // 3/2 sol state in 2nd set in 5th bit of Output3
          // to set, AND with 0x10 and divide by 0x10 to make 1 
          PWMDTY1 = dc_32*((0x10 & inpram.Output3)>>4);
          
          // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }
          
          waitAwhile(inpram.shift_delay); // wait a fixed period
          
          // reset shift button counters
          UP_avg   = 500;
          DWN_avg  = 500;
          MODE_avg = 500;
 
          }
          
          else {
           // do not shift - will underrev engine
           if (inpram.error_check) outpc.error |= 0x04; // set 3 bit of error code 
          };
  
     break;
  
  case 3:
        
       // Shift direct to Third
 
       if (!inpram.rpm_check ||  ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[3])/ (long) inpram.gear_table[outpc.current_gear]) < inpram.rpm_limit))
        { 
 
        /*- Unlock TCC
          - switch OFF Sol A
          - switch OFF Sol B
          - 3/2 Sol PWM to 90%
          --*/
          
          // if line pressure is too high, or TCC is on, set and wait a bit 
          if (outpc.lock_TCC == 1) outpc.lock_TCC = tcc_unlock(); // function call, unlock TCC and returns 0
     
          if (outpc.PC_duty < inpram.max_shift_pressure) 
            {
             PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
             waitAwhile(inpram.pressure_delay); // allow line pressure & TCC adjust
             } // end if PC_duty 
          
          // switch off Sol A
          // SolA state in 3rd is 5th bit of Output1
          // AND with 0x20 to determine state, and divide by 0x02 to make == 1
          solA((0x20 & inpram.Output1)>>5);  // if 1 in Output1, then send 1 to turn solA on

          // switch Sol B
          // Sol B in 3rd set in 4th bit of Output2
          // to set, AND with 0x20 and divide by 0x20 to make 1
          solB((0x20 & inpram.Output2)>>5);  // if 1 in Output2, then send 1 to turn solB on

          outpc.current_gear = 3;   // set current gear
          
          // Set 3/2 Sol PWM
          // 3/2 sol state in 3rd set in 6th bit of Output3
          // to set, AND with 0x20 and divide by 0x20 to make 1 
          PWMDTY1 = dc_32*((0x20 & inpram.Output3)>>5);
          
          // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }         

          waitAwhile(inpram.pressure_delay);

          // reset shift button counters
          UP_avg   = 500;
          DWN_avg  = 500;
          MODE_avg = 500;

          } // End if (outpc.engine_rpm*inpram.
          
          else {
           // do not shift - will underrev engine
           if (inpram.error_check) outpc.error |= 0x04; // set 3 bit of error code 
          };  // End else
            
     break;
  
  case 4:
   
    // Shift direct to Fourth
 
       if (!inpram.rpm_check ||  ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[4])/ (long) inpram.gear_table[outpc.current_gear]) < inpram.rpm_limit))
        { 
     
        /*- unlock TCC
          - switch ON Sol A
          - switch OFF Sol B
          - 3/2 Sol PWM to 90%
          --*/
                   
          // if line pressure is too high, or TCC is on, set and wait a bit
           
          if (outpc.lock_TCC == 1) outpc.lock_TCC = tcc_unlock(); // function call, unlock TCC and returns 0
      
          if (outpc.PC_duty < inpram.max_shift_pressure) 
            {
             PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
             waitAwhile(inpram.pressure_delay); // allow line pressure & TCC adjust
             } // end if PC_duty 
                    
          // switch on Sol A
          // SolA state in 4th set in 6th bit of Output1
          // AND with 0x40 and divide by 0x40 to make 1
          solA((0x40 & inpram.Output1)>>6);  // if 1 in Output1, then send 1 to turn solA on

          // switch Sol B
          // Sol B in 4th set in 6th bit of Output2
          // to set, AND with 0x40 and divide by 0x40 to make 1 
          solB((0x40 & inpram.Output2)>>6);  // if 1 in Output2, then send 1 to turn solB on

          outpc.current_gear = 4;   // set current gear
          
          // Set 3/2 Sol PWM
          // 3/2 sol state in 4th set in 6th bit of Output3
          // to set, AND with 0x40 
          PWMDTY1 = dc_32*((0x40 & inpram.Output3)>>6); 
          
          // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }       

          waitAwhile(inpram.shift_delay);  // wait a fixed period
          
          // reset shift button counters
          UP_avg   = 500;
          DWN_avg  = 500;
          MODE_avg = 500;

  				}
          
          else {
           // do not shift - will underrev engine
           if (inpram.error_check) outpc.error |= 0x04;   // set 3 bit of error code 
          };
    break;
  
  default:  
    reset_gear();  // We have out-of-range gear, so reset
    break; 
    
  } // End switch cases
  
} // End shift_mode==2


/*-- SHIFT_UP (sequential or manual shift) ----------------------------------------------
 check upshift status to see if threshold has be reached, upshift if it has
 if current_gear < 4 then target_gear = current gear + 1
 Check if new gear will under-rev the engine (unless rpm_check = 0)
 If required, unlock TCC
 - TCC unlocks under certain conditions during upshifts, depending on the vehicle speed 
   and engine LOAD.  It is safe to upshift under light load while the TCC is locked. 
   Most OEM systems keep the torqure converter clutch applied during light throttle upshifts.
 LEDs are set near beginning of main loop.
--*/

if (outpc.upshift_request && !outpc.downshift_request && outpc.auto_mode < 2) {
  
  switch (outpc.current_gear) {
    
  case -1:
  
    // In reverse, can't upshift
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
  
    break;
  
  case 0:
  
    // In neutral, can't upshift
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
  
    break;
  
  case 1: 
     
    // In 1st, upshift to second
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
    
    // Check if downshift will underrev engine, if not, proceed with shift
        
    if (!inpram.rpm_check || ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[2])/ (long) inpram.gear_table[outpc.current_gear]) > inpram.under_rev_limit))
     {
       /*-- Shift_First_Second
          --*/

          // if line pressure is too high, or TCC is on, set and wait a bit 
          if (outpc.lock_TCC == 1) outpc.lock_TCC = tcc_unlock(); // function call, unlock TCC and returns 0
     
          if (outpc.PC_duty < inpram.max_shift_pressure) 
            {
             PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
             waitAwhile(inpram.pressure_delay); // allow line pressure & TCC adjust
            } // end if PC_duty
          
          // Set clutch outputs, 1 then 2
          if (inpram.cltch_enable == 1) 
             {
             // if bit 1 set in clutchNup, turn on 
             if (inpram.clutch1up & 0x01) *pPTTpin[7] |= 0x80;
             if (inpram.clutch2up & 0x01) PORTA |= 0x01;             
             } 
          
          // switch Sol A
          // SolA state for 2nd set in Output1 bit4
          // to set, AND with 0x10 and divide by 0x10 to make 1
          solA((0x10 & inpram.Output1)>>4);  // if 1 in Output1, then send 1 to turn solA on

          // switch Sol B
          // Sol B in 2nd set in 4th bit of Output2
          // to set, AND with 0x10 and divide by 0x10 to make 1 
          solB((0x10 & inpram.Output2)>>4);  // if 1 in Output2, then send 1 to turn solB on

          outpc.current_gear = 2; // set current gear
          
          // Set 3/2 Sol PWM
          // 3/2 sol state in 2nd set in 5th bit of Output3
          // AND with 0x10 to set
          PWMDTY1 = dc_32*((0x10 & inpram.Output3)>>4);
          
          // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }
          
          waitAwhile(inpram.shift_delay);   // wait a fixed period
          
          // if cutches enabled, turn off
          if (inpram.cltch_enable == 1) 
             {
             // if bit 1 set in clutchNup, turn on 
             if (inpram.clutch1up & 0x01) *pPTTpin[7] &= ~0x80;
             if (inpram.clutch2up & 0x01) PORTA &= ~0x01;             
             }
                         
          // reset shift button counters
          UP_avg   = 500;
          DWN_avg  = 500;
          MODE_avg = 500;
  
          }   // End if (outpc.engine_rpm...
          
          else {
           // do not shift - will underrev engine
           if (inpram.error_check) outpc.error |= 0x04; // set third error bit 
          };  // End Else
  
    break;
  
  case 2:
        
       // In 2nd, upshift to third
       outpc.upshift_request   = 0;  // reset shift requests
       outpc.downshift_request = 0;
       
       if (!inpram.rpm_check || ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[3])/ (long) inpram.gear_table[outpc.current_gear]) > inpram.under_rev_limit))
        { 
 
          /*-- Shift_Second_Third
          - switch off Sol B
          --*/

          // if line pressure is too high, set and wait a bit    
          if (outpc.PC_duty < inpram.max_shift_pressure) 
            {
             PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
             waitAwhile(inpram.pressure_delay); // allow line pressure adjust
            } // end if PC_duty

          // Set clutch outputs, 1 then 2
          if (inpram.cltch_enable == 1) 
             {
             // if bit 2 set in clutchNup, turn on 
             if (inpram.clutch1up & 0x02) *pPTTpin[7] |= 0x80;
             if (inpram.clutch2up & 0x02) PORTA |= 0x01;             
             }

          // switch Sol A
          // SolA state in 3rd set in 5th bit of Output1
          // AND with 0x20 to set
         solA((0x20 & inpram.Output1)>>5);  // if 1 in Output1, then send 1 to turn solA on

          // switch Sol B
          // Sol B state in 3rd set in 5th bit of Output2
          // to set, AND with 0x20 and divide by 0x20 to make 1 
          solB((0x20 & inpram.Output2)>>5);  // if 1 in Output2, then send 1 to turn solB on
          
           // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
              } 
          
         waitAwhile(inpram.shift_delay);   // wait a fixed period
         
         // Set clutch outputs, 1 then 2
         if (inpram.cltch_enable == 1) 
           {
           // if bit 2 set in clutchNup, turn off 
           if (inpram.clutch1up & 0x02) *pPTTpin[7] &= ~0x80;
           if (inpram.clutch2up & 0x02) PORTA &= ~0x01;             
           }
                       
         outpc.current_gear = 3; // set current gear
          
         // reset shift button counters
         UP_avg   = 500;
         DWN_avg  = 500;
         MODE_avg = 500;
         }
          
         else 
         {
         // do not shift - will underrev engine
         if (inpram.error_check) outpc.error |= 0x04; // set third error bit 
         };
            
    break;
  
  case 3:
   
    // In 3rd, upshift to fourth
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
 
       if (!inpram.rpm_check || ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[4])/ (long) inpram.gear_table[outpc.current_gear]) > inpram.under_rev_limit)) 
        { 
     
        /*-- Shift_Third_Fourth
          - switch on Sol A
          --*/

          // if line pressure is too high, set and wait a bit    
          if (outpc.PC_duty < inpram.max_shift_pressure) 
            {
             PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
             waitAwhile(inpram.pressure_delay); // allow line pressure adjust
            } // end if PC_duty

          // Set clutch outputs, 1 then 2
          if (inpram.cltch_enable == 1) 
             {
             // if bit 3 set in clutchNup, turn on 
             if (inpram.clutch1up & 0x04) *pPTTpin[7] |= 0x80;
             if (inpram.clutch2up & 0x04) PORTA |= 0x01;             
             }

          // switch Sol A
          // SolA state in 4th set in Output1 bit 6
          // AND with 0x40 and divide by 0x40 to make 1 to set high
          solA((0x40 & inpram.Output1)>>6);  // if 1 in Output1, then send 1 to turn solA on

          // switch Sol B
          // Sol B in 4th set in 6th bit of Output2
          // to set, AND with 0x40 and divide by 0x40 to make 1 
          solB((0x40 & inpram.Output2)>>6);  // if 1 in Output2, then send 1 to turn solB on

          outpc.current_gear = 4; // set current gear
          
          // Set 3/2 Sol PWM
          // 3/2 sol state in 4th set in 6th bit of Output3
          // to set, AND with 0x40 and divide by 0x40 to make 1
          PWMDTY1 = dc_32*((0x40 & inpram.Output3)>>6);
          
          // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }       
          
          waitAwhile(inpram.shift_delay);   // wait a fixed period
          
          // Set clutch outputs, 1 then 2
          if (inpram.cltch_enable == 1) 
             {
             // if bit 3 set in clutchNup, turn off 
             if (inpram.clutch1up & 0x04) *pPTTpin[7] &= ~0x80;
             if (inpram.clutch2up & 0x04) PORTA &= ~0x01;             
             } 
          
          // reset shift button counters
          UP_avg   = 500;
          DWN_avg  = 500;
          MODE_avg = 500;

  				}
          
          else 
          {
           // do not shift - will underrev engine
           if (inpram.error_check) outpc.error |= 0x04;  // set third error bit 
          }
    break;
  
  case 4:
  
    // In 4th, can't shift up
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
  
    break;
  
  default:
    reset_gear(); // We have out-of-range gear, so reset
    break;
  
  }  // end switch current gear
  
} //end if upshift_request


/*-- SHIFT_DOWN (sequential and manual) -----------------------------------------
 check downshift status to see if threshold has be reached, upshift if it has
 if current_gear > 1 then target_gear = current gear - 1
 Check if new gear will over-rev the engine
 If required, unlock TCC
 - TCC always unlocks under during downshifts.
 LEDs are set near beginning of main loop. 
--*/

if (outpc.downshift_request && !outpc.upshift_request && outpc.auto_mode < 2) {
  
  switch (outpc.current_gear) {
     
  case -1:
  
    //In reverse, can't downshift
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
  
    break;
  
  case 0:
  
    // In neutral, can't downshift
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
  
    break;
  
  case 1: 
    
  
    // In 1st, can't downshift
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
    
    break;

  case 2:
  
    // In 2nd, downshift to first
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
    
        /*-- Shift_Second_First
        - switch on Sol A
        - 3/2 Sol PWM from 90% to 0% 
        --*/
        
        // Check if downshift will overrev engine, if not, proceed with shift
        
          if (!inpram.rpm_check || ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[1])/ (long) inpram.gear_table[outpc.current_gear]) < inpram.rpm_limit))
           {

          // if line pressure is too high, set and wait a bit
          if (outpc.lock_TCC == 1) outpc.lock_TCC = tcc_unlock(); // function call, unlock TCC and returns 0
      
          if (outpc.PC_duty < inpram.max_shift_pressure) 
            {
             PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
             waitAwhile(inpram.pressure_delay); // allow line pressure adjust
            } // end if (PC_duty...

          // Set clutch outputs, 1 then 2
          if (inpram.cltch_enable == 1) 
             {
             // if bit 1 set in clutchNdwn, turn on 
             if (inpram.clutch1dwn & 0x01) *pPTTpin[7] |= 0x80;
             if (inpram.clutch2dwn & 0x01) PORTA |= 0x01;             
             }
						 
            // switch Sol A
            // SolA state in 1st set in 3rd bit of Output1
            // to set, AND with 0x08 and divide by 0x08 to make 1 
            solA((0x08 & inpram.Output1)>>3);  // if 1 in Output1, then send 1 to turn solA on
                  
            // switch Sol B
            // Sol B in 1st set in 3rd bit of Output2
            // to set, AND with 0x08 and divide by 0x08 to make 1 
            solB((0x08 & inpram.Output2)>>3);  // if 1 in Output2, then send 1 to turn solB on

            outpc.current_gear = 1; // set current gear
            
            // set 3/2 Duty Cycle
            PWMDTY1 = dc_32*((0x08 & inpram.Output3)>>3);
            
            // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }
           
            waitAwhile(inpram.shift_delay);  // wait a fixed period
            
            // Set clutch outputs, 1 then 2
            if (inpram.cltch_enable == 1) 
             {
             // if bit 1 set in clutchNdwn, turn off 
             if (inpram.clutch1dwn & 0x01) *pPTTpin[7] &= ~0x80;
             if (inpram.clutch2dwn & 0x01) PORTA &= ~0x01;             
             }

          }
          
          else {
           // do not shift - will overrev engine
           if (inpram.error_check) outpc.error |= 0x04; // set third error bit  
          }
             	   
    break;
  
  case 3:
   
    // In 3rd, downshift to second
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
    
        /*-- Shift_Third_Second
        - switch on Sol B
        - 3/2 PWM reduced from 90% based on speed and load
        - check that switchC switches on
        --*/
			         
        // Check if downshift will overrev engine, if not, proceed with shift
        
          if (!inpram.rpm_check || ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[2])/ (long) inpram.gear_table[outpc.current_gear]) < inpram.rpm_limit))
           {
            
            // if line pressure is too high, set and wait a bit 
            if (outpc.lock_TCC == 1) outpc.lock_TCC = tcc_unlock(); // function call, unlock TCC and returns 0
     
            if (outpc.PC_duty < inpram.max_shift_pressure) 
              {
                PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
                waitAwhile(inpram.pressure_delay); // allow line pressure adjust
              } // end if PC_duty

          // Set clutch outputs, 1 then 2
          if (inpram.cltch_enable == 1) 
             {
             // if bit 2 set in clutchNdwn, turn on 
             if (inpram.clutch1dwn & 0x02) *pPTTpin[7] |= 0x80;
             if (inpram.clutch2dwn & 0x02) PORTA |= 0x01;             
             }
              
            // switch Sol A
            // SolA state in 2nd set in 4th bit of Output1
            // to set, AND with 0x10 and divide by 0x10 to make 1 
            solA((0x10 & inpram.Output1)>>4);  // if 1 in Output1, then send 1 to turn solA on
           
            // switch Sol B
            // Sol B state in 2nd set in 4th bit of Output2
            // to set, AND with 0x10 and divide by 0x10 to make 1 
            solB((0x10 & inpram.Output2)>>4);  // if 1 in Output2, then send 1 to turn solB on
            
            outpc.current_gear = 2; // set current gear
                        
            // set 3/2 Duty Cycle
            PWMDTY1 = dc_32*((0x10 & inpram.Output3)>>4);
            
            // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }                        
            
            waitAwhile(inpram.shift_delay);  // wait a fixed period
            
           // Set clutch outputs, 1 then 2
           if (inpram.cltch_enable == 1) 
             {
             // if bit 2 set in clutchNdwn, turn off 
             if (inpram.clutch1dwn & 0x02) *pPTTpin[7] &= ~0x80;
             if (inpram.clutch2dwn & 0x02) PORTA &= ~0x01;             
             } 

            // reset shift button counters
            UP_avg   = 500;
            DWN_avg  = 500;
            MODE_avg = 500;

          }
          
          else {
           // do not shift - will overrev engine
           if (inpram.error_check) outpc.error |= 0x04;    // set third error bit  
          }
              
    break;
  
  case 4:
  
    // In 4th, downshift to third
    outpc.upshift_request   = 0;  // reset shift requests
    outpc.downshift_request = 0;
    
        /*-- Shift_Fourth_Third
        - switch off Sol A
        --*/
			         
        // Check if downshift will overrev engine, if not, proceed with shift
        
          if (!inpram.rpm_check || ((int)(((long) outpc.engine_rpm* (long) inpram.gear_table[3])/ (long) inpram.gear_table[outpc.current_gear]) < inpram.rpm_limit))
           {         
            
            // if line pressure is too high, set and wait a bit 
            if (outpc.lock_TCC == 1) outpc.lock_TCC = tcc_unlock(); // function call, unlock TCC and returns 0
     
            if (outpc.PC_duty < inpram.max_shift_pressure) 
              {
                PWMDTY2 = (unsigned char)((inpram.max_shift_pressure * PWMPER2)/100);
                waitAwhile(inpram.pressure_delay); // allow line pressure adjust
              } // end if PC_duty

           // Set clutch outputs, 1 then 2 
           if (inpram.cltch_enable == 1) 
               {
               // if bit 3 set in clutchNdwn, turn on 
               if (inpram.clutch1dwn & 0x04) *pPTTpin[7] |= 0x80;
               if (inpram.clutch2dwn & 0x04) PORTA |= 0x02;             
               } 
						 
            // switch Sol A
            // SolA state in 3rd set in 5th bit of Output1
            // to set, AND with 0x20 and divide by 0x20 to make 1 
            solA((0x20 & inpram.Output1)>>5);  // if 1 in Output1, then send 1 to turn solA on
 
            // switch Sol B
            // Sol B state in 2nd set in 5th bit of Output2
            // to set, AND with 0x20 and divide by 0x20 to make 1 
            solB((0x20 & inpram.Output2)>>5);  // if 1 in Output2, then send 1 to turn solB on
  
            outpc.current_gear = 3; // set current gear
            
  				  // 3/2 Sol PWM to 0% from 90%
            PWMDTY1 = dc_32*((0x20 & inpram.Output3)>>5);   // transition, so use 1/2 PWM duty cycle
            
                        // Set third bit of sol state for datalog
            if (PWMDTY1) 
              {
              outpc.solst |= 0x04;
              } 
              else 
              {
              outpc.solst &= ~0x04;
               }  
        
            waitAwhile(inpram.shift_delay);  // wait a fixed period
            
            // Set clutch outputs, 1 then 2 
           if (inpram.cltch_enable == 1) 
               {
               // if bit 3 set in clutchNdwn, turn off 
               if (inpram.clutch1dwn & 0x04) *pPTTpin[7] &= ~0x80;
               if (inpram.clutch2dwn & 0x04) PORTA &= ~0x02;             
               } 
            
            // reset shift button counters
            UP_avg   = 500;
            DWN_avg  = 500;
            MODE_avg = 500;

             }         
          else {
           // do not shift - will overrev engine
           if (inpram.error_check) outpc.error |= 0x04; // set third error bit 
          }  // end if (!inpram.rpm_check ||
          
    break;
  
  default:
    reset_gear();
    break;
  
  } // end case
  
} //end if downshift_request

SKIP_SHIFT:
  
  DISABLE_INTERRUPTS
  ultmp = lmms;
  ENABLE_INTERRUPTS  


/***************************************************************************
**
** Check whether to burn flash
**
**************************************************************************/

BURN_FLASH:
// Burn Flash if flag set
if ((outpc.loop_count % 50) == 0) // slow down the flash burns to every 50 main loops (~50/8000 = ~6 milliseconds)
  {
      // burn flash 512 byte(256 word) sector(s)
      if(burn_flag >= 1)  
        {
        fburner(tableWordFlash(burn_idx, 0), tableWordRam(burn_idx, 0), tableWords(burn_idx));
        if(burn_flag >= tableWords(burn_idx))  
          {
          burn_flag = 0;
          flocker = 0;
          }
        else
          burn_flag++;
        }
   }
    
/***************************************************************************
**
** Check for reinit command
**
**************************************************************************/
    if(reinit_flag)  
      {
      reinit_flag = 0;
      }   // End if(reinit_flag)



/***************************************************************************
**
**  Check for serial, CAN receiver timeout
**
**************************************************************************/

    DISABLE_INTERRUPTS
    ultmp = lmms;
    ultmp2 = rcv_timeout;
    ENABLE_INTERRUPTS
    if(ultmp > ultmp2)  
      {
      txmode = 0;    // break out of current receive sequence
      rcv_timeout = 0xFFFFFFFF;
      } // end if (ultmp > ultmp2)
    
    DISABLE_INTERRUPTS
    ultmp2 = ltch_CAN;
    ENABLE_INTERRUPTS
    
    if(ultmp > ultmp2)  
      {
      getCANdat = 0;    // break out of current receive sequence
      ltch_CAN = 0xFFFFFFFF;
      }  //end if (ultmp > ultmp2)
    
    if(kill_ser)  
      {
      if(outpc.seconds > kill_ser_t)  
        {
        kill_ser = 0;
        SCI0CR2 |= 0x24;     // = 11000 -> rcv, rcvint re-enable
        }  // end if (outpc.seconds > ...
      }  // end if (kill_ser)
  
/***************************************************************************
**
**  Check for CAN reset
**
**************************************************************************/
	  if(can_reset)  
	    {
		  /* Re-initialize CAN comms */
		  CanInit();
		  can_reset = 0;
	    }  // end if (can_reset) ...
  
// ----- Spare Port Switching -------------------------------------------------------
// This trans code has 2 spare port (PT7 and PA0, on VB1 and VB2, AMP 11 and 12).
// PT7 -> VB1 must be jumpered from one end of 25x2 header to near the other end.
// The code lets these be set by speed, load, and gear. All conditions are 'AND'ed.
// The operator is binary 'bits':
// - 00 (=0) = no condition, 
// - 01 (=1) = greater than or equal, 
// - 10 (=2) = less than or equal
//
// ----- Spare Port 1 (PE0/VB1/AMP11) -----------------------------------------------
// Use spare port 1 if spare ports enabled or if using TCC on spare port 2 with LUF on PT3
if ((inpram.cltch_enable == 0) || (inpram.cltch_enable == 2))
{
  if (inpram.sp1speed_cond == 2) // less than, 00 (=0) = no condition, 
                               //            01 (=1) = greater than or equal, 
                               //            10 (=2) = less than or equal
    {
    if (inpram.sp1speed > outpc.speedo) // sp2speed_cond 00 = no condition, 01 = greater than, 10 = less than
        {
        tmp1 = 1; // condition is true
        }
        else
        {
        tmp1 = 0; //condition is false
        goto END_SPR1; // didn't meet condition, since we are ANDing, we abort the remaining conditions
        }
    
    }
  else // sp1speed_cond = 1 or 0
    {
    if (inpram.sp1speed_cond == 1) //greater than
      {
      if (inpram.sp1speed < outpc.speedo) 
        {
        tmp1 = 1;
        }
        else
        {
        tmp1 = 0;
        goto END_SPR1;
        }          
       }
     else tmp1 = 1; // no condition, set to 'true' and carry on
     }

  if (inpram.sp1rpm_cond == 2) // less than
    {
    if (inpram.sp1rpm > outpc.engine_rpm) 
        {
        tmp1 = tmp1 + 1;
        }
        else
        {
        tmp1 = 0;
        goto END_SPR1; // didn't meet condition, since we are ANDing, we abort the remaining conditions
        }
    
    }
  else if (inpram.sp1rpm_cond == 1) //greater than
      {
      if (inpram.sp1rpm < outpc.engine_rpm) 
        {
        tmp1 = tmp1 + 1;
        }
        else
        {
        tmp1 = 0;
        goto END_SPR1;
        }          
      }
      else tmp1 = tmp1 + 1;
      // if no condition (spr1rpm_cond = 0), we set it 'true' and carry on

   if (inpram.sp1load_cond == 2) // less than
    {
    if (inpram.sp1load > outpc.LOAD) 
        {
        tmp1 = tmp1 + 1;
        }
        else
        {
        tmp1 = 0;
        goto END_SPR1; // didn't meet condition, since we are ANDing, we abort the remaining conditions
        }
     
    }
  else if (inpram.sp1load_cond == 1) //greater than
      {
      if (inpram.sp1load < outpc.LOAD) 
        {
        tmp1 = tmp1 + 1;
        }
        else
        {
        tmp1 = 0;
        goto END_SPR1;
        }          
      }
    else tmp1 = tmp1 + 1; // no condition
    
  if (inpram.sp1gear_cond == 2) // less than or equal
    {
    if (inpram.sp1gear >= outpc.current_gear)
        {
        tmp1 = tmp1 + 1;
        }
        else
        {
        tmp1 = 0;
        goto END_SPR1; // didn't meet condition, since we are ANDing, we abort the remaining conditions
        }
    
    }
  else if (inpram.sp1gear_cond == 1) // greater than or equal
      {
      if (inpram.sp1gear <= outpc.current_gear)
        {
        tmp1 = tmp1 + 1;
        }
        else
        {
        tmp1 = 0;
        goto END_SPR1;
        }          
      }
      else // no condiion, so set to true
      {
      tmp1 = tmp1 + 1;
      }
    
  if (tmp1 >= 1) // at least one of the conditons was true 
               // (none were false, or we would have jumped past this point)
     {
     spr1(1); 
     } // end if tmp1 = ...
  
  END_SPR1:  // go here if any one of the spare port 1 conditions is not met


// ----- Check if SP1 hysteresis conditions met (turn PT7 off if met) ----------

  if (inpram.sp1speed_cond == 2) // less than, 00 (=0) = no condition, 
                               //            01 (=1) = greater than or equal, 
                               //            10 (=2) = less than or equal
    {
    if (inpram.sp1speed < (outpc.speedo - inpram.sp1speed_hyst)) // 00 = no condition, 01 = greater than, 10 = less than
        {
        spr1(0);
        goto END_SPR1H;
        }
    
    }
  else // sp1speed_cond = 1 or 0
    {
    if (inpram.sp1speed_cond == 1) //greater than, 00 = no condition, 01 = greater than, 10 = less than
      {
      if (inpram.sp1speed > (outpc.speedo + inpram.sp1speed_hyst))
       {
        spr1(0);
        goto END_SPR1H;
        }
      }
    }

  if (inpram.sp1rpm_cond == 2) // less than
    {
    if (inpram.sp1rpm < (outpc.engine_rpm - inpram.sp1rpm_hyst))
        {
        spr1(0);
        goto END_SPR1H;
        }
    
    }
  else if (inpram.sp1rpm_cond == 1) //greater than
      {
      if (inpram.sp1rpm > (outpc.engine_rpm + inpram.sp1rpm_hyst))
        {
        spr1(0);
        goto END_SPR1H;
        }         
      }

  if (inpram.sp1load_cond == 2) // less than
    {
    if (inpram.sp1load < (outpc.LOAD - inpram.sp1load_hyst))
        {
        spr1(0);
        goto END_SPR1H;
        }
     
    }
  else if (inpram.sp1load_cond == 1) //greater than
      {
      if (inpram.sp1load > (outpc.LOAD + inpram.sp1load_hyst))
        {
        spr1(0);
        goto END_SPR1H;
        }         
      }
    
  if (inpram.sp1gear_cond == 2) // less than
    {
    if (inpram.sp1gear < outpc.current_gear)
        {
        spr1(0);
        goto END_SPR1H;
        }   
    }

  else if (inpram.sp1gear_cond == 1) // greater than 
   {
   if (inpram.sp1gear > outpc.current_gear)
      {
      spr1(0);
      goto END_SPR1H;
      }  
   }

END_SPR1H:  // goto here if any spare port1 hysteresis conditions fail

//----- SPARE PORT 2 --------------------------------------------------
// Use spare port 2 if spare ports enabled but not if using TCC on spare port 2 with LUF on PT3
if (inpram.cltch_enable == 0)   // if spare ports enabled
  {  
  // ----- Spare Port 2 (PA0/VB2/AMP 12) ------------------------------------------------------
  if (inpram.sp2speed_cond == 2) // less than, 00 (=0) = no condition, 
                               //            01 (=1) = greater than or equal, 
                               //            10 (=2) = less than or equal
    {
    if (inpram.sp2speed > outpc.speedo)
        {
        tmp2 = 1;
        }
        else
        {
        goto END_SPR2; // didn't meet condition, since we are ANDing, we abort the remaining conditions
        }
    
    }
  else 
    {
    if (inpram.sp2speed_cond == 1) // greater than
      {
      if (inpram.sp2speed < outpc.speedo)
       {
        tmp2 = 1;
        }
        else
        {
        tmp2 = 0;
        goto END_SPR2;
        }          
      }
      else
      {
      tmp2 = tmp2 + 1;
      }
    }
  // if no condition, we simply carry on

  if (inpram.sp2rpm_cond == 2) // less than
    {
    if (inpram.sp2rpm > outpc.engine_rpm)
        {
        tmp2 = tmp2 + 1;
        }
        else
        {
        tmp2 = 0;
        goto END_SPR2; // didn't meet condition, since we are ANDing, we abort the remaining conditions
        }
    
    }
  else if (inpram.sp2rpm_cond == 1) //greater than
      {
      if (inpram.sp2rpm < outpc.engine_rpm)
        {
        tmp2 = tmp2 + 1;
        }
        else
        {
        tmp2 = 0;
        goto END_SPR2;
        }          
      }
      else
      {
      tmp2 = tmp2 + 1; // no condition, set to true and carry on
      }
  // if no condition, we simply carry on

  if (inpram.sp2load_cond == 2) // less than
    {
    if (inpram.sp2load > outpc.LOAD)
        {
        tmp2 = tmp2 + 1;
        }
        else
        {
        tmp2 = 0;
        goto END_SPR2; // didn't meet condition, since we are ANDing, we abort the remaining conditions
        }
     
    }
  else if (inpram.sp2load_cond == 1) // greater than
      {
      if (inpram.sp2load < outpc.LOAD)
        {
        tmp2 = tmp2 + 1;
        }
        else
        {
        tmp2 = 0;
        goto END_SPR2;
        }          
      }
      else
      {
      tmp2 = tmp2 + 1;
      }
      // if no condition, we set to 'true' and carry on
      
  if (inpram.sp2gear_cond == 2) // less than
    {
    if (inpram.sp2gear > outpc.current_gear)
        {
        tmp2 = tmp2 + 1;
        }
        else
        {
        tmp2 = 0;
        goto END_SPR2; // didn't meet condition, since we are ANDing, we abort the remaining conditions
        }
    
    }
  else if (inpram.sp2gear_cond == 1) // greater than
      {
      if (inpram.sp2gear < outpc.current_gear)
        {
        tmp2 = tmp2 + 1;
        }
        else
        {
        tmp2 = 0;
        goto END_SPR2;
        }          
      }
      else
      {
      tmp2 = tmp2 + 1;
      }

  if (tmp2 >= 1) 
  {
  spr2(1);
  }
  END_SPR2: // goto point if any spare port2 condition fails

  // ----- Check if SP2 hysteresis conditions met (turn PA0 off if met) ----------

  if (inpram.sp2speed_cond == 2) // less than, 00 (=0) = no condition, 
                                 //            01 (=1) = greater than or equal, 
                                 //            10 (=2) = less than or equal
    {
    if (inpram.sp2speed < (outpc.speedo - inpram.sp2speed_hyst))
        {
        spr2(0);
        goto END_SPR2H;
        }
    
    }
  else // sp2speed_cond = 1 or 0
    {
    if (inpram.sp2speed_cond == 1) //greater than
      {
      if (inpram.sp2speed > (outpc.speedo + inpram.sp2speed_hyst))
       {
        spr2(0);
        goto END_SPR2H;
        }
      }
    }

  if (inpram.sp2rpm_cond == 2) // less than
    {
    if (inpram.sp2rpm < (outpc.engine_rpm - inpram.sp2rpm_hyst))
        {
        spr2(0);
        goto END_SPR2H;
        }
    
    }
  else if (inpram.sp2rpm_cond == 1) //greater than
      {
      if (inpram.sp2rpm > (outpc.engine_rpm + inpram.sp2rpm_hyst))
        {
        spr2(0);
        goto END_SPR2H;
        }         
      }

  if (inpram.sp2load_cond == 2) // less than
    {
    if (inpram.sp2load < (outpc.LOAD - inpram.sp2load_hyst))
        {
        spr2(0);
        goto END_SPR2H;
        }
     
    }
  else if (inpram.sp2load_cond == 1) //greater than
      {
      if (inpram.sp2load > (outpc.LOAD + inpram.sp2load_hyst))
        {
        spr2(0);
        goto END_SPR2H;
        }         
      }
    
  if (inpram.sp2gear_cond == 2) // less than or equal
    {
    if (inpram.sp2gear < outpc.current_gear)
        {
        spr2(0);
        goto END_SPR2H;
        }   
    }

  else if (inpram.sp2gear_cond == 1) // greater than or equal
   {
   if (inpram.sp2gear > outpc.current_gear)
      {
      spr2(0);
      goto END_SPR2H;
      }  
   }

  END_SPR2H:  // goto point if any spare port2 hysteresis conditions fail
  tmp1 = 0; // reinitialize
  tmp2 = 0;
} // end if (inpram.cltch_enable == 0)
else
 {
  // update the TCC LUF PWM%
   }
} // end if cltch_enable == 0

} // end for(;;) main loop
  
goto MAIN_LOOP;  // we should *never* be here - goto beginning if we are!
                 // might indicate wrong number or placement of brackets.... 

} // end main() function

/** =========== FUNCTIONS ===================================================== **/

// ------------ Unlock TCC --------------------------------------------------------
char tcc_unlock(void)  {
 // Unlock without conditions 
    // set PT7 high 
    if ((inpram.cltch_enable == 0) || (inpram.cltch_enable == 1))
       {
       PWMDTY3 = 0;    // set PT3 low (DC =0%)
       } 
    else // cutch enable == 2, so set SPR2 low and set PWMDTY3 for LUF 
       {
       PORTA &= ~0x01; // turn PA0 off
       PWMDTY3 = (char) ((int) (inpram.LUF_off*PWMPER3)/100);  // set PT3 to off state
       }   // end if ((inpram.cltch_enable == 0) ...
    return 0;          // return 0 for outpc.lock_TCC flag
} // end tcc_unlock

// ------------ Lock TCC ----------------------------------------------------------
char tcc_lock(void)  {
 unsigned char tmp3;
 // Most conditions are checked before requesting function call,
 // however here we check for braking and speed. *Never* lock while braking.
 if ((outpc.brake == 0) && (outpc.speedo > inpram.minTCCspeed))
    { // lock TCC
    if ((inpram.cltch_enable == 0) || (inpram.cltch_enable == 1) || ((inpram.LUF_taper > 0) && (LUF_counter == inpram.LUF_taper)))
       {
       PWMDTY3 = PWMPER3;        // set PT3 high (DC = 100%)
       } 
    else // cutch enable == 2, so set SPR2 high and set PWMDTY3 for LUF 
       {
       PORTA |= 0x01; // turn PA0 on
       tmp3 = Calc_LUF(); // get LUF PWM% start value
       PWMDTY3 = (char) (((tmp3 + (char) ((int) ((100-tmp3)*LUF_counter)/inpram.LUF_taper)) * PWMPER3)/100);  // Set LUF DC - we will update this every 0.010 seconds
       }   // end if ((inpram.cltch_enable == 0)
    return 1;                 // return 1 for outpc.lock_TCC flag for datalog
    }
 else
    {
    // do not lock TCC
    return 0;                 // return 0 for outpc.lock_TCC flag for datalog
    }   // end if ((outpc.brake == 0) && ...
  }   // end tcc_lock()


// Calculate the LUF PWM Percentage
char Calc_LUF(void)
  {
  char percent = 0;
  // percent is calculated as:
  //  - equal to LUF_loPWM if load is less than or equal to LUF_loLoad
  //  - equal to LUF_hiPWM if load is greater than or equal to LUF_hiLoad
  //  - linear interpolation if load is between LUF_loLoad and LUF_hiLoad
  
  if (outpc.LOAD < inpram.LUF_loLoad)
    {
    percent = inpram.LUF_loPWM;
    } 
  else if (outpc.LOAD > inpram.LUF_hiLoad)
    {    
    percent = inpram.LUF_hiPWM;
    } 
  else // between high and lo load, so interpolate
    {
    if (inpram.LUF_hiPWM > inpram.LUF_loPWM)   // postive slope to interpolation
       { 
       percent = inpram.LUF_loPWM+(inpram.LUF_hiPWM-inpram.LUF_loPWM)*(outpc.LOAD-inpram.LUF_loLoad)/(inpram.LUF_hiLoad-inpram.LUF_loLoad);
       } 
       else   // negative slope
       {
       percent = inpram.LUF_loPWM-(inpram.LUF_loPWM-inpram.LUF_hiPWM)*(outpc.LOAD-inpram.LUF_loLoad)/(inpram.LUF_hiLoad-inpram.LUF_loLoad);       
       }  // end if (inpram.LUF_hiPWM > ...
    }   // end if (outpc.LOAD <
  return percent;
  }  // end Calc_LUF()


// ------------ Reset switch manifold --------------------------------------------
void reset_gear(void) 
  {
    if (inpram.error_check) outpc.error |= 0x20; // out of range, set 00X00000 in error code 
    outpc.current_gear=99; // set flag so we poll until have a gear lever position
    reset_switches();
    return;
  }   // end reset_gear(

void reset_switches(void)
  {                
    // reset switches  to 3rd gear until we determine actual gear
    switchA = 0;
    switchB = 1;
    switchC = 1;
    // reset switch accumulator counts re-establish current gear
    switchAacc = inpram.debounce >> 1;   // right shift the bits 1 position 
    switchBacc = inpram.debounce >> 1;   // this is the same as dividing by 2 and discarding the remainder)
    switchCacc = inpram.debounce >> 1;   // for example 01100111 (decimal 103) becomes 00110011 (51)
    return; 
  }  // end reset_switches()

//--------------- vss_reset -------------------------------------------------------
#pragma CODE_SEG DEFAULT

void VSS_reset(void)  
  { 
  // reinitialize
  if (inpram.error_check) outpc.error |= 0x08;   // set 4th bit of error code 
                                                 // (0x08 = 00001000)
  vss_timer_overflow = 0;
  TC_ovflow = 0;
  TC_ov_ix = 0;
  ic1_count = 0;
  ic2_count = 0;
 
  // Reset the speedo and error counter
  VSS_error = 0;    // set the error value to zero 
  restart = 1;      // set restart flag
  
  return;
  } // end vss_rest()

void reset(void)  
  {
  return;
  }
#pragma CODE_SEG DEFAULT

//--------------- get_adc -------------------------------------------------------
int get_adc(char chan1)  {

// chan = 0 is manual gear lever switch A
// chan = 1 is manual gear lever switch B
// chan = 2 is transmission temperature
// chan = 3 is manual gear lever switch C
// chan = 4 is line pressure
// chan = 5 is non-CAN MAP

// the ATD0DRx ADC result register contains the ten bit result of the 
// ADC conversion for channel x, we use this as an index to the 'lookup'_table 
// to get the actual value
 
    switch(chan1)  {
      case 0:
        adcval = ATD0DR0; // set value to ADC count for swA (manual shift lever postion)
        break;
      case 1:
        adcval = ATD0DR1; // set value to ADC count for swB (manual shift lever postion)
        break;
      case 2:
      // transmission temperature sensor
        if (((inpram.stdin_cfg & 0x04) == 0) && ((inpram.stdin_cfg & 0x02) == 0)) // must be both 'log only' and 'log ADC'
           adcval = ATD0DR2;
        else   // use temperature or log temperature
           adcval = cltfactor_table[ATD0DR2]; // look up temperature in degrees F x 10 
        // end if
        if (inpram.Metric_Units && !(inpram.stdin_cfg & 0x04)) adcval = (adcval-320)*5/9; // convert to Celsius
        break;
      case 3:
        adcval = ATD0DR3; // set value to ADC count for swC (manual shift lever postion)
        break;
      case 4:
      // line pressure
      // ADC count if inpram.line_units = 1, line pressure table value if line_units=0
        adcval = ATD0DR4*inpram.line_units + (linefactor_table[ATD0DR4]*(1-inpram.line_units));
        break;
      case 5:
      // non-CAN MAP
        adcval = loadfactor_table[ATD0DR5];
        break;
      case 6:
        break;
      case 7:
        break;
      default:
        break;
    }			 // end of switch
  
  return(adcval);
}  // end get_adc()

#pragma CODE_SEG DEFAULT

#pragma CODE_SEG ROM_7000

void switch_page(unsigned char sub_no)  {
// This resides in non-banked memory and is used to call subroutines
//  from another page.

  switch(sub_no)  {
          
    case 0:
      PPAGE = 0x3D;
      PPAGE = 0x3C;
      break;
      
    case 1:
      PPAGE = 0x3C;
//      get_adc(carg1);
      PPAGE = 0x3D;
      break;

    case 2:
      PPAGE = 0x3D;
      PPAGE = 0x3C;
      break;
    
    case 3:
      PPAGE = 0x3D;
      PPAGE = 0x3C;
      break;
      
    default:
      break;
  }
  
  return;
}

// --------------- intrp_1dctable -------------------------------------------------------
//
// 1D Interpolation Routine
// ------------------------
// sgnx     = 
// x        = the index value for the lookup
// n		    = the number of rows in (size of) the lookup table
// x_table  = the name of the lookup table
// sgny     =
// z_table  = the 'looked up' values
// return the interpolated value as an integer

int intrp_1dctable(char sgnx, int x, unsigned char n, int * x_table, char * z_table)  {
  int ix;
  long interp, interp3;
  // bound input arguments
  if((sgnx && (x > x_table[n-1])) ||
     (!sgnx && ((unsigned int)x > (unsigned int)x_table[n-1])))  {
      return((int)z_table[n -1]);
  }
  if((sgnx && (x < x_table[0])) ||
     (!sgnx && ((unsigned int)x < (unsigned int)x_table[0])))  {
      return((int)z_table[0]);
  }
  for(ix = n - 2; ix > -1; ix--)  { 
    if((sgnx && (x > x_table[ix])) ||
       (!sgnx && ((unsigned int)x > (unsigned int)x_table[ix])))  {
   		break;
  	}
  }
  if(ix < 0)ix = 0;
 
  interp =	(unsigned int)x_table[ix + 1] - (unsigned int)x_table[ix];
  if(interp != 0)  {
    interp3 = (unsigned int)x - (unsigned int)x_table[ix];
    interp3 = (100 * interp3);
    interp = interp3 / interp;
  }

  return((int)((char)z_table[ix] +
	    interp * ((char)z_table[ix+1] - (char)z_table[ix])/ 100));
}


// --------------- intrp_2dctable -------------------------------------------------------

int intrp_2dctable(unsigned int x, int y, unsigned char nx, unsigned char ny,
  unsigned int * x_table, int * y_table, unsigned char * z_table)  {
  int ix,jx;
  long interp1, interp2, interp3;
  // bound input arguments
  if(x > x_table[nx-1])x = x_table[nx-1];
  else if(x < x_table[0])x = x_table[0];
  if(y > y_table[ny-1])y = y_table[ny-1];
  else if(y < y_table[0])y = y_table[0];
  // Find bounding indices in table
  for(ix = ny - 2; ix > -1; ix--)  {  // Start w highest index
	//  because will generally have least time for calculations at hi y
  	if(y > y_table[ix])  {
   		break;
  	}
  }
  if(ix < 0)ix = 0;
  for(jx = nx - 2; jx > -1; jx--)  {  // Start w highest index
	// because will generally have least time for calculations at hi x
	  if(x > x_table[jx])  {
	    break;
    }
  }
  if(jx < 0)jx = 0;
  // do 2D interpolate
  interp1 = y_table[ix + 1] - y_table[ix];
  if(interp1 != 0)  {
    interp3 = (y - y_table[ix]); 
    interp3 = (100 * interp3); 
    interp1 = interp3 / interp1; 
  }
  interp2 =	x_table[jx + 1] - x_table[jx];
  if(interp2 != 0)  {
    interp3 = (x - x_table[jx]); 
    interp3 = (100 * interp3); 
    interp2 = interp3 / interp2; 
  }
  return((int)(((100 - interp1) * (100 - interp2) * z_table[ix*nx+jx]
	  + interp1 * (100 - interp2) * z_table[(ix+1)*nx+jx]
	  + interp2 * (100 - interp1) * z_table[ix*nx+jx+1]
	  + interp1 * interp2 * z_table[(ix+1)*nx+jx+1]) / 10000));
}

// --------------- auto_gear_lookup -------------------------------------------------------

unsigned char auto_gear_lookup(int x, int y, unsigned char nx, unsigned char ny, unsigned int * x_table, int * y_table, unsigned char * z_table)  
  {
  // This function has NO INTERPOLATION, it is designed to look up target gears only 
  // (we don't want an interpolated gear, since we might interpolate to the wrong gear 
  // when the integer is truncated and returned).
  // This function is simpler than the intrp_2dctable() function above (which is used 
  // in this code to inpterpolate the PC table values), but auto_gear_lookup uses 
  // similar naming conventions, etc..
  // x is mph x10, (outpc.vss)
  // y is kpa x10, (outpc.LOAD_short)
  // nx = 12 = NO_MPH = number of speed bins
  // ny = 12 = NO_MAP = number of kpa bins
  // * x_table is pointer to the speed bins
  // * y_table is pointer to the kpa bins
  // * x_table is pointer to the gear table
   
  int ix,jx;
  // ix is the index that contains the bin 
  //    number of the mph bin immediately 
  //    below the actual mph value
  // jx is the index that contains the bin 
  //    number of the kpa bin immediately 
  //    below the actual kpa value
  
  // multiply mph and load bins by 10 below 
  // (because values are x10, but bins are not)
  
  unsigned char gear; // returned gear

  gear = outpc.current_gear; // set current gear as default

  // look-up gear only if hysteresis condition or WOT flag is set
  if (   (WOT_flag)                              // always look up gear if at WOT
         ||((x <= (last_mph - inpram.gear_hyst)) // if any hysteresis conditions met
         || (x >= (last_mph + inpram.gear_hyst))
         || (x <  (inpram.hyst_enable_speed))    // always look up gear if mph is less than user setting      
         || (y <= (last_kpa - inpram.kpa_hyst)) 
         || (y >= (last_kpa + inpram.kpa_hyst)))
       ) 
  { 
     
  // bound input arguments
  if(x >= (x_table[nx-1]*10))   x = (x_table[nx-1]*10); // use maximum mph (nx-1 because indices are 0-11 for a 12x12 table)
  else if(x <= (x_table[0]*10)) x = (x_table[0]*10);    // use minimum mph
  
  if(y >= (y_table[ny-1]*10))   y = (y_table[ny-1]*10); // use max load
  else if(y <= (y_table[0]*10)) y = (y_table[0]*10);    // use min load
  
  // ----- Find indices in table -----
  
  // First find y bin (kPa) - this is the 'row'
  for (ix = 0; ix < ny-1; ix++)
    {
  	if(y <= (y_table[ix]*10))  
  	  {
   		break;  // break if y is greater than bin value, save ix
  	  } // end if y < (y_table[... 
    } // end for loop
    
  // Now find x bin (speed) - this is the 'column'
  for (jx = 0; jx < nx-1; jx++)  
    {
	  if(x <= (x_table[jx]*10))  
	    {
	    break;
      } // end if
    } // end for
  
  // ------ Look Up Gear ----------------------------------------------------

  gear = z_table[(ix*nx)+jx]; // index runs from 0 to 143 for 12x12 table
  
  last_mph = x; // last_mph is a global variable, and its value will be retained
                // from one function call to the next              
  last_kpa = y; // last kpa             
  
  } // end if (last_mph - ...)
  
 return(gear);  // the next higher value
}

#pragma CODE_SEG DEFAULT
#pragma CODE_SEG NON_BANKED // Interrupts must be in non-banked memory, 
                            // because the vectors are only 16 bit and 
                            // will not accomodate the appended 6 bit 
                            // ppage register. 

// --------------- VSS Timer ----------------------------------------
// Time the period on the VSS input, set in array ic_period[]

INTERRUPT void VSS_timer(void)  {
  unsigned long period1;
  
  // set interrupt received flag
  VSS_rcvd++;     // will clear flag (set to zero) in 0.010 second section
  // reset interrupt
  TFLG1 = 0x01;  // write a one (00000001) to TFLG to clear flag for timer channel 0  
  period1 = (TC0 - tcsav1);   // tcsav1 is a global variable - it is the time of the last interrupt

  // increment tooth counter (regardless of filtering)
  outpc.vss_teeth++;
  
  // set interrupt received flag
  VSS_rcvd++;     // will clear flag (set to zero) in 0.010 second section

  // reject if outside mask range of current average
  // ic_per_avg is ~100 times the period
  // We calculate long_period and short_period in 0.010 section of timer

  if (((period1 < long_period_vss) &&     // if the period is less than 
       (period1 > short_period_vss)) ||   // AND the period is greater than
        restart ||                        // OR restarting
       (outpc.speedo < 40) ||             // OR just started moving
       (VSS_error > inpram.vss_error_max)        // OR getting a lot of errors (possibly frozen speedo)
       )                
    {
      ic1_period[ic1_count] = period1;    // 5.333 µsec ticks
      tcsav1 = TC0;                       // save time of new trigger edge
//      if ((VSS_error > 0)) VSS_error--;   // reduce the error count because we had a good tooth
    } 
    else if (period1 > long_period_vss)           // long period1 and not restart or slow speed
    { // long period
      ic1_period[ic1_count] = long_period_vss;    // use the long period                  
      if (VSS_error < inpram.vss_error_max) VSS_error++; // increment the error count because we had a bad long tooth    
    } 
    else if (VSS_error < inpram.vss_error_max) VSS_error++; // increment the error count because we had a bad short tooth    

    // do not record short period - ignore it (unless restart or slow speed)
        
  ic1_count++;   // sample counter for last 20 input capture periods
  if (ic1_count > 20) ic1_count=1;  
  
  // reset interrupt
  TFLG1 = 0x01;           // write a one (00000001) to TFLG to clear flag for timer channel 0 
  
  return;
}  // end VSS_timer()


// --------------- ISS/non-CAN Tach Timer ----------------------------------------
// Time the period on the VSS input, set in array ic_period[]

INTERRUPT void ISS_timer(void)  {
  unsigned long period2;
  
  // set interrupt received flag
  // ISS_rcvd++;     // will clear flag (set to zero) in 0.010 second section
  // reset interrupt
  TFLG1 |= 0x20;     // write a one to TFLG bit 5 (00100000) to clear flag for timer channel 0     
  period2 = (TC5 - tcsav2);   // tcsav2 is a global variable - it is the time of the last interrupt

  // reject if outside mask range of current average
  // ic2_per_avg is ~100 times the period
  // We calculate long_period and short_period in 0.010 section of timer

  if (((period2 < long_period_iss) &&    // if the period is less than 
       (period2 > short_period_iss)) ||  // AND the period is greater than
        restart )                        // OR restarting                
       {
        //  calculate last period, us
        ic2_period[ic2_count] = period2; // 5.333 µsec ticks
        tcsav2 = TC5;                    // save time of new trigger edge
       }  // end if (utmp <...

//  if (period2 > short_period_iss)        // if not too short (too long is okay)
//    {
    
//    } // end if (period ...
  
  ic2_count++;   // sample counter for last 20 input capture periods
  if (ic2_count > 20) ic2_count=1;  
  return;
}

#pragma CODE_SEG  NON_BANKED
INTERRUPT void Timer_Clock_ISR(void)  {
  short ix, icx;
  unsigned long tmp,tmp1,tmp2;
  
  // .128 ms clock interrupt - clear flag immediately to resume count
  CRGFLG = 0x80;  // clear RTI interrupt flag
  // also generate 1.024 ms, .10035 sec and 1.0035 sec clocks
  lmms++;         // free running clock(.128 ms tics) good for ~ 110 hrs
  can_mmsclk++;
  mms++;          // in .128 ms tics - reset every 8 tics = 1.024 ms
      
  // check mms to generate other clocks
  if(mms < 8)goto CLK_DONE;
  mms = 0;
  
  
//////////////////////////// 0.001 millisecond section ///////////////////////

  millisec++;     // actually 1.024 ms, for seconds count

  // Pressure Control Output Dither Pulse
  dither_counter++;          // increment the counter
  if (dither_counter > inpram.dither_intPC)
    {
    if (outpc.auto_mode && (outpc.current_gear == outpc.target_gear) && (outpc.LOAD_long < 750))
      {
       dither_flag |= 0x01;  // set the dither_flag first bit to one
       PWMDTY2 = 0;    // set duty cycle to 0% == 100% pressure
      }  // end if (outpc.auto_mode...
    }  // end if dither_counter ...
  if (dither_counter > (inpram.dither_intPC + inpram.dither_durPC - 1))
    {
     dither_counter = 0;     // reset the dither counter to zero
     dither_flag &= ~0x01;   // reset the dither_flag first bit to zero
    }  // end if dither_counter ...
    
  // Output1 and Output 2 Refresh Pulse
  refresh_counter++;         // increment the counter
  if (refresh_counter > inpram.refresh_int)
    { // we handle the outputs separately, since they won't both necessarily be on at the same time
    if (SOLAst)              // if output1 (solA) is active
      {
       dither_flag |= 0x02;  // set the dither_flag second bit to one
       PORTE       |= 0x10;  // set PE4 high
      }   // end if (SOLAst)...
    if (SOLBst)              // if output2 (solB) is active
      {
       dither_flag |= 0x04;  // set the dither_flag third bit to one
       *pPTMpin[2] |= 0x04;  // set PM2 high
      }  // end if (SOLAst)...
    }    // end if refresh_counter ...
  if (refresh_counter > (inpram.refresh_int + inpram.refresh_dur - 1))
    {
     refresh_counter = 0;    // reset the refresh counter to zero
     dither_flag &= ~0x06;   // reset the dither_flag second and third bits to zero
    }  // end if refresh_counter ...
  
  // Get MS-II output variables via CAN. Grab 8 bytes every can_var_rate (.128 ms tics)
    /* CAN Xmt mssge ring buffer:
      can[0] is to hold Rx,TxISR messages,
      can[1] for main loop messages (so don't get clobbered by ISR)
      cxno = number of messages in queue waiting to be sent out
      cxno_in = index for inserting a msg in queue (increment after insert)
      cxno_out = index for sending out a msg (increment after loading CAN buffer)
      cx_msg_type = CMD,REQ,RESP,XSUB = set value, 
                                        request value, 
                                        respond to a request for value, 
                                        execute a subroutine.
      varblk,varoffset,varbyte = blk number of data structure, 
                                 byte offset from start of structure, 
                                 number of bytes of data.
                                 - these are prefixed by "cx_my" & "cx_dest" to represent 
                                   this CPU and other CPU respectively
      datbuf = the actual data (max of 8 bytes),
      cx_dest = CAN id number of device to which msg being sent. 
  */
  if(can_mmsclk > inpram.can_var_rate)  {
      // load xmt interrupt ring buffer(can[0]) - request data
      ix = can[0].cxno_in;
      can[0].cx_msg_type[ix] = MSG_REQ; // message type is 'request data'
      // Get all data from var. block canvar_blkptr[varblk]=outpc in MSII)      
      can[0].cx_destvarblk[ix] = inpram.msvarBLK;     // was 6
      // Put data rcvd from MSII into var block canvar_blkptr[varblk]=msvar in GPIO)      
      can[0].cx_myvarblk[ix] = 2;       // msvar is cx_myvarblk[2] 
      can[0].cx_myvaroff[ix] = can_varoff;
      can[0].cx_dest[ix] = inpram.ms2canID;	  // send to MS II
      // below is offset from start of outpc in MSII
      can[0].cx_destvaroff[ix] = can_varoff;
      // calculate no bytes, and offset for next time
      can_varoff += 8;
      if(can_varoff < NO_MSVAR_BYTES)
        can[0].cx_varbyt[ix] = 8;	      // get 8 bytes
      else  {
                                        // get remaining bytes
        can[0].cx_varbyt[ix] = NO_MSVAR_BYTES - (can_varoff - 8);
        can_varoff = 0; 
      }

      // This is where (in xmt ring buffer) to put next message
      if(can[0].cxno_in < (NO_CANMSG - 1))
        can[0].cxno_in++;
      else
        can[0].cxno_in = 0;
      // increment counter
      if(can[0].cxno < NO_CANMSG)
        can[0].cxno++;
      else
        can[0].cxno = NO_CANMSG;
      
      if(!(CANTIER & 0x07))  {
        // Following will cause entry to TxIsr without sending msg
        // since when CANTIER = 0, CANTFLG left as buff empty(>0).
        // If CANTIER has at least 1 int buf enabled, will enter
        // TxIsr automatically.
        CANTBSEL = CANTFLG;
        CANTIER = CANTBSEL;
      }
      can_mmsclk = 0;
  }
  
//////////////////////////// END of 0.001 second section ///////////////////////


//////////////////////////// 0.010 second section ////////////////////////////////////////
//                                                                                      //
if (millisec % 10 == 0) // every 1/100th second (actually 10/976 = 10.24 milliseconds)  //
//                                                                                      //
//           Everything in this section will execute ~100 times/second.                 //
//                                                                                      //
//////////////////////////////////////////////////////////////////////////////////////////
  {
	/**** Slow Speedometer Decay *********************************************
  If the VSS rate drops to zero quickly, the speedo will be frozen at a finite 
  value (large period = small frequency) that is greater than zero, since the 
  VSS_timer only works if the interrupt is called, so zeros are never filled 
  into the ic_period[] array.
  So instead, we force this array to zero gradually - not fast enough to 
  affect the speedo at any reasonable input rate but enough that it eventually 
  goes to zero if the interrupt is not called.     */

  if ((outpc.speedo <  50) && (outpc.speedo > 0))	 // if running slowly (less than 8 mph/kph)
    {
    if (ic1_period[ic1_recount] < 0xFFFFF)      // if not already at max
      {
       ic1_period[ic1_recount] = ic1_period[ic1_recount]*(((100-outpc.speedo)/50)+outpc.brake);  // up to 2x the existing period
                                                                                                 // 3x if braking                                                                                         
       ic1_recount++;                              // change next array index value next time
       if (ic1_recount > 20) ic1_recount = 1;
       if (outpc.speedo < 15) outpc.speedo=0; 
       } //end if  (ic_period[ic1_count] ...	        
    } // end if (!(outpc.speedo ...	 
    
  // average last 20 periods for both VSS and ISS/tach for filtering
  ic1_per_acc = 0;  // initialize accumulator variables
  ic2_per_acc = 0; 

  for (icx=1;icx < 21;icx++)
    {
     if (ic1_per_acc < 0x00FFFFFF) ic1_per_acc = ic1_per_acc + ic1_period[icx];  // VSS
     if (ic2_per_acc < 0x00FFFFFF) ic2_per_acc = ic2_per_acc + ic2_period[icx];  // ISS
    }
    
	// Calculate periods for VSS/ISS input masking:
	//
  // short_period_mask = average*(100 - mask%/2)/100
  //                   = (icx_per_acc/20) * (100 - (xss_mask/2))/100
	//                   = icx_per_acc * (100 - (xss_mask/2))/2000
	//
	// long_period_mask  = average*(100 + mask%)/100
	//                   = (icx_per_acc/20) * (100 + xss_mask)/100
	//                   = icx_per_acc * (100 + xss_mask)/2000
	// 
	// For example, if you enter 33%, and the current count for 
	// the 20 periods is 1440 (=1440/20 = 72 tics/tooth), then the 
	// mask is set to accept periods from 
	// 72-(33%/2) = 72-(0.165*72) = 60 tics to 
	// 72+33% = 96 tics. 
	// So the range is not based on 100% being no filtering. 
	// Instead, low numbers mean more filtering, high numbers mean less 
	// filtering (the INI limits the range to plausible values, you can 
	// edit the INI to expand the range, of course).
	//
	// Dividing the short period mask by two makes a bigger difference as the 
	// mask increases. For example, if you put in a mask of 100%, it allows 
	// periods from 36 to 144 in the above example, instead of from 
	// 0 to 144 if the 'divide by 2' wasn't used. 0 is any input frequency, 
	// no matter how fast! Instead, the range is half as fast to twice as fast.
	// The permissable mask% is also increased from 100% to 200%. This change
	// should help to more effectively filter the very short 'noise' pulses 
	// at low speeds.
	 
  short_period_vss = ((ic1_per_acc*(100-(inpram.vss_mask/2)))/2000);
  long_period_vss =  ((ic1_per_acc*(100+inpram.vss_mask))/2000);
  
  short_period_iss = ((ic2_per_acc*(100-(inpram.iss_mask/2)))/2000);
  long_period_iss =  ((ic2_per_acc*(100+inpram.iss_mask))/2000);
	  
  // Set the odometer
  // use VSS tooth count for high resolution
    if (!inpram.Metric_Units) {
       /* Calculate trip odometer (miles x 1000) */
       // odo = vss_teeth * vss_divide (inch/2000teeth) * 1/(5280*12) (mile/inch) * 1000 (to get miles X1000)
       // odo = vss_teeth * vss_divide * (1000/(2000*5280*12))
       // odo = vss_teeth * vss_divide / 126720
       // First do 2WD teeth
       outpc.odo = (unsigned long)(((unsigned long) ((outpc.vss_teeth/10 * (unsigned long) vss_divide))) /(12672));
       // Then add 4WD teeth
 //      outpc.odo += (unsigned long)(((unsigned long) vss_teeth_4wd/40 * (unsigned long) vss_divide) /(6020)*inpram.FWD_factor/1000);    
       }   // End if (!inpram.Metric_Units)

    else {  // metric  - tire_diam in cm (kph x 10)
       /* Calculate trip odometer (km x 1000) */
       // First do 2WD teeth
       outpc.odo = (unsigned long)(((unsigned long) (outpc.vss_teeth/inpram.no_teeth) * (unsigned long) vss_divide) /(204019)); // km x 100
       // Then add 4WD teeth
//       outpc.odo += (unsigned long)(((unsigned long) vss_teeth_4wd/40 * (unsigned long) vss_divide) /(9327)*inpram.FWD_factor/1000);    
       }  // End else (!inpram.Metric_Units)

  // Check 2WD/4WD switch on PE1
  // 2WD if high, 4WD if low
  
  if (PORTE & 0x02) {outpc.FWD = 1;} else outpc.FWD=0; 
  
 // Check brakes, unlock tcc if brakes on 
 // Check if braking 100 times per second.
 // Brake input is on GPI4 = AD7.
 // if not braking, update LOAD short term average. 
 
 if ((PORTAD0 & 0x80) == (inpram.brake_ON_polarity * 0x80)) // 0x80 = 10000000, i.e., if PAD07 is high
  {  
  outpc.brake = 1;   // braking
  error_flags = error_flags & ~0x80; // clear brake flag
  // always unlock TCC while braking
  outpc.lock_TCC = tcc_unlock();
  }
  else
  {
  outpc.brake = 0;   // not braking
  }
  
  // Update the LUF duty cycle if TCC is on and LUF enabled
  if ((inpram.cltch_enable > 1))
    {
      if (outpc.lock_TCC == 1) // TCC locked
        {
          tmp2 = Calc_LUF(); // get start LUF PWM% value
          PWMDTY3 = (char) (((tmp2 + (char) ((int) ((100-tmp2)*LUF_counter)/inpram.LUF_taper)) * PWMPER3)/100);  // Set LUF DC - we will update this every 0.010 seconds
        } 
      else  // TCC not locked
        {
        PWMDTY3 = (char) ((inpram.LUF_off * PWMPER3)/100);  // Set LUF DC - we will update this every 0.010 seconds  
        }  // end if (outpc.lock_TCC)
    
    } // end if ((inpram.cltch_enable ...
    
  /*-- COMPUTE VEHICLE SPEED
  
   We do this here so that the speedo updates regardles of any delays in the code
   (like the shift and pressure delays).
  
   For imperial units: 
 
   Vehicle speed = VSS_rpm * tire_diam/(5280*12) * 60 minutes/hr * PI/axle_ratio  (miles/hour)

  --*/
  // vehicle speed is delta X / delta T
  // delta T is the time for icx == 20 teeth = ic_per_acc, in 5.333 µsec tics
  
  outpc.ic1_per_avg = (ic1_per_acc/10*5333)/100; // the time for 20 VSS teeth (microseconds); 
  outpc.ic2_per_avg = (ic2_per_acc/10*5333)/100; // the time for 20 ISS teeth (microseconds);

  // delta X is the distance travelled by 20 teeth (in inches x100)
  // = tire_distance/rev * 1/axle_ratio * 20/no_teeth
  // = PI*tire_diam * 1/axle_ratio * 20/no_teeth == vss_divide
  //
  // so speedo = (pi*tire_diam * 1/axle_ratio * 20/no_teeth)/(ic_per_acc * constant)
  //           = constant * vss_divide/ic_per_acc

  if (!inpram.Metric_Units) { 
    
     // imperial	 - adjust for 2/4WD
     if (outpc.FWD)
        {
        outpc.speedo = (unsigned int) ((5680*vss_divide)/(outpc.ic1_per_avg));
        } 
        else        // if 4WD and speedo switching allowed, divide by speedo factor (x1000)
        {
        if (inpram.stdin_cfg & 0x01) outpc.speedo = (unsigned int) ((((5680*vss_divide)/(outpc.ic1_per_avg))*1000)/inpram.FWD_factor);
        }
     }   // End if (!inpram.Metric_Units)

  else {  // metric  - adjust for 2/4WD
     if (outpc.FWD) // if 2WD
        {     
        outpc.speedo = (unsigned int) ((9142*vss_divide)/(outpc.ic1_per_avg));
        }
        else        // if 4WD, divide by speedo factor (x1000)
        {
        if (inpram.stdin_cfg & 0x01) outpc.speedo = (unsigned int) ((((9142*vss_divide)/(outpc.ic1_per_avg))*1000)/inpram.FWD_factor);
        }
     }  // End else (!inpram.Metric_Units) 
     
if (outpc.speedo > 4000) outpc.speedo = 4000; // rail the speedometer
if (outpc.speedo < inpram.min_speed) outpc.speedo = 0; // set the speedo to zero

// Calculate Fuel Efficiency
// - Use current speed and averaged pulse width to calculate miles per gallon
// - fuel density - gasoline = 6.073 lb/USgal = 7.290 lb/Imp.gal
                                    
if ((inpram.CAN_enabled == 1) && (outpc.speedo > 0) && (outpc.engine_rpm > 0))  // if engine running and vehicle moving
   {
   tmp1 = ((long) outpc.speedo * (long) inpram.fuel_density);
   tmp2 = (120000 / (long) outpc.engine_rpm);
   outpc.mileage =  (short) (tmp1/((long) inpram.inj_flow)*100*tmp2/(long) inpram.nSquirts/((long) pulsewidth1-((long) inpram.open_time*10)));
   } 
   else // else mileage is zero
   {
   outpc.mileage = 0;
   }
   
// unlock the TCC if the speed has fallen 5 mph/kph below user set lock-up threshold
if (outpc.speedo < (inpram.minTCCspeed-50)) tcc_unlock();
  
// Calculate converter slippage (100% is no slip)
tmp = outpc.current_gear; 
outpc.os_rpm = (unsigned int)(((1000000/outpc.ic1_per_avg) * (2000/inpram.no_teeth)) * 600/1050);

if (inpram.ic2_usage & 0x01) // if IC2 used for input shaft sensor
  {   // Calculate input shaft RPM from last 20 teeth
    outpc.is_rpm = (unsigned int) (1200000000/(long) (inpram.iss_divider * (long) outpc.ic2_per_avg)); 
  } 
  else   // calculate input shaft speed from output shaft speed and gear ratios 
  {
  outpc.is_rpm = (unsigned int)(((long)outpc.os_rpm * (long)inpram.gear_table[tmp])/1000);   
  } // end if ic2_usage

outpc.converter_slip = (uchar)((long)outpc.engine_rpm*100/(long)outpc.is_rpm);

/*  -----  Update RPM, MAP & Battery Voltage with CAN ----------------------------------- */
// Update outpc. rpm, battery voltage, MAP, etc. for calculations using CAN data
// received from MS-II
// (that is, all the info is already there in the msvar array, the CAN data is 
// grabbed in sync with the timer updates in Timer_Clock_ISR(), but not all of 
// info that is assigned to specific variables).
// 
// This code only uses the rpm, kpa, adv_deg, pulsewidth1, and vBatt data from the 
// msvar array. (Other variables are included for reference and future development. To 
// be used, the corresponding variable must be uncommented and the variable added to 
// this code's outpc. structure - and the [CHANNELS] section of the INI 
// file if you want to log it, etc.)

if (inpram.CAN_enabled) 
  {
  // outpc.seconds        = *(int *)(msvar + 0);   // 2 bytes (int) at offset 0 in msvar[]
  pulsewidth1             = *(int *)(msvar + 2);   // 2 bytes (int) at offset 2 in msvar[]
  // outpc.pw2            = *(int *)(msvar + 4);   // 2 bytes (int) at offset 4 in msvar[]
  outpc.engine_rpm = *(int *)(msvar + 6);   // rpm is 2 bytes (int) at offset 6 in msvar[]
  outpc.adv_deg    = *(int *)(msvar + 8);   // 2 bytes (int) at offset 8 in msvar[]
  // outpc.squirt         = *(char *)(msvar + 10); // 1 byte (char) at offset 10 in msvar[]
  // outpc.engine         = *(char *)(msvar + 11); // 1 byte (char) at offset 11 in msvar[]
  // outpc.afrtgt1        = *(char *)(msvar + 12); // 1 byte (char) at offset 12 in msvar[]
  // outpc.afrtgt1        = *(char *)(msvar + 13); // 1 byte (char) at offset 13 in msvar[]
  // outpc.wbo2_en1       = *(char *)(msvar + 14); // 1 byte (char) at offset 14 in msvar[]
  // outpc.wbo2_en2       = *(char *)(msvar + 15); // 1 byte (char) at offset 15 in msvar[]
  // outpc.barometer      = *(int *)(msvar + 16);  // 2 bytes (int) at offset 16 in msvar[]
  outpc.LOAD       = *(int *)(msvar + 18 + (inpram.load_type*6));  // kPa is 2 bytes (int) at offset 18 in msvar[]
  // outpc.mat            = *(int *)(msvar + 20);  // 2 bytes (int) at offset 20 in msvar[]
  // outpc.coolant        = *(int *)(msvar + 22);  // 2 bytes (int) at offset 22 in msvar[]
  outpc.tps        = *(int *)(msvar + 24);  // 2 bytes (int) at offset 24 in msvar[]
  outpc.vBatt      = *(int *)(msvar + 26);  // 2 bytes (int) at offset 26 in msvar[]
  // outpc.afr1           = *(int *)(msvar + 28);  // 2 bytes (int) at offset 28 in msvar[]
  // outpc.afr2           = *(int *)(msvar + 30);  // 2 bytes (int) at offset 30 in msvar[]
  // outpc.knock          = *(int *)(msvar + 32);  // 2 bytes (int) at offset 32 in msvar[]
  // outpc.egoCorrection1 = *(int *)(msvar + 34);  // 2 bytes (int) at offset 34 in msvar[]
  // outpc.egoCorrection1 = *(int *)(msvar + 36);  // 2 bytes (int) at offset 36 in msvar[]
  // outpc.airCorrection  = *(int *)(msvar + 38);  // 2 bytes (int) at offset 38 in msvar[]
  // outpc.warmupEnrich   = *(int *)(msvar + 40);  // 2 bytes (int) at offset 40 in msvar[]
  // outpc.accelEnrich    = *(int *)(msvar + 42);  // 2 bytes (int) at offset 42 in msvar[]
  // outpc.tpsfuelcut     = *(int *)(msvar + 44);  // 2 bytes (int) at offset 44 in msvar[]
  // outpc.baroCorrection = *(int *)(msvar + 46);  // 2 bytes (int) at offset 46 in msvar[]
  // outpc.gammaEnrich    = *(int *)(msvar + 48);  // 2 bytes (int) at offset 48 in msvar[]
  // outpc.veCurr1        = *(int *)(msvar + 50);  // 2 bytes (int) at offset 50 in msvar[]
  // outpc.veCurr2        = *(int *)(msvar + 52);  // 2 bytes (int) at offset 52 in msvar[]
  // outpc.iacstep        = *(int *)(msvar + 54);  // 2 bytes (int) at offset 54 in msvar[]
  // outpc.coldAdvDeg     = *(int *)(msvar + 56);  // 2 bytes (int) at offset 56 in msvar[]
  // outpc.tpsDOT         = *(int *)(msvar + 58);  // 2 bytes (int) at offset 58 in msvar[]
  // outpc.mapDOT         = *(int *)(msvar + 60);  // 2 bytes (int) at offset 60 in msvar[]
  // outpc.dwell          = *(int *)(msvar + 62);  // 2 bytes (int) at offset 62 in msvar[]
  // outpc.maf            = *(int *)(msvar + 64);  // 2 bytes (int) at offset 64 in msvar[]
  // outpc.calcMAP        = *(int *)(msvar + 66);  // 2 bytes (int) at offset 66 in msvar[]
  // outpc.fuelCorrection = *(int *)(msvar + 68);  // 2 bytes (int) at offset 68 in msvar[]
  // outpc.portStatus     = *(char *)(msvar + 70); // 1 byte (char) at offset 70 in msvar[]
  // outpc.knockRetard    = *(char *)(msvar + 71); // 1 byte (char) at offset 71 in msvar[]
  // outpc.xTauFuelCorr1  = *(int *)(msvar + 72);  // 2 bytes (int) at offset 72 in msvar[]
  // outpc.egoV1          = *(int *)(msvar + 74);  // 2 bytes (int) at offset 74 in msvar[]
  // outpc.egoV2          = *(int *)(msvar + 76);  // 2 bytes (int) at offset 76 in msvar[]
  // outpc.amcUpdates     = *(int *)(msvar + 78);  // 2 bytes (int) at offset 78 in msvar[]
  // outpc.aux_voltsix    = *(int *)(msvar + 80);  // 2 bytes (int) at offset 80 in msvar[]
  // outpc.xTauFuelCorr2  = *(int *)(msvar + 82);  // 2 bytes (int) at offset 82 in msvar[]
  // outpc.spare1         = *(int *)(msvar + 84);  // 2 bytes (int) at offset 84 in msvar[]
  // outpc.spare2         = *(int *)(msvar + 86);  // 2 bytes (int) at offset 86 in msvar[]
  // outpc.spare3         = *(int *)(msvar + 88);  // 2 bytes (int) at offset 88 in msvar[]
  // outpc.spare4         = *(int *)(msvar + 90);  // 2 bytes (int) at offset 90 in msvar[]
  // outpc.spare5         = *(int *)(msvar + 92);  // 2 bytes (int) at offset 92 in msvar[]
  // outpc.spare6         = *(int *)(msvar + 94);  // 2 bytes (int) at offset 94 in msvar[]
  // outpc.spare7         = *(int *)(msvar + 96);  // 2 bytes (int) at offset 96 in msvar[]
  // outpc.spare8         = *(int *)(msvar + 98);  // 2 bytes (int) at offset 98 in msvar[]
  // outpc.spare9         = *(int *)(msvar + 100); // 2 bytes (int) at offset 100 in msvar[]
  // outpc.spare10        = *(int *)(msvar + 102); // 2 bytes (int) at offset 102 in msvar[]
  // outpc.spare11        = *(int *)(msvar + 104); // 2 bytes (int) at offset 104 in msvar[]
  // outpc.ospare         = *(char *)(msvar + 106);// 1 byte (cahr) at offset 106 in msvar[]
  // outpc.chksum         = *(char *)(msvar + 107);// 1 byte (char) at offset 107 in msvar[]
  // outpc.deltaT         = *(long *)(msvar + 108);// 4 bytes (long) at offset 108 in msvar[]
  }
else
  {
   // not CAN enabled
   if ((inpram.ic2_usage & 0x02) > 1) // if IC2 usage for tach signal
     { // calculate rpm from IC2 period average
     outpc.engine_rpm = (unsigned int) (1200000/(outpc.ic2_per_avg*inpram.iss_divider));
     } 
     else
     {  // calculate rpm from input shaft speed (which may be measured or may be calc'd from output shaft speed)
      outpc.engine_rpm = outpc.is_rpm;   // set engine rpm to input shaft speed
      inpram.rpm_check = 0;              // don't do rpm checks
     }

   outpc.vBatt = 120;                 // set default voltage to 12.0
  }
  
 // if TPS is high set short-term load to current load (to force immediate downshift, etc.)
 if (inpram.CAN_enabled && (outpc.tps > inpram.WOT_tps_threshold)) 
    {
    outpc.LOAD_short = outpc.LOAD; // reset load to current value (i.e., near maximum)
    WOT_flag = 1;                  // set hysteresis flag
    
    } // end if (CAN_enabled && (tps >

 } // end if (millisecond % 10 ...
//////////////////////////// END of 0.010 second section /////////////////////////////////


/////////////////////////// 0.100 second section /////////////////////////////////////
if (millisec % 98 == 0)     // 98 * 1.024 milliseconds
  {
  // Set VSS interupt received flag to false 10 times/sec (will get set to true in interrupt)
	VSS_rcvd =  0;            // set VSS interrupt flag to zero
	
  // VSS error - reset if get to 1000
  if (VSS_error >= inpram.vss_error_max) 
   {
   outpc.error |= 0x10; // set VSS tooth count reset error 
   VSS_reset();         // reset VSS
   }
	
	// Increment LUF counter if enabled
  if ((outpc.lock_TCC == 1) && (inpram.LUF_taper > 0))   // do not increment if TCC not ON
                                                         // or if taper set to zero 
                                                         // (keep PWM at initial value)
    {
    if(LUF_counter < inpram.LUF_taper) LUF_counter++;
    }
  else
    {
    LUF_counter = 0;
    }

    
   // Check if have 'shifted' manually using the gear lever
   if (prev_gearlever > outpc.manual_gear)  // have 'downshifted' manual lever
     { // check if should down shift (in any mode)
       if (outpc.current_gear > outpc.manual_gear)
         {
          outpc.upshift_request   = 0;  // request a downshift
          outpc.downshift_request = 1;
         }   // end if (outpc.current_gear >
     }
     else if (prev_gearlever < outpc.manual_gear)  // have 'upshifted' manual gear lever
     { // check if should upshift
       if (outpc.current_gear < outpc.manual_gear)
         {
            if ((inpram.shift_mode == 0) || (outpc.engine_rpm >= (inpram.rpm_limit-50))) // only if manual mode or over-reving 
                                                                                      // (otherwise let algorithm decide in auto modes)
               {
                outpc.upshift_request   = 1;  // request an upshift
                outpc.downshift_request = 0;
               } // end if ((inpram.shift_mode == 0
         }  // end if (outpc.current_gear <
     }  // end if (prev_gearlever >
  }  // end if (millisec % 98 ...
  
//////////////////////////// END of 0.100 second section ///////////////////////  


///////////////////////// 1.000 second section ///////////////////////////////////////////
//                                                                                      //
//           Everything in this section will execute 1 time/second.                     //  
if(millisec > 976) 
 {
//                                                                                      //
//                                                                                      //
//////////////////////////////////////////////////////////////////////////////////////////
  millisec = 0;
  // update seconds to send back to PC
  outpc.seconds++;
  if (outpc.seconds == 5) restart = 0; // clear reboot flag at 5 seconds
  
  // set speedo to zero if VSS interrupt inactive
  if (!VSS_rcvd)
    {
    outpc.speedo = 0; // reset the speedo

    for (ix=1;ix<21;ix++)         // reset the VSS period array
        {
        ic1_period[ix] = 0x0FFFF; // set the VSS period high (speed low)  
        }
    ic1_count=1; // reset counter  
    }
    
    if (WOT_flag) WOT_flag++;       // increment the WOT flag
    if (WOT_flag >10) WOT_flag = 0; // reset after ten seconds
   
  } // end 1.000 second section
   
////////////////////////  END of 1.000 second section ////////////////////////////////////

///////  Other timing increments (besides 0.001, 0.010, 0.100, and 1.000) ////////////////
  
// Get ADC values every ~25 millseconds (= 24/976) 
// - get one at a time by offsetting milliseconds
if (millisec % 24 == 0)      outpc.swADC         = get_adc(0); // swA for manual shift lever position load
if ((millisec+4) % 24 == 0)  outpc.swBDC         = get_adc(1); // swA for manual shift lever position load
if ((millisec+8) % 24 == 0)  outpc.clt           = get_adc(2); // get temperature (°F, unless Metric units, in which case Celsius)
if ((millisec+12) % 24 == 0) outpc.swCDC         = get_adc(3); // swA for manual shift lever position load
if ((millisec+16) % 24 == 0) outpc.line_pressure = (outpc.line_pressure*9 + get_adc(4))/10; // get line pressure (psi)
if ((millisec+20) % 24 == 0) outpc.aux_volts     = (outpc.aux_volts*9 + get_adc(5))/10;     // aux. data channel (0-5 volts), also used for non-CAN load

if (!inpram.CAN_enabled) // if no CAN connection
  {
   // Calculate load from auxillary channel voltage (external MAP, TPS, MAF, etc.)   
   // LOAD = (LOADmult * aux_volts) + LOADzero  
   // LOADmult and LOADzero are x100, get_adc(5) is volts x1000, LOAD is x10 
   outpc.LOAD = (uint) ((((long) inpram.LOADmult * (long) outpc.aux_volts)/10000) + (inpram.LOADzero)/10); // non-CAN MAP (volts to kPa)
  }

CLK_DONE:   // Statements after this label are executed every 0.128 millisecond clock 'tic'

//////////////////////////// 0.000128 millisecond section ///////////////////////  


if (inpram.out12_pwm_off)     // if PWM set
{
  // Bit-bang PWM on output1 (PE4) and output2 (PM2)
  out12_pwm_count++;       // increment PWM counter

  if (!(dither_flag & 0x02))  // if not on a refresh cycle on output1 (solA)
    {
     // Toggle solA (aka. output1)
     if (SOLAst && (out12_pwm_count < inpram.out12_pwm_on))    // counter below out_pwm_on value (start with on portion of cycle)
         {
          PORTE |= 0x10;      // if solA is active, set PE4 high
         }
     else     // counter between out_pwm_on and (out_pwm_off+out_pwm_on)
         {
          PORTE &= ~0x10;     // set PE4 low
         }    // end if  (SOLAst && ...
    }  // end if (!dither_flag...
  

  if (!(dither_flag & 0x04))  // if not on a refresh cycle on output2 (solB)
    { 
     // Toggle solB (aka. output2)
     if (SOLBst && (out12_pwm_count < inpram.out12_pwm_on))     // counter below out_pwm_on value
         {
          *pPTMpin[2] |= 0x04; // if solB is active, set PM2 high
         }
     else     // counter between out_pwm_on and (out_pwm_off+out_pwm_on)
         {
          *pPTMpin[2] &= ~0x04;// set PM2 low
         }                     // end if  (SOLBst && ...
    }  // end if (!dither_flag...
  
  if (out12_pwm_count > (inpram.out12_pwm_off + inpram.out12_pwm_on - 1)) out12_pwm_count = 0; // reset counter when total is reached
 
 } // end if (inpram.out12_pwm_off)

///////////////////////// end of 0.000128 millisecond section //////////////////// 

return;
}

// --------------- ISR_TimerOverflow ------------------------------------

INTERRUPT void Timer_Overflow_ISR(void) 
// Timer interrupt overflow service routine
// for TOI (bit 7 of TSCR2).
// Interrupts when TOF flag set if TOI == 1 
// The clock ticks are counted in the variable TCNT (which is defined in the 
// hsc12def.h file at memory location 0x0044). 
// There are 0xFFFF 'tics' (65,536 counts).
// In our case each 'tic' is 1/0.1875MHz  = 5.333 µs, so the overflow occurs 
// every 65535 * 5.3333 = 349.53 millisecond.
  {
  // Get display seconds from continuously running TCNT
  TC_ovflow++;
  if(TC_ovflow >= TC_ovfla[TC_ov_ix])  
    {
    // update seconds to send back to PC
    outpc.seconds++;     // increment secL in datalog
    if(TC_ov_ix >= 59)  
      {
      TC_ov_ix = 0;
      TC_ovflow = 0;
    }
    else
      TC_ov_ix++;
  }

  vss_timer_overflow++;

  // clear timer overflow interrupt flag (TOF)
  TFLG2 = 0x80;
  return;
}


/**************************************************************************
**
** Serial Communications (SCI)
**
** Communications is established when the PC communications program sends
** a command character - the particular character sets the mode:
**
** "a" = send all of the realtime display variables (outpc structure) via txport.
** "w"+<offset lsb>+<offset msb>+<nobytes>+<newbytes> = 
**    receive updated data parameter(s) and write into offset location
**    relative to start of data block
** "e" = same as "w" above, followed by "r" below to echo back value
** "r"+<offset lsb>+<offset msb>+<nobytes>+<newbytes> = read and
**    send back value of a data parameter or block in offset location
** "y" = verify inpram data block = inpflash data block, return no. bytes different.
** "b" = jump to flash burner routine and burn a ram data block into a flash 
**    data block.
** "t" = receive new data for clt/mat/ego/maf tables
** "T" = receive new table data for CAN re-transmission to GPIO
** "c" = Test communications - echo back Seconds
** "Q" = Send over Embedded Code Revision Number
** "S" = Send program title.
**
**************************************************************************/
/*
To use SCI1 for 8 bit, non-parity communication:

1. Select the baud rate via SCI0BDH/L Baud Rate Registers (we did this earlier). 
2. Enable transmission and/or reception as desired by setting the TE and/or RE bits in 
the SCI0CR2 Control Register.

         bit        7       6      5     4     3     2     1     0
                   TIE     TCIE   RIE   ILIE   TE    RE   RWU   SBK
 
3. For:

- transmission - poll the TDRE flag and write data to the SCI0DRL register at 
                 address $00D7 when the TDRE flag is set. The data we write 
                 to the SCI0DRL register is the data that will be immediately 
                 output from the TX pin. 
- reception    - poll the RDRF register (SCI0SR1). When the RDRF register is 
                 set ((SCI0SR1 & 0x20) <> 0), we can then 
                 read the incoming data by reading the SCI1DRL register. The 
                 data we read in from this register is the input via the RX pin. 
*/

#pragma CODE_SEG NON_BANKED

INTERRUPT void Serial_Comm_ISR(void)  {
  char dummy,save_page;
  int ix;
  static int rd_wr,xcntr;
  static unsigned char CANid,ibuf,sendCANdat=0,next_txmode,cksum;
  unsigned char sect,nsect;
  static unsigned int txptr,tble_word,ntword;
  
#define getCANid             40
#define getTableId           41
#define setBurningParameters 99
  
  // if RDRF register not set, => transmit interrupt
  if(!(SCI0SR1 & 0x20))goto XMT_INT;

// Receive Interrupt
  // Clear the RDRF bit by reading SCISR1 register (done above), then read data
  //  (in SCIDRL reg).
  // Check if we are receiving new input parameter update
txgoal = 0;
rcv_timeout = 0xFFFFFFFF;
if(SCI0SR1 & 0x08)  {	 // check for rcv Overrun error
   txmode = 0;
   dummy = SCI0DRL;
   SCI0CR2 &= ~0xAC;   // rcv, xmt disable, interrupt disable
   kill_ser = 1;
   kill_ser_t = outpc.seconds + SER_TOUT;
   return;
}

switch(txmode)  {

case 0:

  switch(SCI0DRL)  {
    case 'a':				 // send back all real time ram output variables
  next_txmode = 1;
  txmode = getCANid;
  cksum = 0;
  break;

    case 'w':		  // receive new ram input data and write into offset location;
                  // also used for forwarding CAN msgs bet. MT & auxilliary boards.
                  // In this CAN mode, no_bytes must be >0 and <=8.
  next_txmode = 5;
  txmode = getCANid;
  rd_wr = 1;
  break;

    case 'e':		  // same as 'w', but verify by echoing back values. Don't use with CAN
  next_txmode = 5;
  txmode = getCANid;
  rd_wr = 2;
  break;

    case 'r':		  // read and send back ram input data from offset location;
                  //  also used for forwarding CAN msgs bet. MT & auxilliary boards
  next_txmode = 5;
  txmode = getCANid;
  rd_wr = 0;
  cksum = 0;
  break;

    case 'y':      // Verify that a flash data block matches a
  next_txmode = 2; //  corresponding ram data block
  txmode = getCANid;
  break;
   	
    case 'b':      // burn a block of ram input values into flash;
                   // also used for forwarding CAN msgs between 
                   // tuning software & auxillary boards
  next_txmode = setBurningParameters;
  txmode      = getCANid;
  break;

    case 't':      // update a flash table with following serial data
  txmode = 20;
  cksum = 0;
  break;

    case '!':      // start receiving reinit/reboot command
  txmode = 30;
  break;

    case 'c':      // send back seconds to test comms
  txcnt = 0;         
  txmode = 1;
  txgoal = 2;      // seconds is 1st 2 bytes of outpc structure
  txbuf.seconds = outpc.seconds;
  SCI0DRL = *(char *)&txbuf;
  SCI0CR2 &= ~0x24; // rcv, rcvint disable
  SCI0CR2 |= 0x88; // xmit enable & xmit interrupt enable
  break;

    case 'Q':     // send code rev no.
  txcnt = 0;
  txmode = 4;
  txgoal = 20;
  SCI0DRL = RevNum[0]; 
  SCI0CR2 &= ~0x24;   // rcv, rcvint disable
  SCI0CR2 |= 0x88;    // xmit enable & xmit interrupt enable
  break;

    case 'S':         // send program title
  txcnt = 0;
  txmode = 5;
  txgoal = 32;
  SCI0DRL = Signature[0]; 
  SCI0CR2 &= ~0x24;   // rcv, rcvint disable
  SCI0CR2 |= 0x88;    // xmit enable & xmit interrupt enable
  break;
  
    case 'k':         // request checksum.
  txcnt = 0;
  txmode = 0;
  txgoal = 1;
  SCI0DRL = cksum; 
  SCI0CR2 &= ~0x24;   // rcv, rcvint disable
  SCI0CR2 |= 0x88;    // xmit enable & xmit interrupt enable
  break;

    default:
  break;
  }     // End of switch for received command
    break;

case getCANid:   // Get CAN id for current command.
   CANid = SCI0DRL;
   if (CANid < MAX_CANBOARDS)
     txmode = getTableId;
   else	 {
  	 // CANid wrong - kill comms since don't know where to send data
     txmode = 0;
     SCI0CR2 &= ~0xAC;   // rcv, xmt disable, interrupt disable
     kill_ser = 1;
     kill_ser_t = outpc.seconds + SER_TOUT;
     return;
   }
   break;
case getTableId: // Get table id for current command.
   tble_idx = SCI0DRL;   // 
   if(CANid != can_id)  
     {
     txmode = next_txmode;
     } 
   else if(tble_idx >= NO_TBLES)  
     { // if CANid is this board and table index is not a valid value
  	   // tble index wrong
     txmode = 0;
     //  kill comms since don't know which/ how much data
     SCI0CR2 &= ~0xAC;   // rcv, xmt disable, interrupt disable
     kill_ser = 1;
     kill_ser_t = outpc.seconds + SER_TOUT;
     return;
     }
   else  // if CANid is not this board, and table index is valid
     txmode = next_txmode;
   next_txmode = 0;
   if (txmode == 1) 
     { 
     txcnt = 0;
     txgoal = tableBytes(tble_idx);
     // load output variables into txbuf to avoid incoherent word data.
     // To work all words in structure must be word aligned, all longs
     // aligned on long boundaries.
     xcntr = 0;
     txptr = 0;
     *tableWordRam(tble_idx,0) = *((unsigned int *)(&outpc) + 0);
     *tableWordRam(tble_idx,1) = *((unsigned int *)(&outpc) + 1);
     SCI0DRL = *tableByteRam(tble_idx, 0);
     cksum += SCI0DRL; 
     SCI0CR2 &= ~0x24;   // rcv, rcvint disable
     SCI0CR2 |= 0x88;    // xmit enable & xmit interrupt enable
     }
   else if (txmode == setBurningParameters) 
     { // Burn command
     if(CANid != can_id)  
       {
  	   // set up single CAN message & forward to aux board
  	   ix = can[0].cxno_in;
  	   can[0].cx_msg_type[ix] = MSG_BURN; 
  	   can[0].cx_destvarblk[ix] = tble_idx;
  	   can[0].cx_destvaroff[ix] = 0;
  	   can[0].cx_dest[ix] = CANid;
  	   can[0].cx_varbyt[ix] = 0;		 // no data bytes
  	   // This is where (in xmt ring buffer) to put next message
  	   if(can[0].cxno_in < (NO_CANMSG - 1)) can[0].cxno_in++;
  	   else can[0].cxno_in = 0;	 // overwrite oldest msg in queue
  	   // increment counter
  	   if(can[0].cxno < NO_CANMSG)
  	     can[0].cxno++;
  	   else
  	     can[0].cxno = NO_CANMSG;
  	   if(!(CANTIER & 0x07))  
  	     {
  	     // Following will cause entry to TxIsr without sending msg
  	     // since when CANTIER = 0, CANTFLG left as buff empty(>0).
  	     // If CANTIER has at least 1 int buf enabled, will enter
  	     // TxIsr automatically.
  	     CANTBSEL = CANTFLG;
  	     CANTIER = CANTBSEL;
  	     } 
       }
     else if(flocker == 0) 
       { 
       flocker     = 0xCC;    // set semaphore to prevent burning flash thru runaway code
       burn_flag   = 1;
       burn_idx = tble_idx;
       }
     txmode      = 0;			 // handle burning in main loop
   } 
   else if(txmode == 2)  {
     vfy_flg = 1;
   	 vfy_fail = 0;
   }
   break;

case 5:
  	if(CANid != can_id)  {
  	  // MT wants to send data to aux board via CAN
  	  if(rd_wr == 1)  {
  	    sendCANdat = 1;
  	  } 
  	  // MT wants to request data from aux board via CAN
  	  else if(rd_wr == 0)  {
  	    getCANdat = 1;
  	    ltch_CAN = lmms + 3906;   // 0.5 sec timeout to get CAN data
  	  } 
  	  else  {
  	    sendCANdat = 0;
  	    getCANdat = 0;
  	  }
  	}
  	rxoffset = (SCI0DRL << 8); // byte offset(msb) from start of inpram
  	txmode++;
  	break;

case 6:
  	rxoffset |= SCI0DRL;       // byte offset(lsb) from start of inpram
  	txmode++;
  	break;
  	
case 7:
  	// nbytes must be <= 8 for CAN interaction
  	rxnbytes = (SCI0DRL << 8); 	// no. bytes (msb)
  	txmode++;
  	break;

case 8:
  	rxnbytes |= SCI0DRL;		// no. bytes (lsb)
  	rxcnt = 0;
  	// check won't blow table arrays
  	if(rxoffset + rxnbytes > tableBytes(tble_idx))  {
  	  //  kill comms since don't know how much data being sent
  	  txmode = 0;
  	  SCI0CR2 &= ~0xAC;   // rcv, xmt disable, interrupt disable
  	  kill_ser = 1;
  	  kill_ser_t = outpc.seconds + SER_TOUT;
  	  return;
  	}
  	if(rd_wr == 0)  {		    // read & send back input data
  	  if(getCANdat)  {
  	    txmode = 0;         // done receiving/writing all the data
  	    // set up single CAN message & forward to aux board
  	    //   MT requesting data from aux board.
  	    //   Note: MT must only deal with 1 board at a time and wait til
  	    //   data back or timeout.
  	    ix = can[0].cxno_in;
  	    can[0].cx_msg_type[ix] = MSG_REQ; 
  	    can[0].cx_destvarblk[ix] = tble_idx;
  	    can[0].cx_myvarblk[ix] = tble_idx;  // MT will have duplicate
  	                                    // varblks for the aux boards
  	    can[0].cx_destvaroff[ix] = rxoffset;
  	    can[0].cx_myvaroff[ix] = rxoffset;
  	    can[0].cx_dest[ix] = CANid;
  	    can[0].cx_varbyt[ix] = (unsigned char)rxnbytes;
  	    // This is where (in xmt ring buffer) to put next message
  	    if(can[0].cxno_in < (NO_CANMSG - 1))
  	      can[0].cxno_in++;
  	    else
  	      can[0].cxno_in = 0;	 // overwrite oldest msg in queue
  	    // increment counter
  	    if(can[0].cxno < NO_CANMSG)
  	      can[0].cxno++;
  	    else
  	      can[0].cxno = NO_CANMSG;
  	    if(!(CANTIER & 0x07))  {
  	      // Following will cause entry to TxIsr without sending msg
  	      // since when CANTIER = 0, CANTFLG left as buff empty(>0).
  	      // If CANTIER has at least 1 int buf enabled, will enter
  	      // TxIsr automatically.
  	      CANTBSEL = CANTFLG;
  	      CANTIER = CANTBSEL;
  	    }
  	  } 
  	  else  {
  	    txcnt = 0;
  	    txmode = 3;
  	    txgoal = rxnbytes;
  	    SCI0DRL = *tableByteRam(tble_idx, rxoffset);
  	    cksum += SCI0DRL;
  	    SCI0CR2 &= ~0x24;     // rcv, rcvint disable
  	    SCI0CR2 |= 0x88;      // xmit enable & xmit interrupt enable
  	  }
  	}
  	else  {                   // write data to input data block buffer to maintain 
  	                          // coherence of inputs while awaiting serial bytes
  	  if((rxnbytes > 1) && (rxnbytes < sizeof(txbuf)))
  	    ibuf = 1;
  	  else
  	    ibuf = 0; 
  	  txmode++;
  	}
  	break;

case 9:
  	// Check for data overrun.  Note: this
  	// still not bullet proof because could write half the bytes
  	// then have overrun; need to store and write all at once if
  	// all is valid.
  	if((SCI0SR1 & 0x08) == 0)  {   // none yet- write data.
  	  if(sendCANdat)
  	    *((char *)&txbuf + rxcnt) = SCI0DRL;
  	  else if(!ibuf)
  	    *tableByteRam(tble_idx, rxoffset + rxcnt) = SCI0DRL;
  	  else
  	    *((char *)&txbuf + rxcnt) = SCI0DRL;
  	}
  	rxcnt++;
  	if(rxcnt >= rxnbytes)  {
  	  if(sendCANdat)  {
  	    txmode = 0;         // done receiving/writing all the data
  	    sendCANdat = 0;
  	    // set up single CAN message & forward to aux board
  	    //   MT sending data to aux board
  	    for(ix = 0; ix < rxnbytes; ix++)  {
  	      can[0].cx_datbuf[can[0].cxno_in][ix] = *((char *)&txbuf + ix);
  	    }
  	    ix = can[0].cxno_in;
  	    can[0].cx_msg_type[ix] = MSG_CMD; 
  	    can[0].cx_destvarblk[ix] = tble_idx;
  	    can[0].cx_destvaroff[ix] = rxoffset;
  	    can[0].cx_dest[ix] = CANid;
  	    can[0].cx_varbyt[ix] = (unsigned char)rxnbytes;
  	    // This is where (in xmt ring buffer) to put next message
  	    if(can[0].cxno_in < (NO_CANMSG - 1))
  	      can[0].cxno_in++;
  	    else
  	      can[0].cxno_in = 0;	 // overwrite oldest msg in queue
  	    // increment counter
  	    if(can[0].cxno < NO_CANMSG)
  	      can[0].cxno++;
  	    else
  	      can[0].cxno = NO_CANMSG;
  	    if(!(CANTIER & 0x07))  {
  	      // Following will cause entry to TxIsr without sending msg
  	      // since when CANTIER = 0, CANTFLG left as buff empty(>0).
  	      // If CANTIER has at least 1 int buf enabled, will enter
  	      // TxIsr automatically.
  	      CANTBSEL = CANTFLG;
  	      CANTIER = CANTBSEL;
  	    } 
  	  } 
  	  else if(ibuf)  {
  	    for(ix = 0; ix < rxnbytes; ix++)  {
  	      *tableByteRam(tble_idx,rxoffset + ix) = *((char *)&txbuf + ix);
  	    }
  	  }
  	  dummy = SCI0DRL;      // clear OR bit (read sci0sr1(done), sci0drl)
  	  if(rd_wr == 1)
  	    txmode = 0;         // done receiving/writing all the data
  	  else  {
  	    txcnt = 0;				// send back data just written
  	    txmode = 3;				//	 for verification
  	    txgoal = rxnbytes;
  	    SCI0DRL = *tableByteRam(tble_idx, rxoffset); 
  	    SCI0CR2 &= ~0x24;   // rcv, rcvint disable
  	    SCI0CR2 |= 0x88;    // xmit enable & xmit interrupt enable
  	  }
  	}       // finished all rxnbytes
  	break;

case 20:	 // d/load & burn tables - *** Do while engine NOT turning ***
  	       // Note: this type table has no ram copy - its not intended for tuning
  	tble_idx = SCI0DRL;    // type of table
  	if(tble_idx > NO_TBLES)	 {
  	  // tble index wrong - kill comms since don't know how much data being sent
  	  txmode = 0;
  	  SCI0CR2 &= ~0xAC;   // rcv, xmt disable, interrupt disable
  	  kill_ser = 1;
  	  kill_ser_t = outpc.seconds + SER_TOUT;
  	  return;
  	}
  	ntword = 0;
  	flocker = 0xCC; // set semaphore to prevent burning flash thru runaway code
  	save_page = PPAGE;
  	PPAGE = 0x3C;
	  // erase table
  	nsect = (unsigned char)(tableWords(tble_idx)/SECTOR_WORDS);
  	if(tableWords(tble_idx) > (nsect*SECTOR_WORDS))nsect++;
  	for (sect = 0; sect < nsect; sect++)  {
  	  Flash_Erase_Sector(tableWordFlash(tble_idx, 0) + sect * SECTOR_WORDS);
  	}
  	PPAGE = save_page;
  	flocker = 0;
  	txmode++;
  	break;

case 21:
  	cksum += SCI0DRL;
  	tble_word = (SCI0DRL << 8);    // msb
  	txmode++;
  	break;

case 22:
  cksum += SCI0DRL;
  tble_word |= SCI0DRL;            // lsb
  flocker = 0xCC; // set semaphore to prevent burning flash thru runaway code
  save_page = PPAGE;
  PPAGE = 0x3C;
  // write table word
  Flash_Write_Word(tableWordFlash(tble_idx, ntword), tble_word);
  flocker = 0;
  ntword++;
  if(ntword < tableWords(tble_idx))
    txmode = 21;
  else  {
    txmode = 0;
    VSS_reset();
  }
  PPAGE = save_page;
  break;
/*
case 24:
  	// nbytes must be <= 8 for CAN interaction
  	rxnbytes = (SCI0DRL << 8); 	// no. bytes (msb)
  	txmode++;
  	break;

case 25:
  	rxnbytes |= SCI0DRL;        // no. bytes (lsb)
  	rxcnt = 0;
  	Tcntr = 0;
  	txmode++;
  	break;

case 26:
  	// get 4 table words (8 bytes). Receive msb,then lsb for each word 
  	cksum += SCI0DRL;
  	*((char *)&txbuf + Tcntr) = SCI0DRL;
  	Tcntr++;
  	rxcnt++;
  	if((Tcntr >= 8) || (rxcnt >= rxnbytes))  {
  	    // set up single CAN message & forward to aux board
  	    //   MT sending data to aux board
  	    for(ix = 0; ix < Tcntr; ix++)  {
  	      can[0].cx_datbuf[can[0].cxno_in][ix] = *((char *)&txbuf + ix);
  	    }
  	    ix = can[0].cxno_in;
  	    can[0].cx_msg_type[ix] = MSG_CMD; 
  	    can[0].cx_destvarblk[ix] = tble_idx;
  	    can[0].cx_destvaroff[ix] = 0;
  	    can[0].cx_dest[ix] = CANid;
  	    can[0].cx_varbyt[ix] = Tcntr;
  	    send_can(0);
  	    Tcntr = 0;
  	    if(rxcnt >= rxnbytes)     // done
  	      txmode = 0;
  	}
  	break;
*/
case 30:
  	if(SCI0DRL == '!')    // receive 2nd ! for reboot signal
  	  txmode++;
  	else if(SCI0DRL == 'x')  {  // received reinit signal (!x)
  	  reinit_flag = 1;
  	  txmode = 0;
  	}
  	else
  	  txmode = 0;
  	break;

case 31:
  	if(SCI0DRL == 'x')  {   // received complete reboot signal(!!x)
  	}
  	else if(SCI0DRL == '!')  {   // received complete reload signal(!!!)	  
  	}
  	txmode = 0;
  	break;

default:
  dummy = SCI0DRL;   // dummy read of reg to clear int flag
  break;

  }     // End of case switch for received data
    
  if((txgoal == 0) && (txmode > 0))
    rcv_timeout = lmms + 3906;   // 0.5 sec timeout
  return;

// Transmit Interrupt
XMT_INT:
  // Clear the TDRE bit by reading SCISR1 register (done), then write data
  txcnt++;
  if(txcnt < txgoal)  {
    if(txmode == 3)  {
  	  SCI0DRL = *tableByteRam(tble_idx, rxoffset + txcnt);
  	  if(rd_wr == 0)
  	    cksum += SCI0DRL;
    }
    else if(txmode == 4)  {
  	  SCI0DRL = RevNum[txcnt];
    }
    else if(txmode == 5)  {
  	  SCI0DRL = Signature[txcnt];
    }
    else if(txmode == 2)  {
  	  SCI0DRL = *((char *)&vfy_fail + txcnt);
    } 
    else if(txmode == 6)  {
  	  // pass on CAN msg to MT
  	  SCI0DRL = *((char *)&txbuf + txcnt);
    } 
    else  {
      xcntr++;
      SCI0DRL = *tableByteRam(tble_idx, xcntr);
      cksum += SCI0DRL;
      if(xcntr >= 3)  {	 // transmitted 4 bytes, load 2 new words
        xcntr = -1;
        txptr += 2;
        *tableWordRam(tble_idx,0) = *((unsigned int *)(&outpc) + txptr);
        *tableWordRam(tble_idx,1) = *((unsigned int *)(&outpc) + txptr+1);
      }
    }
  } 
  else  {   // done transmitting
    if(txmode == 1)
//      outpc.cksum = cksum;
    txcnt = 0;
    txgoal = 0;
    txmode = 0;
    SCI0CR2 &= ~0x88;    // xmit disable & xmit interrupt disable
    SCI0CR2 |= 0x24;     // rcv, rcvint re-enable
  } 
  return;
}													 

#pragma CODE_SEG DEFAULT        

void CanInit(void)
{
unsigned char ix;
	/* Set up CAN communications */
	/* Enable CAN, set Init mode so can change registers */
	CANCTL1 |= 0x80;
	CANCTL0 |= 0x01;
	
	/* clear ring buffers */
	for(ix = 0;ix < 2;ix++)  {
	  can[ix].cxno = 0;
	  can[ix].cxno_in = 0;
	  can[ix].cxno_out = 0;
	}
	can_status = 0;
	
	while(!(CANCTL1 & 0x01));  // make sure in init mode
	
	/* Set Can enable, use IPBusclk (24 MHz),clear rest */
	CANCTL1 = 0xC0;  
	/* Set timing for .5Mbits/ sec */
	CANBTR0 = 0xC2;  /* SJW=4,BR Prescaler= 3(24MHz CAN clk) */
	CANBTR1 = 0x1C;  /* Set time quanta: tseg2 =2,tseg1=13 
	              (16 Tq total including sync seg (=1)) */
	CANIDAC = 0x00;   /* 2 32-bit acceptance filters */
	/* CAN message format:
	 Reg Bits: 7 <-------------------- 0
	  IDR0:    |---var_off(11 bits)----|  (Header bits 28 <-- 21)
	  IDR1:    |cont'd 1 1 --msg type--|  (Header bits 20 <-- 15)
	  IDR2:    |---From ID--|--To ID---|  (Header bits 14 <--  7)
	  IDR3:    |--var_blk-|--spare--rtr|  (Header bits  6 <-- 0,rtr)
	*/  
	/* Set identifier acceptance and mask registers to accept 
	     messages only for can_id or device #15 (=> all devices) */
	/* 1st 32-bit filter bank-to mask filtering, set bit=1 */
	CANIDMR0 = 0xFF;       // anything ok in IDR0(var offset)
	CANIDAR1 = 0x18;       // 0,0,0,SRR=IDE=1
	CANIDMR1 = 0xE7;		   // anything ok for var_off cont'd, msgtype
	CANIDAR2 = can_id;     // rcv msg must be to can_id, but
	CANIDMR2 = 0xF0;			 // can be from any other device
	CANIDMR3 = 0xFF;       // any var_blk, spare, rtr
	/* 2nd 32-bit filter bank */
	CANIDMR4 = 0xFF;       // anything ok in IDR0(var offset)
	CANIDAR5 = 0x18;       // 0,0,0,SRR=IDE=1
	CANIDMR5 = 0xE7;		   // anything ok for var_off cont'd, msgtype
	CANIDAR6 = 0x0F;			 // rcv msg can be to everyone (id=15), and
	CANIDMR6 = 0xF0;			 // can be from any other device
	CANIDMR7 = 0xFF;       // any var_blk, spare, rtr
	
	/* clear init mode */
	CANCTL0 &= 0xFE;  
	/* wait for synch to bus */
	while(!(CANCTL0 & 0x10));
	
	/* no xmit yet */
	CANTIER = 0x00;
	/* clear RX flag to ready for CAN recv interrupt */
	CANRFLG = 0xC3;
	/* set CAN rcv full interrupt bit */
	CANRIER = 0x01;
	return;
}


#pragma CODE_SEG  NON_BANKED

INTERRUPT void CanTxIsr(void)
{
unsigned char ix,jx,kx;

outpc.CANtx++; // increment CAN transmit counter
  
/* CAN Xmit Interrupt */
CANTBSEL = CANTFLG;    // select MSCAN xmit buffer
// Check ring buffers and xfer to MSCAN buffer
for(ix = 0;ix < 2;ix++)  {
 if(can[ix].cxno)  {
 	jx = can[ix].cxno_out;
 	/* Set up identifier registers */
	CAN_TB0_IDR0 = (unsigned char)(can[ix].cx_destvaroff[jx] >> 3);
	                      // 8 high bits in IDR0, 3 low bits in IDR1
	CAN_TB0_IDR1 = 
	  (unsigned char)((can[ix].cx_destvaroff[jx] & 0x0007) << 5) | 
	               0x18 |           // SRR=IDE=1
                 can[ix].cx_msg_type[jx];			// 3 bits
	CAN_TB0_IDR2 = (can_id << 4) | can[ix].cx_dest[jx];
	CAN_TB0_IDR3 = (can[ix].cx_destvarblk[jx] << 4);
	/* Set xmt buffer priorities (lower is > priority) */
	CAN_TB0_TBPR = 0x02;
	
	/* set data in buffer */
	switch(can[ix].cx_msg_type[jx])  {
		case MSG_CMD:  // msg for dest ecu to set a variable to val in msg
		case MSG_RSP:  // msg in reply to dest ecu's request for a var val
		  CAN_TB0_DLR = can[ix].cx_varbyt[jx];
		  for(kx = 0;kx < CAN_TB0_DLR;kx++)  {
		    *(&CAN_TB0_DSR0 + kx) = can[ix].cx_datbuf[jx][kx];
		  }
		  break;
		
		case MSG_REQ:  // msg to send back current value of variable(s)
		  // this 1st byte holds var blk for where to put rcvd data
		  // 2nd,3rd bytes hold var offset rel. to var blk and how
		  // many consecutive bytes to be sent back
		  CAN_TB0_DLR = 3;
		  CAN_TB0_DSR0 = can[ix].cx_myvarblk[jx];
		  CAN_TB0_DSR1 = (unsigned char)(can[ix].cx_myvaroff[jx] >> 3);
		  CAN_TB0_DSR2 = (unsigned char)((can[ix].cx_myvaroff[jx] & 0x0007) << 5) | 
		                                  can[ix].cx_varbyt[jx];
		  break;
	  
		case MSG_XSUB:
		  CAN_TB0_DLR = 0;
		  break;
		  
		case MSG_BURN:
		  CAN_TB0_DLR = 0;
		  break;
	}   // end mtype switch
	// This is where (in xmt buffer) to get next outgoing message
	if(can[ix].cxno_out < (NO_CANMSG - 1))
		can[ix].cxno_out++;
	else
		can[ix].cxno_out = 0;
	if(can[ix].cxno > 0)
		can[ix].cxno--;    // TB buf loaded for xmit - decrement ring buf count
	can_status &= CLR_XMT_ERR;
	/* set CAN xmt interrupt bit and clear flag to initiate transmit */
	CANTIER |= CANTBSEL;  
	CANTFLG = CANTBSEL;  // 1 clears(buffer full), 0 is ignored
	break;               // only handle 1 buffer entry at time
 }
 if(ix == 1)  {    // nothing left in either ring buffer
  CANTIER = 0x00;  // leave CANTFLG as buffer empty, but disable interrupt
                   // Note: if this last xmt in buf, will re-neter ISR, but 
                   //  then exit because can[0,1].cxno = 0. 
  break;  
 }
}		        // end for loop
if((CANRFLG & 0x0C) != 0)  {
        // Xmt error count
        can_status |= XMT_ERR;
        //can_reset = 1;
}
return;
}

INTERRUPT void CanRxIsr(void)
{
unsigned char rcv_id,msg_type,var_blk,var_byt,jx,kx;
unsigned short var_off,dvar_off;

outpc.CANrx++; // increment CAN transmit counter

    /* CAN Recv Interrupt */
    if(CANRFLG & 0x01)  {
    
        var_off = ((unsigned short)CAN_RB_IDR0 << 3) |
                       ((CAN_RB_IDR1 & 0xE0) >> 5);
        msg_type = (CAN_RB_IDR1 & 0x07);
        rcv_id = CAN_RB_IDR2 >> 4;		// message from device rcv_id
        var_blk = CAN_RB_IDR3 >> 4;
        var_byt = (CAN_RB_DLR & 0x0F); // &0x0F = 00001111, get first 4 bits

        switch(msg_type)  {
        
          case MSG_CMD:  // message for this ecu to set a variable to value in message
          case MSG_RSP:  // message in reply to this ecu's request for a variable value
        	               // value in the data buffer in received msesage
            if(getCANdat)  {
              // set up for serial xmit of the recvd CAN bytes (<= 8)
              // MegaTune getting back data requested from auxillary board
              txcnt = 0;
              txgoal = var_byt;
              if((txgoal > 0) && (txgoal <= 8))  {
                txmode = 6;
                for(jx = 0;jx < var_byt;jx++)  {
                  *((char *)&txbuf + jx) = *(&CAN_RB_DSR0 + jx);
                }
                SCI0DRL = *((char *)&txbuf);
                SCI0CR2 &= ~0x24;       // rcv, rcvint disable
                SCI0CR2 |= 0x88;        // xmit enable & xmit interrupt enable
              } 
              else  {
                txgoal = 0;
                txmode = 0;
              }
              getCANdat = 0;
              ltch_CAN = 0xFFFFFFFF;
              break;
            } 
            else  {
              // update variable value with received data
              for(jx = 0;jx < var_byt;jx++)  {
                *(canvar_blkptr[var_blk] + var_off + jx) = 
                       *(&CAN_RB_DSR0 + jx);
                       
             /* testing CAN comms */ 
             // rpm is offset 6, kpa is offset 18 (don't use baro = 16)
             //
             // var_blk is the table (block) reference ID (2 for msvar[])
             //
             // We grab 8 bytes at a time, so:
             //
             // var_off runs 0 to number of bytes in the data in 8 byte increments,
             //    ex. 0 to 104 for 112 byte MS-II outpc.
             //
             // jx runs 0 to 7 bytes (added to the var_off bytes to get the total offset)
             // 
             // So the value at offset 36 is referenced by
             // var_blk == 2  (msvar)
             // var_off == 32 (the next smaller that is evenly divisible by 8)
             // jx = 3 (which is added to 32 to get 36)
                     
                msvar[var_off + jx]  =  *(&CAN_RB_DSR0 + jx); // fill the msvar[] array

              }
            }
            break;
        		
          case MSG_REQ:  // msg to send back current value of variable(s)
            // Update related parameters for xmt ring buffer
            jx = can[0].cxno_in;
            can[0].cx_msg_type[jx] = MSG_RSP;
            // destination var blk
            can[0].cx_destvarblk[jx] = CAN_RB_DSR0;
            dvar_off = ((unsigned short)CAN_RB_DSR1 << 3) |
                                     ((CAN_RB_DSR2 & 0xE0) >> 5);
            can[0].cx_destvaroff[jx] = dvar_off;
            can[0].cx_dest[jx] = rcv_id;
            var_byt = CAN_RB_DSR2 & 0x1F;
            can[0].cx_varbyt[jx] = var_byt;
            // put variable value(s) in xmit ring buffer
            for(kx = 0;kx < var_byt;kx++)  {
              can[0].cx_datbuf[jx][kx] = 
                   *(canvar_blkptr[var_blk] + var_off + kx);
            }
            // This is where (in xmt buffer) to put next messge
            if(can[0].cxno_in < (NO_CANMSG - 1))
              can[0].cxno_in++;
            else
              can[0].cxno_in = 0;
            // increment counter
            if(can[0].cxno < NO_CANMSG)
              can[0].cxno++;
            else
              can[0].cxno = NO_CANMSG;
            if(!(CANTIER & 0x07))  {
              // Following will cause entry to TxIsr without sending msg
              // since when CANTIER = 0, CANTFLG left as buff empty.
              // If CANTIER has at least 1 int buf enabled, will enter
              // TxIsr automatically. 
              CANTBSEL = CANTFLG;
              CANTIER = CANTBSEL;
            }
            break;
        		
          case MSG_XSUB:  // msg to execute a subroutine
            switch(rcv_id)  {
              case 1:           // message to this ecu from device 1
                can_xsub01();		// execute sub immediately here (set
                                // flag if can execute in main loop) 
                break;
            }
        	  break;
        	  
          case MSG_BURN:  // msg to burn data table
            flocker     = 0xCC;    // set to prevent burning flash thru runaway code
            burn_idx = var_blk;
            burn_flag   = 1;
        	  break;
        }					 // end msg_type switch
        can_status &= CLR_RCV_ERR;
    }
    if((CANRFLG & 0x72) != 0)  {
        // Rcv error or overrun on receive
        can_status |= RCV_ERR;
        //can_reset = 1;
    }
    /* clear RX buf full, err flags to ready for next rcv int */
    /*  (Note: can't clear err count bits) */
    CANRFLG = 0xC3;
	
    return;
}

#pragma CODE_SEG DEFAULT        

void can_xsub01(void)  {
  // This is a 'place holder' function for a routine 
  // to be executed via a CAN message from another 
  // conrtroller
  return;
}

#pragma CODE_SEG DEFAULT


// ------ Solenoid functions -------------------------------------------------

void solA(unsigned char set_to) {
// set off if set_to=0, on otherwise
if (set_to)
   { // SolA state in first is 3rd bit in Output1 
    PORTE |= 0x10;         // turn on SolA
    SOLAst = 1;            // Set state indicator
    outpc.solst |= 0x01;   // set first bit for datalog
   }
  else
   {
    PORTE &= ~0x10;        // turn off SolA
    SOLAst =0;             // Set state indicator
    outpc.solst &= ~0x01;  // set first bit for datalog
    }
 return;
}

void solB(unsigned char set_to) {
// set off if set_to=0, on otherwise
if (set_to)
   { // SolB state in first is 3rd bit in Output1 
    *pPTMpin[2] |= 0x04;   // turn on Sol B, 0x04 = 00000100, 
                           // so set port M pin 2 to 1 
    SOLBst = 1;            // Set state indicator
    outpc.solst |= 0x02;   // set second bit for datalog
   }
  else
   {
    *pPTMpin[2] &= ~0x04;  // turn off Sol B, ~0x04 = 11111011, 
                           // so set port M pin 2 to 0 
    SOLBst = 0;            // Set state indicator
    outpc.solst &= ~0x02;  // set second bit for datalog
   }
 return;
}

// -------------- Spare Port functions --------------------------------- 

void spr1(unsigned char set_to) {
// set off if set_to=0, on otherwise
if (set_to)
   {
   *pPTTpin[7] |= 0x80;  // turn on PT7
   outpc.sp1 = 1;        // set datalog value
   }
   else
   {
   *pPTTpin[7] &= ~0x80; // shut off PT7
   outpc.sp1 = 0;        // set datalog value
   }
return;
}

void spr2(unsigned char set_to) {
// set off if set_to=0, on otherwise
if (set_to)
   {
   PORTA |= 0x01;        // turn on PA0
   outpc.sp2 = 1;        // set datalog value
   }
   else
   {
   PORTA &= ~0x01;       // turn on PA0
   outpc.sp2 = 0;        // set datalog value
   }
return;
}


// ------ Unimplemented ISRs -------------------------------------------------
#pragma CODE_SEG  NON_BANKED
INTERRUPT void UnimplementedISR(void) {
   /* Unimplemented ISRs trap.*/
   // If we got here by interrupt on IRQ (after reset in 4WD), 
   // then disable the IRQ interrupt (use as gneral input only)
//   INTCR = 0x00;              // disable IRQ on PE1 so it won't be called again
   return;
}

#pragma CODE_SEG DEFAULT 

//------- flash burner -------------------------------------------------------
/******************************************************************************
Arguments:	

      progAdr		Pointer to the start of the destination 
			          Flash location to be programmed.
      bufferPtr	Pointer to the start of the source data. 
      size      Number of WORDS to be programmed.
                  
 This function will program non-paged flash only 
******************************************************************************/
void fburner(unsigned int* progAdr, unsigned int* bufferPtr, 
                                          unsigned int no_words)
{
unsigned char sect,no_sectors;
																
  	if(flocker != 0xCC) return; // if not set, we got here unintentionally
  	// sector = 1024 bytes (512 words)
  	if(burn_flag == 1)  {
  	  no_sectors = (unsigned char)(no_words / SECTOR_WORDS);
  	  if(no_words > (no_sectors * SECTOR_WORDS))no_sectors++;
  	  for (sect = 0; sect < no_sectors; sect++)  {  	  
  	    Flash_Erase_Sector(progAdr + sect * SECTOR_WORDS);
  	  }
  	}
  	Flash_Write_Word(progAdr + burn_flag-1, *(bufferPtr + burn_flag-1));
    	
	return;
}

//*****************************************************************************
//* Function Name: Flash_Init
//* Description : Initialize Flash NVM for HCS12 by programming
//* FCLKDIV based on passed oscillator frequency, then
//* uprotect the array, and finally ensure PVIOL and
//* ACCERR are cleared by writing to them.
//*
//*****************************************************************************
void Flash_Init(unsigned long oscclk)  {
unsigned char fclk_val;
unsigned long temp;
  /* Next, initialize FCLKDIV register to ensure we can program/erase */
  temp = oscclk;
  if (oscclk >= 12000) {
    fclk_val = oscclk/8/200 - 1; /* FDIV8 set since above 12MHz clock */
    FCLKDIV = FCLKDIV | fclk_val | FDIV8;
  }
  else
  {
    fclk_val = oscclk/8/200 - 1;
    FCLKDIV = FCLKDIV | fclk_val;
  }
  FPROT = 0xFF; /* Disable all protection (only in special modes)*/
  FSTAT = FSTAT | (PVIOL|ACCERR);/* Clear any errors */
  return;
}

void Flash_Erase_Sector(unsigned int *address)  {
  FSTAT = (ACCERR | PVIOL); // clear errors
  (*address) = 0xFFFF;/* Dummy store to page to be erased */
  FCMD = ERASE;

/*********************************************************************
;* Allow final steps in a flash prog/erase command to execute out
;* of RAM while flash is out of the memory map
;* This routine can be used for flash word-program or erase commands
;*
;********************************************************************/
/* Execute the sub out of ram  */
// Note that "asm" means the following statements -- in {} here -- are in 
// assembly language
   asm {
     ldaa  #CBEIF
     jsr  RamBurnPgm
   };
   return;
}

void Flash_Write_Word(unsigned int *address, unsigned int data)  {
  FSTAT = (ACCERR | PVIOL); // clear errors
  (*address) = data;        // Store desired data to address being programmed
  FCMD = PROG;              // Store programming command in FCMD

   /* Execute the sub out of ram  */
   asm {
     ldaa  #CBEIF
     jsr  RamBurnPgm
   };
  return;
}

#pragma CODE_SEG ROM_7000  
void waitAwhile(unsigned int cycles)
// This is a 'do nothing' function that simply waits.
// A cycle value of 250 is approximately 1 second (1 tic = ~4 milliseconds).
// Cycles can run from 0 to 65535.

  {
  unsigned short int i;
  unsigned short int start;
  i=0;
next_cycle:
    start = TCNT;
  // The timer clock ticks are counted in the variable TCNT (which is defined in the hsc12def.h file 
  // at memory location 0x0044). There are 0xFFFF 'tics' (65,536 counts).
  // In our case each 'tic' is 1/0.1875MHz  = 5.333 µs
  // However, there is some overhead associated with calling the function, 
  // comparing the values, etc.
check_again:
    if (TCNT>start) goto check_again; // have not rolled over, proceed to next_step when do 
next_step:
    if (TCNT<start) goto next_step;   // have rolled over, and exceeded original TCNT,
                                      // so completed one cycle of TCNT 
   i++;                               // increment i
   if (i<cycles) goto next_cycle;     // and if i is less than cycles, do another
   
    return;                           // otherwise return
  }
