Open Import Fuel Tuner


Detailed Software Architecture

This document gives a detailed organizational layout of the initial OIFT 68HC16 software structure. In order to keep this document as small as possible, it is written in outline note style, and lacks complete explanation. A good understanding of the MC68HC16Z1 microcontroller is necessary to fully understand this document. A brief overview of the MC68HC16Z and its more relevant features is presented here.

The MC68HC16 microcontroller is a member of Motorola's family of Modular microcrontrollers. The internal structure of this MPU consists of a CPU16 processing unit, an Inter-Module Bus (IMB), and several different modules which perform various I/O and processing functions. The MC68HC16Z1 contains the following modules:

  • System Integration Module (SIM) This module performs many functions, including system clock management, watchdog timer, external bus control, external I/O pin control, and Programmable Interrupt Timer (PIT) control. In the OIFT application, the PIT is used for an "UpdateEngine" timer. Driven at 1 kilohertz, this timer causes the OIFT device to read the current input engine status, translate the necessary signals, and drive the output engine signals.
  • General Purpose Timer (GPT) The GPT module controls the Input Capture, Output Compare, Pulse Accumulator, and Pulse Width Modulation functions. Extensive use of the Input Capture and Output Compare functions is made in the OIFT device.
  • Queued Serial Module (QSM) The QSM module contains two separate serial devices - the Queued Serial Peripheral Interface (QSPI) and the Serial Communications Interface (SCI). In the OIFT application, the QSPI is used to transfer data to a D/A converter, and the SCI is used for host serial communications (configuration and datalogging.)
  • Analog to Digital Converter (ADC) The ADC module contains a multiplexed 8-input 10-bit analog to digital converter which is capable of converting individual samples at a 120 kHz rate. Several engine signals are sampled with the ADC module in the OIFT application.

One of the primary design goals of this project, as with any engine management controller, is absolute maximum reliability. For this reason the software has been structured so that all engine management functions take place within Interrupt Service Routines (ISRs). There is no "software polling" at all, and the only function performed by the main loop is that of communicating with the host PC for configuration and datalogging. Since the code has been structured in this manner, the bulk of this documentation concerns the ISRs. Extreme care must be taken to evaluate the "worst-case" scenario with regard to function timing and ensure that everything still works properly even in the "worst case." In order to firmly understand the flow of the software in this system, it is important to understand the MC68HC16's interrupt structure.

The CPU16 unit does not generate any Interrupt Requests (IRQs) itself - it can only generate Exceptions (software errors). All of the IRQs, which in turn cause the execution of the ISRs, are generated by the MC68HC16's internal Modules. Each module can select one or more IRQ Levels which may be activated when it makes an interrupt request. The CPU16 supports 7 interrupt levels, called (appropriately enough) 1-7. 1 is the lowest IRQ Level, and 7 is the highest. IRQ7 is non-maskable, but other interrupt levels may be temporarily disabled by manipulating an IRQMask field in a special CPU register. For the OIFT application, the GPT is initialized for IRQ Level 6, the SIM for IRQ Level 4, the SCI transceiver in the QSM module for 2, and the QSPI transceiver in the QSM module for 0 (interrupts disabled). The ADC module does not support interrupts.

Some interrupts may be disabled in code by changing the value of the "Interrupt Priority" (IRQMask) bit-field in the Condition Code CPU register. IRQ Level 7 cannot be disabled, but all others can. There is no interrupt device in the OIFT which uses IRQ Level 7. Setting the IRQMask to a certain value will disable all IRQs with an equal or lesser IQR Level. For example, setting the IRQMask to 0 will enable all interrupts, but changing it to 3 will disable IRQ Levels 1, 2, and 3. By temporarily disabling the ISRs during time-critical operations, the software may guarantee certain operating characteristics.

The GPT is capable of generating so many interrupts (12), that the module is only capable of generating one IRQ Level itself, and the individual GPT interrupts are organized internally. For a full explanation, consult the Motorla GPTRM/AD reference manual, or the MC68HC16ZUM/AD user's manual. To simplify the GPT module's interrupt structure, it has 12 internal interrupt sources which come from the Input Capture, Output Compare, Pulse Accumulator, and Pulse Width Modulator sub-modules. GPT Interrupt Level 0 is the highest-priority, and Level 11 is the lowest. The GPT Interrupt Levels don't work like the CPU's Interrupt Levels. If the CPU is already in a GPT ISR and another GPT Interrupt source generates an interrupt, the currently exectuing ISR will *not* be interrupted (unless the CPU's IRQMask has been changed). The GPT Interrupt Levels are only pertinent when multiple GPT interrupts happen before any get serviced by the CPU. For example, assume that the CPU is executing some code and sets the IRQMask to 7. No interrupts (on the OIFT) can occur in this condition. Now consider what would happen if two GPT interrupts then occured. The CPU would not service them, since the IRQMask is set to 7. But once the time-critical portion of code is complete and the IRQMask gets set to a lower value, the CPU will immediately service the GPT module. It will service this module before the SIM or QSM modules if they also had pending interrupts, because the GPT's CPU IRQ Level is set to 6 while the other modules are 4 and 2. So now when the CPU services the GPT's IRQ Level 6 request, the GPT must decide which of its two internal interrupts to service first. It will select the internal GPT interrupt source with the highest priority (lowest value) first. For example, if the GPT had an Input Capture 1 (GPT Interrupt Level=1) and an Output Compare 2 (GPT Interrupt Level=5) interrupt pending, it would instruct the CPU to service the Input Capture 1 ISR first. When this ISR returned or decreased the CPU's IRQMask field, the next highest-priority GPT interrupt would be serviced.

One unique feature of this software structure which requires further explanation is the A/D conversion timing. Unfortunately the ADC module in the 68HC16 MPU is unable to generate interrupts, but the OIFT application requires that the ADC be operated in a continuous high-speed mode. This places very strict constraints upon the timing of code which is necessary to read the sampled input data and store it in memory before it gets overwritten by the next cycle of conversions. Since software polling is not a viable option, a more clever solution was designed to solve this issue. The A/D conversion timing works from one of the GPT Output Compare timers. Each Output Compare timer may be initialized to generate an interrupt at an arbitrary frequency. For the OIFT application, Output Compare 1 was selected for use as the A/D conversion timer. The GPT Prescaler, GPT OC1 timer, and the A/D conversion timer are configured so that the Output Compare 1 interrupt occurs at *exactly* the same frequency as the A/D conversion cycle. Since both the GPT clock and ADC clock are driven by the system clock, these two clocks should remain synchronous with respect to each other. The remaining piece of this puzzle lies in the GPT Output Compare 1 ISR code. In order to guarantee synchronization, the two clocks must be started synchronously. The GPT OC1 ISR code accomplishes this task. First, the GPT is started and the OC1 timer is activated. When this ISR runs for the first time (after about 69 us) it immediately starts the A/D converter and returns. This guarantees that the final A/D conversion (in a sequence of 8) will always occur several clock cycles after the GPT Output Compare 1 IRQ is requested. This guarantees software synchronization with the A/D converter and ensures that the input engine signal data is not corrupted by time-based errors.

A detailed outline of the designed software structure of the Open Import Fuel Tuner follows, and a detailed timing analysis is at the end of this page.

I. Initialization / startup code

  1. Software Initialization
    1. Call initialization routines for ISRs (data buffer & state machines)
    2. Call initialization routines for Engine Control Logic (look-up tables, etc)
    3. Call initialization routine for Data Dump
    4. Disable IRQs, set ISR addresses, re-enable IRQs
  2. Output Hardware Initialization
    1. Setup Modules: GPTMCR (IRQL=6), QSMCR, ADCMCR, SIMCR
    2. Setup LED output line
    3. Setup GPT Prescaler = 64 (2.543us period)
    4. Setup QSPI Serial output registers
    5. Setup external D/A converter
    6. Setup SCI Serial output registers
    7. Setup Output Compare registers
  3. Input Hardware Initialization
    1. Setup ADC Registers
      • ADCTL0 = 010000101b ; 10-bit resolution, ADC clock = 25.1MHz / 12
      • ADCTL1 = 001110000b ; continuous, multichannel, 8-chan, external inputs
    2. Setup A/D Timer Output Compare, wait for first cycle complete
    3. Setup Input Capture registers
    4. Setup PIT timer registers (UpdateEngine timer)
    5. Jump to Main Loop

II. Interrupt Service Routines

  1. A/D Timer OC1 [IRQL=6.0 Highest] (max response = 200 cycles 8us)
    The sample frequency of the A/D converter equals ADC clock / 18 = 116.203kHz.
    The interrupt frequency of the A/D timer equals sample frequency / 8 = 14.525kHz
    (period = 69us).
    1. If ADTimerState == 0 then:
      • Set ADTimerState = 1, start A/D conversions, Enable IRQs & return
    2. Copy relevant A/D values into RAM buffer
    3. Increment 32-bit Main Timer value, set next OC1 compare value
    4. Reset OC1 IRQ, Set IRQMask = 5
    5. Add input values (MAP, TPS, IAT, O2) into average buffers
    6. Set KnockWindow flag according to current engine time
    7. If KnockWindow == 1 then:
      • Add knock sensor input values into sliding knock sensor sample buffer
    8. Return
  2. Input Capture IC1 (IGN) [IRQL=6.1] (max response = 250 cycles 10us)
    1. Calculate output timing delay from MAP average
    2. If delay == 0 then:
      • Assert IGN Output bit & Set IGN Output Compare for pulse width
      • else Set IGN Output Compare for pulse start
    3. Store Input Capture value for timing analysis
    4. Reset IC1 IRQ & return
  3. Input Capture IC2 (TDC) [IRQL=6.2] (max response = 5000 cycles 200us)
    1. Calculate & store timing for knock detection window
    2. Reset IC2 IRQ, Set IRQMask = 5
    3. Subtract last TDC time value, calculate RPM
    4. Update engine status value for RPM
    5. Update last TDC time value
    6. Calculate last revs actual spark timing, update engine status value
    7. Return
  4. Input Capture IC3 (Karman) [IRQL=6.3] (response time not critical)
    1. Subtract last Karman time value, calculate frequency
    2. Update engine status value for Karman frequency
    3. Reset IC3 IRQ & return
  5. IGN Output Compare OC2 [IRQL=6.5] (max resp = 12,500 cycles for 500us pulse)
    1. If IGN Output bit == 0 then enable IRQs and return
    2. Set IGN Output Compare for output pulse width
    3. Reset OC2 IRQ & return
  6. Karman Output Compare OC3 [IRQL=6.6] (max response = 5000 cycles 200us)
    1. If OC3 Output bit == 0 then set Karman Output Compare for pulse OFF time
    2. Else set Karman Output Compare for pulse ON time
    3. Reset OC3 IRQ & return
  7. PIT Engine Update Timer [IRQL=4] (response time not critical)
    1. Decrement TimeToDataDump value, if == 0 then set DataDumpNow flag
      • if DataDumpNow flag was previously set, then set CPUTaxed flag
    2. Decrement TimeToFlipLED value, if == 0 then flip LED state & reset value
    3. Call UpdateEngine code, Reset PIT IRQ & return
  8. RS-232 (SCI) serial input [IRQL=2] (max response = 13,120 cycles 521us)
    1. Copy data to serial input buffer, update pointers
    2. Reset SCI input IRQ & return
  9. RS-232 (SCI) serial output [IRQL=2] (max response = 13,120 cycles 521us)
    1. If there is data remaining in the serial output buffer:
      • send it to the SCI
      • reset buffer pointers, initiate SCI transfer
    2. Else, clear SCISerialOutput flag
    3. Reset SCI output IRQ & return

III. Engine Control Logic

  1. UpdateEngine
    1. Reset TimeToUpdateEngine value
    2. If InUpdateEngine flag == 1 then flip LED state & return (error)
    3. Set IRQMask = 7
    4. Copy input average buffers & engine status values, reset buffers
    5. Set InUpdateEngine flag = 1, set IRQMask = 0
    6. Calculate averages & finish static input engine table
    7. Call all of the ECL functions
    8. Set InUpdateEngine flag = 0, return
  2. ECL0 - MAP re-mapping & adjustment by RPM
    1. Calculate new output MAP voltage from translation & RPM factor tables
    2. Insert MAP D/A command into QSPI D/A serial output buffer
    3. Return
  3. ECL1 - Karman re-mapping & adjustment by RPM
    1. If Karman value == old Karman value then return
    2. Calculate new output Karman frequency from translation & RPM factor tables
    3. Update Karman OC3 values
    4. Return
  4. ECL2 - IAT modification
    1. Calculate IAT output state from engine status, update IAT output control bit
    2. Return
  5. ECL3 - TPS modification
    1. Calculate output TPS value from engine status
    2. Insert TPS D/A command into QSPI D/A serial output buffer
    3. Activate D/A serial output, return
  6. ECL4 - IGN modification
    1. Update sliding average MAP sensor buffer & return
  7. ECL5 - KnockUpdate
    1. If oldKnockWindow == 1 or KnockWindow == 1 then:
      • Execute knock computation for new input samples, fix buffer pointers
    2. If oldKnockWindow == KnockWindow then return
    3. If oldKnockWindow == 0 and KnockWindow == 1 then:
      • Set oldKnockWindow = 1 & return
    4. Set oldKnockWindow = 0
    5. Calculate final knock value for this power stroke, update engine status
    6. Clear sliding knock sensor input buffer & state variables
    7. Return

IV. Main Loop
  1. If RS-232 serial input buffer contains <eot> character then:
    1. Copy serial input data up to <eot> to command buffer, reset serial buffer
    2. Execute command
  2. If DataDumpNow flag is set then:
    1. Reset TimeToDataDump value, clear DataDumpNow flag
    2. If SCISerialOutput flag is set, set SerialTooFast flag and return
    3. Disable IRQs, copy engine status table, enable IRQs
    4. Insert selected output data in RS-232 output buffer
    5. Set SCISerialOutput flag, Initiate SCI transfer
    6. Clear SerialTooFast, and CPUTaxed flags
  3. Loop to A


Software Timing Analysis

Timebase Notes:
CPU Frequency = (2^23 * 3) Hz == 25.1MHz
A/D Timer freq = 25.1MHz / (12 * 18 * 8) == 14.525kHz
GPT Timer freq = 25.1MHz / (64) == 393.216kHz
GPT Timer freq = 27 * A/D Timer freq
SCI frequency = 19.181 kHz (SCBR=41, -0.09756% error [19,200bps] 512.5 bits max)

Basic CPU Load Notes: (Tx = function execution time)
Value
Function
Max Freq
Min Period
Max CPU Cycles
T1 = A/D Timer OC1 14.563kHz 68.84us 1,728
T2 = IGN Input IC1 266.7Hz 3.75ms (8k rpm) 94,125
T3 = TDC Input IC2 133.3Hz 7.5ms (8k rpm) 188,250
T4 = Karman Input IC3 2.5 kHz 400 us 10,040
T5 = IGN Output OC2 533.3 Hz 1.875 ms 47,062
T6 = Karman Output OC3 2.5 kHz 400 us 10,040
T7 = PIT Update Timer 1 kHz 1 ms 25,165
T8 = SCI Serial Input 1.918 kHz 521.34 us 13,120
T9 = SCI Serial Output 1.918 kHz 521.34 us 13,120

  1. Max ISR CPU Usage U1 = (T1 / 68.84us) + (T2 / 3.75ms) + (T3 / 7.5ms) + (T4 / 400us) + (T5 / 1.875ms) + (T6 / 400us) + (T7 / 1 ms) + (T8 / 521.34us) + (T9 / 521.34us)
    • U1 *MUST* be smaller than 1.0
    • U1 should probably be lower than 0.5 for safety

Critical Timing Constraints:

  1. A/D Timer OC1 response time < 200 cycles.
    • If failure: A/D values get overwritten.
    • Worst case time = Maximum single-ISR IRQMask >=6 time
  2. Ignition IC1 response time < 250 cycles.
    • If failure: Ignition retard out of spec.
    • Worst case time = Maximum single-ISR IRQMask >=6 time + A/D Timer IRQMask >=6 time
  3. Max A/D Timer ISR completion time < 1728 cycles.
    • If failure: A/D values overwritten.
    • Worst case time = Sum of IRQL6.1-6.6 max times + OC1 ISR max time
  4. Max PIT Timer ISR completion time < 25,000 cycles.
    • If failure: Missed Update frame.
    • Worst case time = Sum of all max ISR times during 1ms + UpdateEngine max time
  5. Other critical response & completion times exist, but are guaranteed by previous constraints.

Back

Hardware
FS Home Page